파이썬 트릭

저자: 한스 마이네(Hans Meine)
한글판 johnsonj 2008.10.21 화

여기에 교훈이 있거나 쓸모가 있는 파이썬 조각코드를 모아놓고 있다. 서브페이지에 가면 파이썬에서 JPype-사용 해크를 이용하여 Weka의 자바 클래스에 접근하는 법을 볼 수 있다.

또, 흥미로운 링크들을 공유하고 싶다:

파이썬 콘솔에서의 자동완성

파이썬 프롬프트에서 파이썬 객체의 특성을 탭을 눌러 완성할 수 있으면 편리하다. 요즈음 나는 언제나 IPython을 사용하고 있는데. 파이썬 상호대화 콘솔을 상당히 개선하였으므로 정말 설치해 볼 가치가 있다. (자동완성과 들여쓰기 그리고 구문 강조와 마크로, 입력/출력 캐싱과 세션 관리 그리고 개선된 히스토리와 디버거, 역추적 등등;-) )

그렇지만, 흥미로운 점을 지적하자면 표준 파이썬 콘솔에서 자동완성이 가능하다는 것이다 (readline과 함께 컴파일되었다면 말이다. 실제로 그렇게 컴파일되었으리라 생각한다.)! 다음 코드는 그를 활성화시키기 위해 사용한다:

# .pythonrc.py
import readline, rlcompleter
readline.parse_and_bind("tab: complete")

이 코드를 ~/.pythonrc.py 등등의 파일에 넣고, PYTHONSTARTUP 변수를 이용하여 파이썬에게 그 위치를 알려준다! (즉, 나는 "export PYTHONSTARTUP=$HOME/.pythonrc.py"를 쉘 환경 설정 안에 집어 넣었다.)

이제, 가서 IPython을 점검해 보자. ;-)

리스트에서 중복원소 제거하기

리스트에서 중복 원소를 제거하고 싶다면, 그냥 원소를 모조리 사전에 키로 집어 넣자 (예를 들어, 값은 None으로 한다). 그리고 dict.keys()를 점검하자. 이 방법을 최적화한 버전을 웹에서 발견하였다:

from operator import setitem
def distinct(l):
    d = {}
    map(setitem, (d,)*len(l), l, [])
    return d.keys()

map은 더 짧은 (이 경우 원소가 비어 있으면) 리스트를 None으로 채운다. 새로운 파이썬 버전에서는 위의 것보다 더 간결하게 표현할 수 있다:*

def distinct(l):
    return dict.fromkeys(l).keys() # 파이썬 2.3+에서 작동

def distinct24(l):
    return list(set(l)) # 파이썬 2.4 이상 필요

확실히 더 간결해서 더 손댈 필요없이 사용해도 좋을 정도이다. 지금까지 모든 변형은 두 가지 한계가 있다:

리스트 펴는 방법

flatten 함수가 파이썬에 내장되지 않았는지 이유가 있다고 짐작한다. 왜냐하면 여러 의미구조적 질문이 제기되는데 어느 것도 직관적으로 답하지 못하기 때문인 것 같다:

그러나 파이썬 트릭에 관하여 말하고 있으므로, 먼저 본인의 해결책이 펴기 문제에 가장 흔하게 사용된다는 사실을 언급해야 하겠다:

# someLists 안에 있는 리스트의 모든 원소들을 someFunc에 건넨다:
someFunc(sum(someLists, []))

실제로, 이는 정확하게 본인이 필요한 유일한 펴기 방법이다. 그리고 깔끔한 제자리 해결책이다. 그러나 어쨌든, 다음은 완벽한 재귀 버전이다:

def flatten(x):
    """flatten(sequence) -> list

    하나로 펴진 리스트를 돌려준다. 
    안에는 연속열에서 열람한 원소들이 모두 담긴다.
    모두 재귀적으로 포함된 부분-연속열이다 (반복가능).

    예제:
    >>> [1, 2, [3,4], (5,6)]
    [1, 2, [3, 4], (5, 6)]
    >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, MyVector(8,9,10)])
    [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10]"""

    result = []
    for el in x:
        #if isinstance(el, (list, tuple)):
        if hasattr(el, "__iter__") and not isinstance(el, basestring):
            result.extend(flatten(el))
        else:
            result.append(el)
    return result

부동소수점수 형식화

종종 부동소수점수를 멋지게 출력하기 위하여 올바른 형식화 플래그를 찾지 못하는 경우가 있다. 그리하여, 이 "작은" 표를 만들었다. 간단한 숫자와 함께 가능한 옵션 을 보여준다:

%s 0 1 3.14159265359 2.3 0.001 1e-10 100
%.3s 0 1 3.1 2.3 0.0 1e- 100
%r 0 1 3.1415926535897931 2.2999999999999998 0.001 1e-10 100
%f 0.000000 1.000000 3.141593 2.300000 0.001000 0.000000 100.000000
%.2f 0.00 1.00 3.14 2.30 0.00 0.00 100.00
%.f 0 1 3 2 0 0 100
%#.f 0. 1. 3. 2. 0. 0. 100.
%e 0.000000e+00 1.000000e+00 3.141593e+00 2.300000e+00 1.000000e-03 1.000000e-10 1.000000e+02
%.2e 0.00e+00 1.00e+00 3.14e+00 2.30e+00 1.00e-03 1.00e-10 1.00e+02
%#.e 0.e+00 1.e+00 3.e+00 2.e+00 1.e-03 1.e-10 1.e+02
%g 0 1 3.14159 2.3 0.001 1e-10 100
%#g 0.00000 1.00000 3.14159 2.30000 0.00100000 1.00000e-10 100.000
%.2g 0 1 3.1 2.3 0.001 1e-10 1e+02
%#.2g 0.0 1.0 3.1 2.3 0.0010 1.0e-10 1.0e+02
%.g 0 1 3 2 0.001 1e-10 1e+02
%#.g 0. 1. 3. 2. 0.001 1.e-10 1.e+02

슬프게도, 여전히 최적의 형식을 찾지 못했다. 내가 찾고 있는 방법은 신속하게

  1. pi를 3.14159로 보여주고 (즉, 소수 5 자리)
  2. 3.1을 3.1로 보여주며 (뒤에 0이 따르지 않음)
  3. 1.234e-13를 0으로 그리고 2.3e+02를 230으로 보여주는 것 (지수승은 보여주지 않는다)
  4. 이상적으로, -1.234e-13를 0으로도 보여주면 좋겠다 (-0이 아니라).

"%#.2g" % 0.001 == '0.0010'인지 그 이유를 알려 주신다면, 진심으로 경청하겠다... ;-)

리스트의 반복/발생자를 거꾸로 처리하기

다음의 간편한 함수는 두 가지 목적을 충족한다. 첫 째, iter(somelist) 예제와 같이 편리하게 작성할 수 있다

for el in reviter(somelist):
    do_something(el)

둘 째, 놀랍도록 유용한 yield-구조를 상기시켜 준다 (yield는 2.3 버전에 도입되었다). 덕분에 발생자(generators)를 정의할 수 있다. 이는 생각건대 복잡한 반복자를 정의하려면 아주 자연스러운 방법이다:

def reviter(x):
    if hasattr(x, 'keys'):
        raise ValueError("사전은 역 반복을 지원하지 않는다")
    i = len(x)
    while i > 0:
        i -= 1
        yield x[i]

yield 서술문에서 반복자로 기여하는 객체에 함수의 전체 상태가 저장된다. 반복자는 양보된 모든 값들을 돌려주고 최초의 함수가 돌아오면 StopIteration을 일으킨다.

이런 목적을 위하여 파이썬 2.4는 이미 "거꾸로 돌려준다".

발생자 함수 하나에 yield가 여러 개 있는 것도 - 실제로 아주 유용하며 - 가능하다. 즉, 다음 코드는 본인의 fig.py 모듈에서 가져 왔으며 6개의 점으로 열린/닫힌 다각형의 좌표 쌍을 작성하는데 사용된다 (한 행당 12개의 좌표). (yield외에도, map을 지혜롭게 사용하는 법도 보여준다. 행당 N=12 회 반복된 원소들을 무리짓는데 말이다):

class PolylineBase(Object):
    # ...

    def _savePointIter(self):
        # 점 리스트를 다음과 같이 편다: x1, y1, x2, y2, x2, ...
        for p in self.points:
            yield p[0]
            yield p[1]
        # 물론, yield도 조건적일 수 있다,
        # 여기에서는 닫힌 다각형의 첫 좌표쌍을 반복하는데 사용된다:
        if self.closed():
            yield self.points[0][0]
            yield self.points[0][1]

    def __str__(self):
        # ...
        i = self._savePointIter()
        # (i, )에 12를 곱해서 같은 반복자로부터 12 개의 참조점을 얻는데,
        # 반복자가 소진되면 map(None, ...)이
        # None으로 "채워 넣는다"는 사실을 이용한다:
        for linePoints in map(None, *(i, )*12):
            # linePoints는 이제 12 개의 좌표가 들어있다 (아니면 끝에 None 값이 들어 있다):
            result += "\t" + " ".join(*[str(p) for p in linePoints if p is not None]) + "\n"
        # ...

삼항 연산자(?:)가 그립다면

C-같은 언어로 프로그래밍하는데 익숙하고 파이썬 버전이 2.4 이하라면 아마도 if-then-else 연산이 그리울 것이다. 모든 경우는 아니라도 많은 경우 그 틈새를 메꾸어 줄 것이 있다: 불리언 연산자의 단축 회로 특성을 사용할 수 있다:

something = condition and true_value or false_value

condition에 "거짓"으로 평가되는 값이 있다면, and-연산자는 true_value를 평가하지 않고 "False"를 돌려준다. 실제로, 단순히 "False"를 돌려주는 것이 아니라, condition의 값을 돌려준다. 이 값은 일종의 False로 간주된다. 이렇게 특별한 방식으로 불리언 연산자가 작동하므로, 다시 말해 TrueFalse가 아니라 그냥 인자중의 하나를 돌려주기 때문에 true_valuefalse_value를 변수 something에 할당할 수 있다.

주의!

위의 코드에서 true_value는 반드시 __nonzero__가 되어야 작동한다. 그렇지 않으면 이상한 결과를 얻는다:

this_will_be_two = cond and None or 2 # 틀렸다!

None은 연산자에 대하여 False와 같으므로 그 결과는 cond의 값에 상관없이 2가 된다. (대신에 not cond and 2 or None사용할 수도 있다 .)

읽기가 너무 어렵고 혼란스러우며 그리고 위험스러운 구문이다. 그러나 그럼에도 불구하고 어떤 경우는 편리하게 사용할 수 있다 ;-)

가끔 변종도 보인다

something = (false_value, true_value)[condition]

이 코드는 bool이 실제로 int에서 파생되었다는 사실을 이용한다. 그리고 False/True0/1이기도 하다 ( 조건이 0/1이 아니라면 bool(condition)으로 작성하자). 그렇지만, 여기에서 단축-회로는 전혀 없다. false_valuetrue_value가 모두 평가될 것이고, 이는 다른 많은 사례에 적용하게 되면 빠질 만한 큰 함정이다.

왜 이 연산자가 빠졌는지 또 삼항 연산자를 애타게 찾는 사람이 당신이 처음인지 궁금하실 것이다; 물론 당신이 처음은 아니다. 그러나 오랫동안 파이썬 공동체는 삼항 연산자 구문에 합의하지 못했다. 그 토론은 PEP 308에서 보자. 그럼에도 불구하고, 귀도(Guido)는 마침내 삼항 연산자를 파이썬에 통합하기로 결정하고, 가장 인기있는 형태들 중에서 true_value if condition else false_value를 골랐다 (이는 최종적으로 파이썬 2.5에 포함되었다).

Do-While 회돌이

파이썬은 do-while 또는 do-until 회돌이 구조가 (아직) 없기 때문에, 다음 관용구가 아주 일반적이 되었다:

while True:
    do_something()
    if condition():
        break

일관성 있게 고수하자. 그러면 조만간 아주 익숙해질 것이다.

[*]패디 맥커씨(Paddy McCarthy)에게 이 모든 것에 되먹임을 주시고 새로운 파이썬 특징을 이용한 변형들을 가르쳐 주심에 고마움을 전한다.
[†]그리고 피트 포먼(Pete Forman)에게 비-단축-회로 cond[bool] 변형을 알려주심을 감사를 드린다.

이 페이지가 최종 수정된 시각: Thursday, March 20, 2008