파이써니스타처럼 코딩하는 법: 파이썬의 관용구

David Goodger
원문위치:http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html
한글판 johnsonj 2008.06.10 화

이 자습서에서는 파이썬의 수 많은 핵심적인 관용구와 테크닉들을 깊이 살펴보고, 바로 사용할 수 있는 도구들을 챙겨보겠다.

©2006-2007, 본 저작물은 창조적 공공재 공여/공유-류 (BY-SA) 라이센스하에 보호된다.

나를 소개한다:

필자는 (텍스트 & 데이터 처리라고 불리운) 이 자습서를 PyCon 2006에서 발표했는데, 나는 당연하다고 생각하며 테크닉을 사용하였으나 이를 본 사람들의 반응에 놀랐다. 그러나 참석자 중 많은 수는 경력 파이썬 프로그래머가 아무 생각없이 사용하는 이런 도구들을 모르고 있었다.

이전에 여러분중 많은 수는 이런 테크닉들을 보셨을 것이다. 모쪼록 미처 보지 못했던 테크닉들을 배우시고 이미 보았더라도 새로운 뭔가를 얻는 기회가 되시기를 바라는 바이다.

파이썬의 정수 (1)

다음은 파이썬의 지도적 근본 원리들이지만, 얼마든지 해석이 개방되어 있다. 적절하게 이해하려면 유머 감각이 있어야 한다.

유랑 극단의 이름을 딴 프로그래밍 언어(파이썬)를 사용하고 있다면, 유머 감각을 가지시는 편이 좋을 것이다.

못 생긴 것보다 잘 생긴 것이 좋다(Beautiful is better than ugly).
묵시적인 것보다 명시적인 것이 좋다(Explicit is better than implicit).
복잡한 것보다 단순한 것이 좋다(Simple is better than complex).
난잡한 것보다 복잡한 것이 좋다(Complex is better than complicated).
둘둘말이보다 펼쳐 놓는 것이 좋다(Flat is better than nested).
빽빽한 것보다 성긴 것이 좋다(Sparse is better than dense).
가독성을 고려하자(Readability counts).
특수한 사례라도 규칙을 깰만큼 특수하지는 않다(Special cases aren't special enough to break the rules).
순수보다 실용이 우선이라도(Although practicality beats purity).
에러는 조용히 넘어가면 안된다(Errors should never pass silently).
명시적으로 조용하라고 시키지 않는 한 말이다(Unless explicitly silenced).

...

파이썬의 정수 (2)

잘 모르겠으면, 추측하고 싶은 유혹을 떨쳐 버려라.
일을 하는 데에는 하나의 방법이—유일한 것이 바람직함— 확실하게 한 가지 방법이 있을 뿐이다.
처음에는 확실해 보이지 않더라도 돌다리를 두드려보고 건널생각이 없지 않는 한.
지금 하는 것이 안 하는 것보다 낫다.
안 하는 것보다 지금 당장 하는 것이 종종 좋을 경우도 있지만.
구현을 설명하는데 어려움이 있으면, 그것은 나쁜 아이디어이다.
구현을 쉽게 설명할 수 있다면, 좋은 아이디어일 것이다.
이름공간은 정말 훌륭한 아이디어이다—더 많이 활용하자!

—팀 피터스(Tim Peters)

이 특별한 "시"는 농담 비슷하게 시작하지만, 그 안에는 파이썬 뒤에 숨겨진 철학에 관하여 수 많은 진실이 담겨있다. 파이썬의 정수는 PEP 20에 공식화되었는데, 그 초록을 읽어보면:

오래전 파이써니어 팀 피터스(Tim Peters)는 파이썬 디자인에 대한 BDFL(귀도 반 로섬)의 지도 원리를 간결하게 20개의 문구로 정리했는데, 그 중에 19개만 기록했다.

http://www.python.org/dev/peps/pep-0020/

여러분 자신이 "파이썬니어(Pythoneer)"인지 "파이써니스타(Pythonista)"인지 스스로 판단하실 수 있다. 두 용어는 약간 함의가 다르다(역주: 파이써니스타가 파이써니어에 비하여 파이썬의 관행을 더 면밀하게 준수한다고 한다.).

잘 모르겠으면 이렇게 해보자:

import this

파이썬 상호대화 인터프리터 안에서 시도해 보자:

>>> import this

다음은 또다른 숨은 재미이다(easter egg):

>>> from __future__ import braces     # 앞으로는 괄호를 블록 구분자로 쓸수 있겠지요?
  File "<stdin>", line 1              # 물어보자 마자,
SyntaxError: not a chance             # 구문에러: 절대 그럴일 없답니다

정말 재미있다! :-)

코딩 스타일: 가독성을 고려하자(Readability Counts)

프로그램은 사람들이 읽기 쉽게 작성되어야 하며, 아주 특별한 경우에만 프로그램이 실행할 수 있도록 작성되어야 한다.

—Abelson & Sussman, Structure and Interpretation of Computer Programs

프로그램을 읽기 쉽게 그리고 명료하게 작성하도록 하자.

PEP 8: 파이썬 코드를 위한 스타일 가이드

꼭 읽어 보실 것:

http://www.python.org/dev/peps/pep-0008/

PEP = 파이썬 개선 제안(Python Enhancement Proposal)

PEP는 디자인 문서로서 파이썬 공동체에 정보를 제공하거나, 파이썬의 새로운 특징이나 그의 처리과정 또는 환경을 기술한다.

파이썬 공동체는 소스 코드가 어떤 모습이어야 하는지 독자적인 표준을 가지고 있으며, 이는 PEP 8에 규정되어 있다. 이런 표준들은 C, C++, C#, Java, VisualBasic, 등등의 다른 공동체가 가진 표준과 다르다.

파이썬에서 들여쓰기와 공백은 너무 중요하기 때문에, 파이썬 코드를 위한 스타일 지도서에서 표준을 정립하였다. 지도서의 지침을 따르는 것이 현명하다! 대부분의 열린-소스 프로젝트와 (다행스럽게도) 사내 프로젝트는 스타일 가이드를 착실히 따른다.

공백 1

공백 2

def make_squares(key, value=0):
    """Return a dictionary and a list..."""
    d = {key: value}
    l = [key, value]
    return d, l

이름짓기

기다란 줄 & 줄 잇기

줄의 길이는 80자 이하를 지키자.

활괄호/각괄호/반괄호 안에 두고 묵시적인 줄 연속을 이용하자:

def __init__(self, first, second, third,
             fourth, fifth, sixth):
    output = (first + second + third
              + fourth + fifth + sixth)

역사선은 최후의 수단으로 의지하자:

VeryLong.left_hand_side \
    = even_longer.right_hand_side()
역사선은 주의할 점이 있다; 역사선으로 반드시 그 줄을 끝내야 한다. 역사선 뒤에 공백문자를 하나 추가하면, 더 이상 효력을 잃는다. 또, 보기에도 좋지 않다.

긴 문자열

인접한 기호상수 문자열은 해석기가 결합해 준다:
>>> print 'o' 'n' "e"
one

기호상수들 사이에 공간은 필수가 아니지만, 읽기에 도움이 된다. 어떤 유형의 인용방법도 허용된다:

>>> print 't' r'\/\/' """o"""
t\/\/o

앞에 붙은 "r"은 "raw" 문자열을 뜻한다. 역사선은 날 문자열에서 피신문자로 평가되지 않으므로, 정규 표현식과 윈도우즈 파일시스템 경로에 유용하다.

이름붙은 문자열 객체는 결합되지 않는다:

>>> a = 'three'
>>> b = 'four'
>>> a b
  File "<stdin>", line 1
    a b
      ^
SyntaxError: invalid syntax

그 이유는 이 자동 결합이 파이썬 해석기/컴파일러의 특징이기 때문이다. 인터프리터의 특징이 아니다. 반드시 "+" 연산자를 사용해야 실행시간에 문자열을 결합할 수 있다.

text = ('Long strings can be made up '
        'of several shorter strings.')

괄호로 묵시적인 줄 연속을 나타낼 수 있다.

여러줄 문자열은 삼중 따옴표를 사용한다:

"""Triple
double
quotes"""
'''\
Triple
single
quotes\
'''
앞의 예제에서 (삼중 홑따옴표), 새줄문자를 피신시키는데 어떻게 역사선이 사용되는지 주목하자. 이렇게 하면 따로 새줄문자를 쓰지 않아도 되며, 동시에 텍스트와 따옴표를 깔끔하게 왼쪽-정렬시킬 수 있다. 역사선은 줄 끝에 두어야 한다.

합성 서술문(Compound Statements)

Good:

if foo == 'blah':
    do_something()
do_one()
do_two()
do_three()

나쁨:

if foo == 'blah': do_something()
do_one(); do_two(); do_three()

공백문자 & 들여쓰기는 프로그램의 흐름을 시각적으로 보여주는데 유용하다. 위에서 "Good"의 두 번째 줄에서 들여쓰기 덕분에 독자는 무언가 일이 진행되는 것을 볼 수 있지만, 반면에 "Bad"에는 들여쓰기가 없으므로 "if" 서술문이 숨어서 보이지 않는다.

한 줄에 여러 서술문을 두는 것은 죄악의 근원이다. 파이썬에서는, 가독성이 중요하다(readability counts).

문서화문자열 & 주석

문서화 문자열 = 코드를 사용하는 법 code

주석 = 이유 (합리적 명분) & 코드 작동 방식

문서화문자열은 코드를 사용하는 법을 설명하고, 코드의 사용자를 위한 것이다. 문서화 문자열을 사용하여:

주석은 를 설명하며, 코드의 유지보수자를 위한 것이다. 예제에는 다음과 같이 여러분 자신을 위한 주의 사항도 포함시킨다:

# !!! 버그: ...

# !!! 고침: 이건 임시 조치이다

# ??? 이건 왜 여기에 있는가?

이 두 그룹 모두 여러분이 포함되며, 그러므로 문서화문자열과 주석을 잘 작성하자!

문서화문자열은 상호대화 사용에서 (help()) 그리고 자동-문서화 시스템에 유용하다.

잘못된 주석 & 문서화문자열은 차라리 없는 것보다 못하다. 그래서 항상 최신으로 유지하자! 수정을 했으면, 확실하게 주석 & 문서화 문자열을 코드와 일치시키고, 따로따로 놀게 만들지 말자.

문서화 문자열에 관한 전체 PEP는 257번, "문서화문자열 관례"에 있다:

http://www.python.org/dev/peps/pep-0257/

실용은 순수를 이긴다(Practicality Beats Purity)

고집스런 일관성은 속좁은 도깨비다(A foolish consistency is the hobgoblin of little minds).

—Ralph Waldo Emerson

(hobgoblin: 무엇인가 공포를 야기하는 것; 유령).)

언제나 예외는 있기 마련이다. PEP 8에 의하면:

그러나 가장 중요한 것은: 일관성을 포기해야 할 때를 아는 것이다 -- 어떤 경우에는 스타일 지도서가 적용되지 않는다. 잘 모르겠으면, 최선을 다해 판단하자. 다른 예들을 살펴보고 무엇이 가장 좋게 보이는지 결정하자. 그리고 주저없이 물어보자!

특별한 규칙을 깨려면 두 가지 좋은 이유가 있어야 한다:

  1. 규칙을 적용하면 코드가 더 읽기 어려워질 때, 특히나 그 규칙들을 준수하는 코드를 읽는데 익숙해진 사람에게 말이다.
  2. (아마도 역사적인 이유로) 똑같이 규칙을 어기는 주변의 코드와 일관성을 유지해야 하는 경우 -- 그렇지만 이 기회에 다른 사람의 난잡한 코드를 (진짜 XP 스타일로) 청소하는 것이 좋다.

... 그러나 "실용이 순수를 철저하게 이기는 것은" 아니다"!

잡다한 관용구들

작고 유용한 관용구 모음.

이제 문서의 핵심으로 들어갈 차례이다: 관용구 모음으로 말이다.

좀 쉬운 것부터 시작해서 연구를 진척시켜 보자.

값 교환하기

다른 언어에서는:

temp = a
a = b
b = temp

파이썬에서는:

b, a = a, b
이것을 본 적이 있을 것이다. 그러나 어떻게 작동하는지 아시는지?

왼쪽에 있는 터플의 이름 안으로 오른쪽이 풀려 들어간다.

터플 풀기의 예를 더 보여주면:

>>> l =['David', 'Pythonista', '+1-514-555-1234']
>>> name, title, phone = l
>>> name
'David'
>>> title
'Pythonista'
>>> phone
'+1-514-555-1234'

구조화된 데이터를 회돌이할 때 유용하다:

위의 l (L)은 방금 만든 리스트이다 (David의 정보). 그래서 people은 두 개의 요소가 담기어 있으며, 각 요소는 3-요소 리스트이다.

>>> people = [l, ['Guido', 'BDFL', 'unlisted']]
>>> for (name, title, phone) in people:
...     print name, phone
...
David +1-514-555-1234
Guido unlisted

people의 각 요소는 (name, title, phone) 터플로 풀린다.

얼마든지 내포시킬 수 있다 (단 확실하게 왼쪽 & 오른쪽 구조를 맞추자!):

>>> david, (gname, gtitle, gphone) = people
>>> gname
'Guido'
>>> gtitle
'BDFL'
>>> gphone
'unlisted'
>>> david
['David', 'Pythonista', '+1-514-555-1234']

터플에 대하여 더 자세히

터플 구성자는 반괄호가 아니라 쉼표(comma)이다. 예제:
>>> 1,
(1,)
파이썬 인터프리터는 명료성을 위하여 반괄호를 보여주며, 여러분 역시 반괄호를 쓰시기를 권장한다:
>>> (1,)
(1,)
쉼표를 잊지 말자!
>>> (1)
1
한개짜리-터플에서, 반드시 뒤에 쉼표가 따라야 한다; 2개이상-터플에서는, 뒤따르는 컴마는 선택사항이다. 0-터플, 다시 말해 빈 터플에서 한 쌍의 반괄호는 지름길 구문이다:
>>> ()
()
>>> tuple()
()
흔한 식자오류는 터플을 의도한게 아닌데 쉼표를 그대로 두는 것이다. 코드에서 실수하기 쉽다:
>>> value = 1,
>>> value
(1,)
터플을 원한 곳이 아닌 곳에서 터플을 보면, 쉼표를 찾아 보자!

상호대화 쉘에서의 "_"

이는 정말 쓸모가 있는 특징으로서 놀랍게도 모르는 사람이 많다.

상호대화 인터프리터에서, 표현식을 평가하거나 함수를 호출할 때마다, 그 결과는 임시 이름인 _(밑줄문자)에 묶인다:

>>> 1 + 1
2
>>> _
2

_에는 최근에 인쇄된 표현식이 저장된다.

결과가 None이면, 아무것도 인쇄되지 않는다. 그래서 _는 바뀌지 않는다. 참 편리하다!

이는 상호대화 인터프리터 안에서만 작동하고, 모듈에서는 작동하지 않는다.

문제를 상호대화적으로 풀고자 할 때 그리고 그 결과를 저장해 다음 단계에 사용하고 싶을 때 특히 유용하다:

>>> import math
>>> math.pi / 3
1.0471975511965976
>>> angle = _
>>> math.cos(angle)
0.50000000000000011
>>> _
0.50000000000000011

하부문자열로부터 문자열 만들기

문자열 리스트로 시작하자:
colors = ['red', 'blue', 'green', 'yellow']
문자열을 모두 하나로 모아 결합하고 싶다. 특히 하부문자열의 개수가 많을 때...

이렇게 하면 안 된다:

result = ''
for s in colors:
    result += s

이는 아주 효율성이 떨어진다.

메모리 사용이 엄청나고 수행성능이 떨어진다. "더하면서" 각 중간 단계를 계산하고 저장한 다음 버린다.

대신에 이렇게 하자:

result = ''.join(colors)

join() 문자열 메쏘드는 한 번에 복사를 완수한다.

수 십 또는 수 백개의 문자열을 다룰 뿐이라면, 별 차이가 없을 것이다. 그러나 효율적으로 문자열을 구성하는 습관을 들이자. 수 천개의 문자열 또는 회돌이라면, 차이가 있을 것이다.

문자열 구성하기, 변화도 1

다음은 join() 문자열 메쏘드를 사용하는 몇가지 테크닉이다.

하부문자열 사이에 공백을 넣고 싶으면:

result = ' '.join(colors)

또는 컴마와 공백을 넣고 싶으면:

result = ', '.join(colors)

다음은 자주 보는 것이다:

colors = ['red', 'blue', 'green', 'yellow']
print 'Choose', ', '.join(colors[:-1]), \
      'or', colors[-1]

문법적으로 깔끔하게 보이는 문장을 만들기 위해, 마지막 쌍을 제외하고 사이에 모두 쉼표를 넣고 싶다. 단어는 "or"로 하자. 조각썰기 구문이 일을 해준다. "-1이 될때까지 조각썰기하면" ([:-1]) 마지막 값을 빼고 모두 돌려준다. 이것을 쉼표-공간으로 결합한다.

물론, 이 코드는 모서리 사례, 즉 길이가 0 또는 1인 리스트에는 작동하지 않는다.

출력:
Choose red, blue, green or yellow.

문자열 구성하기, 변화도 2

함수를 적용해서 하부문자열을 만들고 싶다면:

result = ''.join(fn(i) for i in items)
여기에는 발생자 표현식(generator expression)이 관련되어 있는데, 나중에 다루어 보겠다.

조금씩 하부문자열을 계산하고 싶다면, 먼저 그것들을 리스트 안에 축적시키자:

items = []
...
items.append(item)  # 여러 번
...
# 이제 요소들 축적 완료
result = ''.join(fn(i) for i in items)
효율성을 위하여, join 문자열 메쏘드를 적용할 수 있도록
조각들을 리스트로 축적한다.

가능하면 in을 사용하자 (1)

좋음:

for key in d:
    print key

나쁨:

for key in d.keys():
    print key
이 연산자는 keys() 메쏘드를 갖춘 객체에는 제한이 있다.

가능하면 in을 사용하자 (2)

그러나 사전을 수정할 때에는 .keys()필요하다:

for key in d.keys():
    d[str(key)] = d[key]
d.keys()는 사전의 키로 구성된 정적 사전을 만든다. 그렇지 않으면, "RuntimeError: dictionary changed size during iteration" 에러를 맞이할 것이다.

일관성을 유지하기 위하여, key in dict 형태를 사용하고, dict.has_key()를 사용하지 말자:

# 이렇게 하시고:
if key in d:
    ...do something with d[key]

# 이렇게 하지 말자:
if d.has_key(key):
    ...do something with d[key]
이렇게 in을 사용하는 법은 연산자로서 사용하는 법이다.

사전의 get 메쏘드

종종 사용하기 전에 사전을 초기화할 필요가 있다:

다음은 좀 유치한 방법이다:
navs = {}
for (portfolio, equity, position) in data:
    if portfolio not in navs:
        navs[portfolio] = 0
    navs[portfolio] += position * prices[equity]

dict.get(key, default)를 사용하면 테스트할 필요가 없다:

navs = {}
for (portfolio, equity, position) in data:
    navs[portfolio] = (navs.get(portfolio, 0)
                       + position * prices[equity])
훨씬 더 직접적이다.

사전 setdefault 메쏘드 (1)

여기에서 수정가능한 사전 값들을 초기화할 필요가 있다. 각 사전 값은 리스트가 될 것이다. 다음은 유치한 방법이다:

수정가능한 사전 값 초기화하기:

equities = {}
for (portfolio, equity) in data:
    if portfolio in equities:
        equities[portfolio].append(equity)
    else:
        equities[portfolio] = [equity]

dict.setdefault(key, default)가 훨씬 더 효율적으로 일을 해준다:

equities = {}
for (portfolio, equity) in data:
    equities.setdefault(portfolio, []).append(
                                         equity)

dict.setdefault() 메쏘드는 "얻어라, 또는 설정하고 & 얻어와라"와 같은 뜻이다. 또는 "필요하면 설정하고, 얻어와라". 사전의 키가 계산하기에 비싸거나 타자하기에 길다면 특히 효율적이다.

dict.setdefault()에서 유일한 문제는 기본 값이 언제나, 필요하거나 말거나 평가된다는 것이다. 이는 기본 값이 계산하기에 비쌀 경우에만 문제가 된다.

기본 값이 계산하기에 비싸다면, defaultdict 클래스를 사용해도 좋다. 잠시 후에 살펴보겠다.

사전 setdefault 메쏘드 (2)

여기에서는 setdefault 사전 메쏘드가 단독적인 서술문으로 사용이 될 수도 있다:

setdefault 메쏘드는 독립적인 서술문으로 사용할 수도 있다:

navs = {}
for (portfolio, equity, position) in data:
    navs.setdefault(portfolio, 0)
    navs[portfolio] += position * prices[equity]
사전의 setdefault 메쏘드는 기본 값을 돌려 주지만, 여기에서는 그것을 무시한다. setdefault 메쏘드는 아무 값도 없을 경우에만 사전 값을 설정하는 부작용이 있고, 이를 이용한다.

defaultdict

파이썬 2.5에 새로 도입되었다.

defaultdict는 파이썬 2.5에 새로 도입되었고, collections 모듈에 포함되어 있다. defaultdict는 두 가지 점만 제외하면 일반 사전과 동일하다:

defaultdict를 얻는 두 가지 방법이 있다:

import collections
d = collections.defaultdict(...)
from collections import defaultdict
d = defaultdict(...)
다음은 앞의 예제인데, 여기에서 각 사전 값은 반드시 빈 리스트로 초기화되어야 하고, defaultdict로 재작성되어야 한다:
from collections import defaultdict

equities = defaultdict(list)
for (portfolio, equity) in data:
    equities[portfolio].append(equity)

지금 당장은 아무 문제가 없다. 이 경우, 기본 공장 함수는 list이고, 빈 리스트를 돌려준다.

다음은 기본 값이 0인 사전을 얻는 방법이다: int를 기본 공장 함수로 사용하자:

navs = defaultdict(int)
for (portfolio, equity, position) in data:
    navs[portfolio] += position * prices[equity]
그렇지만 defaultdict에 주의해야 한다. 제대로 초기화된 defaultdict 실체로부터 KeyError 예외를 얻을 수 없으니 주의하자. 특정 키가 존재하는지 알고 싶으면 "key in dict" 조건을 사용해야 한다.

사전을 구성하고 & 분리하기

다음은 두 리스트 (또는 연속열)로부터 사전을 구성하는 유용한 테크닉이다: 한 리스트는 키로, 다른 한 리스트는 값으로 한다.
given = ['John', 'Eric', 'Terry', 'Michael']
family = ['Cleese', 'Idle', 'Gilliam', 'Palin']
pythons = dict(zip(given, family))
>>> pprint.pprint(pythons)
{'John': 'Cleese',
 'Michael': 'Palin',
 'Eric': 'Idle',
 'Terry': 'Gilliam'}
물론, 역변환은 아무것도 아니다:
>>> pythons.keys()
['John', 'Michael', 'Eric', 'Terry']
>>> pythons.values()
['Cleese', 'Palin', 'Idle', 'Gilliam']
.keys()와 .values()의 결과의 순서가 사전을 구성할 때의 순서와 다른 것을 주목하자. 들어오는 순서는 나가는 순서와 다르다. 이는 사전이 본질적으로 순서가 없기 때문이다. 그렇지만, 호출하는 동안 사전이 변하지 않는 한, 그 순서는 일관성이 있다 (다른 말로 해서, 키의 순서는 값의 순서에 상응한다).

진리값 테스트하기

# 이렇게 하시고:        # 이렇게 하면 안된다:
if x:             if x == True:
    pass              pass
파이썬 객체에 내재된 진리 값(즉 불리언 값)을 이용하는 것이 우아하고 효율적이다.

리스트 테스트하기:

# 이렇게 하시고:        # 이렇게 하면 안된다:
if items:         if len(items) != 0:
    pass              pass

                  # 그리고 이렇게 하는 것은 확실히 안된다:
                  if items != []:
                      pass

진리 값

이름 TrueFalsebool유형의 내장된 실체로서, 불리언 값이다. None처럼, 오직 각각에 대하여 실체가 하나 뿐이다.
False True
False (== 0) True (== 1)
"" (빈 문자열) ""을 제외하고 어떤 문자든지 (" ", "anything")
0, 0.0 0을 제외하고 무슨 숫자든지 (1, 0.1, -1, 3.14)
[], (), {}, set() 비어있지-않은 포용자 ([0], (None,), [''])
None 명시적으로 False가 아닌 모든 객체

객체의 진리 값을 보여주는 예제:

>>> class C:
...  pass
...
>>> o = C()
>>> bool(o)
True
>>> bool(C)
True

(예제: truth.py를 실행해보자.)

사용자-정의 클래스의 실체가 가진 진리 값을 제어하려면, __nonzero__ 또는 __len__ 특수 메쏘드를 사용하자. 클래스가 길이가 있는 포용자라면 __len__을 사용하자:

class MyContainer(object):

    def __init__(self, data):
        self.data = data

    def __len__(self):
        """나의 길이를 돌려준다."""
        return len(self.data)

클래스가 포용자가 아니라면, __nonzero__를 사용하자:

class MyClass(object):

    def __init__(self, value):
        self.value = value

    def __nonzero__(self):
        """나의 진리값을 돌려준다 (True 또는 False)."""
        # 이것은 얼마든지 복잡하게 할 수 있다:
        return bool(self.value)

파이썬 3.0에서, __nonzero__bool 내장 유형과 일관성을 유지하기 위하여 이름이 __bool__로 바뀌었다. 호환성을 위해서, 다음 코드를 클래스 정의에 추가하자:

__bool__ = __nonzero__

인덱스 & 원소 (1)

다음은 단어 리스트를 타자하고 싶을 때 타자수를 줄이는 교묘한 방법이다:
>>> items = 'zero one two three'.split()
>>> print items
['zero', 'one', 'two', 'three']

항목들을 반복하고 싶고, 그 항목의 지표와 그 자체가 필요하다고 해 보자:

                  - or -
i = 0
for item in items:      for i in range(len(items)):
    print i, item               print i, items[i]
    i += 1

인덱스 & 원소 (2): enumerate

enumerate 함수는 리스트를 받아서 (인덱스, 항목) 쌍을 돌려준다:

>>> print list(enumerate(items))
[(0, 'zero'), (1, 'one'), (2, 'two'), (3, 'three')]
리스트(list) 포장자를 사용하여 결과를 인쇄할 필요가 있는 데 그 이유는 enumerate가 게으른 함수이기 때문이다: 이 함수는 오직 요구할 때만, 한번에 항목 하나, 즉, 한 쌍을 만든다. for 회돌이는 한 번에 결과 하나를 요구하는 곳이다. enumerate는 발생자(generator)의 한 예이다. 나중에 이에 관하여 더 자세하게 다루겠다. print 서술문은 한 번에 결과 하나를 받지 않는다 -- 그 결과 전체를 원하므로, 인쇄할 때 명시적으로 그 발생자를 리스트로 변환할 필요가 있다.

회돌이는 훨씬 더 간단해진다:

for (index, item) in enumerate(items):
    print index, item
# 비교:              # 비교:
index = 0               for i in range(len(items)):
for item in items:              print i, items[i]
    print index, item
    index += 1

enumerate 버전이 왼쪽의 버전보다 더욱 더 짧고 간단하며, 훨씬 더 읽고 이해하기에도 쉽다.

다음은 어떻게 enumerate 함수가 실제로는 반복자를 돌려주는지 보여주는 예이다 (발생자는 일종의 반복자이다):

>>> enumerate(items)
<enumerate object at 0x011EA1C0>
>>> e = enumerate(items)
>>> e.next()
(0, 'zero')
>>> e.next()
(1, 'one')
>>> e.next()
(2, 'two')
>>> e.next()
(3, 'three')
>>> e.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
StopIteration

다른 언어에는 "변수(variables)"가 있다

다른 언어에서, 한 변수에 할당하는 것은 한 값을 상자 안에 넣는 것이다.
int a = 1;
a1box.png

"a" 상자는 이제 정수 1이 담긴다.

같은 변수에 다른 값을 할당하면 상자 안의 내용물이 교체된다:

a = 2;
a2box.png

이제 "a" 상자에는 정수 2가 있다.

한 변수를 또다른 변수에 할당하면 그 값의 복사본을 만들어서 그것을 새로운 상자 안에 넣는다:

int b = a;
b2box.png a2box.png
"b"는 두 번째 상자로서, 정수 2의 사본이 들어 있다. "a" 상자는 따로 사본을 가진다.

파이썬은 "이름(names)"을 가진다

파이썬에서, "이름(name)" 또는 "식별자(identifier)"는 소포에 붙는 꼬리표 (즉 이름표)와 같으며 객체에 붙는다.
a = 1
a1tag.png

여기에서 정수 1 객체는 "a"라는 꼬리표를 가진다.

"a"에 할당을 다시 하려면, 그냥 그 꼬리표를 또다른 객체로 옮기면 된다:

a = 2
a2tag.png 1.png

이제 이름 "a"는 정수 2 객체에 붙었다.

원래의 정수 1 객체는 더 이상 "a"라는 꼬리표가 없다. 계속 살 수도 있겠지만, "a"라는 이름으로는 접근할 수 없다. (객체가 더 이상 참조되지 않거나 꼬리표가 없다면, 메모리에서 제거된다.)

이름을 또다른 이름에 할당하려면, 그냥 또다른 이름표를 기존의 객체에 붙이기만 하면 된다:

b = a
ab2tag.png
이름 "b"는 그냥 같은 객체 "a"에 붙은 두번째 꼬리표일 뿐이다.

(일반적인 전문용어이기 때문에) 보통 파이썬에서도 "변수(variables)"라고 칭하기는 하지만, 실제로는 "이름(names)" 또는 "식별자(identifiers)"라는 뜻이다. 파이썬에, "변수"값에 대한 이름표일 뿐, 꼬리표붙은 상자가 아니다.

이 자습서에서 얻는게 없다면, 파이썬 이름의 작동방식을 이해하시기를 바란다. 잘 이해하면 분명히 보상이 따르며, 이와 같은 사례를 피하는데 도움을 받을 수 있다:

기본 매개변수 값

다음은 초보자가 자주 저지르는 실수이다. 파이썬 이름이라는 것을 이해하지 못하면, 심지어 경력 프로그래머조차도 이런 실수를 한다.
def bad_append(new_item, a_list=[]):
    a_list.append(new_item)
    return a_list
문제는 기본 값이, 즉 빈 리스트인 a_list가 함수 정의 시간에 평가된다는 것이다. 그래서 함수를 호출할 때마다, 똑 같은 기본 값을 얻는다. 여러 번 시도해 보자:
>>> print bad_append('one')
['one']
>>> print bad_append('two')
['one', 'two']
리스트는 변경가능 객체이다; 내용을 바꿀 수 있다는 뜻이다. 기본 리스트를 (또는 사전이나 집합을) 얻는 올바른 방법은 실행 시간에 만드는 것, 즉 그 함수 안에 만드는 것이다:
def good_append(new_item, a_list=None):
    if a_list is None:
        a_list = []
    a_list.append(new_item)
    return a_list

% 문자열 포맷

파이썬의 % 연산자는 마치 C의 sprintf 함수처럼 작동한다.

C를 모르더라도, 별로 도움이 되지 않는다. 기본적으로, 주형틀 또는 형식 그리고 병치 값들을 제공한다.

이 예제에서, 주형틀에는 두 가지 변환 지정이 들어있다: "%s"는 "여기에 문자열을 삽입하라"는 뜻이고, "%i"는 "정수를 문자열로 변환해서 여기에 삽입하라"는 의미이다. "%s"는 파이썬에 내장된 str() 함수를 사용하여 어떤 객체든지 문자열로 변환하기 때문에 부분적으로 쓸모가 있다.

삽입 값들은 주형틀에 짝이 맞아야 한다; 여기에서는 터플로서, 값이 두 개이다.

name = 'David'
messages = 3
text = ('Hello %s, you have %i messages'
        % (name, messages))
print text

출력:

Hello David, you have 3 messages
자세한 것은 파이썬 라이브러리 참조서, 섹션 2.3.6.2의 "문자열 포맷 연산"에 있다. 즐겨찾기 해 두자!
아직 그렇게 하지 못했다면, python.org에 가서, (집파일 또는 타르볼 형태의) HTML 문서를 내려받아, 컴퓨터에 설치하자. 확실한 자원을 여러분의 손가락 끝에 확보하는 것 만큼 좋은 것은 없다.

고급의 % 문자열 포맷

많은 사람들이 문자열 포맷에 다른, 더 유연한 방법들이 있다는 것을 인지하지 못하고 있다:

사전에서 이름 사용하기:

values = {'name': name, 'messages': messages}
print ('Hello %(name)s, you have %(messages)i '
       'messages' % values)

여기에서는 삽입 값의 이름을 공급된 사전에서 찾아 지정한다.

좀 중복이 있지 않은가? "name"과 "messages"라는 이름은 이미 지역 이름공간에 정의되어 있다. 이것을 이용할 수 있다.

지역 이름공간에서 이름 사용하기:

print ('Hello %(name)s, you have %(messages)i '
       'messages' % locals())

locals() 함수는 지역에 있는 이름을 모두 담은 사전을 돌려준다.

이는 아주 강력하다. 이것으로 주형틀에 삽입하는 값이 짝이 맞는지 걱정할 필요가 없이 원하는 모든 문자열 포맷팅을 수행할 수 있다.

그러나 강력하면 그 만큼 위험할 수 있다. ("힘이 강할 수록 책임이 큰 법이다.") 외부에서 공급된 주형틀 문자열에 locals() 형태를 사용하면, 그 호출자에게 전체 지역 이름공간을 보여주는 셈이 된다. 이것을 꼭 염두에 두어야 한다.

지역 이름공간을 조사하려면:

>>> from pprint import pprint
>>> pprint(locals())
pprint는 아주 유용한 모듈이다. 아직 모르고 있다면, 가지고 놀아보자. 데이터 구조를 디버그하기가 훨씬 더 수월해진다!

고급의 % 문자열 포맷팅

한 객체의 실체 속성이 담긴 이름공간은 그냥 사전으로서, self.__dict__이다.

실체 이름공간에서 이름 사용하기:

print ("We found %(error_count)d errors"
       % self.__dict__)

동등하지만, 더욱 유연하다:

print ("We found %d errors"
       % self.error_count)
주의: 클래스 속성은 __dict__ 클래스에 있다. 이름공간 검색은 실제로는 사슬처럼 엮인 사전을 찾는 것이다.

리스트 통합(List Comprehensions)

리스트통합은 (약어로 "listcomps"라고 칭) 이런 일반 패턴을 위한 문법적 지름길이다:

전통적인 방식으로, forif 서술문으로 표현하면:

new_list = []
for item in a_list:
    if condition(item):
        new_list.append(fn(item))

리스트 통합으로 표현하면:

new_list = [fn(item) for item in a_list
            if condition(item)]
리스트통합은 명료하고 & 어느 정도는 간결하다. 리스트통합 안에 얼마든지 for-회돌이와 if-조건을 가질 수 있지만, 두 세개가 넘어가면, 다시 말해 조건이 복잡하면, 일반적인 for 회돌이를 사용하시기를 바란다. 파이썬의 정수를 적용하여, 더 가독성이 높은 방법을 선택하자.

예를 들어, 0–9 까지의 제곱 리스트는:

>>> [n ** 2 for n in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

0–9까지의 홀수 제곱 리스트는:

>>> [n ** 2 for n in range(10) if n % 2]
[1, 9, 25, 49, 81]

발생자 표현식 (1)

100까지 숫자의 제곱을 모두 더해보자:

회돌이를 사용하면:

total = 0
for num in range(1, 101):
    total += num * num
sum 함수를 사용하면 신속하게 일을 처리할 수 있다. 적절하게 연속열을 구성해 주기만 하면 된다.

리스트 통합으로 처리하면:

total = sum([num * num for num in range(1, 101)])

발생자 표현식으로 처리하면:

total = sum(num * num for num in xrange(1, 101))

발생자 표현식("genexps")은 꼭 리스트 통합을 꼭 닮았다. 단 리스트통합은 탐욕적인 반면, 발생자 표현식은 게으르다는 점은 제외하고 말이다. 리스트통합은 리스트로, 전체 결과 리스트를 한 번에 계산한다. 발생자 표현식은 개별 값으로서, 필요할 때마다, 한 번에 하나의 값을 계산한다. 이는 기다란 연속열에 특히 유용하다. 계산된 리스트가 그냥 중간 단계일 뿐이고 최종 결과가 아닐 경우에 말이다.

이 경우에는 그 총합에만 관심이 있으므로; 제곱의 중간 리스트는 필요가 없다. 같은 이유로 xrange를 사용했는데: 한 번에 하나씩, 게으르게 값을 산출하기 때문이다.

발생자 표현식 (2)

예를 들어, 수조개가 넘는 정수의 제곱을 모두 더하고 있다면, 리스트 통합으로는 메모리가 모자라겠지만, 발생자 표현식은 문제가 없다. 그렇지만, 시간이 걸린다!
total = sum(num * num
            for num in xrange(1, 1000000000))
구문의 차이는 리스트통합은 각괄호가 있지만, 발생자 표현식은 없다는 것이다. 발생자 표현식은 보통 괄호로 둘러쌀 필요가 있다. 그래서 언제나 괄호를 사용해야 한다.

제일 중요한 규칙:

다음은 본인이 실무현장에서 최근에 본 예이다.

선물 계약을 위하여 (문자열과 정수로) 월 번호를 월 코드에 짝지은 사전이 필요했다. 한 줄의 논리 코드로 완수할 수 있다.

다음과 같은 방식으로 일이 처리된다:

최근의 예:

month_codes = dict((fn(i+1), code)
    for i, code in enumerate('FGHJKMNQUVXZ')
    for fn in (int, str))

month_codes result:

{ 1:  'F',  2:  'G',  3:  'H',  4:  'J', ...
 '1': 'F', '2': 'G', '3': 'H', '4': 'J', ...}

정렬

파이썬에서 리스트는 쉽게 정렬된다:
a_list.sort()

(리스트가 제자리에서 정렬됨을 주목하자: 원래 리스트가 정렬되며, sort 메쏘드는 그 리스트나 사본을 돌려주지 않는다.)

그러나 가지고 있는 데이터 리스트를 정렬해야 하는데, 자연스럽게 정렬되지 않는다면 어떻게 할까 (즉, 첫 컬럼에 먼저, 다음에 두 번째 컬럼에 정렬, 등등.)? 먼저 두 번째 컬럼을 기준으로, 다음에 네 번째 컬럼을 기준으로 정렬하고 싶을 수도 있다.

맞춤 함수로 리스트의 내장 sort 메쏘드를 사용할 수 있다:

def custom_cmp(item1, item2):
    returm cmp((item1[1], item1[3]),
               (item2[1], item2[3]))

a_list.sort(custom_cmp)
작동은 하지만, 리스트가 크면 극도로 느리다.

DSU로 정렬하기

DSU = Decorate-Sort-Undecorate

맞춤 비교 함수를 만드는 대신에, 자연스럽게 정렬될 리스트를 보조로 하나 더 만든다:
# Decorate:
to_sort = [(item[1], item[3], item)
           for item in a_list]

# Sort:
to_sort.sort()

# Undecorate:
a_list = [item[-1] for item in to_sort]

첫줄에서 터플이 담긴 리스트가 만들어진다: 우선 순위 대로 정렬 조건 사본, 다음에 완전한 데이터 레코드가 따른다.

두번째 줄에서 파이썬 고유의 정렬을 수행하는데, 이것이 아주 빠르고 효율적이다.

세번째 줄에서 정렬된 리스트로부터 마지막 값을 열람한다. 기억하자. 이 마지막 값은 완료 데이터 레코드이다. 정렬 조건을 버리고 있는데, 제 몫을 다 했으며 더 이상 필요하지 않기 때문이다.

이것은 시간에 대하여 공간 그리고 복잡도 사이의 교환관계이다. 더 쉽고 빠르지만, 원래 리스트를 복제할 필요가 있다.

발생자

이미 발생자 표현식을 살펴보았다. 마치 함수처럼 자신만의 복잡한 발생자를 고안할 수 있다:
def my_range_generator(stop):
    value = 0
    while value < stop:
        yield value
        value += 1

for i in my_range_generator(10):
    do_something(i)

yield 키워드는 함수를 발생자로 변환시켜 준다. 발생자 함수를 호출하면, 코드를 바로 실행하지 않고, 발생자 객체를 돌려준다. 이것이 반복자이다; 반복자는 next 메쏘드가 있다. for는 그냥 반복자의 next 메쏘드를 호출할 뿐이다. StopIteration 예외가 일어날 때까지 말이다. 명시적으로 또는 묵시적으로 위와 같이 발생자의 끝에 떨어트려서 StopIteration 예외를 일으키면 된다.

발생자는 연속열/반복자 처리를 간결하게 해줄 수 있다. 왜냐하면 구체적으로 리스트를 구축할 필요가 없기 때문이며; 단지 한 번에 한 값만 계산하면 되기 때문이다. 발생자 함수는 상태를 유지한다.

다음은 for 회돌이가 실제로 작동하는 방식이다. 파이썬은 in 키워드 뒤에 공급된 연속열을 찾아 본다. 단순한 포용자라면 (리스트나 터플 사전 또는 집합이나 사용자-정의 포용자라면) 파이썬은 그것을 반복자로 변환시킨다. 이미 반복자라면, 그대로 둔다.

다음 파이썬은 반복적으로 그 반복자의 next 메쏘드를 호출하면서, 반환 값을 회돌이 계수기에 할당한다 (이 경우에는 i이다). 그리고 들여쓰기된 코드를 실행한다. 이 과정은 StopIteration이 일어나거나 break 서술문이 실행될 때까지 끊임없이 반복된다.

for 회돌이는 else 절을 가질 수 있는데, 그 코드는 반복자가 끝난 후에 실행된다. 그러나 break 서술문이 실행된 후에는 실행되지 않는다. 이 차이점 덕분에 몇가지 우아한 사용법이 있다. else 절이 언제나 또는 자주 for 회돌이에 사용되는 것은 아니다. 그러나 편리하게 사용될 수 있다. 가끔 else 절은 필요한 로직을 완벽하게 표현해 준다.

예를 들어, 연속열에서 요소 일부, 어떤 요소든지 조건이 부과되어 있는지 점검할 필요가 있다면:

for item in sequence:
    if condition(item):
        break
else:
    raise Exception('Condition not satisfied.')

발생자 예제

CSV 판독기로부터 빈 줄을 (또는 리스트로부터 요소를) 여과해 보자 :

def filter_rows(row_iterator):
    for row in row_iterator:
        if row:
            yield row

data_file = open(path, 'rb')
irows = filter_rows(csv.reader(data_file))

텍스트/데이터 파일로부터 줄 읽기

datafile = open('datafile')
for line in datafile:
    do_something(line)

이것이 가능한 이유는 파일이 반복자라면 갖추고 있는 next 메쏘드를 지원하기 때문이다: 리스트, 터플, (키에 대하여)사전, 발생자는 모두 이 메쏘드를 갖추고 있다.

한가지 약점이 있다: 버퍼 관리 문제 때문에, 파이썬 2.5+을 쓰고 있지 않는 한 .next & .read* 메쏘드를 혼용할 수 없다..

EAFP vs. LBYL

허락보다 용서를 구하는 것이 더 쉽다(It's easier to ask forgiveness than permission)

뛰기 전에 먼저 살피자(Look before you leap)

보통은 EAFP가 바람직하지만, 언제나 그런 것은 아니다.

EAFP try/except 예제

예외-성향의 코드를 try/except 블록에 싸면 에러를 나포할 수 있으며, 아마도 모든 가능성을 예측하려고 시도하는 것보다 훨씬 더 일반적인 해결책을 얻을 것이다.
try:
    return str(x)
except TypeError:
    ...
주의: 언제나 예외를 지정하여 나포하자. 달랑 except 절만 사용하지는 말자. 나홀로 except 절은 예측하지 못한 예외를 잡을 것이며, 이 때문에 코드를 디버그하기가 아주 어렵게 될 것이다.

반입(Importing)

from module import *

아마도 이런 "만능 문자"를 쓴 반입 서술문을 보았을 것이다. 심지어 좋아할 수도 있다. 절대 사용하지 말자.

유명한 영화 대사로 설명해 보면:

(장면: 다고바(Dagobah) 행성, 정글 속, 늪, 그리고 안개.)

루크: 명시적인 반입보다 from module import *가 더 낫지 않습니까?

요다: 아니, 더 좋은 것은 아니다. 단지 더 빠르고, 더 쉽고, 좀 더 매력적일 뿐이야.

루크: 그렇지만 왜 만능 문자 형태보다 명시적인 반입이 더 좋은지 어떻게 알 수 있습니까?

요다: 앞으로 6개월 후에 자네 코드를 읽어 볼 수 밖에 없게 되면 알게 될게야.

만능 문자 반입은 파이썬의 어둠의 세계로부터 온 것이다.

절대 사용하지 말자!

from module import *의 만능 문자 스타일은 이름공간을 오염시킨다. 지역 이름공간에서 예상하지 못 한 일을 맞이할 것이다. 반입된 이름이 모듈-정의된 지역 이름을 지워 버릴 수도 있다. 이름의 출처를 알아낼 수 없을 것이다. 편리한 지름길이지만, 생산 코드에 사용되면 안된다.

교훈: 만능 문자 반입을 사용하지 말자!

다음과 같이 하는 것이 더 좋다:

이름공간 오염 주의!

대신에,

모듈을 통해서 이름을 참조하자 (완전히 자격을 갖춘 식별자):
import module
module.name
또는 이름이 긴 모듈은 짧은 이름으로 반입하자 (별칭):
import long_module_name as mod
mod.name
그렇지 않으면 꼭 필요한 이름만 명시적으로 반입하자:
from module import name
name

이 형태는 상호대화 인터프리터 안에서는 이득이 없음을 주의하자. 상호대화 모드에서는 모듈을 편집하다가 "reload()"로 적재하면 된다.

모듈 & 스크립트

반입 모듈이면서 동시에 실행 스크립트로 만들려면:

if __name__ == '__main__':
    # 스크립트 코드는 여기에 둔다

모듈이 반입되면, __name__ 속성이 그 모듈의 파일 이름에 설정된다. ".py" 없이 말이다. 그래서 위와 같이 if 서술문으로 보호되는 코드는 반입될 때 실행되지 않는다. 그렇지만 스크립트로 실행하면, __name__ 속성이 "__main__"에 설정되고, 스크립트 코드가 실행된다.

특별한 경우를 제외하고, 최상위 수준에 주 실행 코드를 두지 말자. 코드는 함수나 클래스 또는 메쏘드에 두고, if __name__ == '__main__' 코드로 보호하자.

모듈 구조

"""module docstring"""

# imports
# constants
# exception classes
# interface functions
# classes
# internal functions & classes

def main(...):
    ...

if __name__ == '__main__':
    status = main()
    sys.exit(status)
다음은 모듈을 구성하는 방법이다.

명령-줄 처리

예제: cmdline.py:

#!/usr/bin/env python

"""
모듈의 문서화문자열.
"""

import sys
import optparse

def process_command_line(argv):
    """
    2-터플을 돌려준다: (설정 객체, 인자 리스트).
    `argv`는 인자 리스트이고, 또는 `None` for ``sys.argv[1:]``.
    """
    if argv is None:
        argv = sys.argv[1:]

    # 해석기 객체를 초기화한다:
    parser = optparse.OptionParser(
        formatter=optparse.TitledHelpFormatter(width=78),
        add_help_option=None)

    # 옵션은 여기에 정의한다:
    parser.add_option(      # 맞춤 설명; put --help last
        '-h', '--help', action='help',
        help='Show this help message and exit.')

    settings, args = parser.parse_args(argv)

    # 인자의 개수를 점검하고, 값을 검증한다, 등등:
    if args:
        parser.error('program takes no command-line arguments; '
                     '"%s" ignored.' % (args,))

    # 필요하면 설정환경 & 인자를 더 처리한다

    return settings, args

def main(argv=None):
    settings, args = process_command_line(argv)
    # 어플리케이션 코드는 여기에 둔다, 다음과 같은 형태로:
    # run(settings, args)
    return 0        # 성공이면

if __name__ == '__main__':
    status = main()
    sys.exit(status)

꾸러미

package/
    __init__.py
    module1.py
    subpackage/
        __init__.py
        module2.py

예제:

import package.module1
from packages.subpackage import module2
from packages.subpackage.module2 import name

파이썬 2.5에서는 절대 반입과 상대 반입이 가능하다:

from __future__ import absolute_import

아직 깊게 연구해 보지 않았다. 그래서 이 연구는 간력하게 대신하겠다.

복합보다 단순이 더 좋다

처음에는 디버깅이 코드를 작성하는 것보다 배나 어렵다. 그러므로, 코드를 최대한 지혜롭게 작성한다고 할지라도, 그 정의에 따르면, 코드를 디버그하는 것 만큼 똑똑하지는 못하다.

—Brian W. Kernighan, co-author of The C Programming Language and the "K" in "AWK"

다른 말로 해서, 프로그램을 단순하게 유지하자!

쓸데없이 헛수고하지 말자

어떤 코드든 작성하기 전에 먼저,
➔ ➔ ➔ ➔

References