23 | 환영합니다!
24 |27 |{{ data.title }} - {{ data.english }}
{{ data.year }}년 {{ data.translator }} 번역
├── README.md ├── documents ├── algorithm-education-in-python │ ├── Algorithm Education in Python_files │ │ ├── hufftree.jpg │ │ ├── ugraph.jpg │ │ └── wgraph.jpg │ └── index.html ├── all-about-python-and-unicode │ ├── All About Python and Unicode boodebr.org_files │ │ ├── gfilesel_01.jpg │ │ ├── gnome_terminal_01.jpg │ │ ├── kfilesel_01.jpg │ │ ├── konq_01.jpg │ │ ├── konsole_01.jpg │ │ ├── mlterm_01.jpg │ │ ├── naut_01.jpg │ │ ├── rxvt_01.jpg │ │ ├── samba_01.jpg │ │ ├── urxvt_01.jpg │ │ ├── win32_01.jpg │ │ ├── win32_02.jpg │ │ ├── xfce_01.jpg │ │ └── xfm_01.jpg │ └── index.html ├── beautifulsoup4 │ ├── index.html │ └── 뷰티플수프 문서 — 뷰티플수프 4.0.0 문서_files │ │ ├── 6.jpg │ │ └── index.css ├── how-to-think-like-a-computer-scientist │ ├── app01.htm │ ├── app02.htm │ ├── app03.htm │ ├── app04.htm │ ├── chap01.htm │ ├── chap02.htm │ ├── chap03.htm │ ├── chap04.htm │ ├── chap05.htm │ ├── chap06.htm │ ├── chap07.htm │ ├── chap08.htm │ ├── chap09.htm │ ├── chap10.htm │ ├── chap11.htm │ ├── chap12.htm │ ├── chap13.htm │ ├── chap14.htm │ ├── chap15.htm │ ├── chap16.htm │ ├── chap17.htm │ ├── chap18.htm │ ├── chap19.htm │ ├── chap20.htm │ ├── chap21.htm │ ├── contrib.htm │ ├── dex.htm │ ├── doctest.htm │ ├── fdl.htm │ ├── fdl │ │ └── gnu-head-sm.jpg │ ├── foreword.htm │ ├── gasp.html │ ├── illustrations │ │ ├── assign2.png │ │ ├── banana.png │ │ ├── compile.png │ │ ├── fibonacci.png │ │ ├── gasp01.png │ │ ├── gasp02.png │ │ ├── gasp03.png │ │ ├── interpret.png │ │ ├── link1.png │ │ ├── link2.png │ │ ├── link3.png │ │ ├── link4.png │ │ ├── link5.png │ │ ├── list1.png │ │ ├── list2.png │ │ ├── list3.png │ │ ├── matrix.png │ │ ├── point.png │ │ ├── pydoc_firefox.png │ │ ├── pydoc_keyword_firefox.png │ │ ├── pydoc_tk.png │ │ ├── queue1.png │ │ ├── rectangle.png │ │ ├── rectangle2.png │ │ ├── sparse.png │ │ ├── stack.png │ │ ├── stack2.png │ │ ├── stack3.png │ │ ├── stack4.png │ │ ├── stack5.png │ │ ├── state2.png │ │ ├── time.png │ │ ├── tree1.png │ │ ├── tree2.png │ │ ├── tree3.png │ │ ├── tree4.png │ │ ├── tree5.png │ │ └── treeadt.png │ ├── images │ │ ├── blank.png │ │ ├── headertitle.png │ │ ├── index.png │ │ ├── next.png │ │ ├── prev.png │ │ └── up.png │ ├── index.html │ ├── johnsonj.htm │ ├── minicase.html │ ├── modulefile.htm │ └── preface.htm ├── patterns-in-python │ └── index.html ├── python-decorators-dont-have-to-be-scary │ └── index.html ├── python-idioms-and-efficiency-suggestions │ └── index.html ├── python-tips-tricks-and-hacks │ ├── Python Tips, Tricks, and Hacks_files │ │ └── cc-by-nc-sa.png │ └── index.html └── type-checking-in-python │ └── index.html ├── fa ├── css │ └── font-awesome.min.css └── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── index.html ├── js └── angular.min.js ├── list.json ├── params.json └── stylesheets ├── github-light.css ├── normalize.css └── stylesheet.css /README.md: -------------------------------------------------------------------------------- 1 | # What is this repository? 2 | - [coreapython 문서고](http://coreapython.hosting.paran.com) 문서 백업 3 | 4 | # Contribution 5 | Fork & Pull Request 6 | 7 | # Contribution guide 8 | - documents 폴더 내에 문서당 하나의 폴더만 생성해야 함 9 | 그림 파일이 있다면 URL에 맞게 추가해야 함 10 | 문서 내의 링크에 문제가 있다면 고쳐야 함 11 | (예: 절대 경로인 경우 상대 경로로 바꿔야 함) 12 | - list.json 파일을 규칙에 맞게 작성해야 함 13 | - 절대 원본 저장소로 바로 Push 하지말 것 14 | - 문서 오타/링크 문제는 [Issue](https://github.com/cryptosan/pythondocuments/issues)이용 15 | 16 | # License 17 | All the documents are under the coreapython 18 | -------------------------------------------------------------------------------- /documents/algorithm-education-in-python/Algorithm Education in Python_files/hufftree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/algorithm-education-in-python/Algorithm Education in Python_files/hufftree.jpg -------------------------------------------------------------------------------- /documents/algorithm-education-in-python/Algorithm Education in Python_files/ugraph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/algorithm-education-in-python/Algorithm Education in Python_files/ugraph.jpg -------------------------------------------------------------------------------- /documents/algorithm-education-in-python/Algorithm Education in Python_files/wgraph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/algorithm-education-in-python/Algorithm Education in Python_files/wgraph.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/gfilesel_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/gfilesel_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/gnome_terminal_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/gnome_terminal_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/kfilesel_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/kfilesel_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/konq_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/konq_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/konsole_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/konsole_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/mlterm_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/mlterm_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/naut_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/naut_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/rxvt_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/rxvt_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/samba_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/samba_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/urxvt_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/urxvt_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/win32_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/win32_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/win32_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/win32_02.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/xfce_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/xfce_01.jpg -------------------------------------------------------------------------------- /documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/xfm_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/all-about-python-and-unicode/All About Python and Unicode boodebr.org_files/xfm_01.jpg -------------------------------------------------------------------------------- /documents/beautifulsoup4/뷰티플수프 문서 — 뷰티플수프 4.0.0 문서_files/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsahn-org/pythondocuments/2d9df9bd129da3f5aa9edcfcfb9c5091edff02b1/documents/beautifulsoup4/뷰티플수프 문서 — 뷰티플수프 4.0.0 문서_files/6.jpg -------------------------------------------------------------------------------- /documents/beautifulsoup4/뷰티플수프 문서 — 뷰티플수프 4.0.0 문서_files/index.css: -------------------------------------------------------------------------------- 1 | /* ::::: http://www.crummy.com/software/BeautifulSoup/bs4/doc/_static/default.css ::::: */ 2 | 3 | /* ::::: http://www.crummy.com/software/BeautifulSoup/bs4/doc/_static/basic.css ::::: */ 4 | 5 | div.clearer { clear: both; } 6 | div.related { width: 100%; font-size: 90%; } 7 | div.related h3 { display: none; } 8 | div.related ul { margin: 0px; padding: 0px 0px 0px 10px; list-style: none outside none; } 9 | div.related li { display: inline; } 10 | div.related li.right { float: right; margin-right: 5px; } 11 | div.sphinxsidebarwrapper { padding: 10px 5px 0px 10px; } 12 | div.sphinxsidebar { float: left; width: 230px; margin-left: -100%; font-size: 90%; } 13 | div.sphinxsidebar ul { list-style: none outside none; } 14 | div.sphinxsidebar ul ul, div.sphinxsidebar ul.want-points { margin-left: 20px; list-style: square outside none; } 15 | div.sphinxsidebar ul ul { margin-top: 0px; margin-bottom: 0px; } 16 | div.sphinxsidebar form { margin-top: 10px; } 17 | div.sphinxsidebar input { border: 1px solid rgb(152, 219, 204); font-family: sans-serif; font-size: 1em; } 18 | div.sphinxsidebar #searchbox input[type="text"] { width: 170px; } 19 | div.sphinxsidebar #searchbox input[type="submit"] { width: 30px; } 20 | img { border: 0px none; } 21 | a.headerlink { visibility: hidden; } 22 | h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink { visibility: visible; } 23 | div.body td { text-align: left; } 24 | .first { margin-top: 0px ! important; } 25 | img.align-right, .figure.align-right, object.align-right { clear: right; float: right; margin-left: 1em; } 26 | .align-right { text-align: right; } 27 | table.docutils { border: 0px none; border-collapse: collapse; } 28 | table.docutils td, table.docutils th { padding: 1px 8px 1px 5px; border-width: 0px 0px 1px; border-style: none none solid; border-color: -moz-use-text-color -moz-use-text-color rgb(170, 170, 170); } 29 | pre { overflow-x: auto; overflow-y: hidden; } 30 | tt.xref, a tt { background-color: transparent; font-weight: bold; } 31 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { background-color: transparent; } 32 | body { font-family: sans-serif; font-size: 100%; background-color: rgb(17, 48, 61); color: rgb(0, 0, 0); margin: 0px; padding: 0px; } 33 | div.document { background-color: rgb(28, 78, 99); } 34 | div.documentwrapper { float: left; width: 100%; } 35 | div.bodywrapper { margin: 0px 0px 0px 230px; } 36 | div.body { background-color: rgb(255, 255, 255); color: rgb(0, 0, 0); padding: 0px 20px 30px; } 37 | div.footer { color: rgb(255, 255, 255); width: 100%; padding: 9px 0px; text-align: center; font-size: 75%; } 38 | div.footer a { color: rgb(255, 255, 255); text-decoration: underline; } 39 | div.related { background-color: rgb(19, 63, 82); line-height: 30px; color: rgb(255, 255, 255); } 40 | div.related a { color: rgb(255, 255, 255); } 41 | div.sphinxsidebar { } 42 | div.sphinxsidebar h3 { font-family: 'Trebuchet MS',sans-serif; color: rgb(255, 255, 255); font-size: 1.4em; font-weight: normal; margin: 0px; padding: 0px; } 43 | div.sphinxsidebar h3 a { color: rgb(255, 255, 255); } 44 | div.sphinxsidebar p { color: rgb(255, 255, 255); } 45 | div.sphinxsidebar ul { margin: 10px; padding: 0px; color: rgb(255, 255, 255); } 46 | div.sphinxsidebar a { color: rgb(152, 219, 204); } 47 | div.sphinxsidebar input { border: 1px solid rgb(152, 219, 204); font-family: sans-serif; font-size: 1em; } 48 | a { color: rgb(53, 95, 124); text-decoration: none; } 49 | a:visited { color: rgb(53, 95, 124); text-decoration: none; } 50 | a:hover { text-decoration: underline; } 51 | div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Trebuchet MS',sans-serif; background-color: rgb(242, 242, 242); font-weight: normal; color: rgb(32, 67, 92); border-bottom: 1px solid rgb(204, 204, 204); margin: 20px -20px 10px; padding: 3px 0px 3px 10px; } 52 | div.body h1 { margin-top: 0px; font-size: 200%; } 53 | div.body h2 { font-size: 160%; } 54 | div.body h3 { font-size: 140%; } 55 | div.body h4 { font-size: 120%; } 56 | a.headerlink { color: rgb(198, 15, 15); font-size: 0.8em; padding: 0px 4px; text-decoration: none; } 57 | a.headerlink:hover { background-color: rgb(198, 15, 15); color: white; } 58 | div.body p, div.body dd, div.body li { text-align: justify; line-height: 130%; } 59 | pre { padding: 5px; background-color: rgb(238, 255, 204); color: rgb(51, 51, 51); line-height: 120%; border-width: 1px medium; border-style: solid none; border-color: rgb(170, 204, 153) -moz-use-text-color; -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none; -moz-border-left-colors: none; border-image: none; } 60 | tt { background-color: rgb(236, 240, 243); padding: 0px 1px; font-size: 0.95em; } 61 | /* ::::: http://www.crummy.com/software/BeautifulSoup/bs4/doc/_static/pygments.css ::::: */ 62 | 63 | .highlight { background: none repeat scroll 0% 0% rgb(238, 255, 204); } 64 | .highlight .c { color: rgb(64, 128, 144); font-style: italic; } 65 | .highlight .k { color: rgb(0, 112, 32); font-weight: bold; } 66 | .highlight .o { color: rgb(102, 102, 102); } 67 | .highlight .kn { color: rgb(0, 112, 32); font-weight: bold; } 68 | .highlight .s { color: rgb(64, 112, 160); } 69 | .highlight .nb { color: rgb(0, 112, 32); } 70 | .highlight .nf { color: rgb(6, 40, 126); } 71 | .highlight .nn { color: rgb(14, 132, 181); font-weight: bold; } 72 | .highlight .ow { color: rgb(0, 112, 32); font-weight: bold; } 73 | .highlight .mi { color: rgb(32, 128, 80); } 74 | .highlight .sd { color: rgb(64, 112, 160); font-style: italic; } 75 | .highlight .se { color: rgb(64, 112, 160); font-weight: bold; } 76 | .highlight .bp { color: rgb(0, 112, 32); } 77 | -------------------------------------------------------------------------------- /documents/how-to-think-like-a-computer-scientist/app01.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |![]() |
37 | ![]() |
38 | ![]() |
39 | ![]() |
40 | ![]() |
41 | ![]() |
42 | ![]() |
43 |
52 | 프로그램에서 다양한 종류의 에러가 많이 일어날 수 있다. 디버깅을 해서 에러를 더욱 신속하게 추적해 들어가려면 에러의 종류를 구별하는 것이 좋다:
53 | 54 |68 | 디버깅의 첫 번째 단계는 어떤 종류의 에러를 처리해야 하는지 알아내는 것이다. 다음 섹션은 에러의 종류에 따라서 조직되었지만, 어떤 테크닉들은 여러 상황에 적용될 수 있다.
69 | 70 |73 | 구문에러는 그 에러가 무엇인지 알기만 하면 보통 쉽게 수정할 수 있다. 불행하게도 에러 메시지는 보통 도움이 되지 않는다. 가장 흔한 메시지는 "SyntaxError: invalid syntax"와 "SyntaxError: invalid token"인데 둘 다 별로 정보를 주지 않는다.
74 |75 | 그렇기는 하지만 구문에러 메시지는 문제가 프로그램의 어디에서 일어났는지 말해 준다. 실제로 구문에러 메시지는 파이썬이 문제를 인지한 곳을 알려 주는데, 반드시 에러가 있는 곳일 필요는 없다. 어떤 때는 에러 메시지의 위치 앞에 에러가 있기도 하며, 어떤 때는 바로 앞 줄에 있을 수도 있다.
76 |77 | 만약 프로그램을 점증적으로 구축하고 있다면, 구문에러가 어디에 있는지 확실하게 알수가 있다. 구문에러는 마지막으로 추가한 바로 그 줄에 있을 것이다.
78 |79 | 책에서 코드를 복사했다면 제일 먼저 복사된 코드를 책에 있는 코드와 아주 주의깊게 비교하자. 모든 문자들을 점검하라. 동시에 그 책이 틀릴 수도 있다는 것을 기억하자. 그래서 만약 구문 에러처럼 보이는 어떤 것을 발견한다면, 그것이 바로 구문에러일 것이다.
80 |81 | 다음은 가장 흔하게 구문에러를 유발하는 원인들을 피하기 위한 방법이다:
82 |만약 아무 문제도 없으면 다음 섹션으로 나아가라...
99 | 100 |102 | 컴파일러가 에러가 있다고 말해주는데도 불구하고 그것을 이해하지 못하는 상황이라면 여러분과 컴파일러가 같은 코드를 보고 있는 것이 아니기 때문일 가능성이 있다. 여러분의 환경을 점검하여 편집하고 있는 프로그램이 파이썬이 실행하려고 하는 그 프로그램인지 확인하라. 확신할 수 없다면, 프로그램의 시작부분에 의도적으로 확실한 구문 에러를 배치하여 보라. 이제 그것을 다시 실행(혹은 반입) 하라. 만약 컴파일러가 그 새로운 에러를 발견하지 못하면, 아마도 여러분의 개발환경이 설정되는 방식에 무엇인가 잘못이 있다는 뜻이다.
103 | 104 |107 | 이 시점에서는 구문적으로 정확한 파이썬 코드로 이루어진 파일을 가지고 있다. 파이썬은 프로그램을 반입할 수 있고 그것을 실행할 수 있다. 무엇이 잘못될 수 있을까?
108 | 109 |111 | 이 문제는 거의 대부분 파일이 함수와 클래스로 구성되어 있으며 실제로는 무엇엔가 실행을 시작하도록 요청하지 않을 때이다. 이 모듈을 반입하여 클래스와 함수만을 공급할 계획이라면 이것은 의도적일 수 있다.
112 |113 | 의도적인 것이 아니라면 상호대화 프롬프트에서 함수에 실행을 시작하도록 요청하거나 또는 함수에 함수를 실행하도록 요청하고 있는지 확인하라. 또 아래에 있는 "실행의 흐름(Flow of Execution)" 섹션을 참조하라.
114 | 115 |117 | 프로그램이 중지하고 아무것도 하지 않는 듯이 보인다면, 그것을 "목이 매달려있다(hanging)"고 말한다. 이 말이 뜻하는 바는 종종 무한 회돌이 또는 무한 재귀에 빠졌다는 것을 뜻한다.
118 | 119 |123 | 프로그램을 실행하라. 두 번째 메시지가 아니라 첫 번째 메시지를 만나면 무한 회돌이에 빠진 것이다. 아래에 있는 "무한 회돌이" 섹션으로 가라.
124 |127 | 이러한 에러를 만나지 않을 지라도 재귀 메쏘드 또는 재귀 함수에 문제가 있다고 의심된다면, 여전히 "무한 재귀" 섹션에 있는 있는 테크닉을 사용할 수 있다.
128 |138 | 무한 회돌이를 가지고 있다고 생각하고 어떤 회돌이가 문제를 야기하고 있는지 알고 있다면, print 서술문을 그 회돌이의 마지막에 추가하여 그 조건안에 있는 변수들의 값을 출력하고 그 조건의 값을 출력하라.
139 | 140 |예를 들어:
141 | 142 | 143 |while x > 0 and y < 0 :
144 |
# x에 대해 무언가 처리한다.
145 |
# y에 대해 무언가 처리한다.
146 |
147 |
print "x: ", x
148 |
print "y: ", y
149 |
print "condition: ", (x > 0 and y < 0)
150 |
153 | 이제 프로그램을 실행할 때, 회돌이를 돌 때마다 세 줄의 출력을 볼 것이다. 회돌이의 마지막에서 조건은 false가 되어야 한다. 만약 회돌이가 계속 진행되면, x와 y의 값을 볼 수 있을 것이다. 그리고 어쩌면 그 값들이 왜 갱신되고 있는지 정확하게 알 수 있을지도 모른다.
154 | 155 |157 | 무한 재귀때문에 프로그램은 대부분 잠시동안 실행되다가 "최대 재귀 깊이를 초과함(Maximum recursion depth exceeded)" 에러를 일으킬 것이다.
158 |159 | 만약 한 함수 또는 메쏘드가 무한 재귀를 야기하고 있다고 의심된다면, 기저 케이스가 확실하게 있는지 제일 먼저 점검하라. 다른 말로 하면, 반드시 어떤 조건이 있어야만 그 함수 혹은 메쏘드가 재귀 호출을 하지 않고서도 복귀할 수 있을 것이다. 그렇지 않다면, 그 알고리즘을 재고하고 기저 케이스를 식별할 필요가 있다.
160 |161 | 만약 기저 케이스는 있으나 프로그램이 거기에 도달하지 않는 것 같다면, print 서술문을 그 함수 혹은 메쏘드의 시작부분에 추가하여 매개변수들을 출력시켜라. 이제 프로그램을 실행시키면, 그 함수 혹은 메쏘드가 요청될 때마다 몇 줄의 출력을 볼 것이다, 그리고 여러분은 그 매개변수들을 볼 것이다. 매개변수들이 기저 케이스로 이동하지 않는다면, 왜 이동하지 않는지에 대하여 어떤 아이디어를 얻을 것이다.
162 | 163 |165 | 실행의 흐름이 어떻게 프로그램 전체를 통하여 움직이는지 확신하지 못한다면, print 서술문을 각 함수의 시작부분에 추가하여 다음과 같은 "함수 foo에 들어감"이라는 메시지를 출력하게 하라. 여기에서 foo는 그 함수의 이름이다.
166 |167 | 이제 프로그램을 실행하면, 그 프로그램은 요청이 될 때마다 각 함수를 추적해 출력할 것이다.
168 | 169 |171 | 무언가 실행시간에 잘못되면 파이썬은 그 예외의 이름과 프로그램에서 문제가 발생한 줄 그리고 역추적을 담은 메시지를 출력한다.
172 |173 | 역추적은 현재 실행되고 있는 함수를 식별한다. 그리고 그 함수를 요청하는 함수를, 그리고 또 그것을 요청하는 함수를, 등등을 식별한다. 다른 말로 하면, 역추적은 현재의 위치까지 오게한 함수 요청의 경로를 추적한다. 역추적에는 파일에서 각 호출이 일어난 곳의 줄 번호가 포함된다.
174 |175 | 첫 번째 단계는 프로그램에서 에러가 일어난 위치를 조사하고 무엇이 일어났는지 이해할 수 있는지를 알아 보는 것이다. 다음은 가장 흔한 실행시간 에러들이다:
176 | 177 |207 | 디버깅을 하기 위해 print 서술문을 사용할 때의 문제중 하나는 프로그램이 끝날 때 출력에 파 묻힐 수 있다는 것이다. 두 가지의 처리 방법이 있다: 출력을 간결하게 하라; 또는 프로그램을 간결하게 하라.
208 |209 | 출력을 간결하게 하려면, 별로 도움이 되지 않는 print 서술문들을 제거하거나 주석처리하면 된다. 또는 이해하기 쉽도록 그것들을 결합하거나 출력을 형식화 할 수 있다.
210 |211 | 프로그램을 간결하게 하려면, 여러가지 방법이 있다. 첫 째, 문제를 최소화하여 프로그램을 작동시킨다. 예를 들어, 배열을 정렬하고 있다면, 작은(small) 배열을 정렬하라. 프로그램이 사용자로부터 입력을 받는다면, 입력은 문제를 야기하므로 최소한으로 입력하라.
212 |213 | 두 번째, 프로그램을 깨끗하게 하라. 죽은 코드를 제거하고 프로그램을 재조직하여 되도록이면 쉽게 읽을 수 있도록 하라. 예를 들어, 문제가 프로그램에서 아주 깊숙히 내포된 부분에 있다고 의심된다면, 그 부분을 더 간결한 구조로 재작성해 보라. 거대한 함수가 의심되면, 그것을 더 작은 함수들로 쪼개어 따로따로 테스트 해보라.
214 |215 | 가끔 최소한의 테스트 사례를 찾는 과정에서 버그를 발견하기도 한다. 한 프로그램이 어떤 상황에서는 작동하고 다른 상황에서는 그렇지 않다면, 그것을 보고 무엇이 진행되고 있는지 실마리를 찾을 수 있다.
216 |217 | 비슷하게, 코드 조각을 약간만 재작성하면 미묘한 버그들을 찾아내는데 도움을 받을 수 있다. 변경을 하면서 프로그램에 영향을 미치지 않을 거라고 생각했지만 영향을 미치고 있다면 그것이 바로 실마리 정보가 될 수 있다.
218 | 219 |222 | 어떤 면에서 의미구조 에러는 디버그하기가 가장 어려운 형태이다. 왜냐하면 컴파일러와 실행시간 시스템은 무엇이 잘못되었는지 어떠한 정보도 제공하지 않기 때문이다. 여러분이 아는 것은 오직 프로그램이 무엇을 했어야 하는지와 그리고 그 프로그램이 그 일을 하지 않는다는 것을 알 뿐이다.
223 |224 | 첫 번째 단계는 프로그램 텍스트와 여러분이 보고 있는 행위 사이에 연결을 짓는 것이다. 프로그램이 실제로 무엇을 하고 있는지에 대한 가설이 필요하다. 연결을 어렵게 하는 것중의 하나는 컴퓨터가 너무 빠르다는 것이다.
225 |226 | 종종 프로그램의 속도를 인간의 속도 정도로 늦출 수 있었으면 하고 바랄 것이다. 그리고 어떤 디버거로는 그렇게 할 수 있다. 그러나 배치가 잘 되도록 print 서술문을 삽입하는 데 걸리는 시간이 디버거를 설정하고 정지점을 삽입하고 제거하면서 에러가 발생한 곳까지 프로그램을 "진행시키는" 시간과 비교하여 짧은 경우가 많다.
227 | 228 |스스로에게 이러한 질문을 물어 보아야 한다:
231 | 232 |242 | 프로그래밍을 하기 위해서 프로그램이 작동하는 방법에 대한 정신적 모형을 가질 필요가 있다. 예상치 못한 행위를 하는 프로그램을 작성한다면, 상당부분의 문제는 그 프로그램에 있지 않다; 문제는 여러분의 정신적 모형에 있는 것이다.
243 | 244 |245 | 여러분의 정신적 모형을 교정하는 가장 좋은 방법은 프로그램을 구성요소들로 쪼개어 (보통은 함수와 메쏘드이다) 각 구성요소를 독립적으로 테스트하는 것이다. 여러분의 정신 모형과 현실사이에 불일치를 발견하기만 하면, 문제를 해결할 수 있다.
246 | 247 |248 | 물론, 구성요소들을 구축하고 테스트하면서 프로그램을 개발해야 할 것이다. 혹시 에러에 마주칠지라도 옳지 않다고 여겨지는 코드는 아주 적은 양의 새로운 코드만이 되어야 할 것이다.
249 | 250 |252 | 복잡한 표현식을 작성하는 것은 읽을 수 있기만 하다면 괜찮다. 그러나 그것은 디버그하기가 어려울 수 있다. 복잡한 표현식을 임시 변수에다 일련의 할당문들로 쪼개는 것이 때로는 좋은 생각이다.
253 | 254 |예를 들면:
255 | 256 | 257 |self.hands[i].addCard (self.hands[self.findNeighbor(i)].popCard())
258 |
이것은 다음과 같이 작성될 수 있다:
263 | 264 | 265 | neighbor = self.findNeighbor (i)
266 |
pickedCard = self.hands[neighbor].popCard()
267 |
self.hands[i].addCard (pickedCard)
268 |
271 | 명시적인 버전이 읽기가 더 쉽다. 그 이유는 변수 이름 자체가 추가로 문서를 제공하기 때문이다. 명시적인 버전은 디버그하기가 더 쉽기도 하다. 그 이유는 간접적인 변수들의 유형을 점검할 수 있고 그 값들을 출력할 수 있기 때문이다.
272 |
273 | 거대한 표현식에서 일어날 수 있는 또 다른 문제는 평가의 순서가 예상한대로가 아닐 수가 있다는 것이다. 예를 들어, 다음의 표현식
파이썬으로 번역한다면 다음과 같이 작성할지도 모른다:x 2 pi
y = x / 2 * math.pi;
277 |
280 | 곱셈과 나눗셈이 같은 우선순위를 가지고 있으며 왼쪽에서 오른쪽으로 평가되기 때문에 이것은 옳지 않다. 그래서 이 표현식은 x pi / 2이라고 계산한다.
281 |282 | 표현식을 디버그하는 좋은 방법은 괄호를 추가하여 평가의 순서를 명시하는 것이다:
283 | 284 | 285 | y = x / (2 * math.pi);
286 |
291 | 평가의 순서를 확신할 수 없을 때마다 괄호를 사용하라. 그 프로그램은 (여러분이 의도하는 바를 실행한다는 의미에서) 정확하게 될 뿐만 아니라, 또한 우선순위의 규칙을 기억하지 못하는 다른 사람들이 더욱 읽기 쉬울 것이다.
292 | 293 |296 | 복잡한 표현식을 가진 return 서술문이 있다면, 반환되기 전에 반환(return) 값을 출력해 볼 기회가 없다. 바로 여기에서 임시 변수를 사용할 수 있다. 예를 들어 다음과 같은 것 대신에:
297 | 298 | 299 |return self.hands[i].removeMatches()
300 |
다음과 같이 작성할 수 있다:
305 | 306 | 307 |count = self.hands[i].removeMatches()
308 |
return count
309 |
311 | 이제 반환전에 count를 출력할 (또는 인쇄할) 기회가 있다.
312 | 313 |316 | 먼저, 컴퓨터로부터 잠시동안 떨어져 있도록 하라. 컴퓨터는 뇌에 영향을 미치는 전파를 발산하여, 다음의 증상을 유발한다:
317 | 318 |327 | 이러한 증상들 때문에 고민하는 자신을 발견한다면 일어나 밖으로 나아가 산책을 하라. 마음이 가라 앉으면 프로그램에 대해 생각하라. 그 프로그램은 무엇을 하고 있는가? 어떤 원인이 그러한 행위를 일으킬 가능성이 있는가? 가장 마지막으로 프로그램이 제대로 작동했던 때는 언제인가. 그리고 그 다음에 여러분은 무엇을 했는가?
328 |329 | 어떤 때는 버그를 발견하는 데 시간이 많이 걸린다. 가끔 컴퓨터에서 떨어져서 마음을 편하게 할 때 버그를 발견하기도 한다. 버그를 발견하기에 제일 좋은 장소는 열차안, 샤워할 때, 그리고 잠들기 바로 전 침대에 누워 있을 때이다.
330 | 331 |333 | 그럴 것이다. 심지어는 특급 프로그래머들도 가끔씩 고민에 빠진다. 어떤 때는 너무 오랫동안 프로그램 작업을 하기 때문에 에러가 눈에 보이지 않을 정도이다. 눈에 상쾌한 바람을 한 번 쐬어 주는 것이 유일한 해결책이다.
334 |335 | 다른 누군가를 부르기 전에, 먼저 이 부록에서 기술한 테크닉을 다 써 보았는지 확인하라. 프로그램은 가능한 한 간단해야 하며, 입력은 에러를 야기하므로 최소한으로 하여 작업해야 한다. 적절한 장소에 print 서술문을 배치해야 한다 (그리고 그 출력들은 이해가 되어야 한다). 문제를 간결하게 기술할 수 있을 만큼 충분히 이해해야 한다.
336 |337 | 누군가를 불러서 도움을 요청한다면, 확실하게 그가 필요한 정보를 주어라:
338 |347 | 버그를 발견하면, 어떻게 했으면 그 버그를 더 빨리 발견할 수 있었을지 잠시 생각해 보라. 다음에 비슷한 어떤 것을 발견하면 그 버그를 더 빨리 발견할 수 있을 것이다.
348 |349 | 기억하라, 목표는 프로그램을 그저 작동하게 하는 데에 있지 않다. 목표는 프로그램을 작동하게 하는 법을 배우는 것이다.
350 | 351 |352 |
![]() |
356 | ![]() |
357 | ![]() |
358 | ![]() |
359 | ![]() |
360 | ![]() |
361 | ![]() |
362 |
![]() |
37 | ![]() |
38 | ![]() |
39 | ![]() |
40 | ![]() |
41 | ![]() |
42 | ![]() |
43 |
52 | 객체-지향 프로그래밍 언어 덕분에 프로그래머는 새로운 데이타 유형을 정의하여 내장 데이타 유형과 아주 비슷하게 행동하도록 만들 수 있다. Fraction이라는 클래스를 구축하여 내장 수치 형, 정수, 배정도 정수, 부동소수점수와 상당히 유사하게 작동시켜 보면서 이런 능력을 탐험해 보자.
53 |54 | 분수는 유리수로도 알려져 있는데, 5/6처럼 정수의 비율로서 표현할 수 있다. 위의 숫자를 분자라고 하고 아래의 숫자를 분모라고 부른다.
55 |56 | 먼저 Fraction 클래스를 정의해 보자. 이 클래스는 생성하고자 하는 분수에 대하여 정수 분모와 분자를 제공하는 구성자 메쏘드를 가진다:
57 | 58 | 59 |class Fraction:
60 |
def __init__(self, numerator, denominator=1):
61 |
self.numerator = numerator
62 |
self.denominator = denominator
63 |
66 | 분모는 선택적인 변수이다. 분수가 (3과 같은) 단지 하나의 매개변수로 생성된다면 그 분수는 3/1과 동등한 분수가 된다.
67 |68 | 이제 분수 하나를 생성하고 그것을 변수에 할당하자. 그 Fraction 클래스를 Fraction.py라고 부르는 파일에 저장해 두었다고 가정하자. 그것을 테스트 하려면 파이썬 안으로 반입하면 된다:
69 | 70 | 71 |>>> from Fraction import *
72 |
>>> spam = Fraction(5,6)
73 |
76 | 다음 단계는 __str__ 메쏘드를 작성하여 의미있는 방식으로 분수를 표시하는 것이다. 여기에서는 "분자/분모"의 형태가 자연스럽다:
77 | 78 | 79 |class Fraction:
80 |
...
81 |
def __str__(self):
82 |
return "%d/%d" % (self.numerator, self.denominator)
83 |
분수를 인쇄하면 다음을 결과로 맞이한다:
88 | 89 | 90 |>>> spam = Fraction(5,6)
91 |
>>> print "The fraction is", spam
92 |
The fraction is 5/6
93 |
100 | 분수에 대하여 정상적인 덧셈, 뺄셈, 곱셈, 그리고 나눗셈 연산을 적용할 수 있기를 원한다. 이렇게 하려면 Fraction 객체의 연산자들을 덮어쓰면 된다.
101 |102 | 구현하기가 가장 쉽기 때문에 곱셈부터 시작해 보겠다. 먼저 두 분수를 곱하면 새로운 분수가 만들어지는데 이 분수의 분자는 원래 분자들을 곱한 것이고 분모는 원래 분모들을 곱한 것이다. __mul__은 * 연산자를 덮어쓰는 함수들을 위해 파이썬이 사용하는 이름이다:
103 | 104 | 105 |class Fraction:
106 |
...
107 |
def __mul__(self, object):
108 |
return Fraction(self.numerator*object.numerator,
109 |
self.denominator*object.denominator)
110 |
115 | 이제 두 분수의 곱을 계산해 보면 이것을 테스트해 볼 수 있다:
116 | 117 | 118 |>>> print Fraction(5,6) * Fraction(3,4)
119 |
15/24
120 |
>>>
121 |
124 | 그러나 다른 것 두가지를 고려해 볼 수 있다. 첫째, 정수에 분수를 곱하기를 원할 수 있다. 이것을 처리하는 한 방법은 그 정수를 그에 동등한 분수로 변환하는 것이다. type 함수를 사용하면 other이 정수인지 테스트해 볼 수 있고 정수라면 그것을 분수로 변환할 수 있다.
125 | 126 | 127 |class Fraction:
128 |
...
129 |
def __mul__(self, other):
130 |
if type(other) == type(5):
131 |
other = Fraction(other)
132 |
return Fraction(self.numerator * other.numerator,
133 |
self.denominator * other.denominator)
134 |
이제 분수와 정수를 곱하는 것이 작동한다. 그러나 오직 그 분수가 왼쪽에 있는 피연산자일 때만 작동한다:
137 | 138 | 139 |>>> print Fraction(5,6) * 4
140 |
20/6
141 |
>>> print 4 * Fraction(5,6)
142 |
TypeError: __mul__ nor __rmul__ defined for these operands
143 |
148 | 곱셈과 같은 이항 연산자들을 평가하기 위해, 파이썬은 먼저 왼쪽의 피연산자를 점검하여 두 번째 연산자의 유형을 지원하는 __mul__을 제공하는지 살펴본다. 이 경우, 내장 정수 연산자는 Fraction를 지원하지 않는다.
149 |150 | 다음으로 파이썬은 오른쪽 피연산자를 점검하여 첫 번째 연산자의 형을 지원하는 __rmul__을 제공하는지 살펴본다. 이 경우, __rmul__이 제공되지 않지만 다음과 같이 제공하면 된다:
151 | 152 | 153 |class Fraction:
154 |
...
155 |
__rmul__ = __mul__
156 |
159 | 이 할당은 __rmul__이 __mul__과 같다고 말해준다. 이제 4 * Fraction(5,6)을 평가하면, 파이썬은 __rmul__을 Fraction 객체에 요청하고 4를 매개변수로 건넨다:
160 | 161 | 162 |>>> print 4 * Fraction(5,6)
163 |
20/6
164 |
171 | 덧셈은 곱셈보다 더 복잡하다. 그러나 그렇게 나쁜것은 아니다. a/b와 c/d의 합은 분수 (a*d+c*b)/b*d이다.
172 |173 | 곱셈 코드를 모델로 삼아 __add__와 __radd__를 작성할 수 있다:
174 | 175 | 176 |class Fraction:
177 |
...
178 |
def __add__(self, object):
179 |
if type(object) == type(5):
180 |
object = Fraction(object)
181 |
return Fraction(self.numerator * object.denominator +
182 |
self.denominator * object.numerator,
183 |
self.denominator * object.denominator)
184 |
185 |
__radd__ = __add__
186 |
189 | 이러한 메쏘드들을 Fraction과 정수로 테스트할 수 있다.
190 | 191 | 192 |>>> print Fraction(5,6) + Fraction(5,6)
193 |
60/36
194 |
>>> print Fraction(5,6) + 3
195 |
23/6
196 |
>>> print 2 + Fraction(5,6)
197 |
17/6
198 |
201 | 앞의 두 예제는 __add__를 요청한다; 마지막 예제는 __radd__를 요청한다.
202 | 203 |206 | 이전의 예제에서 5/6 + 5/6의 합을 계산하고 60/36을 얻었다. 그 값은 옳다. 그러나 해답을 표현하는 가장 좋은 방법은 아니다. 분수를 가장 단순한 항으로 약분(reduce)하려면, 분모와 분자를 최대 공약수(greatest common divisor (GCD))로 나눌 필요가 있는데, 여기에서 그것은 12이다. 그리하여 그 결과는 5/3이다.
207 |208 | 일반적으로 새로운 Fraction 객체를 만들 때마다 그 객체의 분모와 분자를 GCD로 약분해야 한다. 분수가 이미 약분되어 있다면 GCD는 1이다.
209 |210 | 유클리드는 m과 n 두 정수에 대한 GCD를 찾는 메쏘드 또는 알고리즘을 제시하였다:
211 | 212 |m이 n으로 나누어 떨어진다면 n이 그 GCD이다. 그렇지 않으면 그 GCD는 m을 n으로 나눈 나머지와 n과의 GCD이다.
213 | 214 |215 | 이러한 재귀적인 정의는 함수로 간결하게 표현할 수 있다:
216 | 217 | 218 |def gcd (m, n):
219 |
if m % n == 0:
220 |
return n
221 |
else:
222 |
return gcd(n, m%n)
223 |
228 | 첫 번째 줄에서 나머지(modulus) 연산자를 사용하여 나눗셈이 가능한지 점검한다. 마지막 즐에서도 나머지(modulus) 연산자를 사용하여 나눗셈의 나머지를 계산한다.
229 |230 | 231 | 지금까지 연산은 모두 그 결과로 분수(Fraction)를 새로 만들기 때문에 구성자를 수정하면 모든 결과를 약분할 수 있다:
232 | 233 | 234 |class Fraction:
235 |
def __init__(self, numerator, denominator=1):
236 |
g = gcd (numerator, denominator)
237 |
self.numerator = numerator / g
238 |
self.denominator = denominator / g
239 |
244 | 이제 분수(Fraction)를 만들 때마다 가장 단순한 형태로 약분된다:
245 | 246 | 247 |>>> Fraction(100,-36)
248 |
-25/9
249 |
>>>
250 |
253 | gcd 함수의 멋진 특징은 만약 분수가 음수라면 마이너스 표시가 항상 분자로 이동한다는 것이다.
254 | 255 |258 | Fraction 객체인 a와 b 두 개가 있고 a == b를 평가한다고 가정하자. 기본으로 구현된 ==는 얕은 동등성을 테스트한다, 그래서 a와 b가 같은 객체일 때만 참을 반환한다.
259 |260 | 더욱 가능성 있는 예를 들자면 a와 b가 같은 값을 가지면 참을 반환하기를 원한다---다시 말하면, 깊은 동등성 테스트를 원한다고 하자.
261 |262 | 비교를 하려면 분수에게 비교하는 법을 가르쳐 줄 필요가 있다. Card 객체에서 보았듯이 __cmp__ 메쏘드를 제공하면 비교 연산자를 덮어쓸 수 있다.
263 |264 | 관례적으로 __cmp__ 메쏘드는 만약 self가 other보다 작다면 음의 정수를 반환해야 하고, 같다면 0을 돌려주어야 하고, self가 other보다 크다면 양의 정수를 반환해야 한다.
265 |266 | 가장 간단하게 분수를 비교하는 방법은 양쪽으로-곱하는 것이다. 만약 a/b > c/d이라면, ad > bc이다. 이점을 염두에 두고 다음과 같이 __cmp__를 위한 코드를 살펴 보면:
267 | 268 | 269 |class Fraction:
270 |
...
271 |
def __cmp__(self, other):
272 |
diff = (self.numerator * other.denominator -
273 |
other.numerator * self.denominator)
274 |
return diff
275 |
280 | self가 other보다 크다면 diff는 양의 정수가 될 것이다. other가 더 크다면 diff는 음의 정수가 될 것이다. 같다면 diff는 0이다.
281 | 282 |286 | 물론 아직 끝난 것이 아니다. 아직도 __sub__를 덮어 씀으로써 뺄셈을 구현하고 __div__를 덮어씀으로써 나눗셈을 구현할 필요가 있다.
287 |288 | 그러한 연산들을 처리하기 위한 하나의 방법은 __neg__를 덮어씀으로써 보수(negation)를 구현하고 __invert__를 덮어씀으로써 역수(inversion)를 구현하는 것이다. 그러면 두 번째 피연산자를 보수로 만들고 더해서 뺄셈을 할 수 있고, 두 번째 피연산자를 역수로 만들고 곱해서 나눗셈을 할 수 있다.
289 |290 | 마지막으로 __rsub__와 __rdiv__를 제공해야 한다. 불행하게도 덧셈과 나눗셈에 사용한 것과 같은 꼼수를 사용할 수 없다. 왜냐하면 뺄셈과 나눗셈은 위치를 교환할 수 없기 때문이다. 피연산자의 순서는 차이를 만든다.
291 |292 | 사실 __neg__가 필요한 이유는 파이썬이 단항 부인을 처리할 수 있도록 하기 위한 것이다. 그렇지만 __rsub__와 __rdiv__는 __sub__와 __div__처럼 똑같이 단순하게 설정될 수 없다. 이러한 연산에 있어서, 피연산자들의 순서는 차이를 만들어 낸다.
293 |294 | __pow__를 덮어쓰면 분수의 지수승을 계산할 수 있다. 그러나 그 구현은 약간 꼼수적이다. 지수가 정수가 아니라면 그 결과를 분수(Fraction)로 보여주지 못할 수도 있다. 예를 들어, Fraction(2) ** Fraction(1,2)는 2의 제곱근이다. 이것은 무리수이다 (다시 말해 분수로 표현될 수 없다). 그래서 지극히 일반적인 버전으로 __pow__를 작성하기는 쉽지 않다.
295 |296 | Fraction 클래스에 대하여 또 다른 확장 하나를 생각해 볼 수도 있다. 지금까지는 분자와 분모가 정수라고 가정했다. 분자와 분모에 대하여 장정도 정수를 허용하고 싶을 수도 있다.
297 | 298 |연습으로, Fraction 클래스를 완전히 구현하라. 그래서 뺄셈, 나눗셈, 지수, 그리고 장정도 정수를 분자와 분모로 처리할 수 있도록 하라.
299 | 300 |316 |
![]() |
320 | ![]() |
321 | ![]() |
322 | ![]() |
323 | ![]() |
324 | ![]() |
325 | ![]() |
326 |
![]() |
37 | ![]() |
38 | ![]() |
39 | ![]() |
40 | ![]() |
41 | ![]() |
42 | ![]() |
43 |
52 | 여기서 어디로 가야 할까? 여러 방향으로 나아갈 수 있다. 파이썬 그 자체에 대한 지식을 확장하고 일반적인 컴퓨터 과학에 대한 지식 모두를 확장하는 방향으로 나아가야 한다.
53 |54 | 이 책에서는 파이썬 확장의 세계를 의도적으로 언급하지 않았으며 더 근본적인 메시지에 초점을 맞추었다. 그러나 다음 예들은 어떻게 또 파이썬이 이용될 수 있는지를 보여주는 사례들이다.
55 | 56 |75 | 다음은 필자들이 파이썬 언어에 대하여 더 읽기를 권장하는 재료들이다.
76 | 77 |98 | 더 읽어야할 재료로 다음에 제시하는 것들은 필자들이 즐겨 보는 책들을 상당부분 포함하고 있다. 제시된 책들은 좋은 프로그래밍 습관과 컴퓨터 과학 일반을 다루고 있다.
99 |122 |
![]() |
126 | ![]() |
127 | ![]() |
128 | ![]() |
129 | ![]() |
130 | ![]() |
131 | ![]() |
132 |
![]() |
37 | ![]() |
38 | ![]() |
39 | ![]() |
40 | ![]() |
41 | ![]() |
42 | ![]() |
43 |
57 | 사용자-정의 유형의 또 다른 예로서, Time이라고 부르는 클래스 하나를 정의해 하루의 시간을 기록하여 보겠습니다. 클래스 정의는 다음과 같이 보입니다:
58 | 59 | 60 |class Time:
61 |
pass
62 |
67 | Time 객체 하나를 새로 만들고 시와 분 그리고 초에 대한 속성들을 할당합니다:
68 | 69 | 70 |time = Time()
71 |
time.hours = 11
72 |
time.minutes = 59
73 |
time.seconds = 30
74 |
79 | Time 객체에 대한 상태 다이어그램은 다음과 같이 보입니다:
80 | 81 |연습으로, printTime 함수를 작성하여 Time 객체를 인자로 취하고 그 객체를 hours:minutes:seconds의 형태로 인쇄해 보세요.
84 | 85 |두 번째 연습문제로, 불리언 함수 after를 작성하여 Time 객체 t1과 t2를 인자로 취해 만약 시간상으로 t1이 t2보다 뒤에 온다면 참(1)을 반환하고 그렇지 않으면 거짓(0)을 반환하도록 해 보세요.
86 | 87 |91 | 다음 몇 섹션에서는 addTime이라고 부르는 함수를 두 개의 버전으로 작성하겠습니다. addTime 함수는 Time 두 개의 합을 계산합니다. 두 버전은 각각 두 가지 종류의 함수를 예시하여 보여줄 것입니다: 순수한 함수와 변경자가 그것입니다.
92 |93 | 다음은 대략적으로 작성된 addTime 버전입니다:
94 | 95 |def addTime(t1, t2):
96 |
sum = Time()
97 |
sum.hours = t1.hours + t2.hours
98 |
sum.minutes = t1.minutes + t2.minutes
99 |
sum.seconds = t1.seconds + t2.seconds
100 |
return sum
101 |
103 | 이 함수는 Time 객체 하나를 새로 만들고 그의 속성들을 초기화하며 그리고 새로 생성된 객체에 대한 참조점을 돌려줍니다. 이런 함수를 일컬어 순수한 함수(pure function)라고 부르는데 왜냐하면 매개변수로 건네지는 어떠한 객체도 변경하지 않으며 값을 화면에 표시한다든가 또는 사용자 입력을 얻는다든가 하는 다른 작용을 전혀 하지 않기 때문입니다.
104 |105 | 다음은 이 함수를 사용하는 법을 보여줍니다. Time 객체 두 개를 만들겠습니다: 하나는 currentTime으로 현재 시간을 담고 있습니다; 그리고 다른 하나는 breadTime으로 제빵사가 빵을 굽는데 걸린 시간을 담고 있습니다. 그리고 나서 addTime을 사용하여 언제 빵이 완성될지를 알아낼 것입니다. 아직 printTime 함수를 작성하지 못했다면, 다음 코드를 시도하기 전에 먼저 섹션 14.2를 살펴 보세요:
106 | 107 | 108 |>>> currentTime = Time()
109 |
>>> currentTime.hours = 9
110 |
>>> currentTime.minutes = 14
111 |
>>> currentTime.seconds = 30
112 |
113 |
>>> breadTime = Time()
114 |
>>> breadTime.hours = 3
115 |
>>> breadTime.minutes = 35
116 |
>>> breadTime.seconds = 0
117 |
118 |
>>> doneTime = addTime(currentTime, breadTime)
119 |
>>> printTime(doneTime)
120 |
122 | 이 프로그램의 출력결과는 12:49:30이고 옳은 답입니다. 반면에, 그 결과가 옳지 않은 경우가 있습니다. 어떤 것이 있는지 그 중에 하나를 짐작할 수 있나요?
123 |124 | 문제는 이 함수가 초나 분의 숫자가 60을 초과해서 증가하는 경우를 다루지 못한다는 것입니다. 그런 경우가 발생하면 꽉 채워진 초를 분의 자리로 "이동(carry)"시키거나 또는 다 채워진 분을 시간의 자리로 "이동(carry)"시켜야 합니다.
125 |126 | 다음은 이 함수를 수정한 두 번째 버전입니다:
127 |
128 | def addTime(t1, t2):
129 |
sum = Time()
130 |
sum.hours = t1.hours + t2.hours
131 |
sum.minutes = t1.minutes + t2.minutes
132 |
sum.seconds = t1.seconds + t2.seconds
133 |
134 |
if sum.seconds >= 60:
135 |
sum.seconds = sum.seconds - 60
136 |
sum.minutes = sum.minutes + 1
137 |
138 |
if sum.minutes >= 60:
139 |
sum.minutes = sum.minutes - 60
140 |
sum.hours = sum.hours + 1
141 |
142 |
return sum
143 |
145 | 이 함수는 옳기는 하지만 코드가 커지기 시작합니다. 나중에 더 짧은 코드를 만들어 내는 대안을 제시하겠습니다
146 | 147 |151 | 가끔은 함수가 매개변수로 받아들이는 객체들 중의 하나 이상을 변경하는 것이 유용할 때가 있습니다. 보통, 호출자는 건네주는 객체들에 대한 참조점을 유지하고 있습니다. 그래서 그 함수가 어떤 변경을 해도 호출자가 모두 볼 수 있습니다. 이런식으로 작동하는 함수를 변경자(modifiers)라고 부릅니다..
152 |153 | increment는 초를 주어진 개수만큼 Time 객체에 더하기 때문에 아주 자연스럽게 변경자로 작성될 수 있을 것입니다. 함수를 대충 작성해 보면 다음과 같이 보입니다:
154 |
155 | def increment(time, seconds):
156 |
time.seconds = time.seconds + seconds
157 |
158 |
if time.seconds >= 60:
159 |
time.seconds = time.seconds - 60
160 |
time.minutes = time.minutes + 1
161 |
162 |
if time.minutes >= 60:
163 |
time.minutes = time.minutes - 60
164 |
time.hours = time.hours + 1
165 |
170 | 첫 번째 줄은 기본 연산을 수행합니다; 나머지는 이전에 본 특수한 경우들을 다루고 있습니다.
171 | 172 |173 | 다음 함수는 올바른가요? 만약 매개변수 secs가 60보다 훨씬 더 크다면 무슨 일이 일어납니까? 그런 경우에는 한 번의 자리이동만으로는 충분하지 않습니다; seconds가 60보다 작아질 때까지 계속해서 자리이동을 해야 합니다. 한가지 해결책은 if 서술문을 while 서술문으로 대치하는 것입니다:
174 | 175 | 176 |
177 | def increment(time, secs):
178 |
time.seconds = time.seconds + secs
179 |
180 |
while time.seconds >= 60:
181 |
time.seconds = time.seconds - 60
182 |
time.minutes = time.minutes + 1
183 |
184 |
while time.minutes >= 60:
185 |
time.minutes = time.minutes - 60
186 |
time.hours = time.hours + 1
187 |
189 | 이제 이 함수는 올바릅니다. 그러나 최상의 효율적인 해결책은 아닙니다.
190 |연습으로, 이 함수를 재작성하여 회돌이를 전혀 가지지 않도록 해보세요.
191 |두 번째 연습으로, increment를 순수한 함수로 재작성해 보세요. 그리고 두 버전 모두에 대해 함수 호출을 작성해 보세요.
192 | 193 |197 | 변경자로 할 수 있는 모든 것은 순수한 함수로도 역시 모두 할 수 있습니다. 사실, 어떤 프로그래밍 언어들은 오로지 순수한 함수만을 허용합니다. 순수한 함수를 사용하는 프로그램이 변경자를 사용하는 프로그램보다 더 빠르게 개발되고 에러를 덜 야기한다는 증거들이 있습니다. 그럼에도 불구하고, 때로는 변경자가 편리하며 어떤 경우에는 함수적 프로그램이 비효율적입니다.
198 | 199 |200 | 일반적으로, 그럴만한 이유가 있을 때마다 되도록이면 순수한 함수를 작성하기를 권장하며 변경자는 어쩔 수 없는 이점이 있을 때만 고려해 보시기를 권고합니다. 이러한 접근법을 함수적 프로그래밍 스타일이라고 부를 수도 있습니다.
201 | 202 |206 | 이 장에서는 원형 개발(prototype development)이라고 부르는 프로그램 개발에 대한 접근법을 예시해 보여 주었습니다. 각각의 경우에 기본적인 계산을 수행하는 대략적인 초안(또는 원형(prototype))을 작성해서 그 원형을 몇몇 사례에 대하여 테스트 해보고 결점들이 발견되면 교정하였습니다.
207 | 208 |209 | 이런 접근법이 효과적일 수 있음에도 불구하고 불필요하게 복잡한 코드를 만들게 될 가능성이 있습니다---왜냐하면 특수한 경우들을 많이 다루기 때문입니다---그리고 신뢰할 수 없는 코드가 될 가능성이 있습니다---왜냐하면 모든 에러를 발견했는지 알 수 없기 때문입니다.
210 | 211 |212 | 또 다른 대안은 계획된 개발(planned development)이라고 하는데 문제를 더 높은-수준에서 통찰할 수 있기 때문에 훨씬 더 쉽게 프로그래밍을 할수 있습니다. 이 경우에는 Time 객체가 실제로는 60진법의 3-자리 수라는 것을 통찰할 수 있습니다! second 구성요소는 "1의 자리"이고 minute 구성요소는 "60의 자리"이며 그리고 hour 구성요소는 "3600의 자리"입니다.
213 | 214 |215 | addTime과 increment를 작성할 때, 60진법을 사용하여 효과적으로 덧셈을 하고 있었습니다. 60진법을 사용했던 이유는 한 자리에서 다른 자리로 올림을 해야 했기 때문입니다.
216 | 217 |218 | 이렇게 관찰해 보면 전체 문제에 대해서 또 다른 접근법을 생각해 볼 수 있습니다---Time 객체를 한 자리 수로 변환할 수 있으며 컴퓨터가 숫자들을 수리적으로 처리하는 법을 알고 있다는 사실을 이용할 수 있습니다. 다음 함수는Time 객체를 정수로 변환합니다:
219 | 220 | 221 |def convertToSeconds(t):
222 |
minutes = t.hours * 60 + t.minutes
223 |
seconds = minutes * 60 + t.seconds
224 |
return seconds
225 |
이제, 필요한 모든 것은 정수를 Time 객체로 변환하는 방법입니다:
230 | 231 | 232 |def makeTime(secs):
233 |
time = Time()
234 |
time.hours = secs/3600
235 |
secs = secs - time.hours * 3600
236 |
time.minutes = secs/60
237 |
secs = secs - time.minutes * 60
238 |
time.seconds = secs
239 |
return time
240 |
245 | 한 진법에서 다른 진법으로 변환하는 이 테크닉이 옳다는 확신을 하려면 약간 더 생각해 볼 필요성이 있을 것입니다. 확신한다고 생각되면 이런 함수들을 사용해서 addTime을 재작성할 수 있습니다:
246 |
247 | def addTime(t1, t2):
248 |
seconds = convertToSeconds(t1) + convertToSeconds(t2)
249 |
return makeTime(seconds)
250 |
252 | 이 버전이 원래 버전보다 훨씬 더 짧습니다. 그리고 (보통때 처럼 호출되는 함수들이 옳다는 가정하에) 이 버전이 옳다는 것을 훨씬 더 쉽게 보여줄 수 있습니다.
253 | 254 |연습으로, 같은 방식으로 increment 함수를 재작성해 보세요.
255 | 256 |260 | 어떤 면에서 60진법을 10진법으로 그리고 그 반대로 변환하는 것이 단순히 시간을 다루는 것보다 더 어렵습니다. 진법변환이 더 추상적이며; 시간을 다루는데는 직관이 더 좋기 때문입니다.
261 | 262 |263 | 그러나 시간을 60진수로 다룰 통찰력이 있고 변환 함수(convertToSeconds와 makeTime)를 작성하는데 투자한다면, 우리가 얻는 프로그램은 더 짧고 읽고 디버그하기에 더 쉽고, 더욱 신뢰할 만할 것입니다.
264 | 265 |266 | 또 나중에 특징들을 새로 추가하기도 더 쉽습니다. 예를 들어, 두 개의 Time 사이를 빼서 그들 사이의 기간을 계산한다고 상상해 보세요. 초보적인 접근법은 빌림이 있는 뺄셈을 구현하는 것이 될 것입니다. 변환 함수들을 사용하게 되면 더 쉽게 교정될 수 있으며 그리고 교정에 성공할 가능성이 더욱 높을 것입니다.
267 | 268 |269 | 묘하게도 어떤 때는 문제를 더 어렵게(즉 더욱 일반적으로) 만들면 더 쉽게 해결할 수 있게 됩니다. (왜냐하면 특수한 경우들이 더 적으며 에러의 가능성도 더 적기 때문입니다).
270 | 271 |275 | 문제 하나에 대한 구체적인 해결책과는 대조적으로, 한 부류의 문제들에 대하여 일반적인 해결책을 작성한다면 알고리즘(algorithm)을 작성한 것이 됩니다. 이전에 이 단어를 언급한 바 있지만 신중하게 정의하지는 않았습니다. 알고리즘(algorithm)을 정의하기는 쉽지 않습니다. 그래서 여러 접근법을 시도해 보겠습니다.
276 | 277 |먼저, 알고리즘이 아닌 것을 생각해 봅시다. 한-자리 수 곱셈을 배울 때 아마도 구구단을 암기했을 것입니다. 요컨데, 100가지 구체적인 해결책을 암기했던 것입니다. 그런 종류의 지식은 알고리즘적이 아닙니다.
278 | 279 |280 | 그러나 만약 여러분이 "게으르다면(lazy)", 약간의 꼼수를 배워서 속일 수 있었을 것입니다. 예를 들어, n과 9의 곱을 찾으려면, n-1을 첫 번째 자리로 그리고 10-n을 두 번째 자리로 쓸 수 있습니다. 이런 꼼수는 9와 한 자리수 곱셈을 해결하는 일반적인 해결책입니다. 그것이 바로 알고리즘입니다!
281 | 282 |283 | 비슷하게, 덧셈에서 올림과 뺄셈에서 빌림 그리고 배정도 나눗셈에 대하여 배운 테크닉들은 모두 알고리즘입니다. 알고리즘의 특징중 하나는 전혀 지능을 동반할 필요가 없다는 것입니다. 알고리즘은 각 단계가 간단한 규칙의 모둠에 의거하여 마지막까지 이어지는 기계적인 처리과정입니다.
284 | 285 |286 | 알고리즘을 실행하는 법을 배우면서 그 많은 시간을 학교에서 낭비하는 것은 안타까운 일입니다. 알고리즘을 실행하는 법은 문자 그대로 아무런 지능도 요구하지 않습니다.
287 | 288 |289 | 반면에, 알고리즘을 디자인하는 과정은 재미있고 지적인 도전이며 그리고 프로그래밍이라고 부르는 것의 핵심부분입니다.
290 | 291 |292 | 사람들이 자연스럽게, 아무 어려움 없이 또는 의식적인 생각을 하지 않고서 하는 것들은 알고리즘적으로 표현하기가 정말 어렵습니다. 자연어에 대한 이해가 좋은 예입니다. 누구나 자연스럽게 말합니다. 그러나 지금까지 아무도 어떻게 자연스럽게 말할 수 있는지 적어도 알고리즘의 형태로는 설명할 수 없었습니다.
293 | 294 |print_time
함수를 작성하라. 이 함수는 Time
객체를 인자로 받아 hours:minutes:seconds
의 형태로 인쇄한다.
324 | after
를 작성하라. 이 함수는 두 개의 Time
객체 t1
과 t2
를 인자로 받아 시간적으로 t1
이 t2
다음에 오면 True
를 돌려주고 그렇지 않으면 False
를 돌려준다.
328 | increment
함수가 회돌이를 하나도 포함하지 않도록 재작성하라.
332 | increment
를 순수한 함수로 재작성하고, 두 버전 모두에 대하여 함수 호출을 작성하라.
336 | 340 |
![]() |
344 | ![]() |
345 | ![]() |
346 | ![]() |
347 | ![]() |
348 | ![]() |
349 | ![]() |
350 |
![]() |
37 | ![]() |
38 | ![]() |
39 | ![]() |
40 | ![]() |
41 | ![]() |
42 | ![]() |
43 |
56 | 지금까지 살펴 본 데이타 형들은 그 데이타 형을 어떻게 구현될 지 완전하게 지정한다는 의미에서 모두 구체적인 것입니다. 예를 들어 Card 클래스는 두 개의 정수를 사용하여 하나의 카드를 표현합니다. 그 때 연구한 바와 같이 그것만이 카드를 표현하는 유일한 방법은 아닙니다; 다른 방식의 구현이 많이 있습니다.
57 | 58 |추상적인 데이타 유형(abstract data type (ADT))는 한 모둠의 연산(즉 메쏘드)과 그 연산의 의미구조(무엇을 할지)는 지정하지만 그 구현은 지정하지 않습니다. 이런 이유로 추상적이 됩니다.
59 | 60 |왜 ADT는 유용한가?
61 | 62 |74 | ADT에 대하여 논의할 때 종종 클라이언트(client)와 프로바이더(provider)로 구분합니다. 클라이언트는 ADT를 사용하는 코드이고 프로바이더는 ADT를 구현하는 코드입니다.
75 | 76 | 77 | 78 |81 | 이 장에서는 일반적인 ADT 하나, 즉 stack을 살펴 보겠습니다. 스택은 집단입니다. 다시 말해 여러 요소들을 포함하고 있는 데이타 구조를 뜻합니다. 지금까지 본 다른 집단으로는 사전과 리스트가 있습니다.
82 | 83 |84 | ADT는 이 자료에 대해 수행될 수 있는 연산들로 정의되는데, 이것을 접근방식(interface)이라고 부릅니다. 스택에 대한 접근방식은 다음과 같은 연산으로 구성됩니다:
85 | 86 |103 | 종종 스택은 "후입 선출", 즉 LIFO 데이타 구조라고 불리우는데, 가장 마지막으로 추가된 항목이 제일 먼저 제거되기 때문입니다.
104 | 105 |108 | 파이썬이 제공하는 내장 리스트는 스택을 정의하는 연산과 유사합니다. 리스트의 접근방식은 정확하게 예상한대로는 아닙니다. 그러나 스택 ADT로부터 내장 연산으로 번역하는 코드를 작성할 수 있습니다.
109 | 110 |111 | 이런 코드를 스택 ADT의 구현(implementation)이라고 부릅니다. 일반적으로, 구현이란 접근방식에 대한 구문적 요구조건과 의미구조적 요구조건을 만족하는 메쏘드들의 모둠입니다.
112 | 113 |114 | 다음은 파이썬의 리스트를 사용하여 스택 ADT를 구현한 것입니다:
115 | 116 | 117 |class Stack :
118 |
def __init__(self) :
119 |
self.items = []
120 |
121 |
def push(self, item) :
122 |
self.items.append(item)
123 |
124 |
def pop(self) :
125 |
return self.items.pop()
126 |
127 |
def isEmpty(self) :
128 |
return (self.items == [])
129 |
Stack 객체는 items라는 이름의 속성을 가지는데 이것은 스택에 있는 항목들의 리스트입니다. 초기화 메쏘드는 items를 빈 리스트로 설정합니다.
134 |135 | 스택에 새로운 항목 하나를 올려 놓기 위해 push는 항목을 items에 추가합니다. 스택에서 항목 하나를 꺼내기 위해 pop은 똑 같은 이름의 list 메쏘드를 사용하여 리스트에서 마지막 항목을 제거해서 반환합니다.
136 | 137 |138 | 마지막으로 스택이 비었는지 점검하기 위해 isEmpty는 items를 빈 리스트와 비교합니다.
139 |140 | 이와 같이 간단한 구현에서는 메쏘드가 기존의 메쏘드를 단순히 호출하는 것으로 구성되어 있으므로, 이런 구현을 겉껍질(veneer)이라고 부릅니다. 실제 삶에서 겉껍질(veneer)은 좋은 품질의 나무로 된 얇은 코팅막으로서 가구-만들기에 사용되어 밑에 있는 조악한 품질의 나무를 감추어 줍니다. 컴퓨터 과학자는 이 은유법을 사용하여 작은 조각 코드를 기술합니다. 이것은 구현의 세부사항을 감추고 더 간단하고 더욱 표준적인 접근방식을 제공합니다.
141 | 142 |145 | 스택은 총괄 데이타 구조(generic data structure)입니다. 이는 어떤 종류의 항목도 거기에 추가할 수 있다는 것을 의미합니다. 다음 예제는 정수 두 개와 문자열 한 개를 스택에 눌러 넣습니다:
146 | 147 | 148 |>>> s = Stack()
149 |
>>> s.push(54)
150 |
>>> s.push(45)
151 |
>>> s.push("+")
152 |
157 | isEmpty와 pop을 사용하여 스택에 있는 모든 항목들을 제거하고 인쇄합니다:
158 | 159 | 160 |while not s.isEmpty() :
161 |
print s.pop(),
162 |
165 | 출력 결과는 + 45 54입니다. 다른 말로 하면, 오로지 스택을 이용하여 항목들을 거꾸로 인쇄합니다! 허용이 안되는 것은 아니지만, 그것은 리스트를 출력하기 위한 표준적인 방식이 아닙니다. 그러나 스택을 사용하면 놀랍도록 쉽게 처리할 수 있습니다.
166 |167 | 이 코드 조각을 섹션 17.4에 있는 printBackward의 구현과 비교해 보아야 합니다. 여기에서의 스택 알고리즘과 재귀적인 버전의 printBackward 사이에는 자연스럽게 비교 관계가 있습니다. 그 차이는 printBackward가 리스트를 순회하면서 실행시간 스택을 사용하여 노드들을 추적 유지하고, 재귀로부터 복귀하면서 그 노드들을 인쇄한다는 것입니다. 스택 알고리즘도 그와 똑 같은 일을 합니다. 다만 실행시간 스택 대신에 Stack 객체를 사용한다는 점만 빼고 말입니다.
168 | 169 |172 | 대부분의 프로그래밍 언어에서 수학적 표현식은 두 피연산자 사이에 있는 연산자로 작성됩니다, 1+2과 같이 말입니다. 이런 형식을 중위식(infix)이라고 부릅니다. 어떤 계산기는 후위식(postfix)이라는 다른 방식을 사용합니다. 후위식에서 연산자는 피연산자 뒤에 있습니다. 1 2 +와 같이 말입니다.
173 |174 | 후위식이 종종 유용한 이유는 스택을 사용하면 자연스럽게 후위 표현식을 평가할 수 있기 때문입니다:
175 | 176 |연습으로, 이 알고리즘을 1 2 + 3 * 표현식에 적용해 보세요.
193 | 194 |195 | 이 예제는 후위식의 장점중 하나를 보여줍니다---연산의 순서를 제어하기 위하여 괄호를 사용할 필요가 전혀 없습니다. 중위식으로 같은 결과를 얻으려면 (1 + 2) * 3과 같이 작성해야 할 것입니다.
196 | 197 |연습으로, 1 + 2 * 3과 동등한 후위 표현식을 작성해 보세요.
198 | 199 |203 | 이전의 알고리즘을 구현하려면 문자열을 순회하여 그것을 연산자와 피연산자로 쪼갤 수 있어야 합니다. 이 과정은 해석(parsing)의 한 예 입니다. 그리고 그 결과들은--- 분해된 문자열의 덩어리들 각각을 ---토큰(tokens)이라고 부릅니다. 제 1 장에서 이러한 단어들을 본 기억이 나실 것입니다.
204 |205 | 파이썬은 split 메쏘드를 string 모듈과 re (정규 표현식(regular expression)) 모듈 모두에 제공합니다. 다음 함수 string.split은 한 개의 문자를 구분자(delimiter)로 사용하여 문자열을 리스트로 분리합니다. 예를 들어:
206 | 207 | 208 |>>> import string
209 |
>>> string.split("Now is the time"," ")
210 |
['Now', 'is', 'the', 'time']
211 |
216 | 이 경우에 구분자는 공백 문자입니다. 그래서 문자열은 각 공백에서 나뉘어집니다.
217 |218 | 다음 re.split 함수는 더 강력해서, 이 함수를 사용하면 구분자(delimiter) 대신에 정규 표현식을 제공할 수 있습니다. 정규 표현식은 한 모둠의 문자열들을 지정하는 방법입니다. 예를 들어, [A-z]는 모든 문자들의 모둠이고 [0-9]는 모든 숫자들의 모둠입니다. ^연산자는 모둠을 부인합니다. 그래서 [^0-9]는 숫자가 아닌 모든 것의 모둠이며, 바로 이 모둠을 사용하여 후위 표현식을 분리하고자 합니다:
219 | 220 | 221 |>>> import re
222 |
>>> re.split("([^0-9])", "123+456*/")
223 |
['123', '+', '456', '*', '', '/', '']
224 |
229 | 인자들의 순서가 string.split과 다르다는 것을 주목하세요; 구분자는 문자열의 앞에 옵니다.
230 |231 | 결과 리스트에는 피연산자로 123과 456이 있으며 연산자는 *와 /가 있습니다. 또 피연산자 뒤에 삽입되는 두 개의 빈 문자열이 있습니다.
232 | 233 |236 | 후위 표현식을 평가하기 위해서 이전의 섹션에 있는 해석기와 그 이전 섹션에 있는 알고리즘을 사용하겠습니다. 단순함을 유지하기 위해 함께 시작할 평가기(evaluator)는 오직 +와 * 연산자만 구현합니다:
237 | 238 | 239 |def evalPostfix(expr):
240 |
import re
241 |
tokenList = re.split("([^0-9])", expr)
242 |
stack = Stack()
243 |
for token in tokenList:
244 |
if token == '' or token == ' ':
245 |
continue
246 |
if token == '+':
247 |
sum = stack.pop() + stack.pop()
248 |
stack.push(sum)
249 |
elif token == '*':
250 |
product = stack.pop() * stack.pop()
251 |
stack.push(product)
252 |
else:
253 |
stack.push(int(token))
254 |
return stack.pop()
255 |
260 | 첫 번째 조건은 공백과 빈 문자열을 처리합니다. 이후의 두 조건은 연산자(operators)를 처리합니다. 현재, 다른 모든 것은 반드시 피연산자라고 가정합니다. 물론, 잘못된 입력을 점검하고 에러 메시지를 보고하는 것이 더 좋겠지만 나중에 그것을 다루어 보겠습니다.
261 | 262 |263 | 다음 후위식 (56+47)*2 형태를 평가함으로써 이 평가기를 테스트해 봅시다:
264 | 265 | 266 |>>> print evalPostfix ("56 47 + 2 *")
267 |
206
268 |
이제 상당히 비슷하게 구현되었습니다.
271 | 272 |275 | ADT의 기본적인 목표중 하나는 ADT를 구현하는 코드를 작성하는 프로바이더의 관심과 그 ADT를 사용하는 클라이언트의 관심을 분리하는 것입니다. 프로바이더는 오직 구현이---그 ADT의 규격에 맞추어서---올바른 지에 대해서만 걱정하면 됩니다. 그 구현이 어떻게 사용될지는 걱정할 필요가 없습니다.
276 |277 | 반대로, 클라이언트는 ADT의 구현이 올바르다고 가정(assumes)합니다. 그리고 그 세부사항들은 걱정하지 않습니다. 파이썬의 내장 유형중 하나를 사용하면 오로지 클라이언트로서만 생각하면 되는 즐거움을 만끽할 수 있습니다.
278 | 279 |280 | 물론, ADT를 구현할 때 그것을 테스트하기 위한 클라이언트 코드도 작성할 필요가 있습니다. 이 경우에 둘 모두의 역할을 수행해야하고, 이것은 혼란스러울 수 있습니다. 어느 순간에나 어떤 역할을 수행하고 있는지를 추적 유지하도록 노력해야 합니다.
281 | 282 |후위식 알고리즘을 표현식 1 2 + 3 *
에 적용하라.
332 | 이 예제는 후위식의 장점중 하나를 보여준다 --- 즉 연산 순서를 제어하기 위하여 괄호를 사용할 필요가 없다. 중위식으로 같은 결과를 얻으려면 (1 + 2) * 3
으로 작성해야 한다.
1 + 2 * 3
과 동등한 후위식을 작성하라.
337 | 341 |
![]() |
345 | ![]() |
346 | ![]() |
347 | ![]() |
348 | ![]() |
349 | ![]() |
350 | ![]() |
351 |
![]() |
37 | ![]() |
38 | ![]() |
39 | ![]() |
40 | ![]() |
41 | ![]() |
42 | ![]() |
43 |
52 | 이 장에서는 두 개의 ADT를 표현해 보고자 합니다: 큐(Queue)와 우선순위 큐(Priority Queue)가 그것입니다. 실제 세계에서 큐(queue)는 서비스를 기다리는 고객들의 줄과 같습니다. 대부분의 경우, 줄에 있는 첫 번째 고객이 서비스해 줄 다음 고객입니다. 그렇지만, 예외가 있습니다. 공항에서 비행기가 바로 출발하는 고객들은 가끔 그 줄의 중간에서 탑승 수속을 받습니다. 슈퍼마켓에서 정중한 고객은 몇 개 안되는 물건을 가진 고객에게 자리를 양보합니다.
53 |54 | 누가 먼저 가야하는지를 결정하는 그 규칙을 줄서기 규칙(queueing discipline)이라고 부릅니다. 가장 간단한 줄서기 규칙은 FIFO라고 부르는데, "선-입-선-출(first-in-first-out)"을 뜻합니다. 가장 일반적인 줄서기 규칙은 우선순위 줄서기(priority queueing)입니다. 이 규칙에서 각 고객은 우선권을 할당받고, 도착 순서에 상관없이 최고순위의 우선권을 가진 고객이 먼저 서비스됩니다. 이것이 가장 일반적인 규칙이라고 말하는 이유는 우선순위가 어떤 것에라도 기초할 수 있기 때문입니다: 몇 시에 비행기가 이륙하는가; 얼마나 많은 채소를 고객이 가지고 있는가; 또는 그 고객이 얼마나 중요한가. 물론, 모든 줄서기 규칙이 "공정"한 것은 아닙니다. 그러나 공정함이란 보는 사람의 눈에 따라 다릅니다.
55 |56 | 큐 ADT와 우선순위 큐 ADT는 연산 모둠이 같으며 접근방식도 같습니다. 차이는 연산의 의미구조에 있습니다: 큐는 FIFO 정책을 사용합니다; (그 이름이 암시하는 바와 같이) 우선순위 큐는 우선순위 줄서기 정책을 사용합니다.
57 |58 | 대부분의 ADT에서와 마찬가지로 수 많은 방법으로 큐를 구현합니다. 큐는 항목들의 집단이므로 집단을 저장하는 모든 기본적인 메카니즘을 사용할 수 있습니다: 배열(arrays)이나 리스트(lists) 또는 벡터(vectors)가 그것입니다. 어떤 것을 선택할지는 부분적으로 수행속도에, 즉 원하는 연산을 수행하는데에 얼마나 걸리는가에 기초할 것입니다. 또 부분적으로 구현의 용이성에, 즉 얼마나 쉽게 구현할 수 있는가에 기초할 것입니다.
59 | 60 | 61 | 62 |큐 ADT는 다음의 연산으로 정의합니다:
66 | 67 |86 | 처음으로 보게될 큐 ADT는 연결 큐(linked queue)라고 부릅니다. 왜냐하면 이 큐는 연결된 Node 객체들로 구성되기 때문입니다. 다음과 같이 클래스를 정의합니다:
87 | 88 | 89 |class Queue:
90 |
def __init__(self):
91 |
self.length = 0
92 |
self.head = None
93 |
94 |
def isEmpty(self):
95 |
return (self.length == 0)
96 |
97 |
def insert(self, cargo):
98 |
node = Node(cargo)
99 |
node.next = None
100 |
if self.head == None:
101 |
# 리스트가 비어 있으면 새로운 노드가 처음으로 간다.
102 |
self.head = node
103 |
else:
104 |
# 리스트에서 마지막 노드를 찾는다.
105 |
last = self.head
106 |
while last.next: last = last.next
107 |
# 새로운 노드를 추가한다.
108 |
last.next = node
109 |
self.length = self.length + 1
110 |
111 |
def remove(self):
112 |
cargo = self.head.cargo
113 |
self.head = self.head.next
114 |
self.length = self.length - 1
115 |
return cargo
116 |
118 | isEmpty 메쏘드와 remove 메쏘드는 LinkedList의 isEmpty 메쏘드와 removeFirst 메쏘드와 동일합니다. insert 메쏘드는 새로운 것이고 약간 더 복잡합니다.
119 |120 | 새로운 항목을 리스트의 끝에 삽입하고 싶습니다. 큐가 비어 있으면 그냥 head가 그 새로운 노드를 가리키도록 설정하기만 하면 됩니다.
121 |122 | 그렇지 않으면, 리스트를 마지막 노드까지 순회하여 그 새로운 노드를 마지막에 붙입니다. 마지막 노드를 식별할 수 있습니다. 왜냐하면 그의 next 속성이 None이기 때문입니다.
123 |124 | 적절하게 만들어진 Queue 객체에는 불변량이 두 개 있습니다. length의 값은 큐에 있는 노드의 개수가 되어야 합니다. 그리고 마지막 노드의 next는 None과 동등하여야 합니다. 이 메쏘드가 이 두개의 불변량을 유지하는지 직접 확인해 보세요.
125 | 126 |129 | 메쏘드를 호출할 때 보통 구현의 세부사항에 대해서는 신경쓰지 않습니다. 그러나 꼭 알고 싶은 "세부사항" 하나가 있습니다---그 메쏘드가 가지는 수행속도의 특징이 그것입니다. 얼마나 오래 걸리는가 그리고 집단에서 항목의 개수가 증가함에 따라 실행시간은 어떻게 변하는가?
130 |131 | 먼저 remove를 살펴 봅시다. 여기에는 회돌이나 함수가 없습니다. 그래서 언제나 이 메쏘드의 실행시간은 동일하다는 것을 알 수 있습니다. 이러한 메쏘드를 상수-시간(constant-time) 연산이라고 부릅니다. 실제로, 이 메쏘드는 리스트가 비어 있을 때 조건 판단을 하는 몸체를 건너 뛰므로 약간 더 빠를 수도 있습니다. 그러나 그 차이는 큰 의미가 없습니다.
132 |133 | insert의 수행은 대단히 다릅니다. 일반적인 경우에는 리스트를 순회하여 마지막 요소를 찾아야 합니다.
134 |135 | 이 순회는 리스트의 길이에 비례하여 시간이 걸립니다. 실행시간은 길이에 대하여 선형적인 함수이므로 이러한 메쏘드를 선형 시간(linear time)이라고 부릅니다. 상수 시간에 비교하면 성능이 아주 나쁩니다.
136 | 137 | 138 | 139 |142 | 모든 연산이 상수시간에 실행될 수 있도록 큐 ADT를 구현하고자 합니다. 그렇게 하는 한 가지 방법은 큐 클래스를 수정하여 첫 번째 노드와 마지막 노드에 대한 참조점을 유지하도록 하는 것입니다. 다음 그림에서 보여지는 바와 같이 말입니다:
143 | 144 |ImprovedQueue 구현은 다음과 같이 보입니다:
147 | 148 | 149 |class ImprovedQueue:
150 |
def __init__(self):
151 |
self.length = 0
152 |
self.head = None
153 |
self.last = None
154 |
155 |
def isEmpty(self):
156 |
return (self.length == 0)
157 |
160 | 지금까지 유일하게 변경된 것은 last 속성뿐입니다. 이 속성은 insert와 remove 메쏘드에 사용됩니다:
161 | 162 | 163 |class ImprovedQueue:
164 |
...
165 |
def insert(self, cargo):
166 |
node = Node(cargo)
167 |
node.next = None
168 |
if self.length == 0:
169 |
# 리스트가 비어 있다면, 새 노드는 head와 last에 할당된다.
170 |
self.head = self.last = node
171 |
else:
172 |
# 마지막 노드를 찾는다
173 |
last = self.last
174 |
# 새 노드를 추가한다
175 |
last.next = node
176 |
self.last = node
177 |
self.length = self.length + 1
178 |
181 | last는 마지막 노드를 추적 유지하므로 마지막 노드를 탐색할 필요가 없습니다. 결과적으로 이 메쏘드는 상수시간입니다.
182 |183 | 속도의 증가에는 치루어야 할 대가가 따릅니다. remove에 특별한 케이스를 추가하여 마지막 노드가 제거되면 last를 None으로 설정하도록 해야 합니다:
184 | 185 | 186 |class ImprovedQueue:
187 |
...
188 |
def remove(self):
189 |
cargo = self.head.cargo
190 |
self.head = self.head.next
191 |
self.length = self.length - 1
192 |
if self.length == 0:
193 |
self.last = None
194 |
return cargo
195 |
198 | 이 구현은 연결 큐(Linked Queue) 구현보다 더 복잡합니다. 그리고 그것이 옳다는 것을 보여주기는 더욱 어렵습니다. 장점이라면 목적을 달성했다는 것입니다---insert와 remove모두 상수-시간 연산입니다.
199 | 200 |연습으로, 파이썬의 리스트를 사용하여 연결 큐 ADT의 구현을 작성해 보세요. 이 구현의 수행속도를 일정 길이의 큐에 대하여 ImprovedQueue와 비교해 보세요.
201 | 202 |205 | 우선순위 큐 ADT는 큐 ADT와 접근방식이 같습니다. 그러나 의미구조가 다릅니다. 다시, 그 접근방식을 다음에 보여드립니다:
206 | 207 |224 | 의미구조의 차이는 큐로부터 제거되는 그 항목이 반드시 제일 처음에 추가되었던 항목이어야 할 필요는 없다는 것입니다. 오히려 그것은 큐에서 최고 우선순위를 가지는 항목입니다. 우선순위 큐 구현은 우선순위가 무엇인지 그리고 각각의 항목을 어떻게 비교할지를 지정하지 않습니다. 그것은 큐에 있는 항목들에 달려 있습니다.
225 |226 | 예를 들어, 만약 큐에 있는 항목들이 이름을 가진다면, 항목들을 알파벳 순서로 선택할 수 있습니다. 만약 볼링 점수라면, 고득점에서 저득점 순서로 선택할 수 있습니다, 그러나 만약 골프 점수라면, 저득점에서 고득점 순서로 선택할 것입니다. 큐에 있는 항목들을 비교할 수 있는 한, 최고 우선순위를 가진 항목을 찾아서 제거할 수 있습니다.
227 |228 | 다음 우선순위 큐(Priority Queue) 구현은 항목들을 큐에 담고 있는 파이썬 리스트를 속성으로 가집니다.
229 | 230 | 231 |class PriorityQueue:
232 |
def __init__(self):
233 |
self.items = []
234 |
235 |
def isEmpty(self):
236 |
return self.items == []
237 |
238 |
def insert(self, item):
239 |
self.items.append(item)
240 |
245 | 초기화 메쏘드인 isEmpty와 insert는 모두 리스트 연산에 대한 겉껍질(veneers)입니다. 진짜 흥미로운 메쏘드는 remove입니다:
246 | 247 | 248 |class PriorityQueue:
249 |
...
250 |
def remove(self):
251 |
maxi = 0
252 |
for i in range(1,len(self.items)):
253 |
if self.items[i] > self.items[maxi]:
254 |
maxi = i
255 |
item = self.items[maxi]
256 |
self.items[maxi:maxi+1] = []
257 |
return item
258 |
261 | 각 반복의 시작부분에 있는 maxi는 지금까지 본 것중 가장 큰 항목(최고 우선순위)의 지표를 보유합니다. 회돌이를 돌 때마다, 프로그램은 i번 째 항목을 우승후보와 비교합니다. 새 항목이 더 크면 maxi를 i로 교체합니다.
262 | 263 |264 | for 서술문이 완료되면, maxi는 가장 큰 항목의 지표가 됩니다. 이 항목은 리스트로부터 제거되고 반환됩니다.
265 | 266 |구현을 시험하여 봅시다:
267 | 268 | 269 |>>> q = PriorityQueue()
270 |
>>> q.insert(11)
271 |
>>> q.insert(12)
272 |
>>> q.insert(14)
273 |
>>> q.insert(13)
274 |
>>> while not q.isEmpty(): print q.remove()
275 |
14
276 |
13
277 |
12
278 |
11
279 |
282 | 큐가 단순한 숫자 혹은 문자열을 가지고 있다면, 항목들은 수치 순서 혹은 알파벳 순서로 위로부터 아래로 제거됩니다. 파이썬은 내장 비교 연산자를 사용하여 비교할 수 있으므로 가장 큰 정수 또는 문자열을 찾을 수 있습니다.
283 |284 | 큐가 객체 유형이라면 __cmp__ 메쏘드를 제공해야 합니다. > 연산자를 사용하여 항목들을 비교할 때, remove는 __cmp__를 항목들 중의 하나에 대하여 요청하고 다른 항목을 매개변수로 건넵니다. __cmp__ 메쏘드가 올바르게 작동하는 한, 우선순위 큐(Priority Queue)는 작동할 것입니다.
285 | 286 |289 | 우선순위를 특이하게 정의하는 객체의 한 예로서, Golfer라고 부르는 클래스 하나를 구현해 봅시다. 이 클래스는 골퍼의 이름과 점수를 추적 유지합니다. 보통때와 같이, __init__과 __str__을 정의함으로써 시작합니다:
290 | 291 | 292 |class Golfer:
293 |
def __init__(self, name, score):
294 |
self.name = name
295 |
self.score= score
296 |
297 |
def __str__(self):
298 |
return "%-16s: %d" % (self.name, self.score)
299 |
304 | __str__은 형식화 연산자를 사용하여 이름과 점수를 산뜻하게 열에 맞춰 배치합니다.
305 |306 | 다음으로 최하위 점수가 최고 우선순위를 가지는 __cmp__ 버전을 정의합니다. 언제나 그렇듯이, __cmp__는 self가 other" 보다 더 크면" 1을 반환하고, self가 other "보다 더 작으면" -1을, 그리고 같으면 0을 반환합니다.
307 | 308 | 309 |class Golfer:
310 |
...
311 |
def __cmp__(self, other):
312 |
if self.score < other.score: return 1 # less is more
313 |
if self.score > other.score: return -1
314 |
return 0
315 |
318 | 이제 Golfer 클래스로 우선순위 큐를 시험할 준비가 되었습니다:
319 | 320 | 321 |>>> tiger = Golfer("Tiger Woods", 61)
322 |
>>> phil = Golfer("Phil Mickelson", 72)
323 |
>>> hal = Golfer("Hal Sutton", 69)
324 |
>>>
325 |
>>> pq = PriorityQueue()
326 |
>>> pq.insert(tiger)
327 |
>>> pq.insert(phil)
328 |
>>> pq.insert(hal)
329 |
>>> while not pq.isEmpty(): print pq.remove()
330 |
Tiger Woods : 61
331 |
Hal Sutton : 69
332 |
Phil Mickelson : 72
333 |
연습으로, 연결 리스트를 사용하여 우선순위 큐(Priority Queu e) ADT의 구현을 작성해 보세요. 제거가 상수 시간 연산이 되도록 리스트를 정렬하여 유지하여야 합니다. 이 구현의 수행속도를 파이썬의 리스트 구현과 비교해 보세요.
338 | 339 |ImprovedQueue
과 비교하라.
376 | 388 |
![]() |
392 | ![]() |
393 | ![]() |
394 | ![]() |
395 | ![]() |
396 | ![]() |
397 | ![]() |
398 |
![]() |
26 | ![]() |
27 | ![]() |
28 | ![]() |
29 | ![]() |
30 | ![]() |
31 | ![]() |
32 |
41 | 자유 소프트웨어 재단(Free Software Foundation)의 철학으로 말하면 이 책은 자유 강연과 같은 자유이지 공짜 맥주와 같은 의미의 공짜가 아닙니다. 이 책은 협동작업으로 탄생하였으며 GNU 자유 문서 라이센스(GNU Free Documentation License)가 없었다면 태어나지 못했을 것입니다. 그래서 자유 소프트웨어 재단에 이 라이센스를 개발해 준 것에 대하여, 또 물론 그 라이센스를 사용할 수 있도록 허용해 준 것에 대해서 감사를 드리는 바입니다.
42 | 43 |44 | 날카로운-눈을 가진 사려깊은, 백 여명이 넘는 독자들에게 지난 수 년간 제안과 교정을 보내 주심에 감사를 드리는 바입니다. 자유 소프트웨어의 정신에 의하여 공헌자 목록의 형태로 고마운 마음을 전하기로 결정하였습니다. 불행하게도 공헌자 목록은 완전하지 않습니다. 그러나 최선의 노력을 다하여 갱신하고 있습니다.
45 | 46 |47 | 공헌자 목록을 훓어 볼 기회가 된다면 여기에 있는 한분 한분이 시간을 할애해 주셨다는 것을 기억해 주시기를 바랍니다. 그분들이 잠시 시간을 내어 도움을 주신 덕분에 독자들이 기술적인 에러와 애매모호한 설명 때문에 혼란에 빠지지 않고 이 글을 읽을 수 있게 되었습니다.
48 | 49 |50 | 이렇게 수 많은 교정을 거쳤으므로 불가능하긴 하겠지만 혹시라도 이 책에는 여전히 에러가 있을 수 있습니다. 만약 에러를 발견하신다면 잠깐만 시간을 내셔서 연락을 주실 것을 부탁드립니다. 전자우편 주소는 feedback@thinkpython.com입니다. 여러분의 제안으로 수정을 하게 된다면 (빼 달라고 요구하지 않는 한) 다음 버전의 공헌자 목록에 올려 드리겠습니다. 감사합니다!
51 | 52 |horsebet.py
, which was used as a case study in an earlier version of the book. Their program can now be found on the website.catTwice
function in Section 3.10.increment
function in Chapter 13.119 |
![]() |
123 | ![]() |
124 | ![]() |
125 | ![]() |
126 | ![]() |
127 | ![]() |
128 | ![]() |
129 |
![]() |
35 | ![]() |
36 | ![]() |
37 | ![]() |
38 | ![]() |
39 | ![]() |
40 | ![]() |
41 |
doctest
로 테스트 자동화하기doctest
는 파이썬 표준 라이브러리의 모듈로서 파이썬의 문서화 문자열과 상호대화 쉘 그리고 내부검사 능력을 혁신적으로 사용한다.
51 |
57 | 파이썬의 문서화문자열(docstring)은 모듈과 클래스 그리고 메쏘드와 함수를 손쉽게 문서화하는 방법을 제공한다. 문서화문자열은 그냥 문자열 상수로서 한 객체의 정의에서 첫 서술문에 나타난다. 다음은 트리에 관한 장에서 가져온 예로서 그의 사용법을 보여준다: 58 |
59 | 60 |61 | def total(tree): 62 | """total(tree) -> sum 63 | 64 | Return the sum of the values of the elements of a tree of numbers. 65 | """ 66 | if tree == None: return 0 67 | return total(tree.left) + total(tree.right) 68 |69 | 70 |
71 | 위의 예제가 trees.py
이라는 모듈이라고 가정하면, 이제 다음과 같이 할 수 있다:
74 | >>> from trees import * 75 | >>> print total.__doc__ 76 | total(tree) -> sum 77 | 78 | Return the sum of the values of the elements of a tree of numbers. 79 | 80 |81 | 82 | 83 |
doctest
모듈
86 | doctest
모듈은 파이썬 2.3에서 도입되었다. 파이썬 핵심 개발자 팀 피터스(Tim Peters)가 만든 doctest
모듈은 프로그래머가 문서화문자열을 사용하여 테스트를 자동화할 수 있도록 해 준다.
87 |
90 | """ 91 | >>> distance(1, 2, 4, 6) 92 | 5.0 93 | """ 94 | 95 | def distance(x1, y1, x2, y2): 96 | return 0.0 97 | 98 | if __name__ == "__main__": 99 | import doctest 100 | doctest.testmod() 101 |102 | 103 |
104 | 위의 예제는 제 5 장 프로그램 개발 섹션에서 가져 왔다. 이 프로그램을 실행하면 다음과 같이 출력된다: 105 |
106 | 107 |108 | ********************************************************************** 109 | File "doctest_example01.py", line 2, in __main__ 110 | Failed example: 111 | distance(1, 2, 4, 6) 112 | Expected: 113 | 5.0 114 | Got: 115 | 0.0 116 | ********************************************************************** 117 | 1 items had failures: 118 | 1 of 1 in __main__ 119 | ***Test Failed*** 1 failures. 120 |121 | 122 |
123 | 문서테스트는 문서화문자열 안에 샘플 파이썬 인터프리터 세션으로 작성된다. 이 예제에서 문서테스트는 모듈의 문서화문자열에 있지만, 테스트되고 있는 함수의 문서화 문자열에 두어도 된다. 124 |
125 |
126 | 각 테스트는 파이썬 표현식으로 구성된다. 프롬프트(>>>
) 다음에 표현식이 주어지고 다음 줄에 예상 평가가 따른다. doctest 모듈은 인터프리터에서 각 표현식을 실행하여 평가된 것을 예상된 결과와 비교한다.
127 |
132 | 아래의 각 연습문제에서 코드가 doctest를 통과하도록 작성하라. 133 |
134 | 135 |140 | """ 141 | >>> n 142 | 17 143 | """ 144 |145 |
149 | """ 150 | >>> s 151 | 'I am a string!' 152 | """ 153 |154 |
158 | """ 159 | >>> type(m) 160 | <type 'float'> 161 | """ 162 |163 |
167 | """ 168 | >>> type(r) 169 | <type 'int'> 170 | """ 171 |172 |
181 | """ 182 | >>> avg(3, 5) 183 | 4.0 184 | >>> avg(8, 10) 185 | 9.0 186 | """ 187 |188 |
192 | """ 193 | >>> is_even(8) 194 | True 195 | >>> is_even(11) 196 | False 197 | >>> is_even(2) 198 | True 199 | >>> is_even(5) 200 | False 201 | """ 202 |203 |
207 | """ 208 | >>> is_odd(8) 209 | False 210 | >>> is_odd(11) 211 | True 212 | >>> is_odd(2) 213 | False 214 | >>> is_odd(5) 215 | True 216 | """ 217 |218 |
222 | """ 223 | >>> double(3) 224 | 6 225 | >>> double(4.5) 226 | 9.0 227 | >>> double("Pizza") 228 | 'PizzaPizza' 229 | """ 230 |231 |
235 | """ 236 | >>> f(0) 237 | 5 238 | >>> f(1) 239 | 8 240 | >>> f(2) 241 | 11 242 | >>> f(3) 243 | 14 244 | """ 245 |246 |
250 | """ 251 | >>> g(0) 252 | -7 253 | >>> g(1) 254 | -2 255 | >>> g(2) 256 | 3 257 | >>> g(3) 258 | 8 259 | """ 260 |261 |
265 | """ 266 | >>> str_double("Python") 267 | 'Python Python' 268 | >>> str_double(5) 269 | '5 5' 270 | >>> str_double(None) 271 | 'None None' 272 | >>> str_double(True) 273 | 'True True' 274 | """ 275 |276 |
286 | """ 287 | >>> is_prime(3) 288 | True 289 | >>> is_prime(6) 290 | False 291 | >>> is_prime(2) 292 | True 293 | >>> is_prime(9) 294 | False 295 | >>> is_prime(19) 296 | True 297 | >>> is_prime(53) 298 | True 299 | >>> is_prime(55) 300 | False 301 | """ 302 |303 |
313 | """ 314 | >>> s1[4] 315 | '3' 316 | """ 317 |318 |
322 | """ 323 | >>> len(message) 324 | 15 325 | """ 326 |327 |
331 | """ 332 | >>> s2[4:] 333 | 'Python!' 334 | """ 335 |336 |
340 | """ 341 | >>> type(s3) 342 | <type 'str'> 343 | >>> s3[3] 344 | 'q' 345 | >>> s3[7] 346 | '3' 347 | >>> len(s3) 348 | 22 349 | >>> s3[10:16] 350 | 'cheese' 351 | """ 352 |353 |
357 | """ 358 | >>> s4 + s5 359 | 'happy birthday!' 360 | >>> s4 < s5 361 | True 362 | """ 363 |364 |
368 | """ 369 | >>> count_letters('a', 'banana') 370 | 3 371 | >>> count_letters('b', 'banana') 372 | 1 373 | >>> count_letters('n', 'banana') 374 | 2 375 | >>> count_letters('x', 'banana') 376 | 0 377 | >>> count_letters('i', 'Mississippi') 378 | 4 379 | """ 380 |381 |
385 | """ 386 | >>> replace('a', 'i', 'banana') 387 | 'binini' 388 | >>> replace('i', 'o', 'Mississippi') 389 | 'Mossossoppo' 390 | >>> replace('a', '', 'banana') 391 | 'bnn' 392 | >>> replace('a', 'aba', 'banana') 393 | 'babanabanaba' 394 | """ 395 |396 |
400 | """ 401 | >>> reverse("Python") 402 | 'nohtyP' 403 | >>> reverse("Try to say this backwards!") 404 | '!sdrawkcab siht yas ot yrT' 405 | """ 406 |407 |
411 | """ 412 | >>> extract_email("This has bill@gmail.com in it.") 413 | 'bill@gmail.com' 414 | >>> extract_email("Can you find an email address bob@bob.com in this string?") 415 | 'bob@bob.com' 416 | """ 417 |418 |
428 | """ 429 | >>> lst1[4] 430 | 3 431 | >>> lst1[0] 432 | 5 433 | >>> lst1[2] 434 | 17 435 | """ 436 |437 |
441 | """ 442 | >>> lst2[1] 443 | 'banana' 444 | >>> lst2[0] < lst2[1] 445 | True 446 | >>> lst2[2:] 447 | ['cherry', 'date', 'elderberry', 'fig', 'grapefruit'] 448 | """ 449 |450 |
454 | """ 455 | >>> 43 in lst3 456 | False 457 | >>> 22 in lst3 458 | True 459 | >>> len(lst3) 460 | 5 461 | >>> lst3[1:3] 462 | [12, 22] 463 | >>> lst3[0] < lst3[1] 464 | True 465 | >>> lst3[4] < lst3[3] 466 | False 467 | """ 468 |469 |
473 | """ 474 | >>> find_sum([3, 1, 1, 0]) 475 | 5 476 | >>> find_sum([1, 2]) 477 | 3 478 | >>> find_sum([1, 2, 3, 4, 5, 6]) 479 | 21 480 | >>> find_sum([42]) 481 | 42 482 | >>> find_sum([]) 483 | 0 484 | """ 485 |486 |
490 | """ 491 | >>> find_max([1, 2, 3, 4]) 492 | 4 493 | >>> find_max([4, 3, 2, 1]) 494 | 4 495 | >>> find_max([8, 51, 5, 73, 4, 67]) 496 | 73 497 | >>> find_max(['Tsagaank', 'Shitaye', 'Xavier', 'Bao', 'Julia']) 498 | 'Xavier' 499 | """ 500 |501 |
505 | """ 506 | >>> only_evens([1, 2, 3, 4, 5, 6, 7, 8]) 507 | [2, 4, 6, 8] 508 | >>> only_evens([12, 34, 37, 43, 58, 60, 88]) 509 | [12, 34, 58, 60, 88] 510 | >>> only_evens([12, 34, 36, 44]) 511 | [12, 34, 36, 44] 512 | >>> only_evens([13, 35, 37, 49]) 513 | [] 514 | """ 515 |516 |
520 | """ 521 | >>> only_odds([1, 2, 3, 4, 5, 6, 7, 8]) 522 | [1, 3, 5, 7] 523 | >>> only_odds([12, 34, 37, 43, 58, 60, 88]) 524 | [37, 42] 525 | >>> only_odds([12, 34, 36, 44]) 526 | [] 527 | >>> only_odds([13, 35, 37, 49]) 528 | [13, 35, 37, 49] 529 | """ 530 |531 |
535 | """ 536 | >>> index_of(12, [4, 8, 12, 16, 20]) 537 | 2 538 | >>> index_of(20, [4, 8, 12, 16, 20]) 539 | 4 540 | >>> index_of(8, [4, 8, 12, 16, 20]) 541 | 1 542 | >>> index_of(9, [4, 8, 12, 16, 20]) 543 | -1 544 | >>> index_of('Bao', ['Tsagaank', 'Shitaye', 'Xavier', 'Bao', 'Julia']) 545 | 3 546 | """ 547 |548 |
552 | """ 553 | >>> remove_at(0, [5, 4, 3, 2]) 554 | [4, 3, 2] 555 | >>> remove_at(2, [5, 4, 3, 2]) 556 | [5, 4, 2] 557 | >>> remove_at(4, ['a', 'b', 'c', 'd', 'e', 'f']) 558 | ['a', 'b', 'c', 'd', 'f'] 559 | """ 560 |561 |
565 | """ 566 | >>> remove_val(3, [5, 4, 3, 2]) 567 | [5, 4, 2] 568 | >>> remove_val(5, [5, 4, 3, 2]) 569 | [4, 3, 2] 570 | >>> remove_val('e', ['a', 'b', 'c', 'd', 'e', 'f']) 571 | ['a', 'b', 'c', 'd', 'f'] 572 | >>> remove_val(6, [5, 4, 3, 2]) 573 | [5, 4, 3, 2] 574 | """ 575 |576 |
580 | """ 581 | >>> sort_list([3, 7, 1, 8, 2]) 582 | [1, 2, 3, 7, 8] 583 | >>> sort_list([6, 5, 4, 3, 2, 1]) 584 | [1, 2, 3, 4, 5, 6] 585 | >>> sort_list(['cherries', 'pears', 'apples', 'bananas', 'apricots']) 586 | ['apples', 'apricots', 'bananas', 'cherries', 'pears'] 587 | """ 588 |589 |
593 | """ 594 | >>> mean([1, 2]) 595 | 1.5 596 | >>> mean([1, 2, 4, 7]) 597 | 3.5 598 | >>> mean([1, 1, 1, 1, 1]) 599 | 1.0 600 | >>> mean([5, 10, 15, 20, 25]) 601 | 15.0 602 | """ 603 |604 |
608 | """ 609 | >>> median([1, 1, 2, 3, 3]) 610 | 2 611 | >>> median([3, 2, 1, 1, 3]) 612 | 2 613 | >>> median([1, 1, 3, 3]) 614 | 2.0 615 | """ 616 |617 |
621 | """ 622 | >>> mode([1, 2, 2, 3, 4, 5, 6]) 623 | 2 624 | >>> mode([1, 2, 3, 4, 4, 5, 6]) 625 | 4 626 | >>> mode([1, 2, 3, 4, 4, 5, 6, 6, 6, 7]) 627 | 6 628 | """ 629 |630 |
640 | """ 641 | >>> encapsulate(5, ()) 642 | (5, ) 643 | >>> encapsulate(5, []) 644 | [5] 645 | >>> encapsulate(5, '') 646 | '5' 647 | >>> encapsulate('bob', (1, 2)) 648 | ('bob', ) 649 | >>> encapsulate((1, 2), ['a', 'b', 'c']) 650 | [(1, 2)] 651 | """ 652 |653 |
657 | """ 658 | >>> insert_at_end(3, (1, 2)) 659 | (1, 2, 3) 660 | >>> insert_at_end(3, [1, 2]) 661 | [1, 2, 3] 662 | >>> insert_at_end(3, 'ab') 663 | 'ab3' 664 | """ 665 |666 |
670 | """ 671 | >>> insert_in_front('x', 'ab') 672 | 'xab' 673 | >>> insert_in_front('x', (1, 2)) 674 | ('x', 1, 2) 675 | >>> insert_in_front('x', [1, 2]) 676 | ['x', 1, 2] 677 | """ 678 |679 |
689 | """ 690 | >>> d1 = make_dictionary([('this', 'that')]) 691 | >>> d1 692 | {'this': 'that'} 693 | >>> d2 = make_dictionary([('this', 'that'), ('some', 'other'), (4, 'cheese')]) 694 | >>> d2.has_key('some') 695 | True 696 | >>> d2.has_key('other') 697 | False 698 | >>> d2[4] 699 | 'cheese' 700 | """ 701 |702 |
![]() |
727 | ![]() |
728 | ![]() |
729 | ![]() |
730 | ![]() |
731 | ![]() |
732 | ![]() |
733 |
![]() |
26 | ![]() |
27 | ![]() |
28 | ![]() |
29 | ![]() |
30 | ![]() |
31 | ![]() |
32 |
45 |
46 |53 |47 |
This is an unofficial translation of the GNU Free Documentation License 48 | into Korean. It was not published by the Free Software Foundation, and does 49 | not legally state the distribution terms for documents that uses the GNU FDL 50 | -- only the original English text of the GNU FDL does that. However, we hope 51 | that this translation will help Korean speakers understand the GNU FDL 52 | better.
이 문서는 자유 소프트웨어 재단의 GNU Free Documentation License를
55 | 한국어로 번역한 것이다.
56 |
이 문서는 GNU 자유 문서 라이선스(GNU Free Documentation License)가 내포하고 있는 호혜적인 자유와 공유의 57 | 정신을 보다 많은 사람들에게 알리기 위한 희망에서 작성되었지만, 자유 소프트웨어 재단의 공식 문서로 취급될 수는 없다. 이는 원래의 문서가 58 | 의도하고 있는 내용이 왜곡되지 않고 법률적으로 유효하기 위해서 선행되어야 할 양국의 현행 법률과 언어의 적합성 여부에 대한 전문가들의 검토 59 | 작업에 많은 비용이 필요하기 때문이다. 따라서, 자유 소프트웨어 재단은 오역이나 해석상의 난점으로 인해서 발생될 지도 모를 분쟁의 가능성을 60 | 미연에 방지하고, 문서가 담고 있는 내용과 취지를 보다 많은 사람들에게 홍보하려는 상반된 목적을 한국어 번역문을 공식적으로 승인하지 않음으로써 61 | 양립시킬 수 있을 것이다. 62 |
자유 소프트웨어 재단은 GNU 자유 문서 라이선스를 실무에 적용할 경우, 오직 영문판 GNU 자유 문서 라이선스에 의해서만 그 법률적 63 | 효력이 올바르게 발생될 수 있음을 권고하고 있다. 이 번역문은 법률적 검토와 문서간의 동일성 여부에 대한 검증을 거치지 않은 것이며, 이로 64 | 인해서 야기될 수 있을 지도 모를 법률적 문제에 대해서 어떠한 형태의 보증도 제공하지 않는다. 65 |
Original copy: http://www.gnu.org/copyleft/fdl.html
Related document: http://www.gnu.org/copyleft/fdl-howto-opt.ko.html
69 |
70 |
Korean translator: July 2000, 오병일 mailto:antiropy@www.jinbo.net, 양희진 mailto:hj-yang@orgio.net
Revision: July 2000, Song Chang-hun 송창훈 mailto:chsong@gnu.org
75 |
If you want to check the original English text and Korean
76 | translation one-by-one, following this point:
http://korea.gnu.org/copyleft/fdl.ko-checking.html
78 |
79 |
80 |
83 |
1.1 판, 2000년 3월 85 |
Copyright (C) 2000 Free Software Foundation, Inc. 86 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 87 | 저작권과 사용 허가에 대한 본 사항이 명시되는 한, 88 | 어떠한 정보 매체에 의한 본문의 전재나 발췌도 무상으로 허용된다. 89 | 단, 원문에 대한 수정과 첨삭은 허용되지 않는다. 90 |91 |
92 |
93 |
이 라이선스의 목적은 첫째, 매뉴얼이나 책 또는 다른 문서들을 "자유"롭게 만들기 위한 것이다. 여기서 말하는 "자유"란 무료라는 의미가 95 | 아닌 구속되지 않는다는 관점에서의 자유를 말한다. 즉, 상업적이든 비상업적이든 간에 누구나 그것을 수정하거나 그렇지 않은 상태에서 복제 및 96 | 재배포할 수 있는 실질적인 자유를 보장하기 위한 것이다. 둘째, 이 라이선스는 저작자나 발행인에게 다른 사람들이 가한 수정에 책임지지 않고 97 | 그들의 저작물에 대한 공로를 인정 받을 수 있는 길을 보장하기 위한 것이다. 98 |
이 라이선스는 일종의 "카피레프트"이다. 즉, 문서의 2차적 저작물 또한 같은 의미에서 자유로워야 한다는 것을 의미한다. 이것은 자유 99 | 소프트웨어를 위해서 고안된 카피레프트 라이선스인 GNU General Public License를 101 | 보완한다. 102 |
자유 소프트웨어는 자유 문서를 필요로 하기 때문에 이 라이선스는 자유 소프트웨어 매뉴얼에 사용되기 위해서 고안되었다. 자유 프로그램에는 103 | 소프트웨어에서와 같은 자유가 제공되는 매뉴얼이 함께 수반되어야 한다. 그러나, 이 라이선스가 단지 소프트웨어 매뉴얼에만 한정되는 것은 아니다. 104 | 이것은 문서의 주제나 그것이 인쇄물로 발행되었는가의 여부에 상관없이 모든 종류의 문서 저작물에 사용될 수 있다. 교육이나 참고를 목적으로 하는 105 | 저작물에는 원칙적으로 이 라이선스를 사용할 것을 추천한다. 106 |
107 |
108 |
본 라이선스는 GNU 자유 문서 라이선스(GNU Free Documentation License, 이하, GNU 자유 문서 라이선스라고 110 | 명칭한다.)의 규정에 따라 배포될 수 있다는 사항이 저작권자에 의해서 명시된 모든 매뉴얼과 문서 저작물에 적용될 수 있으며, 공중의 누구라도 111 | 피양도자가 될 수 있다. 본 라이선스에서 사용되는 [문서(文書, document)]는 GNU 자유 문서 라이선스에 의해서 양도된 112 | 매뉴얼 또는 문서 저작물의 의미한다. 또한, [공중(公衆, public)]이란 불특정 다수의 사람을 의미하고, 113 | [피양도자(被讓渡者, licensee)]란 GNU 자유 문서 라이선스에 의해서 매뉴얼 또는 문서 저작물을 양도받은 사람을 의미한다. 114 | 복제 또는 개작의 대상이 되는 독자적인 문서 창작물의 최초 발행물은 [원문서(原文書, original document)]라고 한다. 115 | 116 |
문서의 [수정판(修正版, modified version)]이란, [원문서] 그대로 복제 또는 개작되거나, 다른 117 | 언어로 번역된 문서의 전부나 일부를 포함하고 있는 저작물을 의미한다. 118 |
문서의 [2차구성부(二次構成部, secondary section)]란, 문서의 저작자나 발행인과 문서의 전체 주제(또는 관련 119 | 내용)와의 관계만을 설명하거나, 문서의 전체 주제와 직접적인 관계가 없는 내용을 포함하고 있는 머리말과 목차 등의 서두나 부록 부분을 가리킨다. 120 | (예를 들면, 문서가 수학 교과서의 일부였을 경우, 이 문서의 [2차구성부]는 수학에 관련된 어떠한 내용도 기술되어서는 안된다.) 121 | [2차구성부]에 포함될 수 있는 내용은 문서의 주제나 관련 사항에 대한 개정 이력이나 법률적, 상업적, 철학적, 윤리적, 정치적 122 | 입장 등이다. 123 |
[변경불가부분(變更不可部分, invariant section]이란, 문서가 본 라이선스에 의해서 배포된다는 사실이, 해당 124 | 부분이 [변경불가부분]이라는 제목과 함께 명시된 2차 구성부의 한 형태를 의미한다. 125 |
[표지구절(表紙句節, cover texts)]이란, 문서가 본 라이선스에 의해서 배포된다는 사실이 문서의 앞 표지나 뒷 표지에 126 | 언급되는 짧은 문장을 의미한다. 127 |
문서의 [투명(透明, transparent )] 복제물이란, 사양이 공중에게 공개되어 있고, 일반적인 문서 편집기, (픽셀로 128 | 구성된 이미지의 경우) 일반적인 페인트 프로그램, (그림의 경우) 널리 사용되는 그림 편집기로 그 내용을 출력시키거나 직접 수정하기에 용이하며, 129 | 조판 프로그램(text formatter)에 입력하기에 적당하거나 조판 프로그램에 입력할 수 있는 다양한 형태의 포맷으로 자동으로 번역되기에 130 | 용이한 형태로 만들어진 기계 판독 가능한 복제물을 의미한다. 피양도자가 문서를 개작하는 것을 방해하거나 금지하기 위해서 조판 형태를 설계한 파일 131 | 포맷은 [투명]한 것이 아니다. [투명] 복제물이 아닌 것을 [불투명(不透明, opaque)] 복제물이라고 132 | 한다. 133 |
투명 복제물로 적절한 포맷의 예는 마크업(markup)이 포함되지 않은 평범한 ASCII 포맷과 Texinfo 입력 포맷, LaTeX 입력 134 | 포맷, 공개적으로 이용되는 DTD를 사용하는 SGML이나 XML, 그리고 표준 규약을 준수하는 간단한 형식의 HTML과 같이 사람이 직접 개작할 135 | 수 있는 형태의 포맷이다. 불투명 복제물에는 PostScript와 PDF, 독점 워드 프로세서에서만 읽고 편집할 수 있는 독점 포맷, 일반적으로 136 | 통용되지 않는 DTD와 처리 도구가 필요한 SGML 및 XML 포맷, 그리고 출력 목적만을 위해서 워드 프로세서로 생성한 기계 생성 HTML 137 | 포맷이 포함된다. 138 |
[제목 페이지(title page)]란, 인쇄된 책의 경우에는 문서의 제목이 표시된 페이지 자체뿐만 아니라, 보다 쉽게 139 | 이해되는데 필요하다고 판단되어 본 라이선스가 제목 페이지에 함께 포함시킬 것을 규정한 후속 페이지들을 모두 의미한다. 제목 페이지가 없는 140 | 저작물의 경우, 제목 페이지는 본문이 시작되기 전에 저작물의 제목에 가장 근접한 형태가 나타난 페이지를 의미한다. 141 |
142 |
143 |
문서의 피양도자는 본 라이선스에 어떠한 사항도 추가하지 않은 상태에서, 본 라이선스와 저작권 사항 그리고 문서의 모든 복제물에 본 145 | 라이선스가 동일하게 적용된다는 사항을 명시하고 이를 문서의 복제물과 함께 제공하는 한, 어떠한 정보 매체에 의해서도 상업적이나 비상업적인 146 | 목적으로 문서를 복제하거나 배포할 수 있다. 문서를 복제하거나 배포할 경우에는 임의의 피양도자가 문서를 열람하거나 복제할 수 없도록 방해하거나 147 | 통제할 수 있는 어떠한 기술적 수단도 사용해서는 안된다. 그러나 복제물을 제공하는데 따른 보상을 청구할 수는 있다. 만약, 충분히 많은 양의 148 | 복제물을 배포할 경우에는 제3조의 규정들을 함께 준수해야만 한다. 149 |
또한 위의 조건을 준수하는 한, 피양도자는 문서의 복제물을 대여하거나 공표(公表)할 수 있다. 150 |
151 |
152 |
만약, 문서의 인쇄 복제물을 100부 이상 발행하며, 문서의 라이선스 표시가 표지 구절의 사용을 규정하고 있는 경우에는 인쇄물의 앞 표지와 154 | 뒷 표지에 삽입될 앞 표지 구절과 뒷 표지 구절이 명확하고 읽기 쉬운 형태로 모든 복제물에 포함되어야 한다. 또한 인쇄 복제물의 발행인에 대한 155 | 정보가 양쪽 표지 모두에 명확하고 읽기 쉬운 형태로 명시되어야 한다. 인쇄물의 앞 표지에는 문서의 완전한 제목이, 제목을 구성하는 모든 문자들이 156 | 동일한 수준의 식별력을 가질 수 있도록 표시되어야 한다. 복제물의 표지에는 추가적인 문장이나 도형, 그림 등의 요소를 추가하는 것이 가능하다. 157 | 문서의 제목이 유지되고 이러한 조건들을 만족하는 한, 표지만 변경시킨 복제물은 표지 이외의 다른 부분에 대한 동일 복제로 간주된다. 158 |
앞 표지나 뒷 표지에 표시될 표지 구절의 내용이 너무 많아서 읽기 힘든 경우에는 실제 표지에는 (적당한 만큼만) 기재하고, 나머지 내용들은 159 | 인접 페이지에 표시할 수 있다. 160 |
100부 이상의 불투명 복제물을 발행하거나 배포하는 경우, 모든 불투명 복제물에 기계 판독이 가능한 투명 복제물을 함께 첨부하거나, 추가된 161 | 것이 없는 완벽한 투명 복제물이 있는 공개적인 접근이 가능한 컴퓨터 네트워크의 위치를 각 불투명 복제물에 명시하여 네트워크를 사용하는 일반 162 | 공중이 공개 표준을 준수하는 네트워크 프로토콜을 이용하여 비용없이 익명으로 다운로드 받을 수 있도록 해야 한다. 후자의 경우, 불투명 복제물을 163 | 대량으로 배포하기 시작할 때에는 매우 신중한 접근을 해야 하는데, 불투명 복제물을 공중에게 (본인이 직접 또는 대리인을 통해서, 또는 소매업자를 164 | 통해서) 배포한 마지막 시점으로부터 적어도 1년 뒤까지 투명 복제물이 명시된 위치에서 접근될 수 있는 상태로 확실히 남아있도록 해야 한다. 165 |
강제 조항은 아니지만, 복제물을 대량으로 배포하기 전에 충분한 시간적 여유를 두고 문서의 저작자와 연락해서 저작자에게 문서의 최신 개정판을 166 | 제공할 수 있는 기회를 주어야 한다. 167 |
168 |
169 |
문서의 수정판은 수정판이 명백하게 본 라이선스에 의해서 관리되는 조건 하에서 제2조와 제3조의 규정에 의해서 복제 및 배포될 수 있다. 171 | 즉, 수정판은 문서의 역할을 유지해야 하며, 수정판의 복제물을 양도받은 임의의 피양도자는 개작과 배포에 대한 동일한 권리를 양수받게 된다. 172 | 또한, 수정판에 대해서 다음의 규정들을 준수해야만 한다. 173 |
174 |
175 |203 |A. 수정판의 제목 페이지에는 (표지가 있다면 표지에도) 문서와 그 이전 판의 문서와 구별되는 제목을 사용한다. (문서의 176 | 개정 이력란이 존재한다면 이러한 사실이 등재되어야 한다.) 이전 판의 발행인이 허락한다면, 이전 판과 같은 제목을 사용할 수 있다. 177 |
B. 수정판의 제목 페이지에는 적어도 5명의 문서의 원저작자 (5명보다 적다면 원저작자 모두)와 함께, 개작에 책임이 있는 178 | 1인 이상의 개인 또는 단체를 저작자로 명시해야 한다. 179 |
C. 수정판의 발행인 성명을 제목 페이지에 발행인으로서 명시한다. 180 |
D. 문서의 모든 저작권 표시를 수정판에 유지해야 한다. 181 |
E. 문서의 저작권 표시 부분에 자신이 개작한 것에 대한 적절한 저작권 사항을 추가해서 수정판에 표시한다. 182 |
F. 저작권 표시 바로 다음에, 본 라이선스의 규정 하에 공중이 수정판을 사용할 수 있다는 라이선스 표시를 본 라이센스의 183 | 부록에 나와있는 형식으로 포함시킨다. 184 |
G. 문서의 라이선스 표시에 포함되어 있던 변경 불가 부분의 목록과 명시할 것을 요구한 표지 구절을 수정판에도 모두 그대로 185 | 유지한다. 186 |
H. 본 라이선스를 변경 없이 그대로 포함시킨다. 187 |
I. [개정이력(履歷, history)]이라는 이름이 붙어 있는 부분과 그 제목을 그대로 유지하고, 적어도 수정판의 제목, 188 | 연도, 수정판 저작자, 발행인에 대한 항목을 제목 페이지에서 명시한 것과 동일하게 이 부분에 추가한다. 문서에 이력 부분이 없을 경우에는, 189 | 제목 페이지와 동일하게 문서의 제목과 연도, 저작자, 발행인을 명시한 이력 부분을 새롭게 만들고 앞에서 언급한 대로 수정판에 대한 사항을 190 | 추가한다. 191 |
J. 공중이 투명 복제물에 접근할 수 있게 하기 위해서 문서에 명시한 네트워크 주소가 존재한다면 이를 수정판에도 그대로 192 | 유지한다. 문서의 이전 판에 포함되어 있던 네트워크 주소가 문서에 기재되어 있는 경우에도, 이를 소급해서 수정판에 그대로 유지한다. 네트워크 193 | 주소는 개정 이력 부분에 기재될 수도 있다. 만일, 네트워크 주소가 문서보다 적어도 4년 전에 발행된 저작물을 위한 것이거나 네트워크 주소가 194 | 최초로 포함된 문서의 발행인이 허락했다면, 이를 생략할 수 있다. 195 |
K. [감사의 글(acknowledgements)] 또는 [헌사(dedications)]라는 표제를 갖고 있는 부분이 196 | 있다면, 이 부분에 기재되어 있는 제목과 기여자에 대한 감사의 글 그리고 헌사의 내용과 어조를 수정판에도 모두 유지한다. 197 |
L. 문서의 모든 변경 불가 부분은 제목과 본문을 변경하지 않고 수정판에 그대로 유지한다. 장(chapter) 또는 198 | 절(section) 번호나 이에 상당하는 것은 변경 불가 부분의 제목의 일부분으로 간주되지 않는다. 199 |
M. [추천사(endorsements)]라는 제목이 붙은 부분은 수정판에서 모두 누락시킨다. 이러한 부분이 수정판에 200 | 포함되어서는 안된다. 201 |
N. 기존의 어떠한 부분도 수정판에서 추천사로 제목을 바꾸지 말고, 제목을 개명하는 부분이 변경 불가 부분의 어떠한 202 | 제목과도 충돌되지 않도록 한다.
만일 수정판이 문서에 포함되어 있지 않던 새로운 서두 부분이나 부록을 2차 구성부의 형태로 포함하게 되면, 이러한 부분의 전체나 일부를 204 | 선택에 따라 변경 불가 부분으로 설정할 수 있다. 변경 불가 부분을 새롭게 설정하기 위해서는 수정판의 라이선스 표시 부분에 포함되어 있는 변경 205 | 불가 부분 목록에 제목을 추가시킨다. 이때, 그 제목들은 다른 부분의 제목들과 구별되어야 한다. 206 |
수정판에만 한정된 추천사가 다양한 주체들에 의해서 제공될 경우에는, 예를 들어, 동료들의 비평문이나 수정판을 특정한 표준의 권위있는 정의로 207 | 인정한다는 관련 기관의 승인이 있을 경우에는 "추천사"라는 제목의 글을 추가할 수 있다. 208 |
수정판의 표지 구절 목록 말미에는 앞 표지 구절과 뒷 표지 구절로 각각 5단어와 25단어 미만의 문장을 덧붙일 수 있다. 한 개인 또는 한 209 | 단체는 (또는 단체에 의해서 만들어진 협약을 통해서) 오직 한 개의 문장만을 각각 앞 표지 구절과 뒷 표지 구절에 추가할 수 있다. 만약, 210 | 문서의 표지 구절에 이미 특정인이나 특정인이 대표하는 단체의 협약에 의해서 포함된 문장이 존재할 경우에는 동일인에 의해서 표지 구절 문장이 211 | 추가될 수 없다. 그러나 문서의 발행인으로부터 명시적인 승인을 받은 경우에는 기존의 문장을 수정판에서 새로운 문장으로 대체할 수 있다. 212 |
문서의 저작자(들)과 발행인(들)은 본 라이선스를 통해서 수정판을 선전하는데 그들의 이름이 사용되거나, 명시적 또는 묵시적인 형태로 그들의 213 | 이름이 수정판을 추천하는데 사용되는 것을 허용한 것이 아니다. 214 |
215 |
216 |
수정판에 대해서 정의된 제4조의 규정에 따라서, 본 라이선스에 의해서 특정 문서를 다른 문서들과 결합할 수 있다. 단, 문서를 결합할 때는 218 | 결합 저작물을 구성하는 개별 문서들의 변경 불가 부분들을 결합 문서에 그대로 포함시켜야 하며, 그 목록을 결합 저작물의 저작권 표시 부분에 219 | 명시해야 한다. 220 |
결합 저작물에는 본 라이선스의 복제물 1부만 포함시키면 되며, 여러 개의 동일한 변경 불가 부분 또한 하나로 통합될 수 있다. 만약, 221 | 동일한 이름을 갖는 변경 불가 부분이 여러 개 존재하지만, 그 내용이 다른 경우에는 각각의 내용과 관련된 저작자와 발행자가 알려져 있을 경우에는 222 | 해당 정보를 각 부분의 말미에 괄호안에 명시하고 그렇지 않은 경우에는 숫자를 이용해서 구분한다. 결합 저작물의 저작권 표시 부분에 있는 변경 223 | 불가 부분 목록에 포함된 제목도 같은 방식으로 조정한다. 224 |
원문서에 존재하던 "개정 이력" 부분은 모두 통합하여, 단일한 "개정 이력" 부분을 결합 저작물 안에 유지해야 한다. "감사의 글"과 225 | "헌사"도 같은 방식으로 조정한다. 단, "추천사" 부분은 모두 삭제해야 한다. 226 |
227 |
228 |
본 라이선스 하에 배포된 문서들을 모아서 구성한 수집 저작물을 만들 수 있다. 또한, 개별 문서에 포함되어 있던 본 라이선스 복제물들을 한 230 | 개로 대체하여 수집 저작물에 포함시킬 수 있다. 이 경우, 다른 모든 부분들은 본 라이선스에 규정된 제2조 동일 복제 규정을 준수해야 한다. 231 |
수집 저작물로부터 하나의 문서를 발췌해서 개별 배포할 경우에는, 본 라이선스의 복제물을 발췌한 문서에 첨부하고 그 이외의 다른 부분들은 232 | 모두 제2조에 규정된 동일 복제 조항을 준수해야 한다. 233 |
234 |
235 |
문서 또는 문서의 2차적 저작물을 독자적인 문서나 저작물과 함께 대량 저장 매체 또는 배포 매체에 구성한 편집물을 만들 경우에는, 저작물의 237 | 구성에 따른 편집 저작권이 주장되지 않는 한, 저작물 전체를 본 라이선스가 규정하는 수정판으로 간주하지 않는다. 이러한 편집물을 집합 238 | 저작물이라고 부르며, 편집 과정에서 문서와 함께 구성된 독자적 저작물이 문서로부터 파생된 것이 아니라면 본 라이선스가 적용되지 않는다. 239 |
제3조의 표지 구절에 대한 요구는 문서의 복제물에 적용된다. 따라서 문서의 양이 전체 편집물의 1/4 보다 작은 경우에는, 문서의 표지 240 | 구절은 편집물 안에서 문서가 위치해 있는 곳의 표지 부분에 포함되어도 무방하다. 그렇지 않은 경우에는, 표지 구절이 전체 편집물의 표지 부분에 241 | 나타나야 한다. 242 |
243 |
244 |
번역은 일종의 개작으로 간주된다. 따라서 문서의 번역물은 제4조의 규정에 따라 배포될 수 있다. 변경 불가 부분을 번역물로 대체하기 246 | 위해서는 저작권자의 명시적인 승인을 얻어야 한다. 그러나 변경 불가 부분의 전체 또는 일부에 대한 번역문을 원문과 함께 표시할 경우에는 247 | 저작권자로부터 별도의 승인을 얻을 필요가 없다. 본 라이선스의 번역판을 첨부할 경우에는 영어 원판을 함께 제공해야 한다. 영어 원판과 번역판 248 | 사이에 충돌이 발생할 경우에는 영문 원판이 우선한다. 249 |
250 |
251 |
본 라이선스 하에 명백하게 제공된 것을 제외하고는 문서를 복제, 개작, 양도 또는 배포할 수 없다. 본 라이선스에 의하지 않고 문서를 253 | 복제, 수정, 양도, 배포하는 어떠한 시도도 무효이며 본 라이선스에 의해서 보장된 권리는 자동으로 소멸된다. 그러나, 본 라이선스에 의해서 254 | 복제물이나 권리를 양도받은 피양도자는 본 라이선스의 규정들을 모두 준수하는 한, 그 권리를 유지할 수 있다. 255 |
256 |
257 |
자유 소프트웨어 재단은 때때로 GNU 자유 문서 라이선스의 개정판을 발행할 것이다. 개정판은 현재의 판과 그 정신에 있어서 유사하겠지만, 259 | 발생 가능한 새로운 문제나 사안을 처리하기 위해서 세부 사항에 차이가 있을 수 있다. http://www.gnu.org/copyleft/의 내용을 참고하기 261 | 바란다. 262 |
라이선스의 각 판(version)은 구별되는 판 번호(version number)를 가진다. 문서가 특정한 번호나 "또는 어떤 판 이후"가 263 | 적용된다고 라이선스의 판을 명시한 경우에는, 특정 판의 규정과 조건을 따르거나 자유 소프트웨어 재단이 발행한 후속 판(시험판이 아닌)을 따를 수 264 | 있다. 문서가 본 라이선스의 판 번호를 특정하고 있지 않다면, 자유 소프트웨어 재단에서 발행한 (초고가 아닌) 어떠한 판을 선택해도 무방하다. 265 |
266 |
267 |
작성한 문서에 본 라이선스를 적용하기 위해서는, 본 라이선스의 복제물을 문서에 첨부하고 다음과 같은 저작권 및 라이선스 표시를 제목 페이지 269 | 다음에 추가한다. 270 |
271 |
272 |276 |Copyright (c) 연도, 본인 성명
GNU 자유 문서 라이선스 1.1판 또는 자유 소프트웨어 재단에서 273 | 발행한 이후 판의 규정에 따라 본 문서를 복제하거나 개작 및 배포할 수 있다. 본 문서의 변경 불가 부분은 "xxx"이고 앞 표지 구절은 274 | "xxx", 뒷 표지 구절은 "xxx"이다. 본 라이선스의 전체 내용은 "GNU 자유 문서 라이선스" 부분에 포함되어 있다. 275 |
만약, 변경 불가 부분이 없는 경우라면, 변경 불가 부분을 명시하는 대신 "변경 불가 부분 없음"이라고 표시한다. 앞 표지
277 | 구절과 뒷 표지 구절이 없는 경우에도 각각 "앞 표지 구절 없음"과 "뒷 표지 구절 없음"이라는 사실을 명시한다.
278 |
만약, 문서가 프로그램 코드의 예를 상당 부분 포함하고 있다면 GNU General Public License와 280 | 같은 자유 소프트웨어 라이선스를 사용해서 프로그램 코드가 자유소프트웨어에서 사용될 수 있도록 라이선스를 병행할 것을 추천한다. 281 |
282 |
294 |
![]() |
298 | ![]() |
299 | ![]() |
300 | ![]() |
301 | ![]() |
302 | ![]() |
303 | ![]() |
304 |
![]() |
24 | ![]() |
25 | ![]() |
26 | ![]() |
27 | ![]() |
28 | ![]() |
29 | ![]() |
데이비드 비즐리(David Beazley) 씀
34 |35 | 교육자이며 연구가 그리고 저자로서 본인은 기쁜 마음으로 이 책이 완성된 것을 보고 있습니다. 파이썬은 재미있고 너무나 쉽게-사용-가능한 프로그래밍 언어로서 지난 수년간 날로 인기가 치솟고 있습니다. 십 여년 전에 귀도 반 로섬에 의해서 개발된 파이썬은 그의 간결한 구문과 전체적인 느낌이 광범위하게 ABC로부터 기원합니다. ABC 언어는 1980년대에 개발된 교육용 언어입니다. 그렇지만, 파이썬은 실제 세계의 문제도 풀도록 디자인되었습니다. 그리고 C++과 자바 그리고 모듈라-3와 스킴 같은 프로그래밍 언어로부터도 다양한 사양을 빌려왔습니다. 이 덕분에 파이썬에서 가장 눈에 띠는 특징중 하나는 파이썬이 전문 소프트웨어 개발자, 과학자, 연구원, 기술자, 그리고 교육자들에게 매력적이라는 것입니다.
36 |37 | 파이썬이 여러 많은 공동체에 매력적임에도 불구하고, 여전히 당신은 궁금할지도 모르겠습니다. "왜 파이썬이지?" 또는 "왜 파이썬으로 프로그래밍을 가르쳐야 하지?" 이러한 질문에 대답하는 것은 쉬운 일이 아닙니다 --- 특히 대중적인 의견이 C++이나 자바 같은 자학적인 대안 언어의 편에 있다면 말이지요. 그렇지만 가장 직접적으로 대답하면 파이썬으로 프로그래밍 하는 것이 그냥 대단히 재미있고 더 생산적이기 때문입니다.
38 |39 | 컴퓨터 과학 강좌를 가르칠 때 본인은 강좌의 내용을 학생들에게 흥미롭고 매력적으로 만드는 것 말고도 중요한 개념들을 다루기를 원합니다. 불행하게도 개론적인 프로그래밍 강좌에서 수학적인 추상화에 너무 많이 초점을 맞추는 경향이 있으며 학생들은 구문과 컴파일 그리고 비밀스런 규칙들을 강요하는 저-수준 세부사항에 관련된 문제들 때문에 고민하면서 좌절감에 빠지는 경향이 있습니다. 그러한 추상화와 형식주의가 전문적인 소프트웨어 기술자들과 앞으로 컴퓨터 과학을 계속해서 공부할 계획이 있는 학생들에게는 중요할지 모릅니다. 그러나 개론적인 강좌에서 그러한 접근법을 취하면 대부분 컴퓨터 과학을 지루한 과목으로 만드는데나 성공할 뿐입니다. 강좌를 가르치면서 나는 느낌이 없는 학생들이 자리를 차지하는 것을 원하지 않습니다. 나는 학생들이 흥미로운 문제들을 풀려고 노력하는 것을 즐겨 보는 편입니다. 학생들은 서로 다른 아이디어들을 탐험해보고 비관례적인 접근법을 취해보며 규칙을 어겨보고 실수를 통해 배웁니다. 그러지 못하고 불분명한 구문 문제들이나 지능적이지 못한 컴파일러 에러 메시지들 또는 프로그램의 수 백 가지 일반 보호 오류를 걸러 내면서 한 학기의 절반을 허비하는 것을 본인은 원하지 않습니다.
40 |41 | 내가 파이썬을 좋아하는 이유는 파이썬이 정말로 실제세계와 개념세계 사이에 훌륭하게 균형을 이룬다는 것입니다. 파이썬은 인터프리터이기 때문에 초보자들은 컴파일과 링크라는 문제에서 길을 잃고 해멜 필요없이 파이썬을 선택하는 그 즉시 깔끔하게 작업을 시작할 수 있습니다. 게다가, 파이썬에는 방대한 라이브러리 모듈이 있어서 웹-프로그래밍에서부터 그래픽까지 아우르는 온갖 종류의 작업을 할 수 있습니다. 실제적인 문제에 초점을 두는 것은 학생들을 이끄는 훌륭한 방법입니다. 그리고 그렇게 함으로써 학생들은 의미있는 프로젝트를 완성할 수 있습니다. 그렇지만 파이썬은 또 중요한 컴퓨터 과학 개념들을 소개하기 위한 훌륭한 토대로 기여할 수 있습니다. 파이썬은 프로시저와 클래스를 완벽하게 지원하므로 학생들은 절차적인 추상화와 데이타 구조 그리고 객체-지향 프로그래밍과 같은 주제들과 차근차근 만날 수 있습니다---이 모든 것들은 나중에 자바 또는 C++ 강좌에 적용할 수 있습니다. 파이썬은 기능적 프로그래밍 언어로부터도 수 많은 특징을 빌려왔으며 이 덕분에 나중에 스킴과 리스프 강좌에서 더욱 자세하게 다루어질 개념들을 소개할 수 있습니다.
42 |43 | 제프리의 서문을 읽으면서 그의 논평에 감동하였습니다. 파이썬으로 그는 "더 높은 수준의 성공과 너 낮은 수준의 좌절"을 볼 수 있었으며 "더 빠르게 움직이고도 더 좋은 결과"를 얻을 수 있었습니다. 비록 이러한 논평이 그의 개론 강좌를 지칭하고 있기는 하지만, 본인 역시 정확하게 똑 같은 이유로 시카고 대학에서 대학 수준의 컴퓨터 과학 강좌에 파이썬을 자주 사용합니다. 이러한 강좌를 진행하다 보면 항상 피 말리는 9 주간의 학기 동안 서로 다른 수 많은 강의 재료들을 완수해야 한다는 엄청난 과제에 시달립니다. C++과 같은 언어를 사용하면 많은 고통과 고민을 확실하게 안겨줄 수 있지만, 나는 이러한 접근법이 비생산적이라는 것을 발견하곤 합니다---특히 강좌가 단지 "프로그래밍"에 관련되지 않은 주제에 관한 것일 때는 말이지요. 파이썬을 사용하면 나로서는 당면한 실제 주제에 더 초점을 맞출 수 있고 반면에 학생들은 가시적인 수업 프로젝트를 완성할 수 있다는 사실을 발견하였습니다.
44 |45 | 비록 여전히 젊고 진화하고 있는 언어임에도 불구하고 파이썬은 교육 분야에 밝은 미래가 있다고 믿습니다. 이 책은 그 방향으로 중요한 한 걸음을 내딛고 있습니다.
46 |
47 | 데이비드 비즐리(David Beazley)
48 | 시카고 대학(University of Chicago)
49 | Python Essential Reference의 저자
51 |
![]() |
57 | ![]() |
58 | ![]() |
59 | ![]() |
60 | ![]() |
61 | ![]() |
62 | ![]() |
28 | by Allen B. Downey,
29 | Jeffrey Elkner and
30 | Chris Meyers
한글판 johnsonj 2008.10.03 開天節
목차 |
---|
권두언 |
서문 |
공헌자 목록 |
제 1장: 프로그램의 방법 |
제 2장: 변수, 표현식 그리고 서술문 |
제 3장: 함수 |
제 4장: 조건과 재귀 |
제 5장: 유익한 함수들 |
제 6장: 반복 |
제 7장: 문자열 |
제 8장: 리스트 |
제 9장: 히스토그램 |
제 10장: 터플과 사전 |
제 11장: 파일과 예외 |
제 12장: 클래스와 객체 |
제 13장: 클래스와 함수 |
제 14장: 메쏘드 |
제 15장: 객체 집합 |
제 16장: 상속 |
제 17장: 연결 리스트 |
제 18장: 스택 |
제 19장: 큐 그리고 선점 규 |
제 20장: 트리 |
제 21장: ADT 트리 |
부록 A: 디버깅 |
부록 B: 새로운 데이타형을 만들기 |
부록 C: 완전한 파이썬 목록들 |
부록 D: 더 읽어야 할 권장 문헌 |
부록 E: 문서테스트 |
부록 F: 모듈과 파일 |
GNU 자유 문서 라이센스 |
색인 |
역자의 말 |
70 | 압축 파일을 내려 받으시려면
71 | 여기를 클릭하세요
72 |
![]() |
37 | ![]() |
38 | ![]() |
39 | ![]() |
40 | ![]() |
41 | ![]() |
42 | ![]() |
43 |
How to think like a Computer Scientist !
47 |
by Allen B. Downey, Jeffrey Elkner and Chris Meyersby
컴퓨터 과학자같이 생각하는법!
49 |역 자 : johnsonj
51 |현재버젼 : 2008.10.03
52 |번역기간 : 2008.09.15 - 2008.10.02
53 |원본위치 : 파이썬문서고
54 |56 |
한글판 johnsonj 2008.10.03 개천절
63 |![]() |
67 | ![]() |
68 | ![]() |
69 | ![]() |
70 | ![]() |
71 | ![]() |
72 | ![]() |
73 |
![]() |
26 | ![]() |
27 | ![]() |
28 | ![]() |
29 | ![]() |
30 | ![]() |
31 | ![]() |
32 |
제프 엘크너(Jeff Elkner) 씀
41 |42 | 이 책은 인터넷과 자유 소프트웨어 운동에 힘을 얻은 협동작업 덕분에 존재하고 있습니다. 이 책의 저자 세 명은 --- 대학 교수와 고등학교 교사 그리고 전문 프로그래머로서 --- 아직까지 얼굴을 마주한 적이 없습니다. 그러나 서로 밀접하게 함께 작업할 수 있었고 수 많은 훌륭한 분들이 시간과 열정을 바쳐서 이 책을 개선하는데 도움을 주셨습니다.
43 | 44 |45 | 리차드 스톨만과 자유 소프트웨어 연합이 제시한 작업틀 안에서 서로 협력하여 탄생시킨 이 책은 미래의 가능성과 혜택을 가늠하는 좋은 시금석이라고 생각합니다.
46 | 47 |51 | 1999년 처음으로 대학 위원회의 AP(Advanced Placement) 컴퓨터 과학 시험이 C++로 출제 되었습니다. 온 도시를 통틀어 많은 고등학교들이 사용하여 왔던 터이므로, 언어를 변경한다는 결정은 욕타운 고등 학교의 컴퓨터 과학 강의에 직접적으로 충격을 주었습니다. 제가 시무하고 있는 욕타운 고등학교는 버지니아주 알링턴시에 있습니다. 이 시점까지 파스칼이 첫-해와 AP 강좌에 사용되는 교육 언어였습니다. 학생들에게 같은 언어로 2년간 가르친다는 과거의 관례를 따르기 위하여 1997-98 년동안 C++로 바꾸기로 결정하였습니다. 그렇게 그 다음해에 대학 위원회가 AP 코스를 변경하는 것에 보조를 맞추려고 하였습니다.
52 | 53 |54 | 2 년이 지나자 C++은 학생들에게 컴퓨터 과학을 소개하기에 좋지 않은 선택이라는 확신이 들었습니다. C++은 대단히 강력한 프로그래밍 언어이기는 하지만 또한 배우고 가르치기에 극도로 어렵습니다. C++의 어려운 구문과 다양한 처리 방법에 대하여 끊임없이 싸우고 있는 나 자신을 발견하였습니다. 그리고 결과적으로 불필요하게 많은 학생들을 잃고 있었습니다. 첫-해 강의에 더 좋은 언어를 선택할 수 있으리라는 확신을 하고서 C++을 대신할 대안을 찾았습니다.
55 | 56 |57 | 컴퓨터실에서 사용하는 리눅스 머신 뿐만 아니라 대부분의 학생들이 집에서 사용하는 윈도우즈와 매킨토시 플랫폼에서도 실행되는 언어가 필요했습니다. 그 언어가 오픈 소스이기를 원했습니다. 그래야 학생들이 용돈에 신경쓰지 않고 집에서 그것을 사용할 수 있을 테니까요. 전문 프로그래머가 사용하는 언어를 원했습니다. 그리고 적극적인 개발자 공동체가 그 언어를 지원하기를 원했습니다. 그 언어는 절차와 객체-지향 프로그래밍 모두를 지원할 필요가 있었습니다. 그리고 무엇보다도 중요한 것은 배우기에 쉬워야 하고 가르치기에 쉬워야 했습니다. 이러한 목표들을 염두에 두고 조사를 마치고 나니까 파이썬이 목표에 가장 훌륭한 후보로 우뚝 서 있었습니다.
58 | 59 |60 | 욕타운의 재능있는 학생중, 매트 아렌스(Matt Ahrens)에게 파이썬을 한 번 시험해 보라고 권했습니다. 두 달이 지나자 그는 그 언어를 다 배웠을 뿐만 아니라 pyTicket 이라고 부르는 어플리케이션을 작성하였습니다. 그 프로그램으로 우리 운영진은 웹을 통하여 기술적인 문제를 보고할 수 있었습니다. C++이었다면 그가 그 정도 크기의 어플리케이션을 그렇게 단기간에 작성할 수는 없었을 것이라고 확신합니다. 그가 파이썬에 적극적으로 매달려 얻은 이 성과는 파이썬이 바로 내가 찾고 있던 해결책이라는 확신을 주었습니다.
61 | 62 |66 | 그 다음해 모든 컴퓨터 과학 개론 수업에 파이썬을 사용하기로 결심했습니다. 그러나 교과서가 없다는 골치 아픈 문제에 봉착했습니다.
67 | 68 |69 | 무료 웹소가 구세주가 되어 주었습니다. 그 해 상반기에 리차드 스톨만(Richard Stallman)은 나에게 알렌 다우니(Allen Downey)를 소개해 주었습니다. 우리 둘은 리차드에게 편지를 보내어 자유 교육 웹소를 개발하는데 관심을 표명하였습니다. 알렌은 이미 신입생용 컴퓨터 과학 교과서인 컴퓨터 과학자같이 생각하는 법을 집필하였습니다. 이 책을 읽는 순간 그것을 내 수업에 사용하고 싶었습니다. 내가 본 컴퓨터 과학 교과서 중에 가장 도움이 되는 명료한 책이었습니다. 그 책은 특별한 언어의 사양보다는 프로그래밍과 연관된 사고의 과정에 초점을 맞추고 있었습니다. 그 책을 읽고 나는 더욱 훌륭한 선생님이 되었습니다.
70 | 71 |컴퓨터 과학자같이 생각하는 법은 훌륭한 책일 뿐만 아니라 GPL(GNU public license)아래에서 배포되어 왔습니다. 이는 사용자의 필요에 맞추어 자유롭게 변경하고 사용할 수 있다는 것을 의미합니다. 파이썬을 사용하기로 결정하자 알렌의 원래 자바 버전 교과서를 새로운 언어로 번역할 수 있겠다는 생각이 들었습니다. 나 혼자로는 교과서를 집필하지 못하였을 테지만, 알렌의 책을 가지게 됨으로써 책을 집필할 수 있었으며, 동시에 소프트웨어에 잘 적용되던 협동적인 개발 모델이 교육적인 웹소에 대해서도 잘 작동한다는 것을 보여줄 수 있었습니다.
72 | 73 |74 | 이 책에 대해서 지난 2 년 동안 작업해 오면서 나와 학생들은 많은 보상을 받았습니다. 그리고 나의 학생들이 그 과정에서 중요한 역할을 하였습니다. 누군가가 어려운 구문이나 철자 에러를 발견하면 바로바로 수정을 할수 있었으므로 그들에게 이 책에서 실수를 찾기를 독려하였으며 이 교과서에 개선을 제안할 때마다 학생들에게 보너스 점수를 주었습니다. 이렇게 하여 학생들이 더욱 세심하게 교과서를 읽도록 독려하게 되는 장점뿐만 아니라 비판적으로 교과서 전체를 검토할 수 있는 장점도 얻을 수 있었으며, 학생들은 이런 교과서를 사용하여 컴퓨터 과학을 배웠습니다.
75 | 76 |77 | 이 책의 후반부에 있는 객체-지향 프로그래밍에 대하여 나보다는 실전 프로그래밍 경험을 가지고 있는 누군가가 정확하게 처리해줄 필요가 있다는 사실을 깨닫았습니다. 이 책은 더 좋은 웹소를 위하여 완성되지 못하고 일 년 동안을 지냈습니다. 드디어 오픈 소스 공동체가 책의 완성을 위하여 필요한 방법을 다시 제공하여 주었습니다.
78 |79 | 크리스 메이어스(Chris Meyers)가 이 책에 관심이 있다는 전자메일을 보내 왔습니다. 그는 전문 프로그래머로서 지난 해에 오레곤주의 레인 단과대학(Lane Community College)에서 파이썬을 이용한 프로그래밍 강좌를 시작하였습니다. 강좌 계획으로 그는 이 책에 관심을 가지게 되었고, 즉시 돕기 시작하였습니다. 그 학기의 마지막에 다다를 즈음, 그는 우리의 웹 사이트 http://www.ibiblio.org/obp에 재미로 하는 파이썬이라고 부르는 동료 프로젝트를 개설하였고 나의 우수한 학생들에게 왕 선생님의 역할을 하여 주어서, 내가 이끌어 주지 못하는 높은 곳까지 학생들을 이끌어 주었습니다.
80 | 81 |84 | 지난 이 년간 컴퓨터 과학자 같이 생각하는 법을 번역하고 사용해 오면서 파이썬이 이제 시작하는 학생들에게 적절하다는 확신을 갖게 되었습니다. 파이썬은 프로그래밍 예제를 놀랍도록 단순화시켜 주며 중요한 프로그래밍 아이디어를 더 쉽게 가르치도록 하여줍니다.
85 | 86 |87 | 이 교과서의 첫 번째 예제는 이 장점을 보여줍니다. 전통적인 "hello, world" 프로그램으로서 C++ 버전의 교과서에서는 다음과 같이 보입니다:
88 | 89 | 90 |91 | #include <iostream.h> 92 | 93 | void main() 94 | { 95 | cout << "Hello, world." << endl; 96 | } 97 |98 | 99 |
파이썬 버전으로는 다음과 같이 됩니다:
100 | 101 | 102 |103 | print "Hello, World!" 104 |105 | 106 |
107 | 비록 이것이 사소한 예제일지는 모르나, 파이썬이 보여주는 장점은 명백합니다. 욕타운의 컴퓨터 과학 I 강좌는 아무런 필수조건도 요구하지 않습니다. 너무나 많은 학생들이 첫 프로그램으로 이 예제를 볼 것입니다. 어떤 학생들은 컴퓨터 프로그래밍은 배우기가 어렵다는 소문을 듣고서 약간은 긴장해 있을 것입니다. C++ 버전은 항상 나에게 만족스럽지 못한 두 개의 선택사항 중 하나를 선택하도록 강요합니다: #include
, void main()
, {, and }를 모두 설명해 주고서 이제 겨우 시작하는 학생들을 의기소침하게 하거나 혼란에 빠트리는 위험을 감수해야 합니다. 또는 그들에게 다음과 같이 말해줍니다, "지금으로서는 그런 모든 것들에 신경쓰지마라; 나중에 그것에 관하여 알게 될거야." 그리고 같은 위험을 감수합니다. 컴퓨터 과학 강좌의 교육 목표는 학생들에게 프로그래밍 서술문에 대한 아이디어를 소개하고 그들 만의 첫 번째 프로그램을 작성하도록 도와 주며, 그럼으로써 프로그래밍 환경을 소개해 주는 것입니다. 파이썬 프로그램은 이러한 일들을 하는데 필요한 것들을 정확하게 가지고 있습니다.
109 | 예제 프로그램을 설명한 부분을 각 버전별로 비교해 보면 이것이 시작하는 학생들에게 무엇을 의미하는 것인지 더욱 잘 알 수 있습니다. C++ 버전에서는 "Hello, world!"를 설명하는데 열 세 개의 문단이 있습니다; 파이썬 버전에서는 단 두개의 문단만 있습니다. 더욱 중요한 것은 빠진 열 한개의 문단이 컴퓨터 프로그래밍에 있어서의 "중요한 아이디어"를 다루고 있는 것이 아니라 C++ 구문의 세부사항을 다루고 있다는 것입니다. 이와 똑 같은 상황이 책 전체를 통하여 일어나는 것을 알게 되었습니다. 전체 문단이 파이썬 버전의 텍스트에서는 쉽게 사라집니다. 왜냐하면 파이썬의 더욱 명료한 구문 덕분에 그 문단은 불필요하게 되기 때문입니다.
110 | 111 |112 | 파이썬 같이 아주 높은-수준의 언어를 사용하면 선생님들은 기계의 저-수준 세부사항에 대한 언급을 유보할 수 있으며 학생들은 그 동안 그 세부사항을 더 잘 이해하기 위해 필요한 배경(지식)을 익힐 수 있습니다. 그런식으로 교육적으로 "중요한 것을 먼저" 다루는 능력이 생깁니다. 이러한 것중 가장 훌륭한 예는 파이썬이 변수들을 다루는 방식입니다. C++에서 변수는 어떤 것을 보유하고 있는 장소에 대한 이름입니다. 변수들은 부분적으로라도 유형이 선언될 필요가 있습니다. 왜냐하면 변수들이 참조하고 있는 그 장소의 크기가 미리 결정될 필요가 있기 때문입니다. 그리하여, 변수에 대한 아이디어는 그 기계의 하드웨어에 얽매이게 됩니다. 변수에 대한 강력하고 기본적인 개념들은 시작하는 학생들에게 (컴퓨터 과학과 대수학 모두에서) 이미 충분히 어렵습니다. 바이트와 주소만으로는 도움이 되지 않습니다. 파이썬에서 변수는 어떤 것을 가리키는 이름입니다. 이것은 시작하는 학생들에게 훨씬 더 직관적인 개념이며 수학 수업시간에 배운 "변수"의 의미에 더욱 근접한 것입니다. 과거에 비해 금년에는 학생들에게 변수를 훨씬 더 쉽게 가르쳤고 변수를 사용할 때 발생하는 문제를 도와주는 시간을 훨씬 더 적게 소비하였습니다.
113 | 114 |
115 | 파이썬이 프로그래밍을 가르치고 배우는데 있어서 어떻게 도움을 주는지 보여주는 또 다른 예는 함수 구문에 있습니다. 학생들은 항상 함수를 이해하는 것을 대단히 어려워 했습니다. 주요 문제는 함수정의와 함수호출 사이의 차이 그리고 매개변수와 인자 사이의 구별에 집중됩니다. 파이썬은 아름답기까지 한 구문으로 구세주가 되어 줍니다. 함수 정의는 키워드 def
로 시작합니다. 그래서 학생들에게 단순하게 말해줍니다, "네가 함수를 정의할 때는, def
로 시작하거라. 다음에 함수의 이름을 정의하거라; 함수를 호출할 때는 그냥 그 이름을 호출(타자) 하거라." 매개변수는 정의와 어울립니다; 인자는 호출과 어울립니다. 길을 가로 막는 어떤 반환 유형이나 매개변수 유형 또는 참조와 값 매개변수도 전혀 없습니다. 그래서 이제 이전에 걸렸던 시간의 절반 이하로도 함수를 더 잘 이해할 수 있도록 가르칠 수 있습니다.
117 | 파이썬을 사용하자 우리의 컴퓨터 과학 프로그램은 모든 학생들에게 효율적이 되었습니다. 지난 이 년 동안 C++을 가르치면서 경험했던 것보다 일반적으로 더 높은 수준의 성공과 더 낮은 수준의 실패를 맛보고 있습니다. 더 빠르게 움직이고도 더 좋은 결과를 얻습니다. 강좌를 마칠 즈음이면 더욱 많은 학생들이 의미심장한 프로그램을 창조해 내는 능력을 익히고 이것을 이끌어 내는 프로그래밍을 경험합니다. 그리고 긍정적 태도를 가지고 강좌를 떠납니다.
118 | 119 |123 | 이 책을 사용하여 프로그래밍을 배우고 가르치는 전세계의 모든 사람들이 전자우편을 보내 주셨습니다. 한 사용자 공동체가 탄생해야 했습니다. 많은 사람들이 http://www.thinkpython.com에 있는 동료 웹사이트에 재료들을 보내줌으로써 프로젝트에 공헌하였습니다.
124 |125 | 그 책을 인쇄된 형태로 출간하면서 나는 사용자 공동체가 계속해서 더욱 빠르게 성장할 것이라고 예상합니다. 이 프로젝트에 임하면서 나는 이 사용자 공동체의 탄생과 교육자들 사이의 협동이 제시하는 가능성이 대단히 흥미로왔습니다. 함께 일함으로써 재료의 질을 향상시켜 우리의 목적에 이용할 수 있었고 귀중한 시간을 절약할 수 있었습니다. 여러분에게 우리의 공동체에 참여하기를 권합니다. 당신의 한 말씀을 고대합니다. feedback@thinkpython.com으로 저자들에게 전자우편을 보내 주십시오.
126 | 127 |\ 제프리 엘크너(Jeffrey Elkner)
욕타운 고등학교(Yorktown High School)
버지니아주, 알링턴시( Arlington, Virginia)
\
130 |
![]() |
134 | ![]() |
135 | ![]() |
136 | ![]() |
137 | ![]() |
138 | ![]() |
139 | ![]() |
140 |
28 | "파이썬 요리책(The Python Cookbook)", 특히 앞의 몇 장을 읽어보자. 29 | 작성이 잘된 파이썬 코드의 예가 모인 훌륭한 소스이다. 30 |
31 |32 | 문자열은 리스트로 구축하고 마지막에 ''.join을 사용하자. 33 | join 메쏘드는 리스트가 아니라 가름자(separator)에서 호출된다. 빈 문자열에서 호출되면 가름자 없이 조각들을 결합해 주는데, 이는 파이썬의 버릇으로서 처음 보면 약간 놀랍다. 34 | 35 | 중요한 것은 이것이다: +로 문자열을 구축하면 선형 시간이 아니라 제곱 시간이 걸린다! 관용구 하나를 배워야 한다면, 이 관용구를 익히자. 36 |
37 |나쁨: for s in strings: result += s 38 | 옳음: result = ''.join(strings) 39 |40 |
41 | 42 | 언제나 객체의 유형이 아니라 그의 능력을 사용하자. 파이썬은 동적으로 형이 정의되는 언어이다: 기본적으로 한 객체가 특정 유형인지 걱정하지 않아도 된다. 특정 인터페이스를 지원하기만 하면 된다. 이 때문에 인상적인 다형성을 무료로 사용할 수 있다. 예를 들어, 문자열이 알파벳인지 여부를 점검하는 코드는 다음과 같다: 43 |
44 |for char in string: 45 | if char not in alphabet: 46 | raise ValueError, "Char %s not in alphabet %a" % (char, alphabet) 47 |48 |
49 | alphabet이 문자열인지, 사전인지, 리스트인지, 아니면 내가 정의한 객체인지 문제가 되지 않는다. __contains__ 특수 메쏘드를 지원하기만 하면 된다. 50 |
51 | 52 |53 | 54 | 가능하면 in을 사용하자 55 | (자신의 클래스에서 __contains__를 오버라이드하면 if x in y 구문을 지원할 수 있으며 __iter__를 오버라이드하여 for x in y 구문을 지원할 수 있다). 이 덕분에 서술문을 보편적이고 다형성 있게 유지할 수 있다. 56 |
57 |개선: for key in d: print key # 임의의 문자열에도 작동한다 58 | 나쁨: for key in d.keys(): print key # 객체가 keys()를 가져야 하는 제한이 있다 59 | Better: if key not in d: d[key] = [] 60 | Worse: if not dict.has_key(key): d[key] = [] 61 |62 |
63 | 주의: 사전을 수정하고 싶으면 여전히 d.keys()를 사용할 필요가 있다. for key in d: del d[key] 는 다음 64 | RuntimeError: dictionary changed size during iteration 에러를 야기한다. 대신에 for key in d.keys(): del d[key] 를 사용하자. 65 |
66 |67 | 객체가 특정 유형이어야 한다면 강제형변환을 사용하자. 68 | x가 꼭 문자열이어야만 코드가 작동한다면, isinstance(str, x) 같은 것을 사용하는 대신에 str(x)을 사용하는 것이 어떨까? 에러를 잡고 싶다면, try/except에 싸 넣으면 되고, 모든 가능성을 예측하고 싶은 경우라면, 이것이 훨씬 더 일반적인 해결책이 될 것이다. 69 |
70 |71 | 72 | if x == 0나 if x == "" 또는 if x == None나 if x == False 대신에 if not x을 사용하자; 마찬가지로, if x != 0나 if x != None 등등 대신에 if x을 사용하자. 예외의 경우가 있다면: 숫자에 대하여, 0은 거짓 값이므로, False를 돌려주는 것들과 0을 구별할 필요가 있다. 0으로 추정되는 부동 소수점 수와 int(0) 또는 float(0.0)를 비교할 때 주의하자: 반올림 에러 때문에 버그가 급격히 늘어날 수 있다. 73 |
74 |75 | 문자열 모듈 말고 문자열 메쏘드를 사용하자. 76 | 예를 들어, startswith(s, 'abc') 대신에 s.startswith('abc')를 사용하자. 이렇게 하면 문자열 인터페이스의 일부만을 지원하는 다른 객체들을 사용할 수 있다 (예를 들어, 직접 작성한 것들): string 모듈 함수는 일반적으로 진짜 문자열을 예상한다. 몇몇 경우에는 import string을 사용할 필요가 있다: 예를 들어, maketrans는 여전히 문자열 모듈에서만 사용이 가능하다. 그렇지만, 일반적으로 저런 반입 서술문을 본다면 경고가 표시된다. 77 |
78 |79 | 80 | for line in infile.readlines() 대신에, for line in infile을 사용하자. readlines와 xreadlines는 어쨌거나 파이썬 2.3에서부터 비추천이다. 새로운 반복자 프로토콜을 권장한다. for line in infile 버전을 사용하면 infile이 무엇이든 연속열 같이 행위하기만 하면 된다. 이는 검증에 아주 큰 도움이 될 수 있다. 실제로, 그냥 for line in lines을 사용하자: 기본적으로 줄이 파일에서 왔는지, 문자열 리스트에서 왔는지, 아니면 기타 다른 반복자인지, 사전의 키인지, 기타 무엇으로부터 왔든지 신경쓸 필요가 없다. 81 |
82 |83 | 리스트를 역정렬하려면, 다음과 같이 하자: 84 |
85 |list.sort() 86 | list.reverse() 87 |88 |
89 | 훨씬 더 읽기 쉽고, 게다가 꼼수적인 1-줄 대안책보다 더 빠르다. 90 | 91 | 제-자리 메쏘드인 sort()와 reverse()는 값을 돌려주지 않으니 주의하자. 이 때문에 당황할 수 있는데, 왜냐하면 sorted_list = orig_list.sort()와 같이 해 보면 sorted_list는 None이고 이제 orig_list가 정렬되어 있기 때문이다. 그냥 역정렬된 리스트를 순회하고 싶을 뿐이라면 주의하자. (파이썬 2.5에서 부터) for i in reversed(sorted(orig_list))을 사용하면 된다. 92 |
93 |94 | 95 | 무한 회돌이에는 'while 1:'을 사용하자. 즉, 회돌이 몸체를 적어도 한 번 실행하자. 96 | 이는 파이썬의 관용구이지만, 다른 사람들도 한 번쯤은 자신이 사용했던 언어에서 보았을 것이다. 예를 들어: 97 |
98 |while 1: 99 | curr_line = reader.next() 100 | if not curr_line: 101 | break 102 | curr_line.process() 103 |104 |
105 | 106 | 코드를 특수 사례로 어지럽히지 않기 위해 에러를 피하기 보다는 잡자. 이 관용구는 LBYL ('look before you leap(뛰기 전에 살펴보자)')에 대조하여 EAFP('easier to ask forgiveness than permission(허락보다 용서를 구하는 편이 더 쉽다)')라고 부른다. 이 덕분에 코드의 가독성이 더 좋아지는 경우가 많다. 예를 들어: 107 |
108 |개악: 109 | # int 형변환이 에러를 일으킬지 점검한다 110 | if not isinstance(s, str) or not s.isdigit: 111 | return None 112 | elif len(s) > 10: # int 형변환에 너무 자리수가 많음 113 | return None 114 | else: 115 | return int(str) 116 | 117 | 개선: 118 | try: 119 | return int(str) 120 | except (TypeError, ValueError, OverflowError): # int 형변환 실패함 121 | return None 122 |123 |
124 | (이 경우 주의할 것은 두 번째 버전이 더욱 더 좋은데 125 | , 선두의 +와 - 뿐만 아니라 (32-비트 머신의) 2와 10조 사이의 값도 올바르게 처리하기 때문이다. 가능한 모든 실패 사례를 예측하려고 코드를 어지럽히지 말자: 그냥 시도해 보고 적절하게 예외 처리를 하자.) 126 |
127 |128 | 적절한 에러만 잡자. 129 | 어느 에러를 잡고 싶은지 지정하지 않고서 catch를 사용하는 것은 대단히 위험하다. 왜냐하면 아무거나 다 잡아 버리기 때문이다. ZeroDivisionError나 ValueError 같이, 특정한 종류의 에러가 예상된다면 그 에러만 일어날 거라는 가정하에 다른 어떤 것도 잡지 말자. 그렇지 않으면 메모리가 고갈될 수도 있으며, 또는 올바르지 못한 속성을 가진 또는 그 연산이 구현되지 않은 객체를 건넬 가능성이 있다. 이런 예측불가의 에러를 감춰버리면 디버깅이 아주 어려워진다. 특히 엉터리 에러 메시지를 인쇄한다면 말이다. 130 |
131 |132 | 임시 변수를 사용하지 않고 값을 교환하기. 133 | 대신에, 묵시적인 터플 풀기를 사용하자. a, b = b, a를 사용하면 a와 b를 교환할 수 있다. 사실, 얼마든지 원하는 갯수만큼 이렇게 할 수 있다: 134 | a를 d, 등등에 짝지으려면 a, b, c, d = d, b, c, a. 135 |
136 |137 | 138 | 리스트의 (또는 어떤 연속열이든지) 요소를 그의 인덱스로 얻으려면 zip을 사용하자: 139 |
140 |indices = xrange(maxint) # 여기에서 한 번만 필요하다; 본인은 Utils.py에 정의해 두었다. 141 | for d, index in zip(data, indices): 142 | # 여기에서 d와 인덱스를 가지고 일을 한다 143 |144 |
145 | (파이썬 2.3에서는 enumerate(data)를 지원하는데, 이 함수는 연속열을 게으르게 평가하며, 이 덕분에 이 관용구가 대체로 불필요하게 되었다. 그렇지만, 여러 리스트와 함께 인덱스를 포함시키고 싶을 경우에는 여전히 쓸모가 있다. 예를 들어, zip(list_1, list_2, indices)와 같이 말이다. 146 | 147 | 64-비트 시스템에서는 실패할 수도 있다.) 148 |
149 |150 | 인덱스가 필요없다면, 그냥 다음과 같이 하자: 151 |
152 |for i in items: 153 | something(i) 154 | 155 | ...다음은 추천하지 않는다: 156 | 157 | for index in range(len(items)): 158 | something(items[index]) 159 |160 |
161 | (타자수가 더 많이 들고, 더 못생겼고, 더 느리다.) 162 |
163 | 164 | 165 | 166 | 167 |171 | 언제나 윤곽잡기를 먼저 하고 나서 속도를 위한 최적화를 하자. 172 | 제일 먼저 가독성을 위한 최적화를 하자: '최적화된' 코드를 읽는 것보다 읽기 쉬운 코드를 조율하는게 더 쉽다. 특히 그 최적화가 효과적이지 못하다면 말이다. 코드를 읽기 힘들게 만드는 테크닉을 사용하기 전에, 내장 profile.py 스크립트로 어플리케이션을 실행하여 정말로 병목인지 점검해야 한다. 프로그램이 특정 메쏘드를 실행하는 데 실행시간의 10%를 소모한다면, 그의 속도를 10배나 증가시키더라도 총 실행시간의 9%를 절약할 뿐이다. 173 |
174 |175 | 가능하면 항상 좋은 알고리즘을 사용하자. 176 | 위의 규칙이 적용되지 않는 경우는 대안 알고리즘들의 시간 복잡도에 큰 차이가 있을 경우이다. 제곱시간에서 선형시간으로, 또는 지수시간에서 폴리노미얼 시간으로 실행시간이 감소한다면 데이터 세트가 언제나 작을 것이라고 (20개 요소 이하라고) 확신하지 않는 한 언제나 시도해 볼 가치가 있다. 177 |
178 |179 | 작동 할 만큼만 가장 단순하게 선택하자. 180 | 한 문자열이 특정한 하부문자열로 시작하는지 그저 알고 싶을 뿐이라면 정규 표현식을 사용하지 말자: 대신 .startswith을 사용하자. 그저 한 문자열에 특정 문자가 포함되어 있는지 알고 싶을 뿐이라면 .index를 사용하지 말자: 대신 in을 사용하자. 그냥 문자열 리스트를 사용해도 된다면 StringIO를 사용하지 말자. 일반적으로, 단순할 수록 버그가 줄어들고 코드는 더욱 읽기 쉬워진다. .index 호출을 복잡하게 조합할지라도 정규 표현식보다 훨씬 더 빠르며, 그 결과를 나포하는 것보다 그냥 일치시키는 편이 훨씬 이해하기 쉬울 것이다. 181 |
182 |183 | 리스트로 문자열을 구축하고 끝에 ''.join을 사용하자. 184 | 물론, 이미 이를 위의 "파이썬 관용구"에서 보셨지만, 너무나 중요한 것이라서 다시 한 번 언급해야겠다고 생각했다. 185 | join은 문자열 메쏘드로서 리스트가 아니라 가름자(separator)에 요청된다. 이를 빈 문자열에서 호출하면 가름자 없이 조각들을 결합해주는데, 이는 파이썬의 버릇으로서 처음 보면 약간 놀랍다. 186 | 이것이 중요하다: +로 문자열을 구축하면 선형시간이 아니라 제곱 시간이 걸린다! 187 |
188 |잘못: 189 | for s in strings: result += s 190 | 옳음: 191 | result = ''.join(strings) 192 |193 |
194 | 195 | 가능하면 객체 신분에 대하여 테스트하자: 다시 말해 if x != None보다는 if x is not None이 더 좋다. 동등성보다 신분으로 객체를 테스트하는 것이 좀 더 효율적이다. 왜냐하면 신분은 실제 데이터가 아니라 메모리상의 주소만 점검하기 때문이다 (물리적 위치가 같은 객체이기만 하면 두 객체는 동일하다). 196 |
197 |198 | 199 | 검색에는 리스트가 아니라 사전(또는 집합)을 사용하자. 200 | 두 리스트 사이에 공통 요소가 있는지 알아보려면, 첫 리스트를 사전에 넣고 그 안에서 두 번째 리스트 안의 원소들을 찾아 보자. 리스트에서 요소를 찾는 것은 선형-시간이지만, 반면에 사전이나 집합에서 요소를 찾는 것은 상수 시간이다. 이것으로 종종 검색 시간을 제곱 시간에서 선형 시간으로 줄일 수있다. 201 |
202 |203 | 204 | 가능하면 내장 sort를 사용하자. 205 | 206 | sort는 맞춤 비교 함수를 매개변수로 취할 수 있지만, 이 때문에 아주 느릴 수 있는데 함수가 내부 회돌이에서 적어도 O(n log n) 시간 호출되어야 하기 때문이다. 시간을 절약하려면, 항목 리스트를 터플 리스트로 바꾸자. 여기에서 각 터플의 첫 요소는 각 항목에 대하여 그 함수로 미리 계산된 값이며 (예를 들어, 필드를 추출), 마지막 요소는 그 항목 자체이다.
207 |208 | 이 관용구를 DSU('decorate-sort-undecorate')라고 부른다. 'decorate' 단계에서, (transformed_value, second_key, ... , original value)가 담긴 터플 리스트를 만든다. 'sort' 단계에서, 그 터플에 내장 sort를 사용하자. 209 | 'undecorate' 단계에서, 각 터플로부터 마지막 요소를 추출하여 원래 리스트를 정렬된 순서로 열람하자. 예를 들어: 210 |
211 |aux_list = [i.Count, i.Name, ... i) for i in items] 212 | aux_list.sort() # Count로 정렬한 다음, Name으로 정렬, ... , 다음 요소 그 자체로 정렬한다 213 | sorted_list = [i[-1] for i in items] # 마지막 요소를 추출한다 214 |215 |
216 | 최신 버전의 파이썬에서는 종종 DSU가 불필요하다. 217 | 다음 페이지에 파이썬에서의 다양한 정렬 테크닉에 관하여 연구가 잘 되어 있다. 218 |
219 |220 | 함수를 리스트에 적용하려면 map과 filter를 사용하자. 221 | map은 함수를 리스트의 각 요소에 (기술적으로, 연속열에) 적용해서 그 결과를 리스트로 돌려준다. filter는 함수를 연속열의 각 요소에 적용해서, (__nonzero__ 내장 메쏘드를 사용하여) True로 평가된 요소들만을 모아서 리스트로 돌려준다. 이 함수들로 코드를 많이 줄일 수 있다. 또한 더 빠르게 만들 수 있는데, 회돌이가 전적으로 C API 안에서 일어나며 회돌이 변수를 파이썬 객체에 절대로 엮지 않기 때문이다. 222 |
223 |개악: 224 | strings = [] 225 | for d in data: 226 | strings.append(str(d)) 227 | 228 | 개선: 229 | strings = map(str, data) 230 |231 |
232 | 조건이 부가되어 있거나 함수가 메쏘드일 경우 리스트통합을 사용하자. 아니면 하나 이상의 매개변수를 취하자. 233 | 234 | 이런 사례에서는 map과 filter가 제대로 일을 하지 못하는데, 원하는 연산을 해주는 인자-한개짜리 함수를 새로 만들어야 하기 때문이다. 이 때문에 더욱 더 느려지는데, 파이썬 수준에서 더 많은 일이 수행되어야 하기 때문이다. 235 | 리스트 통합은 놀랍도록 읽기가 좋은 때가 많다. 236 |
237 |개악: 238 | result = [] 239 | for d in data: 240 | if d.Count > 4: 241 | result.append[3*d.Count] 242 | 243 | 개선: 244 | result = [3*d.Count for d in data if d.Count > 4] 245 |246 |
247 | 똑같은 리스트 통합을 반복적으로 하고 있다면, 유틸리티 함수들을 만들어서 map이나 filter를 사용하자: 248 |
249 |def triple(x): 250 | """3 * x.Count를 돌려준다: .Count가 없으면 AttributeError를 일으킨다.""" 251 | return 3 * x.Count 252 | 253 | def check_count(x): 254 | """x.Count가 존재하고 3보다 크면 1을 돌려준다. 그렇지 않으면 0을 돌려준다.""" 255 | try: 256 | return x.Count > 3 257 | except: 258 | return 0 259 | 260 | result = map(triple, filter(check_count, data)) 261 |262 |
263 | 유틸리티 함수를 만들려면 함수 공장을 사용하자. 264 | 가끔, 특히 map과 filter를 아주 많이 사용하고 있다면, 다른 함수나 메쏘드가 단 한개의 매개변수를 취하도록 변환해주는 유틸리티 함수가 필요할 것이다. 특히나, 데이터를 그 함수에 한 번 묶고 난 다음, 다양한 객체들에다 반복적으로 적용하고 싶은 경우가 있을 것이다. 위의 예제에서는 한 객체의 특정 변수를 3배로 만들어주는 함수가 필요했다. 그러나 정말 필요한 것은 어떤 변수 이름이든 값을 돌려주고 그 가족에 곱셈 함수를 붙여주는 함수 공장이다: 265 |
266 |def multiply_by_field(fieldname, multiplier): 267 | """ 필드 "fieldname"에 multiplier를 곱하는 함수를 돌려준다.""" 268 | def multiplier(x): 269 | return getattr(x, fieldname) * multiplier 270 | return multiplier 271 | 272 | triple = multiply_by_field('Count', 3) 273 | quadruple = multiply_by_field('Count', 4) 274 | halve_sum = multiply_by_field('Sum', 0.5) 275 |276 |
277 | 이는 함수를 만들어내는 아주 강력하고 일반적인 테크닉이다. 예를 들어 지정된 필드에서 단어 리스트를 검색하거나, 한 특정 객체의 여러 필드에 여러 조치를 수행하는 등등의 테크닉에 사용된다. 매우 비슷한 일을 하는 자잘한 함수들을 수 없이 작성하는 일은 고통스럽다. 그러나 함수 공장으로 만들어내면 아주 쉬운 일이다. 278 |
279 |280 | 281 | 합이나 곱 등등을 얻으려면 operator 모듈과 reduce를 사용하자. 282 | reduce는 함수 하나와 연속열 하나를 취한다. 먼저 그 함수를 첫 두 요소에 적용한 다음, 그 결과를 취하고 그 결과와 다음 요소에 그 함수를 적용하고, 또 그 결과를 취해서 그 결과와 다음 요소에 그 함수를 또 적용하고, 등등 리스트의 끝에 도달할 때까지 계속된다. 이 덕분에 리스트를 (또는 실제로 어떤 연속열도) 따라서 요소들을 축적하기가 아주 쉬워진다. 파이썬 2.3에는 (숫자 전용의) 내장 sum() 함수가 있음을 주의하자. 이 때문에 그 필요성이 예전만 못하다. 283 |
284 |개악: 285 | sum = 0 286 | for d in data: 287 | sum += d 288 | product = 1 289 | for d in data: 290 | product *= d 291 | 292 | 개선: 293 | from operator import add, mul 294 | sum = reduce(add, data) 295 | product = reduce(mul, data) 296 |297 |
298 | 299 | 필드를 이름에 짝지으려면 zip과 dict를 사용하자. 300 | zip은 한 쌍의 연속열을 터플 리스트로 바꿔준다. 안에는 각 연속열의 첫째값, 둘째값 등등의 값이 담긴다. 예를 들어, zip('abc', [1,2,3]) == [('a',1),('b',2),('c',3)]. 이를 이용하면 이름에 짝짓고 싶은 필드가 순서대로 되어 있을 때 타자수를 절감할 수 있다: 301 |
302 |나쁨: 303 | fields = '|'.split(line) 304 | gi = fields[0] 305 | accession = fields[1] 306 | description = fields[2] 307 | #etc. 308 | lookup = {} 309 | lookup['GI'] = gi 310 | lookup['Accession'] = accession 311 | lookup['Description'] = description 312 | #etc. 313 | 314 | 좋음: 315 | fieldnames = ['GI', 'Accession', 'Description'] #etc. 316 | fields = '|'.split(line) 317 | lookup = dict(zip(fieldnames, fields)) 318 | 319 | 최상: 320 | def FieldWrapper(fieldnames, delimiter, constructor=dict): 321 | """한 줄을 갈라서 그것을 객체로 싸 넣어주는 함수를 돌려준다. 322 | 323 | 필드 이름은 키워드 인자로 건네지므로, 324 | 구성자는 그대로 건네질 것으로 예측한다. 325 | """ 326 | def FieldsToObject(line): 327 | fields = [field.strip() for field in line.split(delimiter)] 328 | result = constructor(**dict(zip(fieldnames, fields))) 329 | return FieldsToObject 330 | 331 | FastaFactory = FieldWrapper(['GI','Accession','Description'], '|', Fasta) 332 | TaxonFactory = FieldWrapper(['TaxonID', 'ParentID', ...], '|', Taxon) 333 | CodonFreqFactory = FieldWrapper(['UUU', 'UUC', 'UUA',...], ' ', CodonFreq) 334 | #등등 싸 넣고 싶은 데이터베이스 테이블을 포함하여, 비슷한 데이터에 적용. 335 |336 | 337 | -------------------------------------------------------------------------------- /documents/python-tips-tricks-and-hacks/Python Tips, Tricks, and Hacks_files/cc-by-nc-sa.png: -------------------------------------------------------------------------------- 1 | 2 | 3 |
7 |
27 |{{ data.title }} - {{ data.english }}
{{ data.year }}년 {{ data.translator }} 번역