제 1 장 파이썬을 이해하기 목 차 제 3 장 객체지향 작업틀 >>

제 2 장. 검사의 힘

2.1. 다이빙해 들어가기

이 장은 파이썬의 능력중 하나를 다룬다: 검사능력이 바로 그것인데, 아시다시피 파이썬에서 모든 것은 객체이다. 검사는 메모리에 있는 다른 모듈과 함수들을 객체로 바라보고, 그 정보를 획득하며, 다루는 코드이다. 그 과정중에 이름없이 함수를 정의하고, 순서없는 인자들로 함수를 호출하며, 그리고 미리 그 이름을 알고 있지 못하는 함수들도 참조해 보겠다.

다음은 완전하게 작동하는 파이썬 프로그램이다. 이것을 보기만 해도 잘 이해할 수 있어야 한다. 숫자가 붙여진 줄들은 파이썬을 이해하기에서 다루어진 개념들을 보여준다. 나머지 코드가 겁나게 보여도 걱정하지 마라; 이장을 통해서 모든 것들을 배우게 될 것이다.

Example 2.1. apihelper.py

아직 그렇게 하지 못했다면, 이 예제와 더불어 이 책에 사용되어진 다른 예제들을 내려 받을 수 있다.(Windows, UNIX, Mac OS).

def help(object, spacing=10, collapse=1): 1 2 3
    """Print methods and doc strings.

    Takes module, class, list, dictionary, or string."""
    methodList = [method for method in dir(object) if callable(getattr(object, method))]
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])

if __name__ == "__main__":                4 5
    print help.__doc__
1 이 모듈은 한 개의 함수, help를 가진다. 함수 선언에 따르면, 세 개의 매개변수를 취한다: object, spacing, 그리고 collapse이 그것이다. 마지막 두 개는 실제로는 선택적 매개변수인데, 그것을 간략하게 살펴보겠다.
2 help 함수는 여러-줄의 문서화 문자열(doc string)을 가지는데 함수의 목적이 간결하게 기술되어 있다. 아무런 반환값도 언급되지 않았음을 주목하라; 이 함수는 값이 아니라 그 효과를 위해서만 사용될 것이다.
3 함수 안의 코드는 들여쓰기 되어 있다.
4 "if __name__" 꼼수로 이 프로그램은 독립적으로 실행 될 때, 다른 프로그램을 위한 모듈로 사용되는 용도를 방해함 없이 뭔가 유용한 것을 할 수 있다. 이 경우에 프로그램은 그저 help함수의 문서화 문자열(doc string)을 출력할 뿐이다.
5 if 서술문은 '=='을 비교를 위해 사용하고, 괄호는 요구되지 않는다.

help 함수는 프로그래머에 의해서 파이썬 IDE에서 작업하는 동안에 사용되도록 디자인 된다. (함수를 가진 모듈 혹은 메쏘드를 가진 리스트 같이) 함수 혹은 메쏘드를 가진 어떤 객체라도 취해서 그 함수와 그의 문서화 문자열(doc string)을 출력한다.

Example 2.2. Sample usage of apihelper.py

>>> from apihelper import help
>>> li = []
>>> help(li)
append     L.append(object) -- append object to end
count      L.count(value) -> integer -- return number of occurrences of value
extend     L.extend(list) -- extend list by appending list elements
index      L.index(value) -> integer -- return index of first occurrence of value
insert     L.insert(index, object) -- insert object before index
pop        L.pop([index]) -> item -- remove and return item at index (default last)
remove     L.remove(value) -- remove first occurrence of value
reverse    L.reverse() -- reverse *IN PLACE*
sort       L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1, 0, 1

기본 값으로 출력은 쉽게 읽을 수 있도록 형식화된다. 여러-줄 문서화 문자열(doc string)은 한개의 기다란 줄로 합쳐진다. 그러나 이 선택사항은 collapse 인자에 0을 지정함으로써 변경될 수 있다. 만약 함수의 이름이 10자를 넘는다면, spacing 인자에 더 큰 값을 지정하여 출력을 더 쉽게 읽을 수 있다.

Example 2.3. Advanced usage of apihelper.py

>>> import odbchelper
>>> help(odbchelper)
buildConnectionString Build a connection string from a dictionary Returns string.
>>> help(odbchelper, 30)
buildConnectionString          Build a connection string from a dictionary Returns string.
>>> help(odbchelper, 30, 0)
buildConnectionString          Build a connection string from a dictionary

    Returns string.

2.2. 선택적 인자와 이름있는 인자

파이썬은 함수 인자들이 기본 값을 가지도록 허용한다; 함수가 인자 없이 불려진다면, 그 인자는 자신의 기본 값을 취한다. 게다가, 인자들은 이름 있는 인자들을 사용함으로써 어떤 순서라도 지정될 수 있다. SQL 서버의 Transact/SQL에 저장된 프로시져들은 이렇게 할 수 있다; 여러분이 SQL서버의 스트립트 전문가라면, 이 장을 건너뛰어도 좋다.

Example 2.4. help, a function with two optional arguments

def help(object, spacing=10, collapse=1):

spacingcollapse는 선택적이다, 왜냐하면 정의된 기본 값을 가지기 때문이다. object는 필수적이다, 왜냐하면 아무런 기본값을 가지지 않기 때문이다. 만약 help가 오직 하나의 인자로만 호출된다면, spacing10을 기본값으로 하고 collapse1을 기본값으로 한다. 만약 help가 두 개의 인자로 호출된다면, collapse는 여전히 1을 기본값으로 한다.

collapse에 하나의 값을 지정하기를 원하지만 spacing을 위한 기본 값은 받아들이기를 원하지 않는다고 해보자. 대부분의 언어에서라면 고민스러울 것이다. 왜냐하면 세 개의 인자로 그 함수를 호출해야만 할 것이기 때문이다. 그러나 파이썬에서는 인자는 이름을 붙이면 어떤 순서라도 지정될 수 있다.

Example 2.5. Valid calls of help

help(odbchelper)                    1
help(odbchelper, 12)                2
help(odbchelper, collapse=0)        3
help(spacing=15, object=odbchelper) 4
1 한개의 인자만으로는, spacing은 기본 값인 10을 가지며 collapse는 기본값인 1을 가진다.
2 두 개의 인자라면, collapse는 기본값인 1을 가진다.
3 여기에서 collapse인자를 명시적으로 지명하고 그의 값을 지정한다. spacing은 여전히 그의 기본값인 10을 가진다.
4 심지어 (아무런 기본 값을 가지지 않는 object와 같이) 필수 인자조차도 이름을 붙일 수 있다. 그리고 이름있는 인자는 어떤 순서라도 나타날 수 있다.

인자가 단순히 사전에 불과하다는 것을 깨닫게 되기 전까지는 정말 이상하게 보일 것이다. 인자 이름 없이 함수를 호출하는 “정상적인” 방법은 실제로는 최단 지름길이어서 거기에서 파이썬은 그 값들을 함수 선언에서 지정된 순서로 인자들의 이름과 맞추어 본다. 그리고 대부분의 시간을 “정상적인” 방식으로 함수들을 호출할 것이다. 그러나 필요하다면 부가적으로 유연성을 발휘할 기회가 항상 있다.

Note
함수를 호출하기 위해서 해야할 유일한 것은 값을 (어떻게 해서든지) 요구되는 각 인자에 지정하는 것이다; 그렇게 하는 방식과 순서는 여러분에게 달려 있다.

더 읽어야 할 것

2.3. type, str, dir, 그리고 다른 내장 함수들

파이썬에는 아주 유용한 내장 함수의 작은 집합이 있다. 다른 모든 함수들은 쪼개어져서 모듈로 들어갔다. 이것은 실제로 의도적인 디자인 선택이었는데, 언어의 핵심부가 (비주얼 베이직과 같은) 다른 스크립트 언어와 같이 부풀어 오르는 것을 막고자 함이었다.

type 함수는 모든 임의적인 객체의 데이타형을 반환한다. 가능한 형들은 types 모듈에 나열되어 있다. 이것은 헬퍼(예제파일) 함수들에 유용해서 여러 종류의 데이타 형들을 처리할 수 있다.

Example 2.6. Introducing type

>>> type(1)           1
<type 'int'>
>>> li = []
>>> type(li)          2
<type 'list'>
>>> import odbchelper
>>> type(odbchelper)  3
<type 'module'>
>>> import types      4
>>> type(odbchelper) == types.ModuleType
1
1 type 은 어떤 것이라도 취해서 그 데이타형을 반환한다. 내가 뜻하는 것은 모든 것이다. 정수(integers), 문자열(strings), 리스트(lists), 사전(dictionaries), 터플(tuples), 함수(functions), 클래스(classes), 모듈(modules), 심지어 형(types)까지 모든 것을 뜻한다.
2 type 은 변수를 취해서 그 데이타형을 반환할 수 있다.
3 type은 또한 모듈에도 작동한다.
4 types모듈의 상수들을 사용해서 객체들의 형을 비교할 수 있다. 이것이 바로 help 함수가 하는 일이고, 그것을 간략하게 살펴 보겠다.

str 은 데이타를 문자열로 강제로 변환시킨다. 모든 데이타형은 문자열로 강제변환될 수 있다.

Example 2.7. Introducing str

>>> str(1)          1
'1'
>>> horsemen = ['war', 'pestilence', 'famine']
>>> horsemen.append('Powerbuilder')
>>> str(horsemen)   2
"['war', 'pestilence', 'famine', 'Powerbuilder']"
>>> str(odbchelper) 3
"<module 'odbchelper' from 'c:\\docbook\\dip\\py\\odbchelper.py'>"
>>> str(None)       4
'None'
1 정수와 같은 단순한 데이타형에 대하여 str 함수가 작동하리라고 예상할 텐테, 왜냐하면 거의 모든 언어에서 정수를 문자열로 바꾸어 주는 함수를 가지고 있기 때문이다.
2 그렇지만, str 함수는 어떠한 형의 어떠한 객체에도 작동한다. 여기에서 조각조각 조립한 리스트에도 작동한다.
3 str 함수는 또한 모듈에도 작동한다. 주목할 것은 모듈의 문자열 표현이 디스크에서의 그 모듈의 경로이름을 포함하고, 그래서 여러분의 것은 다를 수도 있다는 것이다.
4 str의 미묘하지만 중요한 행위는 파이썬의 null 값인, None에도 작동한다는 것이다. 문자열 'None'을 반환한다. 이것을 help함수에 이용할 텐데, 이에 관하여 간략하게 살펴 보겠다.

help 함수의 심장부에는 강력한 dir함수가 있다. dir 함수는 어떠한 객체라도 그 메쏘드들과 속성들의 리스트를 반환한다: 모듈(modules), 함수(functions), 문자열(strings), 리스트(lists), 사전(dictionaries)... 다른 많은 어떤 것이라도 말이다.

Example 2.8. Introducing dir

>>> li = []
>>> dir(li)           1
['append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
>>> d = {}
>>> dir(d)            2
['clear', 'copy', 'get', 'has_key', 'items', 'keys', 'setdefault', 'update', 'values']
>>> import odbchelper
>>> dir(odbchelper)   3
['__builtins__', '__doc__', '__file__', '__name__', 'buildConnectionString']
1 li는 리스트이다. 그래서 dir(li)는 한 리스트의 모든 메쏘드들을 가진 리스트를 반환한다. 주목할 것은 반환된 리스트에 메쏘드의 이름이 메쏘드 자체가 아니라 문자열로 담긴다는 것이다.
2 d 는 사전이다. 그래서 dir(d)는 사전 메쏘드들의 이름을 가진 리스트를 반환한다. 적어도 이것들 중 하나인, 키 (keys)는 익숙하게 보이리라 믿는다.
3 이곳이 진짜로 재미있어 지는 곳이다. odbchelper는 모듈이다. 그래서 dir(odbchelper)는 내장 속성들을 포함하여 __name____doc__, 그리고 정의한 다른 모든 속성과 메쏘드와 같이, 그 모듈에서 정의한 모든 종류의 것들을 가진 리스트 하나를 반환한다. 이 경우에 odbchelper는 오직 하나의 사용자-정의 메쏘드 buildConnectionString 함수를 가지는데 이에 관해서는 파이썬을 이해하기의 장에서 공부한 바 있다.

마지막으로, callable 함수는 어떠한 객체라도 취해서 그 객체가 호출가능하면 1을 반환하고 그렇지 않으면 0 을 반환한다. 호출가능한 객체에는 함수(functions), 클래스(class) 메쏘드(methods), 심지어 클래스 자신까지도 포함된다 (제 3 장에서 클래스에 관하여 더 자세히 다룬다.)

Example 2.9. Introducing callable

>>> import string
>>> string.punctuation           1
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
>>> string.join                  2
<function join at 00C55A7C>
>>> callable(string.punctuation) 3
0
>>> callable(string.join)        4
1
>>> print string.join.__doc__    5
join(list [,sep]) -> string

    Return a string composed of the words in list, with
    intervening occurrences of sep.  The default separator is a
    single space.

    (joinfields and join are synonymous)
1 (여전히 많은 사람들이 join 함수를 사용함에도 불구하고) string 모듈에 있는 함수들은 평판이 좋지 않다. 그러나 그 모듈은 이러한 string.punctuation과 같이 유용한 함수들을 많이 포함하고 있다. 이 함수는 모든 표준적인 구두점 문자들을 포함한다.
2 string.join은 문자열의 리스트를 연결해 주는 함수이다.
3 string.punctuation 은 호출가능하지 않다; 문자열이기 때문이다 (문자열은 호출가능한 메쏘드들을 가지고 있지만, 그 문자열 자체는 호출가능하지 않다.)
4 string.join은 호출가능하다; 이 함수는 두 개의 인자를 가지는 함수이다.
5 호출가능한 객체라면 모두 문서화 문자열(doc string)을 가질 수 있다. callable 함수를 한 객체의 속성에 각각 사용하면, 그 객체에 관하여 미리 아무것도 모르더라더도, 어떤 속성에 관심을 두어야 할지(methods, functions, classes) 그리고 어떤 것을 무시하고 싶은지를 (constants, 등등.) 결정할 수 있다 .

type, str, dir, 그리고 파이썬의 모든 나머지 내장 함수들은 그룹지어져 '__builtins__'라고 불리는 특별한 모듈로 구축되어 들어갔다 (두개의 밑줄을 앞과 뒤에 가짐). 이렇게 생각하는 것이 도움이 될지도 모르겠다. 처음 시작할 때 파이썬이 자동으로 'from __builtins__ import *'를 실행한다고 생각할 수 있다. 모든 “내장(built-in)” 함수들을 이름공간에 반입하고 그래서 그것들을 직접적으로 사용할 수 있다.

이런 방식으로 생각하면 __builtins__ 모듈에 관한 정보를 획득함으로써 모든 내장 함수와 속성에 하나의 그룹으로 접근할 수 있다는 장점이 있다. 그러면 알아맞추어 보자. 그것을 위한 하나의 함수를 가지고 있다; 그것은 help라고 불리운다. 스스로 시도해서 그 리스트를 지금 훓어 보라; 앞으로 그 중요한 함수들중 몇개에 다이빙해 들어가겠다. (AttributeError와 같은, 내장 에러 클래스중 어떤 것은 이미 친숙하게 보이리라 믿는다.)

Example 2.10. Built-in attributes and functions

>>> from apihelper import help
>>> help(__builtins__, 20)
ArithmeticError      Base class for arithmetic errors.
AssertionError       Assertion failed.
AttributeError       Attribute not found.
EOFError             Read beyond end of file.
EnvironmentError     Base class for I/O related errors.
Exception            Common base class for all exceptions.
FloatingPointError   Floating point operation failed.
IOError              I/O operation failed.

[...snip...]
Note
파이썬에는 훌륭한 참조 매뉴얼이 따라 오는데, 그것을 완전히 정독을 해서 파이썬이 제공하는 모든 모듈들을 배워야만 한다. 그러나 대부분의 언어에서 이러한 모듈을 사용하는 법을 다시 떠올리기 위해서 매뉴얼을 (혹은 맨 페이지, 혹은, MSDN을) 참조하고 있는 자신들을 발견하겠지만, 파이썬은 대부분이 자체로-문서화(self-documenting) 되어 있다.

더 읽어야 할 것

2.4. getattr로 객체의 참조점을 획득하기

이미 파이썬 함수는 객체라는 것을 배웠다. 하지만 아직 모르는 것은 getattr 함수를 사용하여, 실행시까지 그 이름을 모르고서도 한 함수의 참조점을 획득할 수 있다는 것이다.

Example 2.11. Introducing getattr

>>> li = ["Larry", "Curly"]
>>> li.pop                       1
<built-in method pop of list object at 010DF884>
>>> getattr(li, "pop")           2
<built-in method pop of list object at 010DF884>
>>> getattr(li, "append")("Moe") 3
>>> li
["Larry", "Curly", "Moe"]
>>> getattr({}, "clear")         4
<built-in method clear of dictionary object at 00F113D4>
>>> getattr((), "pop")           5
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'pop'
1 리스트의 pop메쏘드에 대한 참조점을 획득한다. 이것은 pop메쏘드를 호출하는 것이 아니라는 것을 주목하라; 그 호출은 li.pop()이 될 것이다. 이것은 그 메쏘드 자신이다.
2 이것 또한 pop 메쏘드에 대한 참조점을 반환한다. 그러나 이 경우에 그 메쏘드 이름이 getattr 함수에 문자열 인자로서 지정된다. getattr은 놀라울 정도로 유용한 내장 함수로서 어떤 객체의 어떤 속성이라도 반환한다. 이 경우에 객체는 리스트이고, 속성은 pop 메쏘드이다.
3 이것이 얼마나 유용한지가 머리속에 잘 안 들어온다면, 다음을 시도해 보자: getattr의 반환 값은 메쏘드이다. 그러면 그것을 li.append("Moe")라고 직접적으로 말한 것과 똑 같이 호출할 수 있다. 그러나 함수를 직접적으로 호출하지 않았다; 대신에 함수의 이름을 문자열로 지정했다.
4 getattr은 또한 사전에도 작동한다.
5 이론적으로 터플이 어떠한 메쏘드도 가지지 않는다는 점만 제외한다면, getattr은 터플에도 작동할 것이다. 그래서 getattr은 어떠한 속성을 주더라도 예외를 발생시킬 것이다.

getattr 는 단순히 내장 데이타형 만을 위한 것은 아니다. 모듈에도 작동한다.

Example 2.12. getattr in apihelper.py

>>> import odbchelper
>>> odbchelper.buildConnectionString             1
<function buildConnectionString at 00D18DD4>
>>> getattr(odbchelper, "buildConnectionString") 2
<function buildConnectionString at 00D18DD4>
>>> object = odbchelper
>>> method = "buildConnectionString"
>>> getattr(object, method)                      3
<function buildConnectionString at 00D18DD4>
>>> type(getattr(object, method))                4
<type 'function'>
>>> import types
>>> type(getattr(object, method)) == types.FunctionType
1
>>> callable(getattr(object, method))            5
1
1 이것은 odbchelper 모듈에 있는 buildConnectionString 함수에 대한 참조점을 반환한다, 그에 관하여 파이썬을 이해하기에서 공부했다. (여러분이 보는 16진 주소는 내 컴퓨터에 특정한 것이다; 여러분의 출력결과는 다를 것이다.)
2 getattr를 사용하여, 같은 함수에 대한 같은 참조점을 획득할 수 있다. 일반적으로, getattr(object, "attribute")object.attribute과 동등하다. 만약 객체(object)가 모듈이라면, 속성(attribute)은 그 모듈에서 정의된 어떤 것이라도 될 수 있다: 함수(function), 클래스(class), 혹은 전역 변수(global variable) 등등.
3 그리고 이것이 help함수에서 실제로 사용하는 것이다. 객체(object)는 인자로서 그 함수에 넘겨진다; 메쏘드(method)는 문자열로서 메쏘드 혹은 함수의 이름이다.
4 이 경우에 method는 한 함수의 이름이며, 그것의 형(type)을 획득함으로써 증명할 수 있다.
5 메쏘드(method)는 함수이므로, 호출가능하다.

2.5. 리스트를 걸러내기

아시다시피, 파이썬은 리스트 통합을 통해 리스트를 다른 리스트에 짝짓는 강력한 능력을 가지고 있다. 이 능력을 여과 메카니즘과 결합하면, 리스트에 있는 어떤 요소들은 짝지어지고 다른 것들은 완전히 제거할 수 있다.

Example 2.13. List filtering syntax

[mapping-expression for element in source-list if filter-expression]

이것은 여러분이 알고 있고 좋아하는 리스트 통합의 확장이며 앞에서 3분의 2는 동일하다; if로 시작하는, 그 나머지 부분은 걸러내기 표현식이다. 여과 표현식은 참 혹은 거짓을 평가하는 어떤 표현식도 될 수 있다 (파이썬에서 거의 어떤 것도 다 된다). 여과 표현식이 참이라고 평가한 어떠한 요소도 짝짓기에 포함될 것이다. 다른 모든 요소들은 무시된다. 그래서 그것들은 짝짓기 표현식에 참가되지 않으며 출력 목록에도 포함되지 않는다.

Example 2.14. Introducing list filtering

>>> li = ["a", "mpilgrim", "foo", "b", "c", "b", "d", "d"]
>>> [elem for elem in li if len(elem) > 1]       1
['mpilgrim', 'foo']
>>> [elem for elem in li if elem != "b"]         2
['a', 'mpilgrim', 'foo', 'c', 'd', 'd']
>>> [elem for elem in li if li.count(elem) == 1] 3
['a', 'mpilgrim', 'foo', 'c']
1 여기에서 짝짓기 표현식은 단순하다 (그저 각 요소의 값을 반환할 뿐이다). 그래서 여과 표현식에 집중하자. 파이썬은 리스트를 회돌이 하면서, 각 요소를 여과 표현식을 통하여 실행한다; 만약 여과 표현식이 참이라면, 요소는 짝지어져서 짝짓기 표현식의 결과는 반환된 리스트에 포함된다. 여기에서 한-문자 문자열을 모두 걸러내고 있는 중이고, 그래서 그 보다 기다란 문자열의 리스트가 남는다.
2 여기에서 특별한 값, b를 걸러내는 중이다. 주목할 것은 이것이 b의 모든 출현을 걸러낼 것이라는 것이다. 왜냐하면 그것이 출현할 때마다 그 여과 표현식은 거짓이 될 것이기 때문이다.
3 count는 리스트 메쏘드로서 하나의 값이 리스트에서 나타나는 횟수를 반환한다. 원래의 리스트에서 각 값들에 대해 하나씩의 복사본을 포함하는 리스트를 반환한다면, 이 여과기가 하나의 리스트로부터 중복을 제거할 것이라고 생각할 지도 모른다. 그러나 그렇지 않다. 왜냐하면 원래의 리스트에서 두 번 나타나는 값들은 (이 경우에는, bd) 완전히 제외되기 때문이다. 리스트로부터 중복을 제거하는 방법들이 있기는 하지만, 여과가 그 해결책은 아니다.

Example 2.15. Filtering a list in apihelper.py

    methodList = [method for method in dir(object) if callable(getattr(object, method))]

복잡해 보이고 복잡하지만, 기본 구조는 동일하다. 전체 여과 표현식은 리스트 하나를 반환하고, 그 리스트는 methodList 변수에 할당된다. 표현식의 전반부는 리스트 짝짓기 부분이다. 짝짓기 표현식은 자기확인 표현식이다; 그것은 각 요소의 값을 반환한다. dir(object)는 객체(object)의 속성과 메쏘드의 리스트를 반환한다; 이것이 바로 짝짓고 있는 그 리스트이다. 그래서 if이후의 그 새로운 부분만이 여과 표현식이다.

여과 표현식은 두렵게 보인다. 그러나 그렇지 않다. 이미 callable, getattr, 그리고 in에 관하여 배웠다. 이전 섹션에서 본 바와 같이, 만약 객체(object)가 모듈이고 e가 그 모듈에 있는 한 함수의 이름이라면, 표현식 'getattr(object, method)'은 함수객체를 반환한다.

그래서 이 표현식은 object라고 이름지어진 객체 하나를 취해서, 그 객체의 속성, 메쏘드, 함수 그리고 다른 모든 것들의 이름을 가진 리스트를 획득하고, 그 리스트를 걸러내어 관심을 두지 않는 모든 것들을 제거한다. getattr함수를 통하여 각 속성/메쏘드/함수의 이름을 취해서 그 실제 것들에 대한 참조점을 획득함으로써 그 제거를 수행한다. 그리고 그 객체가 호출가능하지를 점검한다. 그것은 어떠한 메쏘드 그리고 함수, (리스트의 pop메쏘드와 같은) 내장 함수와 (odbchelper 모듈의 buildConnectionString함수와 같은) 사용자-정의함수들 어떤 것이라도 될 수 있다. 모든 모듈에 내장되어 들어가 있는 __name__속성과 같은 다른 속성들은 상관하지 않는다.

더 읽어야 할 것

2.6. andor의 특이한 본성

짐작하듯이 파이썬에서 andor는 불리언 논리를 수행한다. 그러나 불리언 값들을 반환하지 않는다; 그것들은 비교하고 있는 실제의 값들 중 하나를 반환한다.

Example 2.16. Introducing and

>>> 'a' and 'b'         1
'b'
>>> '' and 'b'          2
''
>>> 'a' and 'b' and 'c' 3
'c'
1 and를 사용할 때, 값들은 불리언 문맥에서 왼쪽에서 오른쪽으로 평가된다. '0, '', [], (), {}, 그리고 None'은 모두 불리언 문맥에서 거짓이다; 다른 모든 것은 참이다.[3] 만약 모든 값들이 불리언 문맥에서 참이라면, and는 마지막 값을 반환한다. 이 경우에 and'a'를 참으로 평가하고 'b'를 참으로 평가하여 'b'를 반환한다.
2 만약 어떤 값이라도 불리언 문맥에서 거짓이라면 and는 첫번째 거짓 값을 반환한다. 이 경우에 ''이 첫 번째 거짓 값이다.
3 모든 값들이 참이다. 그래서 and는 마지막 값 'c'를 반환한다.

Example 2.17. Introducing or

>>> 'a' or 'b'          1
'a'
>>> '' or 'b'           2
'b'
>>> '' or [] or {}      3
{}
>>> def sidefx():
...     print "in sidefx()"
...     return 1
>>> 'a' or sidefx()     4
'a'
1 or을 사용하면 값들은 불리언 문맥에서 and와 똑 같이 왼쪽에서 오른쪽으로 평가된다. 만약 어떤 값이라도 참이면 or는 그 값을 즉시 반환한다. 이 경우에 'a'가 첫 번째 참 값이다.
2 or''를 거짓으로 평가하고 'b'를 참으로 평가해서, 'b'를 반환한다.
3 만약 모든 값이 거짓이면, or는 마지막 값을 반환한다. or''를 거짓으로 평가하고 []를 거짓으로 평가하며, 그리고 {}를 거짓으로 평가하여, {}를 반환한다.
4 주목할 것은 or은 불리언 문맥에서 참인 첫 번째 값을 찾을 때까지만 평가하며, 그리고 그 나머지를 무시한다는 것이다. 어떤 값들이 부작용을 가질 수 있다면 이러한 특성은 중요하다. 여기에서 sidefx함수는 절대로 호출되지 않는다. 왜냐하면 or'a'를 참으로 평가하고, 즉시 'a'를 반환하기 때문이다.

여러분이 C 해커라면, 여러분은 틀림없이 'bool ? a : b' 표현식에 익숙할 것이다. bool이 참이면, a를 평가하고 그렇지 않으면 b를 평가한다. 파이썬에서 andor가 작동하는 방식때문에, 똑 같은 일을 달성할 수 있다.

Example 2.18. Introducing the and-or trick

>>> a = "first"
>>> b = "second"
>>> 1 and a or b 1
'first'
>>> 0 and a or b 2
'second'
1 이 구문은 C 에서의 'bool ? a : b' 표현식과 비슷하게 보인다. 전체 표현식은 왼쪽에서 오른쪽으로 평가된다. 그래서 and가 먼저 평가된다. 1 and 'first'가 평가되어 'first'로, 그리고 'first' or 'second'가 평가되어 'first'로 된다.
2 0 and 'first'0으로 평가되고, 0 or 'second''second'로 평가된다.

그렇지만, 파이썬의 이러한 표현식은 단순히 불리언 논리이며, 언어의 특별한 구조가 아니므로, 파이썬의 이러한 and-or 꼼수와 C 에서의 'bool ? a : b' 구문 사이에는 너무, 너무, 너무도 중요한 차이가 하나 존재한다. 만약 a의 값이 거짓이라면, 그 표현식은 여러분이 예상한 대로 작동하지 않을 것이다. (여러분은 내가 이런 함정에 빠졌었다는 것을 이해할 수 있는가? 그것도 수 없이 말이다.)

Example 2.19. When the and-or trick fails

>>> a = ""
>>> b = "second"
>>> 1 and a or b 1
'second'
1 a는 빈 문자열이므로 파이썬은 불리언 문맥에서 거짓으로 간주하고, 1 and ''''로 평가되며, '' or 'second''second'로 평가된다. 이런! 우리가 원한 것이 아니다.
Important
and-or 꼼수인, bool and a or b는, a가 불리언 문맥에서 거짓일 때는 C 표현식인 'bool ? a : b'과 같이 작동하지 않을 것이다.

그러면, and-or 꼼수 뒤의 진짜 꼼수는 확실하게 a의 값이 절대로 거짓이 되지 않도록 하는 것이다. 이렇게 하는 일반적인 방법은 a[a]에 집어 넣고 b를 into [b]에 집어 넣어서, 그리고는 그 반환된 리스트의 첫 번째 요소를 취하는 것이다. 그러면 그 결과는 a 또는 b가 될 것이다.

Example 2.20. Using the and-or trick safely

>>> a = ""
>>> b = "second"
>>> (1 and [a] or [b])[0] 1
''
1 [a] 는 비어 있지 않은 리스트이므로, 그것은 절대로 거짓이 아니다. 만약 a0이나 '' 또는 다른 어떤 거짓 값일 지라도, 리스트 [a]는 참이다 왜냐하면 하나의 요소를 가지기 때문이다.

지금까지 이러한 꼼수는 그것이 지니는 가치보다 더 힘들어 보일지 모르겠다. 결국, if 서술문으로 같은 일을 달성한다. 그래서 왜 이런 복잡한 일을 하는가? 음, 많은 경우 두 개의 상수 값 사이를 선택을 한다. 그래서 여러분은 더 쉬운 구문을 사용할 수 있다. 그리고 걱정하지 마라. 왜냐하면 a값이 언제나 항상 참일 것이라는 것을 알기 때문이다. 그리고 더 복잡하고 안전한 형태를 사용할 필요가 있을 지라도, 거기에는 그렇게 해야할 충분한 이유가 있다; 람다(lambda) 함수에서와 같이, 파이썬에서는 if 서술문이 허용되지 않는 경우가 있기 때문이다다.

더 읽어야 할 것

2.7. lambda함수를 사용하기

파이썬은 재미있는 구문을 제공하는데 그것으로 한줄짜리 소형-함수를 임시로 정의할 수 있다. 리스프로부터 빌려 온 이른바 lambda 함수는 함수가 필요한 어느 곳에나 사용될 수 있다.

Example 2.21. Introducing lambda functions

>>> def f(x):
...     return x*2
...     
>>> f(3)
6
>>> g = lambda x: x*2  1
>>> g(3)
6
>>> (lambda x: x*2)(3) 2
6
1 이것은 lambda 함수로서 위의 정상적인 함수와 똑 같은 일을 달성한다. 여기에 생략된 구문에 주목하라: 인자 리스트 주위에는 아무런 괄호가 없다. 그리고 return 키워드가 빠져 있다 (이것은 묵시적이다. 왜냐하면 전체 함수는 오직 하나의 표현식이 될 수 있기 때문이다). 또한 함수에 이름이 없다. 그러나 그것은 자신이 할당된 그 변수를 통하여 호출될 수 있다.
2 심지어 lambda 함수를 변수에 할당하지 않고도 그것을 사용할 수 있다. 이 세상에서 정말로 가장 유용한 함수는 아니지만, 람다가 라인-안 함수라는 것을 보여주고 있다.

일반화하면, lambda 함수는 (선택적 인자를 포함하여) 어떠한 개수의 인자라도 취하며 그리고 한개의 표현식으로 된 값을 반환하는 함수이다. lambda 함수는 명령어를 포함할 수 없고 하나 이상의 표현식을 포함할 수 없다. 너무 많은 것을 lambda 함수로 압축해 넣으려고 하지 마라; 좀 더 복잡한 것이 필요하다면, 대신에 정상적인 함수를 정의해서 원하는 만큼 길게 작성하도록 하라.

Note
lambda 함수는 스타일의 문제이다. 이것을 사용하는 것이 절대적으로 필수적인 것은 아니다; 그것들을 사용할 수 있는 어느 곳에나, 대신에 따로 정상적인 함수들을 정의하고 사용할 수 있다. 나는 특별한, 재사용하지 않을 코드들을 캡슐화하고자 하는 곳에 람다를 사용하여 수 많은 한줄 짜리 작은 함수들로 코드가 지저분해지는 것을 막는다.

Example 2.22. lambda functions in apihelper.py

    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)

여기에서 주목할 만한 몇가지가 있다.
첫째, 단순한 형태로 and-or 꼼수를 사용한다. lambda 함수는 불리언 문맥에서 항상 참이기 때문에 아무 문제가 없다 (이것이 lambda 함수가 거짓 값을 반환할 수 없다는 것을 뜻하는 것은 아니다. 그 함수는 항상 참이다; 그래서 함수의 반환 값은 어떤 것이라도 될 수 있다.)

둘째, split 함수를 인자 없이 사용하고 있다. 이미 그것이 1개 또는 2개의 인자와 함께 사용되는 것을 본 바가 있다. 그러나 인자가 없다면 공백을 기준으로 쪼갤 것이다.

Example 2.23. split with no arguments

>>> s = "this   is\na\ttest"  1
>>> print s
this   is
a	test
>>> print s.split()           2
['this', 'is', 'a', 'test']
>>> print " ".join(s.split()) 3
'this is a test'
1 이것은 여러줄 문자열로서, 삼중 따옴표대신에 탈출 문자열로 정의된다. \n는 나르개 복귀 문자이고; \t 는 탭 문자이다.
2 인자 없는 split는 공백을 기준으로 쪼갠다. 그래서 세개의 공백, 나르개 복귀 문자, 그리고 탭 문자는 모두 동일하다.
3 문자열을 쪼개고 나서 다시 그것을 한개의 공백을 구분자로 해서 결합함으로써 공백을 표준화 할 수 있다. 이것이 바로 여러-줄의 문서화 문자열(doc string)을 눌러서 한 줄로 만드는 help함수가 하는 일이다.

그래서 help 함수는 lambda 함수, split 함수, 그리고 and-or 꼼수를 가지고 실제로 무엇을 하고 있는 것인가?

Example 2.24. Assigning a function to a variable

    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)

processFunc는 이제 함수이다. 이 함수가 어떤 것인가는 collapse 변수의 값에 달려 있다. 만약 collapse가 참이라면, processFunc(string)는 공백들을 한개의 공백으로 축약할 것이다; 그렇지 않으면, processFunc(string) 은 그의 인자 값을 그대로 반환할 것이다.

비쥬얼 베이직과 같이 좀 부실한 언어에서 이렇게 하려면 아마도 함수 하나를 작성하려고 할 것이다. 함수는 문자열 하나와 collapse 인자 하나를 취해서 if 서술문을 사용하여 한개의 공백으로 축약해야 할지 말지를 결정하고, 그리고 그 적절한 값을 반환한다. 이것으로는 불충분한데 그 함수는 모든 가능한 경우를 다루어야할 필요성이 있기 때문이다; 즉 함수를 호출할 때마다, 원하는 것을 줄 수 있기 전에 한개의 공백으로 축약해야할지를 결정해야만 할 필요가 있기 때문이다. 파이썬에서 함수로부터 그러한 결정 논리를 얻을 수 있고 원하는 바를 정확하게 (그리고 그것만을) 주도록 맞춤 재단된 람다 함수를 정의할 수 있다. 이것이 더욱 효율적이고, 더욱 우아하며, 그리고 성가신, 내-생각에-그러한-인자들이-반대로-되는-것과 같은 종류의 에러를 덜 내는 경향이 있다.

더 읽어야 할 것

2.8. 모두 합치기

아직까지 분해하지 않은 유일한 코드인 마지막 줄은 그 모든일을 할 코드이다. 그러나 지금까지 그 일은 어렵지 않았다. 왜냐하면 필요한 모든 것이 이미 필요한 방식으로 준비되어 있기 때문이다. 모든 도미노 조각들은 제 자리에 놓였고; 이제 때려서 넘어뜨릴 시간이다.

Example 2.25. The meat of apihelper.py

    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])

여러 줄에 걸친 한개 짜리 명령어임을 주목하라. 그러나 줄 연속 문자를 사용하지 않는다(“\”). 역사선을 사용하지 않고 어떤 표현식들은 여러 줄로 쪼개어 질 수 있다고 말한 것을 기억하는가? 리스트 통합이 그러한 표현식중 하나이다. 왜냐하면 전체 표현식이 각 괄호에 의해 둘러싸여 있기 때문이다.

자 이제, 끝에서 부터 꺼내어 뒤에서부터 연구해 보자. 다음의 표현식을 보면

for method in methodList

이것이 리스트 통합이라는 것을 알 수 있다. 아시다시피, methodListobject에서 우리가 관심을 가지는 모든 메쏘드를 담고 있는 리스트이다. 그래서 method로 그 리스트를 회돌이를 하고 있는 중이다.

Example 2.26. Getting a doc string dynamically

>>> import odbchelper
>>> object = odbchelper                   1
>>> method = 'buildConnectionString'      2
>>> getattr(object, method)               3
<function buildConnectionString at 010D6D74>
>>> print getattr(object, method).__doc__ 4
Build a connection string from a dictionary of parameters.

    Returns string.
1 help 함수에서, object는 우리가 도움을 구하고 있는 객체로서, 인자로서 건네진다.
2 methodList를 회돌이 하고 있는 동안에, method는 현재 메쏘드의 이름이다.
3 getattr 함수를 사용하여, object 모듈에 있는 method 함수에 대한 참조점을 획득한다.
4 이제, 그 메쏘드의 실제 문서화 문자열(doc string)을 출력하는 일은 식은 죽 먹기다.

퍼즐의 다음 조각은 문서화 문자열(doc string) 언저리에 str을 사용하는 것이다. 기억하시겠지만, str은 내장함수로서 데이타를 문자열로 강제변환한다. 문서화 문자열(doc string)은 항상 문자열이다. 그런데 왜 str 함수로 자꾸 귀찮게 하는가? 해답은 모든 함수들이 doc string을 가진 것은 아니기 때문이다. 다시 말해 만약 없다면, 그의 __doc__ 속성은 None이 되기 때문이다.

Example 2.27. Why use str on a doc string?

>>> {}.keys.__doc__         1
>>> {}.keys.__doc__ == None 2
1
>>> str({}.keys.__doc__)    3
'None'
1 사전의 keys 함수는 문서화 문자열(doc string)을 가지지 않는다. 그래서 그의 __doc__ 속성은 None이다. 헷갈리게도, __doc__ 속성을 직접적으로 평가한다면, 파이썬의 IDE 는 아무것도 찍어 내지 않는다. 그것에 대해 생각해 보면 일리가 있기는 하지만, 역시 불친절하다.
2 __doc__ 속성의 값이 실제로 None인가를 직접적으로 비교함으로써 확인할 수 있다.
3 str함수는 null 값을 취해서 그것의 문자열 표현을, 'None'을 반환한다.
Note
SQL에서 null 값을 비교하기 위하여 = NULL 대신에 IS NULL을 사용할 것이다. 파이썬에는 그러한 특별한 구문이 없다; 다른 어떤 비교에서와 마찬가지로 똑같이 == None을 사용한다.

이제 확실하게 문자열을 가지게 되었으므로, 그 문자열을 processFunc에 넘겨 줄 수 있으며, 그것을 함수로 이미 정의 했는데, 그 함수는 한개의 공백으로 축약하거나 혹은 축약하지 않는다. 이제 None 값을 문자열 표현으로 변환하기 위하여 str을 사용하는 것이 왜 중요했던가가 이해되었을 것이다. processFunc는 문자열 인자를 가정하고 있고 그리고 그의 split 메쏘드를 호출하는데, None을 넘겨준다면 충돌을 일으킬 것이다. 왜냐하면 Nonesplit메쏘드를 가지지 않기 때문이다.

뒤로 조금 더 나아가 보면 또 다시 문자열 형식화를 사용하여 method'의 ljust 메쏘드가 반환하는 값과 processFunc의 반환 값을 연결하고 있다. 이것은 이전에 보지 못했던 새로운 문자열 메쏘드이다.

Example 2.28. Introducing the ljust method

>>> s = 'buildConnectionString'
>>> s.ljust(30) 1
'buildConnectionString         '
>>> s.ljust(20) 2
'buildConnectionString'
1 ljust는 문자열에 주어진 길이에 맞추어 공백을 붙인다. 이것이 바로 help 함수가 사용하는 것으로 두개의 출력 열을 만들고 두번째 열에는 모든 doc string을 정렬한다.
2 만약 주어진 길이가 그 문자열의 길이보다 작다면, ljust 단순히 그 문자열을 그대로 반환할 것이다. 문자열을 잘라내지 않는다.

이제 거의 끝났다. ljust 메쏘드로부터 공백처리된 메쏘드의 이름과 그리고 processFunc에 대한 호출로부터 (아마도 한개짜리 공백으로 축약되었을) 문서화 문자열(doc string)이 주어진다면, 그 둘을 연결해서 문자열 하나를 얻는다. methodList를 짝짓기 했으므로, 문자열의 리스트를 결과로 가진다. 문자열 "\n"join 메쏘드를 사용하여, 이 리스트를 한개의 문자열로 결합한다. 그리고 그 리스트의 각 요소를 따로다로 한 줄로 그 결과를 출력한다.

Example 2.29. Printing a list

>>> li = ['a', 'b', 'c']
>>> print "\n".join(li) 1
a
b
c
1 이것 역시 리스트와 작업할 때 유용한 디버깅 꼼수이다. 그리고 파이썬에서 언제나 리스트와 작동한다.

이것이 퍼즐의 마지막 조각이다. 이 코드는 이제 완전히 이해가 되었으리라 믿는다.

Example 2.30. The meat of apihelper.py, revisited

    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])

2.9. 요 약

apihelper.py 프로그램과 그의 출력은 완전히 이해가 되어야 한다.

Example 2.31. apihelper.py

def help(object, spacing=10, collapse=1):
    """Print methods and doc strings.

    Takes module, class, list, dictionary, or string."""
    methodList = [method for method in dir(object) if callable(getattr(object, method))]
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])

if __name__ == "__main__":
    print help.__doc__

Example 2.32. Output of apihelper.py

>>> from apihelper import help
>>> li = []
>>> help(li)
append     L.append(object) -- append object to end
count      L.count(value) -> integer -- return number of occurrences of value
extend     L.extend(list) -- extend list by appending list elements
index      L.index(value) -> integer -- return index of first occurrence of value
insert     L.insert(index, object) -- insert object before index
pop        L.pop([index]) -> item -- remove and return item at index (default last)
remove     L.remove(value) -- remove first occurrence of value
reverse    L.reverse() -- reverse *IN PLACE*
sort       L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1, 0, 1

다음 장으로 다이빙해 들어가기 전에, 이러한 것 모두를 하는데 여러분이 편안한지를 확인하라:



[3] 음, 거의 모든 것. 기본적으로 클래스의 실체는 불리언 문맥에서 참이다. 그러나 특별한 메쏘드를 클래스에 정의하여 한 실체가 거짓으로 평가되도록 할 수 있다. 클래스와 특별한 메쏘드에 관한 모든 것을 제 3 장에서 배우게 될 것이다.


 제 1 장 파이썬을 이해하기 목 차 제 3 장 객체지향 작업틀 >>