| 저자: | 모쉬 자드카(Moshe Zadka) 한글판 johnsonj 2008.05.09 금 |
|---|
이 문서는 공개 영역에 위치한다.
요약
이 문서는 자습서에 대한 동반자라고 생각하면 된다. 파이썬을 사용하는 법과, 더 중요한 것은 파이썬에서 사용하면 않되는 법을 다룬다.
파이썬은 상대적으로 다른 언어에 비하여 함정이 적지만, 여전히 어떤 구조는 모서리 사례에만 유용하며, 또는 위험해 보인다.
from module import *는 함수 정의 안에서 유효하지 않다. 많은 파이썬 버전이 그 유효성을 점검하지 않지만, 똑똑한 변호사가 무죄를 만드는 것은 아닌 것처럼 점검한다고 할지라도 더 유효성이 높아지는 것은 아니다. 절대로 그와 같이 사용하지 말자. 허용된 버전이라고 할지라도, 그렇게 하면 함수 실행이 더 느려진다. 컴파일러가 어느 이름이 지역이고 어느 이름이 전역인지 알지 못하기 때문이다. 파이썬 2.1에서 이 구조는 경고를 야기하고, 심지어 어떤 경우는 에러를 야기한다.
모듈 수준에서 from module import *를 사용할 수는 있지만, 보통 이는 나쁜 생각이다. 예를 들면, 이렇게 하면 파이썬이 가진 중요한 특성이 상실된다. — 편집기에서 그냥 “검색”기능만 이용하면 각각의 최상위 이름이 어디에 정의되어 있는지 알 수 있다. 또한 모듈에 함수나 클래스가 더 추가되어 성장하면 앞으로의 고난에 스스로 몸을 내맡긴 셈이다.
뉴스그룹에 오르는 심각한 질문중 하나는 왜 이코드가 작동하지 않는가이다:
f = open("www")
f.read()
물론, 잘 작동한다 (이름이“www”인 파일이 있다면 말이다.) 그러나 모듈의 어딘가에 from os import * 서술문이 있으면 작동하지 않는다. os 모듈에 정수를 돌려주는 open()라는 함수가 있기 때문이다. 이 방법은 아주 유용하지만, 별로 유용하지 않은 특성중의 하나가 내장 함수를 가려버리는 것이다.
모듈이 어떤 이름을 반출했는지 확실히 알 수 없다는 것을 기억하자. 그래서 필요한 것만 취하거나 — from module import name1, name2, 또는 모듈에 모셔두고 필요할 때마다 접근하자 — import module;print module.name.
from module import *이 딱 좋은 상황이 있다:
“장식 없는”이라는 단어는 명시적인 사전 없이 사용함을 가리킨다. 이런 경우 장식 없이 이런 구조를 사용하면 코드가 현재 환경에서 평가된다. 이는 from import *가 위험한 것과 마찬가지 이유로 위험하다 — 여러분이 세고 있는 변수를 밟아 버릴 수도 있고 나머지 코드를 엉망으로 만들어 버릴 수도 있다. 제발 그렇게 하지 말자.
나쁜 예제들:
>>> for name in sys.argv[1:]:
>>> exec "%s=1" % name
>>> def func(s, **kw):
>>> for var, val in kw.items():
>>> exec "s.%s=val" % var # invalid!
>>> execfile("handler.py")
>>> handle()
좋은 예제들:
>>> d = {}
>>> for name in sys.argv[1:]:
>>> d[name] = 1
>>> def func(s, **kw):
>>> for var, val in kw.items():
>>> setattr(s, var, val)
>>> d={}
>>> execfile("handle.py", d, d)
>>> handle = d['handle']
>>> handle()
이는 “하지 말아야 할 것”이다. 앞의 “하지 말아야 할 것”에 비하면 훨씬 약하지만 특별한 이유가 없는 한 여전히 해서는 안되는 것이다. 그것이 보통 나쁜 생각인 이유는 갑자기 두 개의 이름공간에 따로 사는 객체를 가지기 때문이다. 한 이름공간에 묶인 객체가 바뀌더라도, 다른 이름공간에 있는 객체는 바뀌지 않는다. 그래서 그 둘 사이에 불일치가 있다. 이런 일은 한 모듈이 적재되거나 또는 함수의 정의를 실행시간에 바꿀 때 일어난다.
나쁜 예제:
# foo.py
a = 1
# bar.py
from foo import a
if something():
a = 2 # danger: foo.a != a
좋은 예제:
# foo.py
a = 1
# bar.py
import foo
if something():
foo.a = 2
파이썬은 except: 절이 있어서, 모든 예외를 잡는다. 파이썬에서 에러는 모두 예외를 일으킨다. 이 때문에 많은 프로그래밍 에러가 실행시간 에러처럼 보이고, 디버깅 처리가 어렵게 된다.
다음 코드는 훌륭한 예를 보여준다:
try:
foo = opne("file") # "open"을 잘못 씀
except:
sys.exit("could not open file!")
두 번째 줄은 NameError를 촉발시키는데 이 예외는 except 절이 잡는다. 프로그램이 종료하고, 이것이 "file"을 읽지 못하는 것과 전혀 관계가 없다는 사실을 알지 못한다.
위의 예제는 다음과 같이 개선할 수 있다.
try:
foo = opne("file") # 실행하면 바로 "open"으로 바뀐다
except IOError:
sys.exit("could not open file")
어떤 상황에서는 except: 절이 유용하다: 예를 들어, 역호출을 실행하는 작업틀에서는, 역호출 때문에 작업틀이 엉망이 되지 않도록 하는 것이 좋다.
예외는 파이썬에서 유용한 특징이다. 예상치 못한 예외가 일어날 때마다 예외를 일으키는 법을 배워야 하고, 그에 관하여 무슨 처리를 할 수 있을 경우에만 잡아야 한다.
다음은 아주 유명한 반-관용구이다
def get_status(file):
if not os.path.exists(file):
print "file not found"
sys.exit(1)
return open(file).readline()
os.path.exists()에 대한 호출이 이루어진 시간과 open()이 호출되는 시간 사이에 파일이 삭제될 경우를 생각해 보자. 다시 말해 마지막 줄에서 IOError가 일어날 것이라는 뜻이다. 파일은 존재하지만 읽기 권한이 없으면 똑 같은 일이 일어난다. 이 때문에 보통의 머신에서 존재하는 파일과 존재하지 않는 파일에 대하여 테스트 해보면 버그가 없어 보인다. 즉, 테스트에서 그 결과는 이상이 없어 보이고, 그 코드는 배포된다. 그러면 처리되지 못한 IOError가 사용자에게로 도망을 치고, 사용자는 보기 흉한 역추적을 보게 된다.
다음은 개선한 방법이다.
def get_status(file):
try:
return open(file).readline()
except (IOError, OSError):
print "file not found"
sys.exit(1)
이 버전에서, *두 파일중 하나*가 열리고 줄이 읽히거나 (그래서 flaky NFS 또는 SMB 접속에도 작동하거나), 그렇지 않으면 메시지가 인쇄되고 어플리케이션은 취소된다.
여전히, get_status()는 너무 많은 가정을 한다 — 오랫 동안 실행되는 서버가 아니라 잠시 실행되는 스크립트에서만 사용될 것이라고 말이다. 물론, 호출자는 다음과 같이 할 수 있다
try:
status = get_status(log)
except SystemExit:
status = None
그래서, 코드에 되도록이면 except 절이 없게 만들자 — 보통 그렇게 하면 main()에서, 또는 언제나 성공하는 호출 안에서 모두 잡을 것이다.
그래서, 가장 좋은 버전은 아마도 다음과 같을 것이다.
def get_status(file):
return open(file).readline()
호출자는 원한다면 (예를 들어, 회돌이에서 여러 파일을 시도할 경우) 예외를 다룰 수 있다. 또는 그냥 예외 여과자를 자신의 호출자에게로 올라가도록 내버려 둘 수 있다.
앞 버전도 별로 좋지 않다 — 구현 상세 때문에, 예외가 일어나더라도 파일은 처리자가 끝날 때까지 닫히지 않을 것이다. 그리고 아마도 비-C 구현에서는 전혀 닫히지 않을 것이다 (예, Jython).
def get_status(file):
fp = open(file)
try:
return fp.readline()
finally:
fp.close()
종종, 사람들은 파이썬 라이브러리를 다시 작성하려고 하는 듯하다. 보통은 엉성하게 말이다. 자주 사용되는 모듈이 인터페이스가 엉성하다면, 보통 파이썬에 따라오는 풍부한 표준 라이브러리와 데이터 유형을 사용한 다음 자신만의 모듈을 창안하는 편이 훨씬 더 좋다.
많은 사람들이 알고 있는 유용한 모듈은 os.path이다. 운영 체제에 대하여 언제나 경로를 올바르게 연산하고, 이를 이용하는 편이 보통 직접 처리하는 것보다 훨씬 더 좋다.
비교해 보자:
# 이런!
return dir+"/"+file
# 더 좋음
return os.path.join(dir, file)
os.path에 있는 더 유용한 함수는 다음과 같다: basename(), dirname() 그리고 splitext().
또한 이런 저런 이유로 사람들이 인지하지 못하는 유용한 함수들이 많이 있다: min()과 max()는 예를 들어 비교 의미구조로 연속열에서 최소값/최대값을 찾아낼 수 있는데, 아직도 많은 사람들이 따로 max()/min()을 작송한다. 또다른 아주 유용한 함수는 reduce()이다. reduce()의 고전적인 사용법은 다음과 같다
import sys, operator
nums = map(float, sys.argv[1:])
print reduce(operator.add, nums)/len(nums)
이 귀여운 스크립트는 명령어 줄에 주어진 모든 숫자의 평균을 인쇄한다. reduce()는 모든 숫자를 합할 뿐이며 나머지는 그저 전처리와 후처리에 불과하다.
같은 의미에서 주목하자. float(), int() 그리고 long()는 모두 문자열 유형을 인자로 받으며, 그래서 파싱에 알맞다 — 일어날 ValueError를 다룰 준비가 되어 있다고 간주하고서 말이다.
파이썬은 새줄문자를 서술문 종료자로 간주하기 때문에, 그리고 서술문은 보통 한 줄에 두는 것이 편하기 때문에, 많은 사람들은 다음과 같이 한다:
if foo.bar()['first'][0] == baz.quux(1, 2)[5:9] and \
calculate_number(10, 20) != forbulate(500, 360):
pass
이는 위험하다는 사실을 깨닫아야 한다: \ 다음에 흩어진 공백문자 때문에 이 줄은 잘못이며, 흩어진 공백문자는 편집기에서 보기 어렵기로 악명이 높다. 이런 경우, 최소한 구문 에러이다. 그러나 코드가 다음과 같다면:
value = foo.bar()['first'][0]*baz.quux(1, 2)[5:9] \
+ calculate_number(10, 20)*forbulate(500, 360)
미묘한 잘못이 있다.
괄호 안에서 묵시적인 줄연속을 사용하는 것이 보통 훨씬 더 좋다:
다음 버전은 검증된 것이다:
value = (foo.bar()['first'][0]*baz.quux(1, 2)[5:9]
+ calculate_number(10, 20)*forbulate(500, 360))