☜ 제 03 장 고유 데이터 유형 | """ Dive Into Python """ 다이빙 파이썬 |
제 05 장 객체와 객체지향 ☞ |
이 장에서는 파이썬의 강력한 힘 중 하나인 내부검사(introspection)를 다루어 보겠습니다. 아시다시피, 파이썬에서 모든 것은 객체입니다. 내부검사는 메모리에 있는 다른 모듈과 함수를 객체로 보고서, 그에 관한 정보를 얻어내고 조작하는 것입니다. 그와 더불어, 이름 없는 함수를 정의하고 순서없이 인자를 가지는 함수를 호출해보며 심지어 미리 이름조차도 알지 못하는 함수들을 참조해 보겠습니다.
다음은 완전하게 작동하는 파이썬 프로그램입니다. 그냥 한 번 보는 것 만으로도 상당히 많은 것을 알 수 있으리라 생각합니다. 번호 붙은 줄은 제 2장 첫 파이썬 프로그램에서 다룬 개념들을 보여줍니다. 나머지 코드가 겁나게 보이더라도 걱정하지 마세요; 이 장을 지나면 그에 관하여 모두 알게 될 겁니다.
아직 그렇게 하지 못했다면 이 장에서 사용된 이런 저런 예제들을 내려 받을 수 있습니다.
def info(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 info.__doc__
① | 이 모듈에는 info라는 함수가 하나 있습니다. 그의 함수 선언에 따르면 매개변수를 세 개 받습니다: object와 spacing 그리고 collapse가 그것입니다. 마지막 두개는 실제로는 선택적인 매개변수로서, 곧 가르쳐 드리겠습니다. |
② | info 함수는 여러-줄의 문서화 문자열(doc string)을 가지는데 이는 함수의 목적을 간결하게 기술합니다. 반환 값이 언급되지 않은 것에 주목하세요; 이 함수는 값보다는 오로지 그 효과를 위해 사용될 것입니다. |
③ | 함수안의 코드는 들여쓰기 되어 있습니다. |
④ | if __name__ 트릭 덕분에 이 프로그램은 홀로 실행될 때 무언가 유용한 일을 할 수 있습니다. 다른 프로그램을 위한 모듈로서의 그의 사용법에 방해를 받지 않고서 말입니다. 이 경우, 프로그램은 단순히 info 함수의 문서화 문자열(doc string)을 인쇄할 뿐입니다 |
⑤ | if 서술문은 비교에 ==을 사용하며, 괄호는 필요하지 않습니다. |
info 함수는 한 편으로 파이썬 IDE에서 작동하며, 또 프로그래머가 사용하도록 설계되어 있습니다. 함수나 메쏘드를 가지는 객체라면 (함수가 있는 모듈이든, 메쏘드가 있는 리스트이든) 뭐든지 취해서 그 함수와 그의 문서화 문자열(doc string)을 인쇄합니다.
>>> from apihelper import info >>> li = [] >>> info(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 인자에 더 큰 값을 지정하여 출력결과를 더 쉽게 읽을 수 있도록 만들 수 있습니다.
>>> import odbchelper >>> info(odbchelper) buildConnectionString Build a connection string from a dictionary Returns string. >>> info(odbchelper, 30) buildConnectionString Build a connection string from a dictionary Returns string. >>> info(odbchelper, 30, 0) buildConnectionString Build a connection string from a dictionary Returns string.
파이썬에서는 함수 인자에 기본 값을 가질 수 있습니다; 인자 없이 함수가 호출되면 그 인자는 기본 값을 가집니다. 게다가, 이름붙은 인자를 사용하면 인자는 순서없이 지정이 가능합니다. SQL Server Transact/SQL이라면 저장 프로시저(Stored procedures)가 이렇게 할 수 있습니다. 그래서 SQL Server 스크립팅 구루(guru)라면 이 부분을 건너 뛰어도 좋습니다.
다음은 info의 예제입니다. 선택적인 인자가 두 개 있습니다:
def info(object, spacing=10, collapse=1):
spacing과 collapse는 선택적인데, 두 인자는 기본적으로 정의된 값이 있기 때문입니다. object는 필수인데, 기본값이 없기 때문입니다. info가 오직 한 개의 인자만으로 호출되면 spacing은 기본값이 10이고 collapse는 기본값이 1입니다. info가 두 개의 인자로 호출되더라도, 여전히 collapse는 기본값이 1입니다.
collapse에는 값을 지정하고 싶지만 spacing에는 기본값을 받고 싶다고 합시다. 대부분의 언어에서는 운이 별로 없을텐데, 함수를 세 개의 인자로 호출할 필요가 있기 때문입니다. 그러나 파이썬에서 인자는 어떤 순서이든 상관없이 이름으로 지정이 가능합니다.
info(odbchelper) ① info(odbchelper, 12) ② info(odbchelper, collapse=0) ③ info(spacing=15, object=odbchelper) ④
혼란스러워 보이지만 인자는 단순히 사전일 뿐이라는 사실을 깨닫게 되면 이 혼란스러움은 사라집니다. 인자 이름 없이 함수를 호출하는 “보통의 방식”은 실제로는 함수 선언에 지정된 순서대로 파이썬이 값을 인자 이름에 일치시켜주는 것에 불과합니다. 대부분 “정상적인 방식”으로 함수를 호출하겠지만, 언제든지 필요하면 더 유연하게 호출할 수 있습니다.
☞ | |
함수를 호출하려면 필수 인자마다 (어떻게든) 값을 지정하기만 하면 됩니다; 어떤 방식으로 어떤 순서로 호출할지는 여러분에게 달려 있습니다. |
파이썬은 아주 쓸모있는 내장 함수들을 작은 모듬으로 갖추고 있습니다. 기타 모든 함수들은 모듈 안으로 조각조각 들어가 있습니다. 이런 디자인은 실제로 지혜로운 결정이었고, 이 덕분에 코어가 다른 스크립팅 언어들(예, 비주얼 베이직(Visual Basic))과는 다르게 뚱뚱해지지 않을 수 있었습니다.
type 함수는 객체의 데이터유형을 돌려줍니다. 가능한 유형들은 types 모듈에 나열되어 있습니다. 이는 여러 유형의 데이터를 다룰 수 있기 때문에 도움자(helper) 함수에 쓸모가 있습니다.
str은 데이터를 문자열로 강제로 변환합니다. 어떠한 데이터 유형도 문자열로 강제로 변환될 수 있습니다.
>>> str(1) ① '1' >>> horsemen = ['war', 'pestilence', 'famine'] >>> horsemen ['war', 'pestilence', 'famine'] >>> horsemen.append('Powerbuilder') >>> str(horsemen) ② "['war', 'pestilence', 'famine', 'Powerbuilder']" >>> str(odbchelper) ③ "<module 'odbchelper' from 'c:\\docbook\\dip\\py\\odbchelper.py'>" >>> str(None) ④ 'None'
① | 정수같이 간단한 데이터유형에 대해서, str이 작동할 것이라 예상하실텐데, 대부분의 언어가 정수를 문자열로 바꾸는 함수가 있기 때문입니다. |
② | 그렇지만, str은 어떤 유형의 어떤 객체에도 작동합니다. 여기에서는 여러분이 일일이 손수 구축한 리스트에 작동합니다. |
③ | str은 또한 모듈에도 작동합니다. 모듈의 문자열 표현은 디스크에서의 모듈 경로를 포함하므로, 여러분의 출력은 다를 것입니다. |
④ | str에서 작지만 중요한 행위는 파이썬 널(null) 값인, None에도 작동한다는 것입니다. 문자열 'None'을 돌려줍니다. 잠시 후에 보여드리겠지만, 이를 info 함수에 이용할 수 있습니다. |
info 함수의 심장은 강력한 dir 함수입니다. dir은 객체의 속성과 메쏘드의 리스트를 돌려줍니다: 모듈, 함수, 문자열, 리스트, 사전... 뭐 아무거라도 됩니다.
>>> li = [] >>> dir(li) ① ['append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] >>> d = {} >>> dir(d) ② ['clear', 'copy', 'get', 'has_key', 'items', 'keys', 'setdefault', 'update', 'values'] >>> import odbchelper >>> dir(odbchelper) ③ ['__builtins__', '__doc__', '__file__', '__name__', 'buildConnectionString']
① | li는 리스트입니다. 그래서 dir(li)는 리스트의 모든 메쏘드를 담은 리스트를 돌려줍니다. 주의할 것은 반환된 리스트에는 메쏘드의 이름이 문자열로 담겨있지, 메쏘드 자체가 들어가 있는 것이 아니라는 것입니다. |
② | d는 사전입니다. 그래서 dir(d)는 사전 메쏘드의 이름들을 담은 리스트를 돌려줍니다. 이 메쏘드들 중 적어도 keys는 틀림없이 눈에 익을 것입니다. |
③ | 이곳이 정말 흥미로와지는 곳입니다. odbchelper는 모듈입니다. 그래서 dir(odbchelper)는 그 모듈에 정의된 온갖 것들을 리스트에 모두 담아 돌려줍니다. 내장 속성을 비롯하여, __name__과 __doc__ 그리고 여러분이 정의한 속성과 메쏘드를 모두 담고 있습니다. 이 경우, odbchelper는 제 2 장에서 설명한 단 한 개의 사용자-정의 메쏘드 buildConnectionString 함수가 있습니다. |
마지막으로, callable 함수는 객체를 취해서 그 객체가 호출가능하면 True를 돌려주고, 그렇지 않으면 False를 돌려줍니다. 호출가능 객체에는 함수와 클래스 메쏘드 그리고 심지어 클래스도 포함됩니다. (다음 장에서 클래스에 관하여 더 자세히 다룹니다.)
>>> import string >>> string.punctuation ① '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' >>> string.join ② <function join at 00C55A7C> >>> callable(string.punctuation) ③ False >>> callable(string.join) ④ True >>> print string.join.__doc__ ⑤ 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)
① | (많은 사람들이 여전히 join 함수를 사용하고 있음에도 불구하고) string 모듈에 있는 함수들은 추천되지 않습니다. 그러나 이 모듈에는 쓸모있는 상수가 많이 담겨 있습니다. 예를 들어 이곳의 string.punctuation 상수에는 표준 구두점 문자들이 모두 들어 있습니다. |
② | string.join은 문자열의 리스트를 연결하는 함수입니다. |
③ | string.punctuation은 호출이 불가능합니다; 문자열이기 때문입니다. (문자열은 호출가능한 메쏘드들을 가지지만, 문자열 그 자체는 호출이 불가능합니다.) |
④ | string.join은 호출이 가능합니다; 두 개의 인자를 취하는 함수입니다. |
⑤ | 호출가능한 객체라면 모두 문서화 문자열(doc string)을 가질 수 있습니다. 한 객체의 속성에다 callable 함수를 사용하면 어느 속성(메쏘드, 함수, 클래스)에 신경을 쓸지 그리고 어느 속성(상수, 등등)을 무시하고 싶은지 결정할 수 있습니다. 미리 그 객체에 관하여 알 필요가 없습니다. |
type, str, dir, 그리고 나머지 파이썬의 내장 함수들은 모두 이른바 __builtin__이라고 부르는 특별한 모듈로 무리지어져 들어가 있습니다 (앞과 뒤에 밑줄문자가 두개입니다.). 파이썬이 시작시에 자동으로 from __builtin__ import *를 실행하는 것이라고 편하게 생각해도 좋습니다. 이렇게 하면 모든 “built-in” 함수들이 이름공간으로 반입되고 그래서 직접 그 함수들을 사용할 수 있습니다.
이런 식으로 생각하면 __builtin__ 모듈에 관한 정보를 얻어서 모든 내장 함수와 속성을 한 그룹으로 접근할 수 있는 이점이 있습니다. 그리고 추측해 보세요. 파이썬에 info라고 부르는 함수가 있습니다. 지금 그 리스트를 손수 둘러보세요. 더 중요한 함수들을 나중에 깊이 다루어 보겠습니다. (AttributeError 같은 내장 에러 클래스는 이미 눈에 익으실 겁니다.)
>>> from apihelper import info >>> import __builtin__ >>> info(__builtin__, 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...]
☞ | |
파이썬에는 훌륭한 참조 매뉴얼이 따라옵니다. 파이썬이 제공하는 모든 모듈들을 배우려면 이를 철저히 정독하여야 하겠습니다. 그러나 파이썬은 대체로 자체로-문서화되어 있습니다. 파이썬과는 다르게 대부분의 다른 언어에서 이런 모듈의 사용법을 기억해 내려면 맨 페이지나 매뉴얼을 다시 몸소 참조해야 합니다. |
이미 파이썬 함수가 객체라는 사실을 알고 있습니다. 그러나 getattr를 사용하면 실행-시간까지 그의 이름을 미리 알지 못하더라도 함수의 참조점을 얻을 수 있다는 사실은 모르실 것입니다.
>>> li = ["Larry", "Curly"] >>> li.pop ① <built-in method pop of list object at 010DF884> >>> getattr(li, "pop") ② <built-in method pop of list object at 010DF884> >>> getattr(li, "append")("Moe") ③ >>> li ["Larry", "Curly", "Moe"] >>> getattr({}, "clear") ④ <built-in method clear of dictionary object at 00F113D4> >>> getattr((), "pop") ⑤ Traceback (innermost last): File "<interactive input>", line 1, in ? AttributeError: 'tuple' object has no attribute 'pop'
① | 여기에서는 리스트의 pop 메쏘드에 대한 참조점을 얻습니다. 주목할 것은 이것이 pop 메쏘드를 호출하고 있는 것이 아니라는 것입니다; 그것은 li.pop()이 될 겁니다. 이는 메쏘드 그 자체입니다. |
② | 이 역시 pop 메쏘드를 가리키는 참조점을 돌려줍니다. 그러나 이번에는 메쏘드 이름이 문자열로 지정되어 getattr 함수에 인자로 건네집니다. getattr은 객체의 속성을 돌려주는 아주 유용한 내장 함수입니다. 이 경우 객체는 리스트이며 그 속성은 pop 메쏘드입니다. |
③ | 이것이 얼마나 유용한지 감명을 받지 못했다면 이렇게 해 보세요: getattr 의 반환값은 메쏘드이며, 그러면 이 메쏘드를 마치 직접 li.append("Moe")라고 말하는 것과 똑 같이 호출할 수 있습니다. 그러나 함수를 직접 호출하지 않았습니다; 대신에 함수 이름을 문자열로 지정했습니다. |
④ | getattr은 사전에도 작동합니다. |
⑤ | 이론적으로, getattr은 터플에도 작동합니다. 단, 터플은 메쏘드가 없습니다. 그래서 거기에 어떤 속성 이름을 주더라도 getattr은 예외를 일으킵니다. |
getattr는 내장 데이터 유형만을 위한 것은 아닙니다. 모듈에도 작동합니다.
>>> import odbchelper >>> odbchelper.buildConnectionString ① <function buildConnectionString at 00D18DD4> >>> getattr(odbchelper, "buildConnectionString") ② <function buildConnectionString at 00D18DD4> >>> object = odbchelper >>> method = "buildConnectionString" >>> getattr(object, method) ③ <function buildConnectionString at 00D18DD4> >>> type(getattr(object, method)) ④ <type 'function'> >>> import types >>> type(getattr(object, method)) == types.FunctionType True >>> callable(getattr(object, method)) ⑤ True
① | 이는 odbchelper 모듈의 buildConnectionString 함수를 가리키는 참조점을 돌려줍니다. 제 2 장, 여러분의 첫 파이썬 프로그램에서 배운 것입니다. (십육진 주소는 본인의 머신에 한정된 것이고; 여러분의 출력은 다릅니다.) |
② | getattr을 사용하면 같은 함수를 가리키는 같은 참조점을 얻을 수 있습니다. 일반적으로, getattr(object, "attribute")는 object.attribute와 동등합니다. 객체(object)가 모듈이라면 attribute는 모듈에 정의된 것이면 무엇이든 됩니다: 함수나 클래스 또는 전역 변수. |
③ | 그리고 이것이 바로 실제로 info 함수에서 사용하는 것입니다. object는 함수에 인자로 건네지고; method는 문자열로서 메쏘드나 함수의 이름입니다. |
④ | 이 경우, method는 함수의 이름입니다. 그의 type을 얻어보면 확실하게 알 수 있습니다. |
⑤ | method는 함수이므로, 호출이 가능합니다. |
getattr의 일반적인 사용 패턴은 분배자입니다. 예를 들어, 다양한 형태로 데이터를 출력하는 프로그램이 있다면 단 하나의 분배 함수를 사용할 수 있습니다. 분배함수에서 각 출력에 대하여 따로따로 함수를 정의하여 그에 맞게 올바른 함수를 호출할 수 있습니다.
예를 들어, HTML과 XML 그리고 평범한 텍스트 포맷으로 사이트 통계를 인쇄하는 프로그램을 생각해 봅시다. 출력 포맷의 선택은 명령 줄에서 지정하거나 또는 환경설정 파일에 정의할 수 있습니다. statsout 모듈에는 세 가지 함수가 정의됩니다. output_html과 output_xml 그리고 output_text 함수가 그것입니다. 주 프로그램에는 다음과 같이 출력 함수가 하나만 정의 됩니다:
import statsout def output(data, format="text"): ① output_function = getattr(statsout, "output_%s" % format) ② return output_function(data) ③
① | output 함수는 필수 인자 data와 선택적 인자 format을 받습니다. 형식(format)이 지정되지 않으면 기본 값은 text가 되고, 결국 평범한 텍스트 출력 함수를 호출하게 됩니다. |
② | format 인자를 "output_"과 결합하여 함수 이름을 만든 다음, 그 함수를 statsout 모듈로부터 얻습니다. 이렇게 하면 나중에 기타 다른 포맷을 지원하도록 쉽게 프로그램을 확장할 수 있습니다. 이 분배자 함수를 바꿀 필요가 없습니다. 그냥 또다른 함수를 statsout에 붙이면 됩니다. 예를 들어 output_pdf과 같은 이름이라면 "pdf"를 format 인자로 하여 output 함수에 건네면 됩니다. |
③ | 이제 다른 함수와 마찬가지로 그냥 출력 함수를 호출하면 됩니다. output_function 변수는 statsout 모듈에서 적절한 함수를 가리킵니다. |
앞의 예제에 버그가 있습니다. 문자열과 함수를 아주 느슨하게 결합하며, 에러를 점검하지 않기 때문입니다. 혹시라도 사용자가 statsout 모듈에 정의된 함수에 상응하지 않는 형식을 건네면 어떻게 될까요? 음, getattr은 None을 돌려주고, 이 값은 output_function에 할당됩니다. 이는 유효한 함수가 아니므로, 다음 줄에서 그 함수를 호출하려고 시도하면 충돌하여 예외가 일어납니다. 안 좋은 일입니다.
다행스럽게도, getattr은 선택적으로 세 번째 인자, 기본 값을 받습니다.
import statsout def output(data, format="text"): output_function = getattr(statsout, "output_%s" % format, statsout.output_text) return output_function(data) ①
① | 이 함수는 확실하게 작동합니다. 왜냐하면 세 번째 매개변수를 getattr를 호출할때 추가했기 때문입니다. 세 번째 매개변수는 두 번째 인자에 지정된 속성이나 메쏘드가 발견되지 않을 경우 돌려주는 기본값입니다. |
보시다시피, getattr은 상당히 강력합니다. 내부 검사의 심장이며, 나중에 훨씬 더 강력한 예들을 보여 드리겠습니다.
아시다시피, 파이썬은 지능형 리스트(list comprehensions)를 통하여 (섹션 3.6, “리스트 짝짓기”) 리스트를 리스트에 짝짓는 강력한 능력이 있습니다. 이 능력을 여과 메커니즘과 조합하면 리스트의 어떤 요소들은 짝짓기되고 또 어떤 요소들은 완전히 무시됩니다.
다음은 리스트 여과 구문입니다:
[mapping-expression for element in source-list if filter-expression]
이는 여러분이 익히 알고 좋아하는 지능형 리스트(list comprehensions)를 확장한 것입니다. 첫 2/3는 같지만; if로 시작하는 뒷부분은 여과 표현식입니다. 여과 표현식은 참 또는 거짓으로 평가되기만 하면 어떤 표현식이든 상관없습니다 (파이썬에서는 거의 모든 것이 여과 표현식이 될 수 있습니다). 어떤 원소든지 여과 표현식에서 참으로 평가되면 짝짓기에 포함됩니다. 다른 원소들은 모두 무시되어서 짝짓기 표현식을 통과하지 못하며 출력 리스트에 포함되지 않습니다.
>>> li = ["a", "mpilgrim", "foo", "b", "c", "b", "d", "d"] >>> [elem for elem in li if len(elem) > 1] ① ['mpilgrim', 'foo'] >>> [elem for elem in li if elem != "b"] ② ['a', 'mpilgrim', 'foo', 'c', 'd', 'd'] >>> [elem for elem in li if li.count(elem) == 1] ③ ['a', 'mpilgrim', 'foo', 'c']
① | 여기에서 짝짓기 표현식은 단순합니다 (그냥 각 원소의 값을 돌려줄 뿐입니다). 그래서 여과 표현식에 집중하겠습니다. 파이썬은 리스트를 회돌이 하면서, 각 원소를 여과 표현식에 집어 넣습니다. 여과 표현식이 참이면 그 원소는 짝짓기 되고 짝짓기 표현식의 결과는 반환된 리스트에 포함됩니다. 여기에서는 길이가-하나인 문자열을 모두 여과하고 있습니다. 그래서 긴 문자열을 모두 담은 리스트가 남습니다. |
② | 여기에서 특정한 값 b를 여과합니다. b가 나타날 때마다 여과 표현식이 거짓이 되기 때문에 b는 모두 여과됩니다. |
③ | count는 리스트에서 값이 나타난 횟수를 돌려주는 리스트 메쏘드입니다. 이 여과기가 리스트에서 중복값을 제거해 줄거라고 생각하실지 모르겠습니다. 원래 리스트에서 각 값을 한 개씩만 담아서 리스트로 돌려줄 것이라고 말입니다. 그러나, 그렇지 않습니다. 원래 리스트에서 두 번 나타난 값은 (이 경우, b와 d) 완전히 배제되기 때문입니다. 리스트에서 중복 값을 제거하는 방법이 있지만, 여과는 해결책이 아닙니다. |
apihelper.py에서 다음 줄을 다시 보세요:
methodList = [method for method in dir(object) if callable(getattr(object, method))]
복잡해 보이고, 사실 복잡하지만, 기본 구조는 같습니다. 전체 여과 표현식은 리스트를 돌려줍니다. 이 리스트는 methodList 변수에 할당됩니다. 표현식에서 앞 부분은 리스트 짝짓기 부분입니다. 짝짓기 표현식은 신분 표현식으로서, 각 원소의 값을 돌려줍니다. dir(object)는 객체(object)의 속성과 메쏘드를 리스트에 담아 돌려줍니다 -- 이 리스트가 바로 짝짓기하고 있는 바로 그 리스트입니다. 그래서 새로운 부분은 if 뒤에 있는 여과 표현식 뿐입니다.
여과 표현식이 좀 겁나게 보이지만, 그렇지 않습니다. 이미 callable과 getattr 그리고 in에 관하여 알고 있습니다. 앞 섹션에서 보셨듯이, 표현식 getattr(object, method)은 object가 모듈이고 method가 그 모듈에 있는 함수 이름이면 함수 객체를 돌려줍니다.
그래서 이 표현식은 (이름이 object인) 객체를 받습니다. 그 다음 그 객체의 속성과 메쏘드 함수 기타 등등의 이름을 리스트로 얻습니다. 다음 그 리스트를 여과하여 관심의 대상이 아닌 것들을 모두 걸러냅니다. 각 속성/메쏘드/함수의 이름을 취해 실제 객체에 대한 참조점은 getattr 함수를 통하여 얻어서 걸러내고 있습니다. 다음 그 객체가 호출가능한지 알아 봅니다. 호출가능 하다면 메쏘드나 함수가 되는데, (리스트의 pop 메쏘드 같이) 내장 메쏘드와 (odbchelper 모듈의 buildConnectionString 함수 같이) 사용자-정의 함수가 될 수 있습니다. 기타 다른 속성은 신경쓰지 않습니다. 예를 들어, 어느 모듈에나 내장되어 있는 __name__ 같은 속성은 신경쓰지 않습니다.
파이썬에서 and와 or는 예상대로 불리언 논리를 수행합니다. 그러나 불리언 값을 돌려주지 않습니다; 대신에, 비교중인 실제 값중의 하나를 돌려줍니다.
>>> 'a' and 'b' ① 'b' >>> '' and 'b' ② '' >>> 'a' and 'b' and 'c' ③ 'c'
① | and를 사용할 때, 값들은 불리언 문맥에서 왼쪽에서 오른쪽으로 가면서 평가됩니다. 0, '', [], (), {}, 그리고 None은 불리언 문맥에서 거짓입니다; 다른 것들은 모두 참입니다. 음, 거의 모든 것입니다. 기본값으로, 클래스의 실체는 불리언 문맥에서 참입니다. 그러나 특별한 메쏘드를 클래스에 설정하면 그 실체가 거짓으로 평가되도록 만들 수 있습니다. 클래스와 특수 메쏘드에 관한 모든 것은 제 5 장에서 배우겠습니다. 불리언 문맥에서 모든 값이 참이면 and는 가장 마지막 값을 돌려줍니다. 이 경우, and는 'a'를 평가하는데, 그 값은 참이며, 다음 'b'를 평가하는데, 그 값이 참입니다. 그래서 'b'를 돌려줍니다. |
② | 불리언 문맥에서 무엇이든 거짓이면 and는 그 첫 거짓 값을 돌려줍니다. 이 경우, '' 가 첫 거짓 값입니다. |
③ | 모든 값이 참입니다. 그래서 and는 마지막 값인 'c'를 돌려줍니다. |
>>> 'a' or 'b' ① 'a' >>> '' or 'b' ② 'b' >>> '' or [] or {} ③ {} >>> def sidefx(): ... print "in sidefx()" ... return 1 >>> 'a' or sidefx() ④ 'a'
① | or을 사용할 때, 값은 불리언 문맥에서 왼쪽에서 오른쪽으로 가면서 평가됩니다. and와 똑 같이 말입니다. 어떤 값이 참이면 or은 즉시 그 값을 돌려줍니다. 이 경우, 'a'가 첫 참 값입니다. |
② | or은 ''를 평가하고, 그 값은 거짓입니다. 다음 'b'를 평가하는데, 그 값은 참입니다. 그래서 'b'를 돌려줍니다. |
③ | 모든 값이 거짓이면 or는 마지막 값을 돌려줍니다. or은 ''을 평가하는데, 이 값은 거짓입니다. 다음 []가 평가되며, 그 값은 거짓입니다. 다음 {}가 평가되는데, 그 값은 거짓입니다. 그래서 {}을 돌려줍니다. |
④ | 주목하세요. or은 불리언 문맥에서 참인 값을 발견할 때까지만 값들을 평가합니다. 그리고 나머지는 무시합니다. 어떤 값들은 부작용을 가질 수 있기 때문에 이 구별은 중요합니다. 여기에서 sidefx 함수는 절대로 호출되지 않습니다. 왜냐하면 or은 'a'를 평가하고, 그 값은 참이므로, 즉시 'a'를 돌려주기 때문입니다. |
여러분이 C 해커라면 분명히 bool ? a : b 표현식을 잘 아실 겁니다. 이 표현식은 bool이 참이면 a로 평가되고, 그렇지 않으면 b로 평가됩니다. 파이썬에서 and와 or의 작동 방식 때문에, 같은 일을 할 수 있습니다.
>>> a = "first" >>> b = "second" >>> 1 and a or b ① 'first' >>> 0 and a or b ② 'second'
그렇지만, 이 파이썬 표현식은 단순히 불리언 논리에 불과하고, 언어의 특별한 구조가 아니기 때문에, 파이썬의 and-or 트릭과 C의 bool ? a : b 구문 사이에 아주 중대한 차이가 하나 있습니다. a의 값이 거짓이면 표현식은 예상대로 작동하지 않습니다 (본인은 과연 이 함정에 빠졌을까요? 몇 번이나 빠졌을까요?)
>>> a = "" >>> b = "second" >>> 1 and a or b ① 'second'
① | a는 빈 문자열이므로, 파이썬은 불리언 문맥에서 거짓으로 간주하고, 1 and ''는 ''로 평가되며, 다음 '' or 'second'는 'second'로 평가됩니다. 이런! 원한게 아니군요. |
불리언 문맥에서 a가 거짓으로 평가될 경우, and-or 트릭인 "bool and a or b"는 C 표현식인 "bool ? a : b"와 똑같이 작동하지 않습니다.
그래서 and-or 트릭 뒤의 진짜 트릭은 a의 값이 절대로 거짓이 되지 않도록 확인하는 것입니다. 이렇게 하는 일반적인 방법은 a를 [a]에 넣고 b를 [b]에 넣은 다음, 반환된 리스트의 첫 원소를 받는 것입니다. 그 원소는 a나 b가 될 것입니다.
>>> a = "" >>> b = "second" >>> (1 and [a] or [b])[0] ① ''
① | [a]는 빈 리스트가 아니므로, 절대 거짓이 아닙니다. a가 0이나 '' 또는 기타 다른 거짓 값이라도 원소가 한개 있으므로 리스트 [a]는 참입니다. |
지금까지는 이 트릭이 그 가치에 비해 너무 복잡해 보일 수도 있습니다. 어쨋든 같은 일을 if 서술문으로 할 수 있습니다. 그런데 왜 이렇게 복잡하게 하는가? 음, 많은 경우, 두 개의 상수 값 중에 하나를 선택합니다. 그래서 더 간단한 구문을 사용할 수 있으면 신경쓰지 않아도 됩니다. 왜냐하면 a 값이 언제나 참이라는 사실을 알기 때문입니다. 더 복잡한 안전한 형태를 사용할 필요가 있다면 그래야 할 충분한 이유가 있습니다. 예를 들어, 어떤 경우는 파이썬에서 lambda 함수 같은 경우 if 서술문이 허용되지 않습니다.
파이썬은 한 줄짜리 미니-함수를 바로바로 정의할 수 있는 흥미로운 구문을 지원합니다. Lisp에서 빌려온 이른바 이 lambda 함수는 함수가 요구되는 곳이면 어디든지 사용할 수 있습니다.
>>> def f(x): ... return x*2 ... >>> f(3) 6 >>> g = lambda x: x*2 ① >>> g(3) 6 >>> (lambda x: x*2)(3) ② 6
일반적으로 말해 람다(lambda) 함수는 (선택적 인자를 포함하여) 인자를 얼마든지 취해 단일한 표현식의 값을 돌려주는 함수입니다. 람다(lambda) 함수는 명령어를 포함할 수 없습니다. 그리고 하나 이상의 표현식을 담을 수 없습니다. 람다(lambda) 함수 안에 너무 많은 것을 구겨 넣으려 하지 마세요; 좀 더 복잡한 식이 필요하면 대신에 보통의 함수를 정의하고 원하는 만큼 만드세요.
☞ | |
람다(lambda) 함수는 스타일의 문제입니다. 반드시 람다 함수를 사용할 필요는 없습니다; 어디든지 람다 함수를 사용할 수 있는 곳이라면 따로 정상 함수를 정의해서 대신에 그 함수를 사용해도 좋습니다. 본인은 재사용이 필요없는 특정한 코드를 캡슐화해 넣고 싶은 경우에 람다 함수를 사용합니다. 수 많은 한 줄짜리 함수로 코드를 어지럽히고 싶지 않기 때문입니다. |
다음은 apihelper.py에 있는 람다(lambda) 함수입니다:
processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
여기에서 단순한 형태의 and-or 트릭을 사용하고 있음에 주목하세요. 지금은 문제가 없습니다. 왜냐하면 람다(lambda) 함수는 불리언 문맥에서 언제나 참이기 때문입니다. (그렇다고 해서 람다(lambda) 함수가 거짓 값을 돌려주지 못한다는 뜻은 아닙니다. 이 함수는 언제나 참입니다; 무엇이든 그의 반환 값이 될 수 있습니다.)
또 주목할 것은 아무 인자 없이 split 함수를 사용하고 있다는 것입니다. 이미 문자열 가르기에서 사용된 것을 보신 바 있습니다. 그러나 인자가 없으면 공백문자를 기준으로 가릅니다.
>>> s = "this is\na\ttest" ① >>> print s this is a test >>> print s.split() ② ['this', 'is', 'a', 'test'] >>> print " ".join(s.split()) ③ 'this is a test'
그래서 info 함수는 이 lambda 함수와 split 함수 그리고 and-or 트릭으로 실제로 무슨 일을 하는가?
processFunc는 이제 함수이지만, 그것이 어느 함수인가는 collapse 변수의 값에 따라 다릅니다. collapse가 참이면 processFunc(string)은 공백으로 축약됩니다; 그렇지 않으면 processFunc(string)는 그의 인자를 그대로 돌려줍니다.
비주얼 베이직(Visual Basic) 같이 유약한 언어에서 이렇게 하려면 아마도 문자열과 collapse 인자를 받고 if 서술문을 사용하여 공백문자로 축약할지 말지를 결정한 다음, 적절한 값을 돌려주는 함수를 만들어야 합니다. 이는 가능한 모든 사례마다 일일이 함수가 처리해야 하기 때문에 비효율적입니다. 호출할 때마다, 여러분이 원하는 것을 돌려주기 전에 공백을 축약해야 할지 말지를 결정할 필요가 있습니다. 파이썬에서는 함수로부터 결정 로직을 분리할 수 있으며 정확하게 (오직) 원하는 것을 돌려주도록 맞춤-재단된 람다(lambda) 함수를 정의할 수 있습니다. 이 방법이 더 효율적이며 더 우아하고 그리고 성가신 에러가 더 적습니다.
코드의 마지막 줄에서 아직 유일하게 분석하지 못한 한가지는 모든 일을 해 주는 코드입니다. 그러나 이제 그 작업은 쉽습니다. 왜냐하면 필요한 모든 것이 필요한 그대로 준비되어 있기 때문입니다. 모든 도미노 조각이 제 자리에 놓였고; 이제 쓰러뜨릴 시간입니다.
다음은 apihelper.py의 핵심입니다:
print "\n".join(["%s %s" % (method.ljust(spacing), processFunc(str(getattr(object, method).__doc__))) for method in methodList])
주목할 것은 이는 한 개짜리 명령어로서 여러 줄에 걸쳐 있지만 줄 연속 문자(\)를 사용하지 않는다는 것입니다. 기억하십니까? 어떤 표현식은 역사선을 사용하지 않고서도 여러 줄로 갈라질 수 있습니다. 지능형 리스트가 그런 표현식 중의 하나입니다. 왜냐하면 전체 표현식이 각 괄호 안에 담기어 있기 때문입니다.
이제, 맨뒤에서 지능형 리스트를 뜯어내어 거꾸로 연구해 보겠습니다.
for method in methodList
위는 지능형 리스트(list comprehension)를 보여줍니다. 아시다시피, methodList는 리스트로서 object에서 관심을 두고 있는 모든 메쏘드를 담고 있습니다. 그래서 method로 그 리스트를 회돌이하고 있습니다.
>>> import odbchelper >>> object = odbchelper ① >>> method = 'buildConnectionString' ② >>> getattr(object, method) ③ <function buildConnectionString at 010D6D74> >>> print getattr(object, method).__doc__ ④ Build a connection string from a dictionary of parameters. Returns string.
다음 퍼즐 조각은 str을 문서화 문자열(doc string) 둘레에 사용하는 것입니다. 기억하시겠지만, str은 데이터를 문자열로 바꾸는 내장 함수입니다. 그러나 문서화 문자열(doc string)은 언제나 문자열입니다. 그런데 왜 str 함수에 신경을 쓸까요? 그 해답은 어느 함수나 다 문서화 문자열(doc string)을 가지고 있는 것은 아니기 때문입니다. 만약 그렇다면 __doc__ 속성이 None이 되기 때문입니다.
>>> >>> def foo(): print 2 >>> >>> foo() 2 >>> >>> foo.__doc__ ① >>> foo.__doc__ == None ② True >>> str(foo.__doc__) ③ 'None'
☞ | |
SQL에서는 널 값을 비교하려면 = NULL 대신에 IS NULL을 사용해야 합니다. 파이썬에서는 == None 또는 is None을 사용할 수 있지만, is None이 더 빠릅니다. |
이제 확실하게 문자열을 확보하였습니다. 그 문자열을 processFunc에 건넬 수 있습니다. 이 함수는 이미 정의해 둔 함수로서 공백문자로 축약하거나 또는 축약하지 않습니다. 이제 str을 사용하여 None 값을 문자열 표현으로 변환하는 것이 왜 중요한지 알 수 있습니다. processFunc는 인자가 문자열이라고 간주하고 자신의 split 메쏘드를 호출합니다. 이럴 경우 None을 건네면 충돌이 일어납니다. 왜냐하면 None은 split 메쏘드가 없기 때문입니다.
뒤로 좀 더 물러서서 보면 문자열 형식화를 또 사용하여 processFunc의 반환값과 method ljust 메쏘드의 반환값을 결합하고 있음을 볼 수 있습니다. ljust 메쏘드는 아직 보지 못한 새로운 문자열 메쏘드입니다.
>>> s = 'buildConnectionString' >>> s.ljust(30) ① 'buildConnectionString ' >>> s.ljust(20) ② 'buildConnectionString'
거의 완성입니다. ljust 메쏘드로 메쏘드 이름이 주어지고 processFunc를 호출하여 (축약될 수도 있는) 문서화 문자열(doc string)이 주어지면 그 두 개의 문자열을 결합해 하나의 문자열을 얻습니다. methodList를 짝짓기 하고 있으므로, 결국 문자열 리스트를 얻습니다. 문자열 "\n"의 join 메쏘드를 사용하여, 이 리스트를 한 개짜리 문자열로 결합합니다. 리스트의 각 원소 마다 따로따로 줄을 차지합니다. 그리고 그 결과를 인쇄합니다.
>>> li = ['a', 'b', 'c'] >>> print "\n".join(li) ① a b c
① | 이는 리스트와 작업할 때 유용한 디버깅 트릭이기도 합니다. 그리고 파이썬에서는 언제나 리스트로 작업합니다. |
이제 마지막 퍼즐 조각입니다. 다음 코드가 이해 되실줄로 믿습니다.
print "\n".join(["%s %s" % (method.ljust(spacing), processFunc(str(getattr(object, method).__doc__))) for method in methodList])
apihelper.py 프로그램과 그의 출력은 이제 완벽하게 이해가 되실 겁니다.
def info(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 info.__doc__
다음은 apihelper.py의 출력입니다:
>>> from apihelper import info >>> li = [] >>> info(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
다음 장으로 넘어가기 전에, 다음과 같은 일을 몸에 익히시기를 바랍니다:
☜ 제 03 장 고유 데이터 유형 | """ Dive Into Python """ 다이빙 파이썬 |
제 05 장 객체와 객체지향 ☞ |