이 자습서에서는 파이썬의 수 많은 핵심적인 관용구와 테크닉들을 깊이 살펴보고, 바로 사용할 수 있는 도구들을 챙겨보겠다.
©2006-2007, 본 저작물은 창조적 공공재 공여/공유-류 (BY-SA) 라이센스하에 보호된다.
나를 소개한다:
필자는 (텍스트 & 데이터 처리라고 불리운) 이 자습서를 PyCon 2006에서 발표했는데, 나는 당연하다고 생각하며 테크닉을 사용하였으나 이를 본 사람들의 반응에 놀랐다. 그러나 참석자 중 많은 수는 경력 파이썬 프로그래머가 아무 생각없이 사용하는 이런 도구들을 모르고 있었다.
이전에 여러분중 많은 수는 이런 테크닉들을 보셨을 것이다. 모쪼록 미처 보지 못했던 테크닉들을 배우시고 이미 보았더라도 새로운 뭔가를 얻는 기회가 되시기를 바라는 바이다.
다음은 파이썬의 지도적 근본 원리들이지만, 얼마든지 해석이 개방되어 있다. 적절하게 이해하려면 유머 감각이 있어야 한다.
유랑 극단의 이름을 딴 프로그래밍 언어(파이썬)를 사용하고 있다면, 유머 감각을 가지시는 편이 좋을 것이다.
못 생긴 것보다 잘 생긴 것이 좋다(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)....
잘 모르겠으면, 추측하고 싶은 유혹을 떨쳐 버려라.일을 하는 데에는 하나의 방법이—유일한 것이 바람직함— 확실하게 한 가지 방법이 있을 뿐이다.처음에는 확실해 보이지 않더라도 돌다리를 두드려보고 건널생각이 없지 않는 한.지금 하는 것이 안 하는 것보다 낫다.안 하는 것보다 지금 당장 하는 것이 종종 좋을 경우도 있지만.구현을 설명하는데 어려움이 있으면, 그것은 나쁜 아이디어이다.구현을 쉽게 설명할 수 있다면, 좋은 아이디어일 것이다.이름공간은 정말 훌륭한 아이디어이다—더 많이 활용하자!—팀 피터스(Tim Peters)
이 특별한 "시"는 농담 비슷하게 시작하지만, 그 안에는 파이썬 뒤에 숨겨진 철학에 관하여 수 많은 진실이 담겨있다. 파이썬의 정수는 PEP 20에 공식화되었는데, 그 초록을 읽어보면:
오래전 파이써니어 팀 피터스(Tim Peters)는 파이썬 디자인에 대한 BDFL(귀도 반 로섬)의 지도 원리를 간결하게 20개의 문구로 정리했는데, 그 중에 19개만 기록했다.
여러분 자신이 "파이썬니어(Pythoneer)"인지 "파이써니스타(Pythonista)"인지 스스로 판단하실 수 있다. 두 용어는 약간 함의가 다르다(역주: 파이써니스타가 파이써니어에 비하여 파이썬의 관행을 더 면밀하게 준수한다고 한다.).
잘 모르겠으면 이렇게 해보자:
import this
파이썬 상호대화 인터프리터 안에서 시도해 보자:
>>> import this
다음은 또다른 숨은 재미이다(easter egg):
>>> from __future__ import braces # 앞으로는 괄호를 블록 구분자로 쓸수 있겠지요? File "<stdin>", line 1 # 물어보자 마자, SyntaxError: not a chance # 구문에러: 절대 그럴일 없답니다
정말 재미있다! :-)
프로그램은 사람들이 읽기 쉽게 작성되어야 하며, 아주 특별한 경우에만 프로그램이 실행할 수 있도록 작성되어야 한다.
—Abelson & Sussman, Structure and Interpretation of Computer Programs
꼭 읽어 보실 것:
http://www.python.org/dev/peps/pep-0008/
PEP = 파이썬 개선 제안(Python Enhancement Proposal)
PEP는 디자인 문서로서 파이썬 공동체에 정보를 제공하거나, 파이썬의 새로운 특징이나 그의 처리과정 또는 환경을 기술한다.
파이썬 공동체는 소스 코드가 어떤 모습이어야 하는지 독자적인 표준을 가지고 있으며, 이는 PEP 8에 규정되어 있다. 이런 표준들은 C, C++, C#, Java, VisualBasic, 등등의 다른 공동체가 가진 표준과 다르다.
파이썬에서 들여쓰기와 공백은 너무 중요하기 때문에, 파이썬 코드를 위한 스타일 지도서에서 표준을 정립하였다. 지도서의 지침을 따르는 것이 현명하다! 대부분의 열린-소스 프로젝트와 (다행스럽게도) 사내 프로젝트는 스타일 가이드를 착실히 따른다.
들여쓰기 수준당 4개의 공백.
하드 탭 불가.
탭과 공백 혼용 절대 불가.
이것을 IDLE과 이맥스의 파이썬 모드에서 정확히 지원한다. 다른 편집기들도 이것들 지원할 것이다.
함수 사이에는 빈 줄 하나.
클래스 사이에는 빈 줄 두개.
def make_squares(key, value=0):
"""Return a dictionary and a list..."""
d = {key: value}
l = [key, value]
return d, l
joined_lower의 형태로
joined_lower나 ALL_CAPS의 형태로
StudlyCaps의 형태로
camelCase의 형태로
속성은 다음과 같이 이름짓는다: interface, _internal, __private
그러나 __private의 형태는 사용하지 말자. 나는 절대로 이렇게 사용해 본적이 없다. 한 번 사용하고 싶겠지만, 훗날 반드시 후회한다.
줄의 길이는 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\ '''
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/
고집스런 일관성은 속좁은 도깨비다(A foolish consistency is the hobgoblin of little minds).
—Ralph Waldo Emerson
(hobgoblin: 무엇인가 공포를 야기하는 것; 유령).)
언제나 예외는 있기 마련이다. PEP 8에 의하면:
그러나 가장 중요한 것은: 일관성을 포기해야 할 때를 아는 것이다 -- 어떤 경우에는 스타일 지도서가 적용되지 않는다. 잘 모르겠으면, 최선을 다해 판단하자. 다른 예들을 살펴보고 무엇이 가장 좋게 보이는지 결정하자. 그리고 주저없이 물어보자!
특별한 규칙을 깨려면 두 가지 좋은 이유가 있어야 한다:
- 규칙을 적용하면 코드가 더 읽기 어려워질 때, 특히나 그 규칙들을 준수하는 코드를 읽는데 익숙해진 사람에게 말이다.
- (아마도 역사적인 이유로) 똑같이 규칙을 어기는 주변의 코드와 일관성을 유지해야 하는 경우 -- 그렇지만 이 기회에 다른 사람의 난잡한 코드를 (진짜 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']
>>> 1, (1,)
>>> (1,) (1,)
>>> (1) 1
>>> () ()
>>> 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() 문자열 메쏘드는 한 번에 복사를 완수한다.
수 십 또는 수 백개의 문자열을 다룰 뿐이라면, 별 차이가 없을 것이다. 그러나 효율적으로 문자열을 구성하는 습관을 들이자. 수 천개의 문자열 또는 회돌이라면, 차이가 있을 것이다.
하부문자열 사이에 공백을 넣고 싶으면:
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.
함수를 적용해서 하부문자열을 만들고 싶다면:
result = ''.join(fn(i) for i in items)
조금씩 하부문자열을 계산하고 싶다면, 먼저 그것들을 리스트 안에 축적시키자:
items = [] ... items.append(item) # 여러 번 ... # 이제 요소들 축적 완료 result = ''.join(fn(i) for i in items)효율성을 위하여, join 문자열 메쏘드를 적용할 수 있도록
좋음:
for key in d:
print key
나쁨:
for key in d.keys():
print key
그러나 사전을 수정할 때에는 .keys()가 필요하다:
for key in d.keys():
d[str(key)] = d[key]
일관성을 유지하기 위하여, 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]
종종 사용하기 전에 사전을 초기화할 필요가 있다:
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])
수정가능한 사전 값 초기화하기:
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 메쏘드는 독립적인 서술문으로 사용할 수도 있다:
navs = {}
for (portfolio, equity, position) in data:
navs.setdefault(portfolio, 0)
navs[portfolio] += position * prices[equity]
파이썬 2.5에 새로 도입되었다.
defaultdict는 파이썬 2.5에 새로 도입되었고, collections 모듈에 포함되어 있다. defaultdict는 두 가지 점만 제외하면 일반 사전과 동일하다:
defaultdict를 얻는 두 가지 방법이 있다:
collections 모듈을 반입해서 이 모듈을 통하여 참조하는 방법,
➔
또는 직접 defaultdict 이름을 반입하는 방법:
➔
import collections d = collections.defaultdict(...)
from collections import defaultdict d = 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]
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']
# 이렇게 하시고: # 이렇게 하면 안된다:
if x: if x == True:
pass pass
리스트 테스트하기:
# 이렇게 하시고: # 이렇게 하면 안된다:
if items: if len(items) != 0:
pass pass
# 그리고 이렇게 하는 것은 확실히 안된다:
if items != []:
pass
| 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__
>>> 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
enumerate 함수는 리스트를 받아서 (인덱스, 항목) 쌍을 돌려준다:
>>> print list(enumerate(items)) [(0, 'zero'), (1, 'one'), (2, 'two'), (3, 'three')]
회돌이는 훨씬 더 간단해진다:
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
int a = 1; |
|
"a" 상자는 이제 정수 1이 담긴다.
같은 변수에 다른 값을 할당하면 상자 안의 내용물이 교체된다:
a = 2; |
|
이제 "a" 상자에는 정수 2가 있다.
한 변수를 또다른 변수에 할당하면 그 값의 복사본을 만들어서 그것을 새로운 상자 안에 넣는다:
int b = a; |
|
|
a = 1 |
|
여기에서 정수 1 객체는 "a"라는 꼬리표를 가진다.
"a"에 할당을 다시 하려면, 그냥 그 꼬리표를 또다른 객체로 옮기면 된다:
a = 2 |
|
|
이제 이름 "a"는 정수 2 객체에 붙었다.
원래의 정수 1 객체는 더 이상 "a"라는 꼬리표가 없다. 계속 살 수도 있겠지만, "a"라는 이름으로는 접근할 수 없다. (객체가 더 이상 참조되지 않거나 꼬리표가 없다면, 메모리에서 제거된다.)
이름을 또다른 이름에 할당하려면, 그냥 또다른 이름표를 기존의 객체에 붙이기만 하면 된다:
b = a |
|
(일반적인 전문용어이기 때문에) 보통 파이썬에서도 "변수(variables)"라고 칭하기는 하지만, 실제로는 "이름(names)" 또는 "식별자(identifiers)"라는 뜻이다. 파이썬에, "변수"값에 대한 이름표일 뿐, 꼬리표붙은 상자가 아니다.
이 자습서에서 얻는게 없다면, 파이썬 이름의 작동방식을 이해하시기를 바란다. 잘 이해하면 분명히 보상이 따르며, 이와 같은 사례를 피하는데 도움을 받을 수 있다:
def bad_append(new_item, a_list=[]):
a_list.append(new_item)
return 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를 모르더라도, 별로 도움이 되지 않는다. 기본적으로, 주형틀 또는 형식 그리고 병치 값들을 제공한다.
이 예제에서, 주형틀에는 두 가지 변환 지정이 들어있다: "%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
사전에서 이름 사용하기:
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())
실체 이름공간에서 이름 사용하기:
print ("We found %(error_count)d errors"
% self.__dict__)
동등하지만, 더욱 유연하다:
print ("We found %d errors"
% self.error_count)
전통적인 방식으로, for와 if 서술문으로 표현하면:
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)]
예를 들어, 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]
회돌이를 사용하면:
total = 0
for num in range(1, 101):
total += num * num
리스트 통합으로 처리하면:
total = sum([num * num for num in range(1, 101)])
발생자 표현식으로 처리하면:
total = sum(num * num for num in xrange(1, 101))
발생자 표현식("genexps")은 꼭 리스트 통합을 꼭 닮았다. 단 리스트통합은 탐욕적인 반면, 발생자 표현식은 게으르다는 점은 제외하고 말이다. 리스트통합은 리스트로, 전체 결과 리스트를 한 번에 계산한다. 발생자 표현식은 개별 값으로서, 필요할 때마다, 한 번에 하나의 값을 계산한다. 이는 기다란 연속열에 특히 유용하다. 계산된 리스트가 그냥 중간 단계일 뿐이고 최종 결과가 아닐 경우에 말이다.
이 경우에는 그 총합에만 관심이 있으므로; 제곱의 중간 리스트는 필요가 없다. 같은 이유로 xrange를 사용했는데: 한 번에 하나씩, 게으르게 값을 산출하기 때문이다.
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 = 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* 메쏘드를 혼용할 수 없다..
허락보다 용서를 구하는 것이 더 쉽다(It's easier to ask forgiveness than permission)
뛰기 전에 먼저 살피자(Look before you leap)
Duck typing
오리처럼 걷고, 오리처럼 꽥꽥 거리며, 오리처럼 생겼다면: 오리가 아니고 뭔가. (비슷하니까, 거위인가?)
예외
한 객체가 특정한 유형이어야 한다면 강제 형변환을 사용하자. 코드가 작동하기 위해 x가 반드시 문자열이어야 한다면, 다음을 호출해 보자
str(x)
다음과 시도해 보는 대신에 말이다
isinstance(x, str)
try:
return str(x)
except TypeError:
...
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
반입 모듈이면서 동시에 실행 스크립트로 만들려면:
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"
파이썬 표준 라이브러리를 점검하고.
파이썬 꾸러미 인덱스 ("치즈 가게(Cheese Shop)")를 점검하자:
웹을 검색하자. 구글(Google)은 항상 함께 해야 할 친구다.