본문 바로가기

Python

4.4. getattr로 객체 참조점 얻는 방법

이미 파이썬 함수가 객체라는 사실을 알고 있습니다. 그러나 getattr를 사용하면 실행-시간까지 그의 이름을 미리 알지 못하더라도 함수의 참조점을 얻을 수 있다는 사실은 모르실 것입니다.
 
예제 4.10.  getattr 소개

 

  1. >>> li = ["Larry", "Curly"]
  2. >>> 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 "", 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은 예외를 일으킵니다.  
 
 
4.4.1.  모듈에 작동하는 getattr 
getattr는 내장 데이터 유형만을 위한 것은 아닙니다. 모듈에도 작동합니다. 
 
예제 4.11. apihelper.py에 있는 getattr 함수

 

  1. >>> import odbchelper
  2. >>> 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는 함수이므로, 호출이 가능합니다.  
 
 
4.4.2. 분배자로서의 getattr 
getattr의 일반적인 사용 패턴은 분배자입니다. 예를 들어, 다양한 형태로 데이터를 출력하는 프로그램이 있다면 단 하나의 분배 함수를 사용할 수 있습니다. 분배함수에서 각 출력에 대하여 따로따로 함수를 정의하여 그에 맞게 올바른 함수를 호출할 수 있습니다. 
 
예를 들어, HTML과 XML 그리고 평범한 텍스트 포맷으로 사이트 통계를 인쇄하는 프로그램을 생각해 봅시다. 출력 포맷의 선택은 명령 줄에서 지정하거나 또는 환경설정 파일에 정의할 수 있습니다. statsout 모듈에는 세 가지 함수가 정의됩니다. output_html과 output_xml 그리고 output_text 함수가 그것입니다. 주 프로그램에는 다음과 같이 출력 함수가 하나만 정의 됩니다: 
 
예제 4.12. getattr로 분배자 만들기

 

  1. import statsout

  2. 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은 선택적으로 세 번째 인자, 기본 값을 받습니다. 
 
예제 4.13. getattr 기본 값 

 

  1. import statsout

  2. def output(data, format="text"):
        output_function = getattr(statsout, "output_%s" % format, statsout.output_text)
        return output_function(data) ①

 

①  이 함수는 확실하게 작동합니다. 왜냐하면 세 번째 매개변수를 getattr를 호출할때 추가했기 때문입니다. 세 번째 매개변수는 두 번째 인자에 지정된 속성이나 메쏘드가 발견되지 않을 경우 돌려주는 기본값입니다.  
 
보시다시피, getattr은 상당히 강력합니다. 내부 검사의 심장이며, 나중에 훨씬 더 강력한 예들을 보여 드리겠습니다. 
 
 
4.5. 리스트 여과하기 
아시다시피, 파이썬은 지능형 리스트(list comprehensions)를 통하여 (섹션 3.6, “리스트 짝짓기”) 리스트를 리스트에 짝짓는 강력한 능력이 있습니다. 이 능력을 여과 메커니즘과 조합하면 리스트의 어떤 요소들은 짝짓기되고 또 어떤 요소들은 완전히 무시됩니다. 
 
다음은 리스트 여과 구문입니다:
[mapping-expression for element in source-list if filter-expression]

 

이는 여러분이 익히 알고 좋아하는 지능형 리스트(list comprehensions)를 확장한 것입니다. 첫 2/3는 같지만; if로 시작하는 뒷부분은 여과 표현식입니다. 여과 표현식은 참 또는 거짓으로 평가되기만 하면 어떤 표현식이든 상관없습니다 (파이썬에서는 거의 모든 것이 여과 표현식이 될 수 있습니다). 어떤 원소든지 여과 표현식에서 참으로 평가되면 짝짓기에 포함됩니다. 다른 원소들은 모두 무시되어서 짝짓기 표현식을 통과하지 못하며 출력 리스트에 포함되지 않습니다. 
 
 
예제 4.14. 리스트 여과

 

  1. >>> li = ["a", "mpilgrim", "foo", "b", "c", "b", "d", "d"]
  2. >>> [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에서 다음 줄을 다시 보세요: 

 

  1.     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__ 같은 속성은 신경쓰지 않습니다.