☜ 제 04 장 강력한 내부검사 """ Dive Into Python """
다이빙 파이썬
제 06 장 예외와 파일처리 ☞

제 5 장 객체 그리고 객체-지향

이 장부터 앞으로는 객체 지향적인 파이썬 프로그래밍을 상당히 많이 다룹니다.

5.1. 뛰어 들기

다음은 완벽하게 작동하는 파이썬 프로그램입니다. 모듈과 클래스 그리고 함수의 문서화 문자열(doc string)을 읽고, 이 프로그램이 무슨 일을 하는지 그리고 어떻게 작동하는지 알아 보세요. 언제나 그렇듯이 이해하지 못하더라도 걱정하지 마세요; 그것을 바로 이 장에서 알려 드리겠습니다.

예제 5.1. fileinfo.py

아직 그렇게 하지 못했다면 이 책에 사용된 예제를 내려 받을 수 있습니다.

"""파일유형-종속적인 메타데이터를 얻기 위한 작업틀.

파일이름을 가지고 적절한 클래스를 실체화한다. 반환된 객체는 사전처럼 행위한다
각각의 메타데이터에 대하여 키-값 쌍을 가진다.
    import fileinfo
    info = fileinfo.MP3FileInfo("/music/ap/mahadeva.mp3")
    print "\\n".join(["%s=%s" % (k, v) for k, v in info.items()])

또는 listDirectory 함수를 사용하여 디렉토리에 있는 모든 파일에 관한 정보를 얻는다.
    for info in fileinfo.listDirectory("/music/ap/", [".mp3"]):
        ...

예를 들어 HTMLFileInfo와 MPGFileInfo 그리고 DOCFileInfo와 같이 
특정한 파일 유형에 대하여 클래스를 추가하면 작업틀을 확장할 수 있다. 
각 클래스는 그의 파일을 적절하게 해석할 책임을 진다; 
예제는 MP3FileInfo를 참고하라.
"""
import os
import sys
from UserDict import UserDict

def stripnulls(data):
    "공간문자와 널문자를 걷어낸다"
    return data.replace("\00", "").strip()

class FileInfo(UserDict):
    "파일 메타데이터를 저장한다"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

class MP3FileInfo(FileInfo):
    "ID3v1.0 MP3 태그를 저장한다"
    tagDataMap = {"title"   : (  3,  33, stripnulls),
                  "artist"  : ( 33,  63, stripnulls),
                  "album"   : ( 63,  93, stripnulls),
                  "year"    : ( 93,  97, stripnulls),
                  "comment" : ( 97, 126, stripnulls),
                  "genre"   : (127, 128, ord)}

    def __parse(self, filename):
        "MP3 파일로부터 가져온 ID3v1.0 태그를 해석한다"
        self.clear()
        try:                               
            fsock = open(filename, "rb", 0)
            try:                           
                fsock.seek(-128, 2)        
                tagdata = fsock.read(128)  
            finally:                       
                fsock.close()              
            if tagdata[:3] == "TAG":
                for tag, (start, end, parseFunc) in self.tagDataMap.items():
                    self[tag] = parseFunc(tagdata[start:end])               
        except IOError:                    
            pass                           

    def __setitem__(self, key, item):
        if key == "name" and item:
            self.__parse(item)
        FileInfo.__setitem__(self, key, item)

def listDirectory(directory, fileExtList):                                        
    "특정한 확장자를 가진 파일에 대하여 파일 정보 객체 리스트를 얻는다"
    fileList = [os.path.normcase(f)
                for f in os.listdir(directory)]           
    fileList = [os.path.join(directory, f) 
               for f in fileList
                if os.path.splitext(f)[1] in fileExtList] 
    def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]):      
        "파일이름 확장자로부터 파일 정보 클래스를 얻는다"                             
        subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:]       
        return hasattr(module, subclass) and getattr(module, subclass) or FileInfo
    return [getFileInfoClass(f)(f) for f in fileList]                             

if __name__ == "__main__":
    for info in listDirectory("/music/_singles/", [".mp3"]): 
        print "\n".join(["%s=%s" % (k, v) for k, v in info.items()])
        print
이 프로그램의 출력은 하드 드라이브에 있는 파일에 따라 다릅니다. 의미 있는 출력을 얻으려면 MP3 파일이 있는 디렉토리를 가리키도록 디렉토리 경로를 바꿀 필요가 있습니다.

다음은 나의 머신에서 얻은 출력입니다. 여러분은 출력이 다를 것입니다. 그렇지 않다면 놀랍게도 여러분과 저는 음악에 대한 취향이 같은 셈입니다.

album=
artist=Ghost in the Machine
title=A Time Long Forgotten (Concept
genre=31
name=/music/_singles/a_time_long_forgotten_con.mp3
year=1999
comment=http://mp3.com/ghostmachine

album=Rave Mix
artist=***DJ MARY-JANE***
title=HELLRAISER****Trance from Hell
genre=31
name=/music/_singles/hellraiser.mp3
year=2000
comment=http://mp3.com/DJMARYJANE

album=Rave Mix
artist=***DJ MARY-JANE***
title=KAIRO****THE BEST GOA
genre=31
name=/music/_singles/kairo.mp3
year=2000
comment=http://mp3.com/DJMARYJANE

album=Journeys
artist=Masters of Balance
title=Long Way Home
genre=31
name=/music/_singles/long_way_home1.mp3
year=2000
comment=http://mp3.com/MastersofBalan

album=
artist=The Cynic Project
title=Sidewinder
genre=18
name=/music/_singles/sidewinder.mp3
year=2000
comment=http://mp3.com/cynicproject

album=Digitosis@128k
artist=VXpanded
title=Spinning
genre=255
name=/music/_singles/spinning.mp3
year=2000
comment=http://mp3.com/artists/95/vxp

5.2. from module import를 사용하여 모듈 반입하기

파이썬은 두 가지 방식으로 모듈을 반입합니다. 둘 모두 쓸모가 있으며, 언제 무엇을 사용해야 할지 알아야 합니다. 한 가지 방식은 import module인데, 이미 섹션 2.4, “모든 것은 객체이다”에서 본 것입니다. 다른 방식도 같은 일을 하지만, 미묘하고 중요한 차이가 있습니다.

다음은 기본적인 from module import 구문입니다:

from UserDict import UserDict

이 구문은 여러분이 잘 알고 즐겨 쓰시는 import module 구문과 비슷하지만 중요한 차이점이 있습니다: 반입된 모듈 유형(types)의 속성과 메쏘드가 직접적으로 지역 이름공간 안으로 반입됩니다. 그래서 모듈 이름으로 자격을 부여할 필요없이 직접 사용할 수 있습니다. 항목을 따로따로 반입할 수 있으며 또는 from module import *를 사용하여 모조리 반입할 수도 있습니다.

파이썬의 from module import * 구문은 Perluse module과 비슷합니다; 파이썬import modulePerlrequire module과 비슷합니다.
파이썬from module import * 구문은 자바(Java)의 import module.*와 비슷합니다; 파이썬import module는 자바(Java)의 import module과 비슷합니다.

예제 5.2. import module vs. from module import

>>> import types
>>> types.FunctionType             
<type 'function'>
>>> FunctionType                   
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
NameError: There is no variable named 'FunctionType'
>>> from types import FunctionType 
>>> FunctionType                   
<type 'function'>
types 모듈에는 아무 메쏘드도 없습니다; 그저 각 파이썬 객체 유형에 대하여 속성만 있을 뿐입니다. FunctionType 속성에 모듈 이름(types)으로 자격을 부여하고 있음을 주목하세요 .
FunctionType 그 자체는 이 이름공간에 정의되어 있지 않습니다; 오직 types라는 문맥에서만 존재합니다.
이 구문은 FunctionType 속성을 types 모듈로부터 직접적으로 지역 이름공간 안으로 반입합니다.
이제 FunctionType에 직접 접근할 수 있습니다. types를 참조할 필요가 없습니다.

언제 from module import 구문을 사용해야 할까요?

  • 속성과 메쏘드에 자주 접근한다면 그리고 모듈 이름을 반복해서 타자하고 싶지 않다면 from module import를 사용하세요.
  • 선택적으로 어떤 속성과 메쏘드는 반입하고 싶지만 다른 것들은 반입하고 싶지 않다면 from module import를 사용하세요.
  • 반입할 모듈에 같은 이름의 속성이나 함수가 있다면 이름 충돌을 막기 위하여 import module를 사용해야 합니다.

그런 것 말고는 그냥 스타일의 문제일 뿐이라서 두 가지 방식 모두를 사용한 파이썬 코드를 자주 보실 겁니다.

되도록이면 from module import * 구문을 아껴 사용하세요. 왜냐하면 특정 함수나 속성이 어디로 부터 왔는지 알기 어렵게 되며 디버깅과 리팩토링이 더욱 어려워지기 때문입니다.

모듈 반입 테크닉에 관하여 더 읽어야 할 것

5.3. 클래스 정의하기

파이썬은 완전하게 객체-지향적입니다: 자신만의 클래스를 정의할 수 있으며 내장 클래스나 자신의 클래스로부터 상속을 받을 수 있고 또한 그렇게 정의한 클래스를 실체화시킬 수 있습니다.

파이썬에서 클래스를 정의하는 일은 쉽습니다. 함수와 마찬가지로 인터페이스 정의가 따로 없습니다. 그냥 클래스를 정의하고 코딩을 시작하면 됩니다. 파이썬 클래스는 예약어 class로 시작하고 다음에 클래스 이름이 따라옵니다. 기술적으로 그것이면 다 된 것입니다. 왜냐하면 클래스는 다른 클래스로부터 상속받을 필요가 없기 때문입니다.

예제 5.3. 가장 단순한 파이썬 클래스

class Loaf: 
    pass     
이 클래스의 이름은 Loaf이며 다른 클래스부터 상속을 받지 않습니다. 클래스 이름은 보통 첫 문자가 대문자가 되어 EachWordLikeThis와 같은 형태가 되지만, 이는 그저 관례일 뿐 필수는 아닙니다.
이 클래스는 속성이나 메쏘드를 정의하지 않지만 구문적으로 무엇인가 정의에 필요한 것이 있습니다. 그래서 pass를 사용합니다. 이 서술어는 파이썬 예약어로서 그냥 “여기에서 아무것도 볼 것이 없으니 그냥 지나가라는 뜻입니다”. 아무것도 하지 않는 서술문입니다. 그리고 함수나 클래스를 세울 때 훌륭한 위치보유자입니다.
아마도 짐작하셨겠지만 클래스에서 모든 것은 들여쓰기 됩니다. 마치 함수 안이나 if 서술문 또는 for 회돌이 등등의 안의 코드가 그런 것처럼 말입니다. 들여쓰기 되지 않는 것은 클래스 안에 있지 않습니다.
파이썬pass 서술문은 마치 자바(Java)나 C에서의 비어 있는 괄호({})와 같습니다.

물론 현실적으로 대부분의 클래스는 다른 클래스로부터 상속되며 자신만의 클래스 메쏘드와 속성을 정의합니다. 그러나 보시다시피 이름 말고는 클래스가 절대적으로로 갖추어야 할 것은 없습니다. 특히 C++ 프로그래머라면 파이썬 클래스에 명시적으로 구성자와 파괴자가 없다는 사실이 기묘하게 느껴질 것입니다. 파이썬 클래스는 구성자와 비슷한 것이 있습니다: __init__ 메쏘드가 바로 그것입니다.

예제 5.4. FileInfo 클래스 정의하기

from UserDict import UserDict

class FileInfo(UserDict): 
파이썬에서 클래스의 조상은 클래스 이름 다음에 바로 괄호 안에 나열됩니다. 그래서 FileInfo 클래스는 UserDict 클래스로부터 상속을 받습니다 (이는 UserDict 모듈로부터 반입되었습니다). UserDict는 사전처럼 행위하는 클래스로서, 사전 데이터 유형을 본질적으로 하위클래스화하여 거기에 여러분만의 행위를 덧붙일 수 있습니다. (비슷하게 UserList 클래스와 UserString 클래스도 있어서 리스트와 문자열을 하위클래스화할 수 있습니다.) 이 뒤에는 약간 어둠의 마법이 있는데, 나중에 이 장에서 UserDict 클래스를 좀 더 깊게 탐험하면서 그 비밀을 파헤쳐 보겠습니다.
파이썬에서 한 클래스의 조상은 단순히 클래스 이름 뒤에 바로 괄호 안에 나열됩니다. 자바(Java)의 extends와 같은 특별한 키워드는 없습니다.

파이썬은 다중 상속을 지원합니다. 클래스 이름 다음에 오는 반괄호 안에다 얼마든지 원하는 만큼 쉼표로 분리하여 조상을 나열할 수 있습니다.

5.3.1. 클래스 초기화와 코딩

이 예제는 __init__ 메쏘드를 사용하여 FileInfo 클래스를 초기화하는 법을 보여줍니다.

예제 5.5. FileInfo 클래스 초기화하기

class FileInfo(UserDict):
    "store file metadata"              
    def __init__(self, filename=None):   
클래스도 문서화 문자열(doc string)이 있습니다 (그리고 모듈과 함수처럼 꼭 있어야 합니다).
__init__은 클래스의 실체가 생성되자 마자 즉시 호출됩니다. 이를 클래스의 구성자라고 말하고 싶지만 올바르지 않습니다. 그런 유혹이 드는 이유는 마치 (관례적으로 __init__은 클래스에 제일 먼저 정의되는 메쏘드이므로) 구성자처럼 보이며, (새로 생성된 클래스 실체에서 제일 먼저 실행되는 코드이며 그리고 심지어 구성자처럼 발음되므로) 구성자처럼 행위하기 때문입니다 (“init” 메쏘드는 확실히 구성자-다운 본성이 있습니다). 그것이 올바르지 않은 이유는 그 객체가 이미 구성되고 난 후에 __init__ 메쏘드가 호출되고, 클래스의 새 실체를 가리키는 유효한 참조점을 이미 확보했기 때문입니다. 그러나 __init__ 메쏘드는 파이썬에서 구성자에 가장 가까운 것이며 거의 같은 역할을 수행합니다.
__init__을 비롯하여 클래스 메쏘드에 건네지는 첫 인자는 언제나 클래스의 현재 실체를 가리키는 참조점입니다. 관례적으로 이 인자는 언제나 이름이 self입니다. __init__ 메쏘드에서 self는 새로 만들어진 객체를 가리킵니다; 다른 클래스 메쏘드에서는 그 메쏘드가 호출된 실체를 가리킵니다. 비록 메쏘드를 정의할 때는 명시적으로 self를 지정해야 하지만, 그 메쏘드를 호출할 때는 지정하지 않습니다; 파이썬이 자동으로 여러분 대신 그것을 추가해 줍니다.
__init__ 메쏘드는 인자를 얼마든지 취할 수 있습니다. 함수와 마찬가지로 인자에 기본 값을 정의할 수 있으며, 호출자는 선택적으로 정의하면 됩니다. 이 경우, filenameNone이라는 기본 값을 가지는데, 이는 파이썬의 널 값입니다.
관례적으로 파이썬 클래스 메쏘드의 첫 인자는 (현재 실체를 가리키는 참조점) self라고 부릅니다. 이 인자는 C++ 또는 자바(Java)에서의 this라는 예약어의 역할을 수행합니다. 그러나 self파이썬에서 예약어가 아니며, 단지 관례상 그렇게 이름을 지었을 뿐입니다. 그럼에도 불구하고 self 말고 다른 이름으로 부르지 맙시다; 이는 아주 강력한 관례입니다.

예제 5.6. FileInfo 클래스 코딩하기

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)        
        self["name"] = filename        
                                       
파워빌더(Powerbuilder) 같은 유사-객체-지향 언어는 구성자와 기타 이벤트를 “확장한다”는 개념이 있습니다. 여기에서 조상의 메쏘드는 자손의 메쏘드가 실행되기 전에 자동으로 호출됩니다. 파이썬은 이렇게 하지 않습니다; 항상 명시적으로 조상클래스에서 적절한 메쏘드를 호출해야 합니다.
이 클래스는 사전처럼 행위한다고 말씀 드린 바 있습니다. 그리고 여기에 그 첫 징후가 있습니다. 인자 filename을 이 객체의 name 키의 값으로 할당하고 있습니다.
__init__ 메쏘드는 값을 돌려 주지 않음에 주목하세요.

5.3.2. self__init__를 사용할 때를 알기

클래스 메쏘드를 정의할 때 __init__을 포함하여 각 메쏘드에 대하여 반드시 self를 첫 인자로 명시적으로 나열해야 합니다. 클래스 안에서 조상 클래스의 메쏘드를 호출할 경우 반드시 self 인자를 포함시켜야 합니다. 그러나 클래스 메쏘드를 바깥으로부터 호출할 경우에는 self 인자에 대하여 아무것도 지정할 필요가 없습니다; 완전히 무시더라라도 파이썬이 여러분 대신 자동으로 실체 참조를 추가해 줍니다. 처음에는 이 때문에 혼란스럽습니다; 실제로 일관성이 없는 것은 아니지만, 일관성이 없어 보이는 이유는 아직 배우지 않은 (묶인 메쏘드와 안묶인 메쏘드 사이의) 구별에 의존하고 있기 때문입니다.

휴. 흡수하기에는 너무 양이 많다는 것을 압니다. 그러나 요점을 이해하실 겁니다. 모든 파이썬 클래스는 같은 방식으로 작동합니다. 그래서 하나를 배우면 모든 것을 안 셈입니다. 다른 것은 다 잊어 버리더라도, 이것 하나만은 기억하세요. 왜냐하면 언젠가는 이 때문에 걸려 넘어질 때가 있기 때문입니다:

__init__ 메쏘드는 선택적입니다. 그러나 정의해 둘 경우, 잊지 말고 꼭 명시적으로 (정의되어 있다면) 조상의 __init__ 메쏘드를 호출해야 합니다. 이는 일반적으로 맞습니다: 자손이 조상의 행위를 확장하고 싶으면 자손의 메쏘드는 명시적으로 조상의 메쏘드를 적절한 시기에 적절한 인자를 가지고 호출해야 합니다.

5.4. 클래스 실체화하기

클래스를 파이썬에서 실체화하는 일은 눈에 보이는 그대로 이해됩니다. 클래스를 실체화하려면 그냥 __init__ 메쏘드에 정의된 인자들을 건네고 클래스를 마치 함수처럼 호출하면 됩니다. 반환 값은 새로 만들어진 객체가 될 것입니다.

예제 5.7. FileInfo 실체 만들기

>>> import fileinfo
>>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3") 
>>> f.__class__                                        
<class fileinfo.FileInfo at 010EC204>
>>> f.__doc__                                          
'store file metadata'
>>> f                                                  
{'name': '/music/_singles/kairo.mp3'}
(fileinfo 모듈에 정의된) FileInfo 클래스의 실체를 하나 만들고 그 새로 만든 실체를 변수 f에 할당합니다. 매개변수 하나, /music/_singles/kairo.mp3를 건네는데, 이 매개변수는 FileInfo 클래스의 __init__ 메쏘드의 filename 인자가 될 것입니다.
클래스 실체라면 내장 속성으로 __class__가 있는데, 이 속성은 그 객체의 클래스입니다. (이 표현에 머신에서의 실체의 물리적 주소도 포함됨에 주의하세요; 여러분의 표현은 다를 것입니다.) 자바(Java) 프로그래머는 Class 클래스에 익숙할 텐데, 여기에는 객체에 관한 메타데이터 정보를 얻기 위한 getName 메쏘드와 getSuperclass 메쏘드가 포함됩니다. 파이썬에서, 이런 종류의 메타데이터는 __class____name__ 그리고 __bases__ 같은 속성들을 통하여 객체 그 자체에 직접적으로 사용가능합니다.
함수나 모듈에서와 마찬가지로 실체의 문서화 문자열(doc string)에 접근할 수 있습니다. 클래스의 모든 실체들은 똑같은 문서화 문자열(doc string)을 공유합니다.
__init__ 메쏘드가 자신의 filename 인자를 self["name"]에 할당하던 때를 기억하십니까? 자, 다음은 그 결과입니다. 클래스 실체를 만들 때 건넨 인자들은 곧바로 __init__ 메쏘드에 보내집니다 (파이썬이 무료로 더해주는, 객체 참조점 self와 함께 말입니다).
파이썬에서, 클래스의 실체를 하나 새로 만들려면 그냥 함수처럼 클래스를 호출하면 됩니다. C++이나 자바(Java)처럼 명시적인 new 연산자는 없습니다.

5.4.1. 쓰레가 수거

새로운 실체를 만드는 것이 쉽다면 없애는 것은 더 쉽습니다. 보통은 실체를 명시적으로 풀어줄 필요가 없습니다. 그 이유는 실체에 할당된 변수가 영역을 벗어나면 자동으로 풀리기 때문입니다. 파이썬에서 메모리 누수는 거의 없습니다.

예제 5.8. 메모리 누수 시도해 보기

>>> def leakmem():
...     f = fileinfo.FileInfo('/music/_singles/kairo.mp3') 
...     
>>> for i in range(100):
...     leakmem()                                          
leakmem 함수가 호출될 때마다, FileInfo 클래스의 실체를 만들어 그것을 변수 f에 할당합니다. 이 변수는 그 함수 안에 존재하는 지역 변수입니다. 함수는 f 변수를 풀어주지 않고 종료합니다. 그래서 메모리 누수가 있지 않을까 예상하지만, 그렇지 않습니다. 함수가 끝나면 지역 변수 f는 영역을 벗어납니다. 이 시점에서는 FileInfo 클래스로부터 새로 만들어진 그 실체를 더 이상 참조하지 않으므로 (f 말고는 다른 곳에 할당한 바가 없으므로), 파이썬이 우리 대신에 그 실체를 파괴합니다.
leakmem 함수를 얼마를 호출하든 상관없이, 메모리 누수는 없습니다. 왜냐하면 매번 파이썬은 새로 만들어진 FileInfo 클래스의 실체를 파괴하고 나서 leakmem으로부터 돌아오기 때문입니다.

이런 형태의 쓰레기 수거를 일컫는 기술적 용어는 “참조횟수 세기(reference counting)”입니다. 파이썬은 생성된 실체마다 참조 목록을 유지합니다. 위의 예제에서는 FileInfo 실체에 대한 참조점이 오직 하나만 있습니다: 지역 변수 f만 있습니다. 함수가 종료하면 변수 f는 범위를 벗어납니다. 그래서 참조 횟수는 0으로 떨어지고, 파이썬은 그 실체를 자동으로 파괴합니다.

이전의 파이썬 버전에서는 참조횟수 세기가 실패해서, 파이썬이 여러분을 따라다니며 정리해 줄 수 없는 상황이 있었습니다. 서로를 참조하는 실체를 두 개 만들었다면 (예를 들면 이중-링크 리스트 같은 경우, 각 노드는 리스트에서 앞노드와 다음 노드를 가리키는 포인터를 가집니다), 두 실체 어느 것도 자동으로 파괴되지 않습니다. 왜냐하면 파이썬이 각 실체를 가리키는 참조점이 항상 있다고 (올바르게) 믿기 때문입니다. 파이썬 2.0에는 쓰레기 수거에 “mark-and-sweep”이라고 부르는 형태가 추가되었습니다. 이는 똑똑하게도 이런 사실상의 격자잠금을 인지하고 순환 참조를 올바르게 정리합니다.

철학이 전공인 본인은 아무도 보아주지 않으면 사물은 사라진다고 생각하는 것이 혼란스러웠습니다. 그러나 그런 일이 바로 파이썬에서 일어나는 일입니다. 일반적으로 그냥 메모리 관리에 관하여 잊어 버리고 파이썬이 여러분을 따라 다니며 정리하도록 두면 됩니다.

쓰레가 수거에 관하여 더 읽어야 할 것

5.5. UserDict 탐험: 포장 클래스

보시다시피 FileInfo는 사전처럼 행동하는 클래스입니다. 더 탐험해 보기 위해, UserDict 모듈에서 UserDict 클래스를 살펴보겠습니다. 이 클래스는 FileInfo 클래스의 조상입니다. 이는 특별한 것이 아닙니다; 클래스는 파이썬으로 작성되고 .py 파일에 저장됩니다. 다른 파이썬 코드와 똑같이 말입니다. 특히, 파이썬 배포 디렉토리의 lib 디렉토리에 저장되어 있습니다.

윈도우즈의 ActivePython IDE라면 라이브러리 경로에 있는 모듈을 신속하게 열 수 있습니다. File->Locate...를 선택하면 됩니다 (Ctrl-L).

예제 5.9. UserDict 클래스 정의하기

class UserDict:                                
    def __init__(self, dict=None):             
        self.data = {}                         
        if dict is not None: self.update(dict)  
UserDict가 바탕 클래스임에 주의하세요. 다른 클래스로부터 상속받지 않았습니다.
이것이 FileInfo 클래스에서 오버라이드 한 __init__ 메쏘드입니다. 이 조상 클래스의 인자 리스트는 자손 클래스의 인자 리스트와 다름에 주의하세요. 좋습니다; 각 하위클래스는 올바른 인자를 가지고 조상을 호출하는 한 자신만의 인자 세트를 가질 수 있습니다. 여기에서 조상 클래스는 (dict 인자에 사전을 건넴으로써) 초기 값을 정의하는 방법이 있습니다. FileInfo 클래스는 이 방법을 사용하지 않았습니다.
파이썬은 데이터 속성을 지원합니다 (자바(Java)와 파워빌더(Powerbuilder)에서는 “실체 변수(instance variables)”라고 부르며, C++에서는 “멤버 변수(member variables)”라고 부릅니다 ). 데이터 속성은 한 클래스의 특정 실체가 보유한 데이터 조각입니다. 이 경우, 각 UserDict 실체는 데이터 속성으로 data를 가질 것입니다. 이 속성을 클래스 바깥의 코드로부터 참조하려면 실체 이름으로 instance.data와 같이 자격을 부여하면 됩니다. 함수에 모듈 이름으로 자격을 부여하는 방식과 똑 같이 말입니다. 데이터 속성을 클래스 안에서 참조하려면 self를 자격부여자로 사용합니다. 관례상, 모든 데이터 속성은 __init__ 메쏘드에서 합리적인 값으로 초기화됩니다. 그렇지만, 이것이 필수적인 것은 아닙니다. 왜냐하면 지역 변수 같은 데이터 속성은 처음 값을 할당하자 마자 갑자기 존재하기 때문입니다.
update 메쏘드는 사전을 복사합니다: 한 사전의 값과 키를 또다른 사전에 모두 복사합니다. 이는 목표 사전을 먼저 지우지 않습니다; 목표 사전에 이미 키가 있으면 소스 사전의 키들로 덮어쓰기 됩니다. 그러나 다른 것들은 그대로 남아 있을 것입니다. update를 복사 함수가 아니라 합병 함수로 생각하세요.
이 구문은 이전에 본 적이 없을 겁니다 (이 책에서는 예제에 사용한 적이 없습니다). if 서술문이지만, 다음 줄에서 들여쓰기 블록을 시작하는 대신에, 그냥 같은 줄 쌍점 다음에 서술문 하나가 있을 뿐입니다. 이는 완벽하게 합법적인 구문이며, 블록에 서술문이 달랑 하나 있을 경우 사용할 수 있는 지름길입니다. (마치 C++에서 괄호 없이 단 한개의 서술문을 지정한 것과 비슷합니다.) 이 구문을 사용하거나 또는 다음 줄에 연이어서 코드를 들여쓰기 해도 되지만, 같은 블록에 두 구문을 모두 사용할 수는 없습니다.
자바(Java)와 파워빌더(Powerbuilder)는 인자 리스트로 함수 오버로딩을 지원합니다. 다시 말해, 클래스는 이름은 같지만 인자 개수가 다르거나 인자 유형이 다르게 여러 메쏘드를 가질 수 있습니다. 다른 언어들은 (특히 PL/SQL는) 심지어 인자 이름으로 함수 오버로딩도 지원합니다; 즉, 클래스는 이름도 같고 인자 유형도 같지만 인자 이름은 다른 메쏘드를 여럿 가질 수 있습니다. 파이썬은 이 중 어느 것도 지원하지 않습니다; 함수 오버로딩 같은 형태는 전혀 없습니다. 메쏘드는 오직 이름만으로 정의되며, 주어진 이름으로 클래스 당 하나의 클래스만 있을 수 있습니다. 그래서 자손 클래스에 __init__ 메쏘드가 있으면 언제나 조상의 __init__ 메쏘드를 오버라이드 합니다. 자손 클래스에 다른 인자 리스트를 가지고 정의되어 있더라도 말입니다. 그리고 같은 규칙이 다른 메쏘드에도 적용됩니다.
처음 파이썬을 만든 귀도(Guido)는 메쏘드 오버라이딩을 다음과 같이 설명합니다: "파생된 클래스는 바탕 클래스의 메쏘드를 오버라이드할 수 있습니다. 메쏘드는 같은 객체의 다른 메쏘드를 호출할 때 특별한 권한이 없기 때문에, 바탕 클래스의 메쏘드가 같은 바탕 클래스 안에 정의된 또다른 메쏘드를 호출할 경우 실제로 그를 오버라이드한 파생 클래스의 메쏘드를 호출할 가능성이 높습니다. (C++ 프로그래머에게 드리는 주의: 파이썬에서 모든 메쏘드는 효과상 가상적입니다.)" 이해가 되지 않으면 무시해도 좋습니다. (정신이 하나도 없군요) 저는 그냥 무시하고 지나쳐도 된다고 생각했습니다.
__init__ 메쏘드에서 실체의 데이터 속성 모두에 언제나 최초값을 할당하세요. 나중에 디버깅 시간을 줄여주고, AttributeError 예외를 찾아 내려가는 수고를 덜어줄 것입니다. 초기화되지 않은 (그러므로 존재하지 않는) 속성을 참조하려고 하기 때문에 말입니다.

예제 5.10. UserDict 정상 메쏘드 

    def clear(self): self.data.clear()          
    def copy(self):                             
        if self.__class__ is UserDict:          
            return UserDict(self.data)         
        import copy                             
        return copy.copy(self)                 
    def keys(self): return self.data.keys()     
    def items(self): return self.data.items()  
    def values(self): return self.data.values()
clear는 보통의 클래스 메쏘드입니다; 공개적으로 언제든지 누구나 호출할 수 있습니다. 주목하세요. 다른 모든 클래스와 마찬가지로 clearself가 그의 첫 인자입니다. (메쏘드를 호출할 때 self를 포함시키지 마세요; 파이썬이 여러분 대신 추가해 준답니다.) 또 이 포장자 클래스의 기본 테크닉에 주목하세요: 실제 사전(data)을 데이터 속성으로 저장하고, 실제 사전에 있는 모든 메쏘드를 정의하며, 그리고 각 클래스 메쏘드를 그에 상응하는 실제 사전의 메쏘드로 방향전환합니다. (혹 잊으셨다면 사전의 clear 메쏘드는 모든 키와 그의 연관 값을 삭제합니다.)
실제 사전의 copy 메쏘드는 원래 사전과 정확하게 똑 같은 복제본을 새로 돌려줍니다 (모든 키-값 쌍이 똑 같습니다). 그러나 UserDict 는 손쉽게 self.data.copy로 방향전환될 수 없습니다. 그 이유는 그 메쏘드가 진짜 사전을 돌려주기 때문입니다. self와 클래스가 같은 새로운 실체를 돌려주고 싶습니다.
__class__ 속성을 사용하여 selfUserDict인지 알아봅니다; 그렇다면 다행입니다. 왜냐하면 UserDict를 복사하는 법을 알기 때문인데: 그냥 새로 UserDict를 만들어서 거기에다 self.data에 저장해 둔 실제 사전을 주면 됩니다. 즉시 그 새로운 UserDict를 돌려주고 다음 줄의 import copy에는 도달조차 하지 않습니다.
self.__class__UserDict가 아니라면 self는 (어쩌면 FileInfo 같이) UserDict의 하위클래스임에 틀림없습니다. 그런 경우라면 좀 피곤해집니다. UserDict는 자신의 자손을 정확하게 복사하는 법을 모릅니다; 예를 들면 그 하위클래스에 다른 데이터 속성이 정의되어 있을 수 있습니다. 그래서 그 속성들을 회돌이하면서 반드시 모두 복사할 필요가 있을 것입니다. 다행스럽게도 파이썬에는 정확하게 이 일을 해주는 모듈이 따라옵니다. 이른바 copy라고 부르는 모듈이 그것입니다. (스스로 깊이 연구해 보셨다면 물론 멋진 모듈이기는 하지만) 깊이 다루지는 않겠습니다. 여기에서 copy파이썬 객체라면 무엇이든 복사할 수 있으며 여기에서 사용하고 있는 것이라는 사실을 언급하는 정도로 마치겠습니다.
나머지 메쏘드는 눈에 보이는 그대로입니다. 호출을 self.data의 내장 메쏘드로 방향전환합니다.
파이썬 2.2 이전의 버전에서는 리스트와 문자열 그리고 사전 같은 내장 데이터유형을 직접 하위클래스화할 수 없었습니다. 이를 보충하기 위하여 파이썬에는 이런 내장 유형을 흉내내는 포장 클래스가 따라옵니다: UserStringUserList 그리고 UserDict가 그것입니다. 표준 메쏘드와 특수 메쏘드를 조합해 사용하여 UserDict 클래스는 훌륭하게 사전을 흉내냅니다. 파이썬 2.2 이후부터는 dict 같은 내장 데이터 유형으로부터 클래스를 바로 상속받을 수 있습니다. 이에 관한 예제는 이 책에 따라오는 예제인 fileinfo_fromdict.py에 있습니다.

파이썬에서는 이 예제에서 보여준 바와 같이 직접 dict 내장 데이터유형으로부터 상속받을 수 있습니다. UserDict 버전에 비해 세 가지 차이점이 있습니다.

예제 5.11. 내장 데이터 유형인 dict로부터 직접 상속받는 법

class FileInfo(dict):                  
    "store file metadata"
    def __init__(self, filename=None): 
        self["name"] = filename
첫 번째 차이점은 UserDict 모듈을 반입할 필요가 없습니다. dict는 내장 데이터유형이고 언제나 사용가능하기 때문입니다. 두 번째 차이점은 UserDict.UserDict가 아니라 dict로부터 바로 상속받습니다.
세 번째 차이점은 미묘하지만 중요합니다. UserDict는 내부적으로 작동하는 방식 때문에 손수 그의 __init__ 메쏘드를 호출해 주어야 제대로 그의 내부 데이터 구조를 초기화할 수 있습니다. dict는 이와 같이 작동하지 않습니다; 포장자가 아니므로 명시적으로 초기화할 필요가 없습니다.

5.6. 특수한 클래스 메쏘드

파이썬은 보통의 클래스 메쏘드 외에도 수 많은 특수 메쏘드를 정의할 수 있습니다. (보통의 메쏘드처럼) 코드에서 직접적으로 호출되는 대신에 특수 메쏘드는 특별한 상황 또는 특정한 구문이 사용되면 여러분 대신 파이썬이 호출합니다.

앞 섹션에서 보았듯이 보통의 메쏘드는 클래스에 사전을 싸 넣는 정도는 훌륭하게 해 냅니다. 그러나 보통의 메쏘드 만으로는 충분하지 않습니다. 왜냐하면 메쏘드를 호출하는 것 말고도 사전으로 할 수 있는 일이 수 없이 많기 때문입니다. 초보자라면 명시적으로 요청하는 메쏘드가 포함되지 않은 구문을 가지고 항목을 얻고(get) 설정(set)할 수 있습니다. 바로 이곳에 특수한 클래스 메쏘드가 들어옵니다: 특수 클래스 메쏘드는 비-메쏘드-호출 구문을 메쏘드 호출에 짝짓는 방법을 제공합니다.

5.6.1. 원소를 설정하고 획득하기

예제 5.12. __getitem__ 특수 메쏘드

    def __getitem__(self, key): return self.data[key]
>>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3")
>>> f
{'name':'/music/_singles/kairo.mp3'}
>>> f.__getitem__("name") 
'/music/_singles/kairo.mp3'
>>> f["name"]             
'/music/_singles/kairo.mp3'
__getitem__ 특수 메쏘드는 아주 단순하게 보입니다. clearkeys 그리고 values 같은 보통의 메쏘드처럼 그냥 사전으로 방향전환되어 그의 값을 돌려줍니다. 그러나 어떻게 호출될까요? 음, 직접적으로 __getitem__을 호출할 수 있지만, 실제로는 관행적으로 그렇게 하지 않습니다; 여기에서는 작동법을 보여주기 위해 그렇게 했을 뿐입니다. 올바르게 __getitem__을 사용하는 법은 파이썬이 여러분을 대신하여 호출하도록 하는 것입니다.
이는 사전에서 값을 얻을 때 사용하는 구문과 똑 같아 보입니다. 그리고 사실, 예상대로 값을 돌려줍니다. 그러나 빠진 링크가 있습니다: 뚜껑 아래에서 파이썬은 이 구문을 메쏘드 호출 f.__getitem__("name")으로 변환합니다. 그 때문에 __getitem__은 특수 클래스 메쏘드입니다; 손수 호출할 수 있을 뿐만 아니라 올바른 구문을 사용하면 파이썬에게 여러분을 대신하여 호출하도록 만들 수 있습니다.

물론 파이썬__getitem__과 더불어 __setitem__ 특수 메쏘드가 있습니다. 다음 예제에 보여주는 바와 같이 말입니다.

예제 5.13. __setitem__ 특수 메쏘드

    def __setitem__(self, key, item): self.data[key] = item
>>> f
{'name':'/music/_singles/kairo.mp3'}
>>> f.__setitem__("genre", 31) 
>>> f
{'name':'/music/_singles/kairo.mp3', 'genre':31}
>>> f["genre"] = 32            
>>> f
{'name':'/music/_singles/kairo.mp3', 'genre':32}
__getitem__ 메쏘드처럼 __setitem__ 메쏘드는 그냥 실제 사전 self.data로 방향전환되어 자신의 일을 합니다. 그리고 __getitem__처럼 보통 다음과 같이 직접 호출하지 않습니다; 올바르게 구문을 사용하면 파이썬이 여러분 대신 __setitem__을 호출합니다.
이는 표준 사전 구문과 비슷합니다. 물론 f가 실제로는 사전인 것처럼 무던히도 애쓰는 클래스라는 사실만 제외하고 말입니다. __setitem__이 그 가면의 핵심 부분입니다. 이 코드 줄은 실제로 뚜껑 아래에서 f.__setitem__("genre", 32)을 호출합니다.

__setitem__은 특수 클래스 메쏘드입니다. 여러분을 대신하여 호출되기 때문입니다. UserDict에서 쉽게 __setitem__ 메쏘드를 정의한 것처럼 자손 클래스에서도 조상 메쏘드를 오버라이드 하도록 재정의 할 수 있습니다. 이 덕분에 어느 정도 사전처럼 행위하는 그러나 내장 사전을 넘어서서 자신만의 행위가 정의된 클래스를 정의할 수 있습니다.

이 개념은 이 장에서 공부중인 전체 작업틀의 토대입니다. 각 파일 유형은 특정 유형의 파일로부터 메타데이터를 얻는 법을 이해하는 처리자 클래스를 가질 수 있습니다. 일단 (파일의 이름이나 위치 같은) 어떤 속성이 알려지면 처리자 클래스는 다른 특성들을 자동으로 추출하는 법을 압니다. 이는 __setitem__ 메쏘드를 오버라이딩하면 되는데, 특정 키를 점검하고 발견되면 추가 처리를 합니다.

예를 들어 MP3FileInfoFileInfo의 자손입니다. MP3FileInfo이름(name)이 설정되면 ( 조상 FileInfo 클래스가 하는 것처럼) 단순히 name 키만 설정하는 것이 아니라; 또한 파일 안을 들여다 보고 MP3 태그를 찾아 키 집합도 모두 채웁니다. 다음 예제는 이것이 어떻게 작동하는지 보여줍니다.

예제 5.14. MP3FileInfo에서 __setitem__ 오버라이딩하기 

    def __setitem__(self, key, item):         
        if key == "name" and item:            
            self.__parse(item)                
        FileInfo.__setitem__(self, key, item) 
__setitem__ 메쏘드가 조상 메쏘드와 정확하게 똑 같이 정의되어 있음에 주목하세요. 이는 중요합니다. 왜냐하면 파이썬 은 여러분을 대신하여 이 메쏘드를 호출하고 일정 개수의 인자와 함께 정의되어 있으리라고 예상하기 때문입니다. (기술적으로 말해 인자의 이름은 문제가 되지 않습니다; 오직 인자의 개수가 중요합니다.)
여기가 전체 MP3FileInfo 클래스의 심장부입니다: 값을 name 키에 할당한다면 무언가 추가로 일을 하고 싶기 때문입니다.
name에 대한 추가 처리는 __parse 메쏘드에 캡슐화됩니다. 이는 MP3FileInfo에 정의된 또다른 클래스 메쏘드입니다. 이 메쏘드를 호출하려면 메쏘드에 self로 자격을 부여합니다. 그냥 __parse를 호출하는 것은 클래스 밖에 정의된 보통 함수처럼 보일 것이고, 이는 원하는 바가 아닙니다. self.__parse로 호출하면 클래스 안에 정의된 클래스 메쏘드처럼 보입니다. 이는 새로운 것이 아닙니다; 데이터 속성을 같은 방식으로 참조한 바가 있습니다.
이 추가 처리가 끝나면 조상 메쏘드를 호출하고 싶습니다. 기억합시다. 이는 파이썬이 여러분을 위하여 절대로 대신해 주지 않습니다; 손수 해야 합니다. __setitem__ 메쏘드가 없음에도 불구하고 직접 조상인 FileInfo를 호출하고 있음에 주목하세요. 그래도 좋습니다. 왜냐하면 파이썬이 조상 트리를 타고 올라가 여러분이 호출한 메쏘드를 가진 클래스를 찾아낼 것입니다. 그래서 이 코드 줄은 결국 UserDict에 정의된 __setitem__ 메쏘드를 찾아 실행하기 때문입니다.
클래스 안의 데이타 속성에 접근할 때 속성 이름에 자격을 부여할 필요가 있습니다: self.attribute로 말입니다. 클래스에서 다른 메쏘드를 호출할 때도 메쏘드 이름에 자격을 부여해야 합니다: self.method와 같이 말입니다.

예제 5.15. MP3FileInfo의 이름(name) 설정하기

>>> import fileinfo
>>> mp3file = fileinfo.MP3FileInfo()                   
>>> mp3file
{'name':None}
>>> mp3file["name"] = "/music/_singles/kairo.mp3"      
>>> mp3file
{'album': 'Rave Mix', 'artist': '***DJ MARY-JANE***', 'genre': 31,
'title': 'KAIRO****THE BEST GOA', 'name': '/music/_singles/kairo.mp3',
'year': '2000', 'comment': 'http://mp3.com/DJMARYJANE'}
>>> mp3file["name"] = "/music/_singles/sidewinder.mp3" 
>>> mp3file
{'album': '', 'artist': 'The Cynic Project', 'genre': 18, 'title': 'Sidewinder', 
'name': '/music/_singles/sidewinder.mp3', 'year': '2000', 
'comment': 'http://mp3.com/cynicproject'}
먼저, 파일이름을 건네지 않고 MP3FileInfo의 실체를 만듭니다. (이렇게 해도 되는 이유는 __init__ 메쏘드의 filename 인자는 선택적이기 때문입니다.) MP3FileInfo에는 __init__ 메쏘드가 없기 때문에 파이썬은 조상 트리를 따라 올라가 FileInfo__init__ 메쏘드를 찾습니다. 이 __init__ 메쏘드는 손수 UserDict__init__ 메쏘드를 호출합니다. 그리고 name 키를 filename에 설정합니다. 파일이름은 None인데, 파일이름을 건네지 않았기 때문입니다. 그리하여 mp3file는 처음에는 키가 name 하나인 사전처럼 보입니다. 그 값은 None입니다.
이제 진짜 재미가 시작됩니다. mp3filename 키를 설정하면 (UserDict가 아니라) MP3FileInfo__setitem__ 메쏘드가 촉발됩니다. name 키에 진짜 값이 설정되는 것을 인지하고 self.__parse를 호출합니다. 아직 __parse 메소드를 추적하지 않았지만 출력을 보면 다른 여러 키를 설정하고 있음을 알 수 있습니다: album, artist, genre, title, year, 그리고 comment.
name 키를 수정하면 같은 과정을 다시 또 반복합니다: 파이썬__setitem__을 호출하고, 이는 self.__parse를 호출하는데, 이 메쏘드는 다른 모든 키를 설정합니다.

5.7. 고급 특수 클래스 메쏘드

파이썬에는 단지 __getitem__ 메쏘드와 __setitem__ 메쏘드 말고도 특수 메쏘드가 많이 있습니다. 그 중에는 생각지도 못했던 기능을 흉내내 주기도 합니다.

다음 예제는 UserDict에 있는 다른 특수 메쏘드중 일부를 보여줍니다.

예제 5.16. UserDict에 있는 특수 메쏘드 심화연구 

    def __repr__(self): return repr(self.data)     
    def __cmp__(self, dict):                       
        if isinstance(dict, UserDict):            
            return cmp(self.data, dict.data)      
        else:                                     
            return cmp(self.data, dict)           
    def __len__(self): return len(self.data)       
    def __delitem__(self, key): del self.data[key] 
__repr__은 특수한 메쏘드로서 repr(instance)를 호출하면 호출됩니다. repr 함수는 객체의 문자열 표현을 돌려주는 내장 함수입니다. 클래스 실체는 물론이고, 어떤 객체에도 작동합니다. 인지하지는 못했지만 이미 repr에 익숙합니다. 상호대화 창에서 그냥 변수 이름을 타자하고 ENTER 키를 누르면 파이썬repr을 사용하여 그 변수의 값을 화면에 표시합니다. 약간의 데이터를 가지고 사전 d를 만든 다음 print repr(d)을 해 보시면 직접 보실 수 있습니다.
__cmp__는 클래스 실체를 비교할 때 호출됩니다. 일반적으로 그냥 ==를 사용하면 클래스 실체는 물론이고 무엇이든 두 개의 파이썬 객체를 비교할 수 있습니다. 내장 데이터유형은 동등하다고 간주하는 규칙이 정의되어 있습니다; 예를 들면 사전은 키가 같고 값이 같으면 동등하며, 문자열은 길이가 같고 같은 문자가 연속되어 담겨 있으면 같습니다. 클래스 실체에 대하서는 __cmp__ 메쏘드를 정의하고 비교 로직을 손수 코드한다음, ==를 사용하여 클래스의 실체를 비교하면 여러분을 대신하여 파이썬__cmp__ 특수 메쏘드를 호출해 줍니다.
__len__len(instance)을 호출할 때 호출됩니다. len 함수는 객체의 길이를 돌려주는 내장 함수로서 길이가 있다고 간주되는 모든 객체에 작동합니다. 문자열의 길이(len)는 문자의 개수입니다; 사전의 길이(len)는 키의 개수입니다; 리스트나 터플의 길이(len)는 원소의 개수입니다. 클래스 실체라면 __len__ 메쏘드를 정의하고 길이 계산을 손수 코드한 다음 len(instance)를 호출하면 파이썬이 여러분을 대신하여 __len__ 특수 메쏘드를 호출해 줍니다.
__delitem__del instance[key]를 호출하면 호출되는데, 아마도 이 방법은 사전에서 개별 항목들을 삭제하는 방법으로 기억하실 것입니다. 클래스 실체에 del을 요청하면 여러분을 대신하여 파이썬__delitem__ 특수 메쏘드를 호출합니다.
자바(Java)에서 두 문자열 변수가 메모리에서 같은 물리적 위치를 가리키는지 알아보려면 str1 == str2를 사용합니다. 이를 객체 신분(object identity)이라고 부르며 파이썬에서는 str1 is str2로 표기됩니다. 문자열 값을 자바(Java)에서 비교하려면 str1.equals(str2)를 사용하는데; 파이썬에서는 str1 == str2를 사용합니다. 자바(Java)에서 ==가 값이 아니라 신분으로 비교되기 때문에 자신의 세상이 더 좋다고 믿도록 교육을 받아온 자바(Java) 프로그래머라면 파이썬에 그런 “함정(gotchas)”이 없다는 사실에 적응하는데 애를 먹을 것입니다.

이 시점에서 이렇게 생각하실지 모르겠습니다. “클래스 안에서 일을 하기 위해 하는 모든 작업은 내장 데이터 유형으로 할 수 있다”고 말입니다. 그리고 사실 사전처럼 내장 데이터유형을 상속받을 수 있다면 더 삶이 편안할 것입니다 (UserDict 클래스가 전혀 필요없을 것입니다). 그러나 그렇다고 할지라도 특수 메쏘드는 여전히 유용합니다. 특수 클래스는 단순히 UserDict 같은 포장 클래스뿐만 아니라 어떤 클래스에도 사용가능하기 때문입니다.

특수 메쏘드란 어떤 클래스이든 그냥 __setitem__ 메쏘드를 정의하기만 하면 사전처럼 키/값 쌍을 저장할 수 있다는 뜻입니다. 어떤 클래스이든지 __getitem__ 메쏘드를 정의하기만 하면 연속열처럼 작동할 수 있습니다. 어떤 클래스이든 __cmp__ 메쏘드가 정의되어 있으면 ==로 비교할 수 있습니다. 클래스가 길이가 있는 어떤 것을 나타낸다면 GetLength 메쏘드를 정의하지 마세요; __len__ 메쏘드를 정의하고 len(instance)을 사용하세요.

다른 객체-지향적 언어에서는 기껏해야 객체의 물리 모델을 정의할 수 있을 뿐이지만 (“이 객체는 GetLength 메쏘드가 있습니다”), __len__ 같은 파이썬의 특수 클래스 메쏘드에서는 객체의 논리 모델을 정의할 수 있습니다 (“이 객체는 길이가 있습니다”).

파이썬은 수 많은 특수 메쏘드가 있습니다. 숫자처럼 행위하는 클래스를 만들기 위한 메쏘드가 모두 갖추어져 있으며, 클래스 실체에 덧셈과 뺄셈 그리고 기타 숫치 연산을 행할 수 있습니다. (이의 모범적인 예는 실수부와 허수부가 있는 숫자인 복소수를 나타내는 클래스입니다.) __call__ 메쏘드는 함수처럼 행위하는 클래스를 만들 수 있으며, 클래스 실체를 직접적으로 호출할 수 있습니다. 클래스가 읽기 전용과 쓰기 전용 데이터 속성을 가지도록 만드는 특수 메쏘드도 있습니다; 다음 장에서 그에 관하여 좀 더 연구해 보겠습니다.

특수 클래스 메쏘드에 대하여 더 읽어야 할 것

5.8. 클래스 속성 소개

이미 데이터 속성에 관하여 배웠습니다. 이는 클래스의 특정 실체가 소유한 변수들입니다. 파이썬은 또한 클래스 속성을 지원합니다. 이는 클래스 자체가 소유한 변수들입니다.

예제 5.17. 클래스 속성 소개

class MP3FileInfo(FileInfo):
    "ID3v1.0 MP3 태그를 저장한다"
    tagDataMap = {"title"   : (  3,  33, stripnulls),
                  "artist"  : ( 33,  63, stripnulls),
                  "album"   : ( 63,  93, stripnulls),
                  "year"    : ( 93,  97, stripnulls),
                  "comment" : ( 97, 126, stripnulls),
                  "genre"   : (127, 128, ord)}
>>> import fileinfo
>>> fileinfo.MP3FileInfo            
<class fileinfo.MP3FileInfo at 01257FDC>
>>> fileinfo.MP3FileInfo.tagDataMap 
{'title': (3, 33, <function stripnulls at 0260C8D4>), 
'genre': (127, 128, <built-in function ord>), 
'artist': (33, 63, <function stripnulls at 0260C8D4>), 
'year': (93, 97, <function stripnulls at 0260C8D4>), 
'comment': (97, 126, <function stripnulls at 0260C8D4>), 
'album': (63, 93, <function stripnulls at 0260C8D4>)}
>>> m = fileinfo.MP3FileInfo()      
>>> m.tagDataMap
{'title': (3, 33, <function stripnulls at 0260C8D4>), 
'genre': (127, 128, <built-in function ord>), 
'artist': (33, 63, <function stripnulls at 0260C8D4>), 
'year': (93, 97, <function stripnulls at 0260C8D4>), 
'comment': (97, 126, <function stripnulls at 0260C8D4>), 
'album': (63, 93, <function stripnulls at 0260C8D4>)}
MP3FileInfo는 클래스 자체입니다. 클래스의 특정한 실체가 아닙니다.
tagDataMap는 클래스 속성입니다: 문자 그대로, 클래스의 속성입니다. 클래스의 실체를 생성하기 전에 존재합니다.
클래스 속성은 그 클래스를 직접 참조할 수도 있고 클래스의 실체로 참조할 수도 있습니다.
자바(Java)에서 (파이썬에서 클래스 속성이라고 부르는) 정적 변수와 (파이썬에서 데이터 속성이라고 부르는) 실체 변수는 모두 클래스가 정의되자 마자 바로 정의됩니다 (하나는 static 키워드로 정의되고, 다른 하나는 static 키워드 없이 정의됩니다). 파이썬은 오직 클래스 속성만 정의됩니다; 데이터 속성은 __init__ 메쏘드에 정의됩니다.

클래스 속성은 클래스-수준의 상수로 사용이 가능합니다 (이 방법을 MP3FileInfo에 사용했습니다). 그러나 진짜 상수는 아니며 여전히 바꿀 수 있습니다.

파이썬에서 상수는 전혀 없습니다. 무엇이든 열심히 노력하면 다 바꿀 수 있습니다. 이는 파이썬의 핵심 원리중의 하나입니다: 나쁜 행위는 권면하지 않을 뿐, 금지되면 안됩니다. 정말로 None의 값을 바꾸고 싶다면 그럴 수 있지만 본인에게 달려와 코드가 디버그할 수 없게 되었다고 항의하지 마세요.

예제 5.18. 클래스 속성 수정하기

>>> class counter:
...     count = 0                     
...     def __init__(self):
...         self.__class__.count += 1 
...     
>>> counter
<class __main__.counter at 010EAECC>
>>> counter.count                     
0
>>> c = counter()
>>> c.count                           
1
>>> counter.count
1
>>> d = counter()                     
>>> d.count
2
>>> c.count
2
>>> counter.count
2
countcounter 클래스의 클래스 속성입니다.
__class__는 (어느 클래스의) 어느 실체에나 내장된 속성으로서 self 실체의 클래스를 가리킵니다 (이 경우, counter 클래스입니다).
count는 클래스 속성이므로 클래스의 실체를 만들기 전에 클래스를 직접 참조하여 얻을 수 있습니다.
클래스의 실체를 만들면 __init__ 메쏘드가 호출되며, 클래스 속성인 count1만큼 증가시킵니다. 이는 클래스 자체에 영향을 미치며, 방금 새로 생성된 실체에는 영향을 미치지 않습니다.
두 번째 실체를 만들면 클래스 속성인 count가 또 증가합니다. 클래스 자신과 그 클래스의 모든 실체들이 어떻게 클래스 속성을 공유하는지 주목하세요.

5.9. 비공개 함수

다른 언어들처럼 파이썬은 비공개 원소라는 개념이 있습니다:

  • 비공개 함수, 이는 모듈 밖에서 호출이 불가능하다
  • 비공개 클래스 메쏘드, 이는 클래스 밖에서 호출이 불가능하다
  • 비공개 속성, 이는 클래스 밖에서 접근이 불가능하다.

다른 언어와는 다르게 파이썬에서 함수나 메쏘드 또는 속성이 비공개인지 공개인지 여부는 전적으로 그의 이름에 따라 결정됩니다.

파이썬에서 함수나 클래스 메쏘드 또는 속성의 이름이 두 개의 밑줄문자로 시작하면 (그러나 두 개의 밑줄문자로 끝나지는 않음), 그것은 비공개입니다; 다른 것은 모두 공개됩니다. 파이썬은 (클래스 자신과 자손 클래스에서만 접근이 가능한) 보호된(protected) 클래스 메쏘드라는 개념이 없습니다. 클래스 메쏘드는 비공개이거나 (클래스 자신에서만 접근 가능) 또는 공개입니다 (다른 곳에서 접근 가능).

MP3FileInfo에는 두 가지 메쏘드가 있습니다: __parse 메쏘드와 __setitem__가 있습니다. 이미 연구한 바와 같이 __setitem__특수 메쏘드입니다; 보통 클래스 실체에 사전 구문을 사용하여 간접적으로 호출합니다. 그러나 그것은 공개되어 있으며 (fileinfo 모듈 바깥에서도) 충분한 이유가 있다면 직접적으로 호출할 수 있습니다. 그렇지만 __parse는 비공개입니다. 그의 이름 앞에 두 개의 밑줄문자가 있기 때문입니다.

파이썬에서 (__setitem__ 같은) 모든 특수 메쏘드와 (__doc__ 같은) 내장 속성은 표준적인 이름짓기 관례를 따릅니다: 두 개의 밑줄문자로 시작하고 끝납니다. 이런 식으로 메쏘드와 속성의 이름을 짓지 마세요. 왜냐하면 나중에 여러분과 (다른 사람들에게) 혼란을 야기할 것이기 때문입니다.

예제 5.19. 비공개 메쏘드 호출 시도해 보기

>>> import fileinfo
>>> m = fileinfo.MP3FileInfo()
>>> m.__parse("/music/_singles/kairo.mp3") 
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'MP3FileInfo' instance has no attribute '__parse'
비공개 메쏘드를 호출한다면 파이썬은 약간 야룻한 예외를 일으킵니다. 그 메쏘드가 존재하지 않는다고 알려줍니다. 물론 존재하지만 그것은 비공개이므로 클래스 바깥에서 접근할 수 없기 때문입니다. 엄밀히 말해 비공개 메쏘드는 클래스 밖에서 접근할 수 있습니다. 단지 쉽게 접근할 수 없을 뿐입니다. 파이썬에서는 진정으로 비공개인 것은 아무것도 없습니다; 내부적으로 비공개 메쏘드와 속성의 이름은 바로 바로 조작되어 주어진 이름으로는 접근할 수 없는 것처럼 보이게 만듭니다. _MP3FileInfo__parse이라는 이름으로 MP3FileInfo 클래스의 __parse 메쏘드에 접근할 수 있습니다. 이렇게 하는 것이 재미는 있지만 실제 코드에서는 절대로 사용하면 안됩니다. 비공개 메소드는 이유가 있어서 비공개입니다. 그러나 파이썬에서 다른 모든 것들과 마찬가지로 그 비공개성은 전적으로 관례의 문제일 뿐, 강제사항은 아닙니다.

비공개 함수에 관하여 더 읽어야 할 것

5.10. 요약

핵심적인 객체 트릭은 이 정도로 충분합니다. 제 12 장에서 특수 클래스 메쏘드가 실제 세계에 적용되는 모습을 보시겠습니다. 제 12 장에서는 getattr을 사용하여 원격 웹 서버에 대한 프록시를 만듭니다.

다음 장에서 계속 이 샘플 코드를 사용하여, 예를 들어 예외와 파일 객체 그리고 for 회돌이 같은 다른 파이썬 개념들을 살펴 보겠습니다.

다음 장으로 들어가기 전에 다음의 일들을 편안하게 해 낼 수 있는지 확인하세요:

☜ 제 04 장 강력한 내부검사 """ Dive Into Python """
다이빙 파이썬
제 06 장 예외와 파일처리 ☞