제 2 장 검사의 힘 목 차 제 4 장 HTML 처리하기 >>

제 3 장. 객체-지향 작업틀

3.1. 다이빙해 들어가기

이 장과 이 장 이후의 모든 장들은 객체-지향적 파이썬 프로그래밍을 더욱 더 많이 다룬다. 기억하는가? 이 책을 읽기 위해서는 객체-지향 언어를 알아야만 한다고 말한 바 있다. 자, 나는 농담한 것이 아니다.

다음은 완전하게 작동하는 파이썬 프로그램이다. 모듈, 클래스, 그리고 함수의 문서화 문자열(doc string)을 읽어서 이 프로그램이 무엇을 하는지 어떻게 움직이는지 개략적으로 알아보라. 예와 같이, 이해하지 못하는 것들에 관하여 걱정하지 마라; 그것은 이 장의 나머지가 해줄 일이다.

Example 3.1. fileinfo.py

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

"""Framework for getting filetype-specific metadata.

Instantiate appropriate class with filename.  Returned object acts like a
dictionary, with key-value pairs for each piece of metadata.
    import fileinfo
    info = fileinfo.MP3FileInfo("/music/ap/mahadeva.mp3")
    print "\\n".join(["%s=%s" % (k, v) for k, v in info.items()])

Or use listDirectory function to get info on all files in a directory.
    for info in fileinfo.listDirectory("/music/ap/", [".mp3"]):
        ...

Framework can be extended by adding classes for particular file types, e.g.
HTMLFileInfo, MPGFileInfo, DOCFileInfo.  Each class is completely responsible for
parsing its files appropriately; see MP3FileInfo for example.
"""
import os
import sys
from UserDict import UserDict

def stripnulls(data):
    "strip whitespace and nulls"
    return data.replace("\00", "").strip()

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

class MP3FileInfo(FileInfo):
    "store ID3v1.0 MP3 tags"
    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):
        "parse ID3v1.0 tags from MP3 file"
        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):
    "get list of file info objects for files of particular extensions"
    fileExtList = [ext.upper() for ext in fileExtList]
    fileList = [os.path.join(directory, f) for f in os.listdir(directory) \
                if os.path.splitext(f)[1].upper() in fileExtList]
    def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]):
        "get file info class from filename extension"
        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"]): 1
        print "\n".join(["%s=%s" % (k, v) for k, v in info.items()])
        print
1 이 프로그램의 출력 결과는 여러분의 하드 디스크에 달려 있다. 의미 있는 출력을 얻으려면, 디렉토리 경로를 바꾸어서 여러분의 컴퓨터에서 MP3 파일이 있는 디렉토리를 가르키도록 할 필요가 있을 것이다.

Example 3.2. Output of fileinfo.py

다음은 나의 컴퓨터에서 얻은 출력이다. 여러분의 출력은 다를 것이다. 그렇지 않다면, 적지 않게 놀라운 우연의 일치로서 음악에 대한 나의 꼼꼼한 취향을 함께 나누게 될 것이다.

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

3.2. 'from module import'을 사용하여 모듈을 반입하기

파이썬은 두가지 방식으로 모듈을 반입한다. 둘 다 유용하며 그리고 언제 각각 사용할지를 알아야 한다. 한 가지 방식은 import module 인데, 이미 제 1 장에서 본 바가 있다. 다른 방식도 같은 일을 달성하지만 미묘하고 중요하게 다른 방식으로 작동한다.

Example 3.3. Basic from module import syntax

from UserDict import UserDict

이것은 여러분이 알고 있고 좋아하는 import module 구문과 비슷하다. 그러나 중요한 차이점이 존재하는데: 반입된 모듈의 types의 속성과 메쏘드는 직접적으로 지역 이름영역으로 반입된다. 그래서 모듈의 이름으로 권한을 부여하지 않더라도, 직접적으로 사용이 가능하다.

Note
파이썬에서의 'from module import'은 펄에서의 'use module'과 비슷하고; 파이썬에서의 'import module'은 펄에서의 'require module'과 비슷하다.

Example 3.4. import module vs. from module import

>>> import types
>>> types.FunctionType             1
<type 'function'>
>>> FunctionType                   2
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
NameError: There is no variable named 'FunctionType'
>>> from types import FunctionType 3
>>> FunctionType                   4
<type 'function'>
1 types모듈은 메쏘드를 가지지 않고, 단지 각각의 파이썬 객체의 형을 위한 속성만을 가진다. FunctionType속성은 그 모듈의 이름 types에 의하여 권한이 부여되야만 한다는 것을 주목하라.
2 FunctionType 자체는 이 이름공간에서 정의되지 않았다; 오로지 types의 문맥에서만 존재한다.
3 이 구문은 FunctionType 속성을 types모듈로부터 직접적으로 지역 이름공간으로 반입한다.
4 이제 FunctionTypetypes을 참조할 필요없이, 직접적으로 접근할 수 있다.

언제 'from module import'를 사용해야 하는가?

이외에 다른 것들은 스타일의 문제이다. 두 가지 방식으로 씌여진 파이썬 코드를 보게 될 것이다.

더 읽어야 할 것

3.3. 클래스를 정의하기

파이썬은 충분하게 객체-지향적이다: 자신만의 클래스를 정의할 수 있고, 자신의 클래스 혹은 내장 클래스로부터 상속받을 수 있으며, 그리고 정의한 클래스를 실체화할 수 있다.

파이썬에서 클래스를 정의하는 것은 어렵지 않다; 함수와 같은 것에 따로 분리된 인터페이스 정의는 없다. 그냥 클래스를 정의하고 코딩을 시작하면 된다. 파이썬의 클래스는 예약어인 class로 시작하고, 그 다음에 그 클래스의 이름이 따른다. 기술적으로 그것이 필요한 모두이다. 왜냐하면 클래스는 다른 어떤 클래스로부터 상속받아야 할 필요가 없기 때문이다.

Example 3.5. The simplest Python class

class foo: 1
    pass   2 3
1 이 클래스의 이름은 foo이다. 다른 어떤 클래스로부터도 상속받지 않는다.
2 이 클래스는 어떤 속성이나 메쏘드도 정의하지 않는다. 그러나 구문적으로 그 정의에는 무엇인가가 필요하다. 그래서 pass를 사용한다. 이것은 파이썬의 예약어로서 단지 “그냥 지나가라, 여기에는 볼 것이 없다”는 것을 뜻한다. 이는 아무것도 하지 않는 서술문으로서 함수나 클래스를 임시 설치할 때 훌륭한 위치 지정자이다.
3 아마도 이것을 예상했겠지만, 함수와 if 서술문 그리고 for 회돌이 등등의 안에서의 코드와 마찬가지로, 클래스안의 모든 것은 들여쓰기 된다. 들여쓰기되지 않은 첫 번째 것은 클래스안에 있는 것이 아니다.
Note
파이썬에서 pass 서술문은 자바나 C 에서의 요소없는 괄호({})와 같다.

물론, 실제적으로 대부분의 클래스는 다른 클래스로부터 상속을 받을 것이다. 그리고 자신만의 클래스의 메쏘드와 속성을 정의할 것이다. 그러나 방금 살펴본 바와 같이, 이름 말고는 클래스가 절대적으로 가져야 하는 것은 없다. 특히나, C++ 프로그래머들은 파이썬의 클래스가 명시적인 구성자와 파괴자를 가지지 않는다는 것을 이상하다고 생각할지도 모른다. 파이썬의 클래스에 구성자와 비슷한 것이 있다면: __init__ 메쏘드가 있다.

Example 3.6. Defining the FileInfo class

from UserDict import UserDict

class FileInfo(UserDict): 1
1 파이썬에서 한 클래스의 조상은 그 클래스 이름뒤 괄호안에 단순히 나열된다. 그래서 FileInfo 클래스는 UserDict 클래스로부터 상속받는다 (UserDict 모듈로부터 반입되었음). UserDict는 사전처럼 행동하는 클래스로서, 사전 데이타형을 간결하게 하부클래스화할 수 있도록 해주고 여러분 자신만의 행위를 추가할 수 있도록 해준다. (UserListUserString 같은 클래스도 있어서 리스트와 문자열을 하부클래스화할 수 있다.) 이 뒤에는 약간은 신비스러운 마법이 있다. 이 장에서 UserDict 클래스를 더욱 깊이 탐험해 보면서 천천히 그 마법을 풀어헤쳐 보겠다.
Note
파이썬에서 한 클래스의 조상은 그 클래스 이름뒤의 괄호안에 단순히 나열된다. 거기에는 자바의 extends와 같이 특별한 키워드는 없다.
Note
이 책에서는 깊이 다루지는 않겠지만, 파이썬은 다중 상속을 지원한다. 클래스 이름 뒤를 따르는 괄호 안에 원하는 갯수 만큼 쉼표로 구분하여 조상클래스들을 나열할 수 있다.

Example 3.7. Initializing the FileInfo class

class FileInfo(UserDict):
    "store file metadata"              1
    def __init__(self, filename=None): 2 3 4
1 클래스는 모듈 그리고 함수와 마찬가지로 똑 같이, 문서화 문자열(doc string)을 가질 수 있으며 (그리고 가져야만 한다).
2 __init__은 클래스의 실체가 하나 생성된 후에 즉시 호출된다. 이것을 클래스의 구성자라고 부르는 것은 매력적일지도 모르지만 부정확하다.

매력적이다. 왜냐하면 구성자와 비슷해 보이고 (관례적으로 __init__은 클래스에서 정의된 첫 번째 메쏘드임), 구성자처럼 행동하며 (새로 생성된 클래스의 실체에서 실행되는 첫 번째 코드 조각임), 그리고 심지어 구성자처럼 들리기 때문이다 (“init”은 확실히 구성자-같은 속성을 보여준다).

부정확하다. 왜냐하면 객체는 __init__이 호출되는 순간까지 이미 구성되어 있기 때문이며, 그리고 이미 클래스의 새로운 실체에 대한 유효한 참조점을 가지고 있기 때문이다. 그러나 __init__은 구성자로 파이썬에서 간주할 수 있는 가장 가까운 것이다. 그리고 거의 같은 역할을 수행한다.

3 __init__을 포함하여, 모든 클래스 메쏘드의 첫 번째 인자는 클래스의 현재 실체에 대한 참조점이다. 관례적으로, 이러한 인자는 항상 self라고 이름 짓는다. __init__ 메쏘드에서, self는 새로 만들어진 객체를 가리킨다; 다른 클래스 메쏘드에서는 자신의 메쏘드가 불려진 그 실체를 가리킨다. 메쏘드를 정의할 때 self를 명시적으로 지정할 필요가 있지만, 메쏘드를 호출할 때에는 지정하지 않는다; 파이썬이 자동으로 추가해 줄 것이다.
4 __init__ 메쏘드는 함수와 마찬가지로 똑 같이, 인자를 얼마든지 취할 수 있고, 인자는 기본값이 정의 될 수 있어서, 호출자가 선택할 수 있다. 이 경우에, filename은 기본 값 None을 가지며, 파이썬의 null 값이다.
Note
관례적으로, 모든 클래스 메쏘드의 첫 번째 인자는 (현재 실체의 참조점은) self 라고 불리운다. 이 인자는 C++이나 자바에 있는 예약어 this의 역할을 수행한다. 그러나 self는 파이썬에서 예약어가 아니다. 단순히 이름짓기 관행일 따름이다. 그럼에도 불구하고, self말고 다른 것으로 부르지 마라; 절대로 따라야 할 강력한 관례이기 때문이다.

Example 3.8. Coding the FileInfo class

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)        1
        self["name"] = filename        2
                                       3
1 파워빌더와 같은 어떤 객체-지향-비슷한 언어들은 구성자와 다른 사건들을 “확장(extending)”한다는 개념을 가지고 있는데, 거기에서는 조상의 메쏘드가 자손의 메쏘드가 실행되기 전에 자동적으로 호출된다. 파이썬은 이렇게 하지 않는다; 항상 명시적으로 조상 클래스의 적절한 메쏘드를 호출해야만 한다.
2 이 클래스는 사전처럼 행동한다고 말한 바 있다. 여기에 그 첫 징후가 있다. 인자 filename을 이 객체의 name키의 값으로 할당한다.
3 __init__ 메쏘드는 값을 절대로 반환하지 않는다는 것을 주목하라.
Note
클래스 메쏘드를 정의할 때, 반드시 __init__을 포함하여, 명시적으로 self를 각 메쏘드의 첫 번째 인자로 나열해야 한다. 클래스 안으로부터 한 조상 클래스의 메쏘드를 호출한다면, 반드시 self 인자를 포함해야만 한다. 그러나 밖으로부터 클래스 메쏘드를 호출한다면, self 인자를 위하여 아무것도 지정하지 않는다; 이런 일을 하지 않더라도, 파이썬이 자동으로 여러분 대신에 그 실체 참조점을 추가한다. 언뜻 보면 혼란스러울 것이다; 실제로 모순은 아니지만, 모순처럼 보이는데 왜냐하면 여러분이 아직 모르는 (엮기 메쏘드 그리고 풀기 메쏘드 사이의) 구별에 의존하기 때문이다.

휴우! 받아들이기에는 너무 내용이 많다는 것을 알지만, 요점을 이해하게 될 것이다. 파이썬에서 모든 클래스들은 같은 방식으로 작동한다. 그래서 한개를 배우기만 하면, 모든 것을 배운 셈이다. 다른 모든 것들을 잊어 버릴지라도, 이 하나만은 기억하라. 왜냐하면 장담하건데 그것 때문에 여러분은 고생할 것이기 때문이다:

Note
__init__ 메쏘드는 선택적이다. 그러나 메쏘드를 정의할 때, 그 조상의 __init__ 메쏘드를 명시적으로 호출해야 한다는 것을 기억해야 한다. 다음은 일반적으로 옳다: 자손이 그 조상의 행위를 확장하고자 할 때 마다, 자손 메쏘드는 반드시 명시적으로 적절한 시기에 적절한 인자를 가지고, 그 조상의 메쏘드를 호출해야만 한다.

더 읽어야 할 것

3.4. 클래스를 실체화하기

파이썬에서 클래스를 실체화하는 것은 쉬운 일이다. 클래스를 실체화하려면 그냥 클래스를 마치 함수인것 처럼 __init__ 메쏘드가 정의한 인자들을 넘겨주고 호출 하면 된다. 반환값은 새로 만들어진 객체가 될 것이다.

Example 3.9. Creating a FileInfo instance

>>> import fileinfo
>>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3") 1
>>> f.__class__                                        2
<class fileinfo.FileInfo at 010EC204>
>>> f.__doc__                                          3
'base class for file info'
>>> f                                                  4
{'name': '/music/_singles/kairo.mp3'}
1 (fileinfo 모듈에서 정의한) FileInfo 클래스의 실체를 만든다. 새로 만들어진 실체를 변수 f에 할당한다. 한개의 매개변수 /music/_singles/kairo.mp3를 넘겨준다. "FileInfo'의 __init__" 메쏘드에 있는 filename인자로 결말이 날 것이다.
2 모든 클래스 실체는 내장 속성으로 __class__를 가지는데, 그 객체의 클래스이다 (주의할 것은 이 표현이 나의 컴퓨터에서 그 실체의 물리적 주소를 포함한다는 것이다; 여러분의 표현은 다를 것이다.) 자바 프로그래머들은 Class 클래스에 익숙할 텐데, 그것은 getName 그리고 getSuperclass와 같은 메쏘드를 가지고 있어서, 한 객체에 대한 추상데이타 정보를 획득한다. 파이썬에서 이러한 종류의 추상데이타는 "__class__, __name__, 그리고 __bases__"와 같은 속성을 통하여 그 객체 자신에 대하여 직접적으로 사용가능하다..
3 실체의 문서화 문자열(doc string)에 함수나 모듈과 마찬가지로 똑 같이 접근할 수 있다. 한 클래스의 모든 실체들은 같은 문서화 문자열(doc string)을 공유한다.
4 __init__ 메쏘드가 filename 인자를 self["name"]에 할당하던 때를 기억하는가? 자, 여기에 그 결과가 있다. 클래스 실체를 생성할 때 넘겨준 인자는 바로 __init__ 메쏘드에 보내어진다 (파이썬은 객체 참조점 self와 함께 그것을 공짜로 추가해 준다).
Note
파이썬에서는 단순히 함수처럼 한 클래스를 호출해서 그 클래스의 새로운 실체를 생성하면 된다. 거기에는 C++ 혹은 자바처럼 아무런 명시적인 new 연산자가 없다.

새로운 실체를 생성하는 것이 쉽지만, 파괴하는 것은 더 쉽다. 명시적으로 실체를 없앨 필요는 일반적으로 없다. 왜냐하면 실체에 할당된 변수는 범위를 벗어날 때 자동으로 제거되기 때문이다. 메모리 누수현상은 파이썬에서 드물다.

Example 3.10. Trying to implement a memory leak

>>> def leakmem():
...     f = fileinfo.FileInfo('/music/_singles/kairo.mp3') 1
...     
>>> for i in range(100):
...     leakmem()                                          2
1 leakmem 함수가 호출될 때마다, FileInfo의 실체를 작성하고 그것을 그 함수의 지역 변수인 변수 f에 할당한다. 그리고 그 함수는 f를 전혀 제거하지 않고 종료한다. 그래서 여러분은 메모리 누수를 예상하겠지만, 그 예상은 빗나갈 것이다. 함수가 끝나면 지역 변수 f는 범위를 벗어난다. 이 시점에서 새로 생성된 FileInfo의 실체에 대한 참조가 더 이상 존재하지 않는다 (f 말고는 다른 어느것에도 할당하지 않았기 때문이다). 그래서 파이썬은 그 실체를 대신 파괴해 준다.
2 아무리 많이 leakmem 함수를 호출한다고 하여도, 절대로 메모리가 누수되지는 않는다. 왜냐하면 매번 파이썬은 새로 생성된 FileInfo 클래스를 leakmem으로부터 돌아오기 전에 파괴할 것이기 때문이다.

이러한 형태의 쓰레기 수집을 표현하는 기술적 용어는 “참조횟수 세기(reference counting)” 이다. 파이썬은 생성되는 모든 실체에 대하여 참조 목록을 유지한다. 위의 예제에서는, FileInfo 실체에 대하여 오직 하나의 참조만이 있다 : 지역 변수 f가 바로 그것이다. 함수가 종료하면, 변수 f는 범위를 벗어나고, 그래서 참조횟수는 0으로 떨어진다. 그리고 파이썬은 그 실체를 자동으로 파괴한다.

이전 버전의 파이썬에서는 참조횟수세기가 실패하고, 파이썬이 여러분을 따라다니며 깨끗히 청소할 수 없는 상황이 있었다. 만약 서로를 참조하는 두 개의 실체를 생성한다면 (예를 들어, 이중-연결 리스트에서, 각 노드는 그 리스트에서 바로 전과 후를 가리키는 포인터를 가지므로), 두 실체 모두는 절대로 자동으로 파괴되지 않을 것이다. 왜냐하면 파이썬은 (정확하게) 각 실체에 대한 참조가 항상 존재한다고 믿기 때문이다. 파이썬 2.0에는 부가적인 형태로 “기록-그리고-청소(mark-and-sweep)”라고 불리우는 쓰레기 수집이 있다. 이 쓰레기 수집기는 이러한 실제적인 교차병목을 알아채고 순환참조를 정확하게 청소할 정도로 똑똑하다.

상상의 나래를 펴게 만드는 고전 철학의 주요 논제가 있다. 아무도 보아주는 사람이 없으면 사물은 사라진다. 그것은 파이썬에서 정확하게 똑 같이 일어나는 일이다. 일반적으로 그냥 메모리 관리에 관하여 잊어 버릴수 있고 파이썬이 여러분을 따라다니며 청소하도록 그대로 두기만 하면 된다.

더 읽어야 할 것

3.5. UserDict: 포장 클래스

보시다시피, FileInfo는 사전처럼 행동하는 클래스이다. 더 깊이 탐구해 보기 위해 FileInfo 클래스의 조상인, UserDict 모듈에 있는 UserDict클래스를 살펴보자. 특별한 것은 아니지만; 이 클래스는 파이썬으로 씌여져 있으며, 우리의 코드와 마찬가지로 똑 같이 .py 파일에 저장되어 있다. 특별한 것은 파이썬이 설치된 lib 디렉토리에 저장되어 있다는 것이다.

Tip
윈도우즈의 파이썬 IDE 에서, 'File->Locate... (Ctrl-L)'로 라이브러리 경로에 있는 어떤 모듈이라도 바로 열 수 있다.

파이썬에서는 문자열, 리스트 그리고 사전과 같은 내장 데이타형을 하부클래스화할 수 없다. 이것을 보상하기 위하여 파이썬에는 이러한 내장 데이타형의 행위를 흉내내는 포장클래스가 따라온다: UserString, UserList, 그리고 UserDict가 바로 그것이다. 일반 메쏘드와 특수 메쏘드를 결합하여 사용함으로써, UserDict 클래스는 사전을 훌륭하게 흉내낸다. 그러나 단지 다른 모든 것과 마찬가지로 하나의 클래스일 뿐이기 때문에, 그것을 하부클래스화하면 FileInfo와 같은 사용자 맞춤 사전-류의 클래스를 제공할 수 있다.

Example 3.11. Defining the UserDict class

class UserDict:                                1
    def __init__(self, dict=None):             2
        self.data = {}                         3
        if dict is not None: self.update(dict) 4
1 주목할 것은 UserDict는 기본 클래스이며, 다른 어떤 클래스로부터도 상속된 것이 아니라는 것이다.
2 이것은 FileInfo 클래스에서 덮어썼던 __init__ 바로 그 메쏘드이다. 이러한 조상 클래스의 인자 목록은 그 자손과는 다르다는 것을 주목하라. 좋다; 정확한 인자들로 그 조상을 호출하는 한, 각 하부클래스는 자신만의 인자 집합을 가질수 있다. 여기에서 그 조상 클래스는 (dict 인자에 하나의 사전을 넘겨줌으로써) 초기화 값을 정의하는 한가지 방법을 가진다. FileInfo는 그 방법을 이용하지 않는다.
3 파이썬은 (자바과 파워빌더에서는 “실체 변수 (instance variables)”이라고 불리우며, C++에서는 “멤버 변수 (member variables)”라고 불리우는) 데이타 속성을 지원한다. 이는 한 클래스의 특별한 실체에 의해 유지되는 데이타이다. 이 경우에 UserDict의 각 실체는 data라는 데이타 속성을 가진다. 이 속성을 그 클래스의 바깥에 있는 코드로부터 참조하려면, 'instance.data'와 같이 함수에 모듈이름으로 권한을 부여한 것과 똑 같은 방식으로, 그 실체 이름으로 권한을 부여해야 한다. 클래스의 안쪽으로부터 데이타 속성을 참조하려면 self를 권한 부여자로 사용한다. 관례적으로, 모든 데이타 속성들은 __init__ 메쏘드에서 합리적인 값들로 초기화된다. 그렇지만, 이것은 필요하지 않다. 왜냐하면 지역 변수 같은 데이타 속성들은 처음으로 하나의 값을 할당받을 때 돌발적으로 출현하기 때문이다.
4 이것은 이전에 보지 못했을 구문이다 (이 책에서 사용한 적이 없음). 이것은 if 서술문이다. 그러나 다음 줄에 들여쓰기된 블록을 가지는 대신에, 같은 줄에 쌍점, 그 다음에 단지 한개의 서술문이 있을 따름이다. 이것은 완전하게 적법한 구문이다. 그리고 한 블록에 오직 한 개의 서술문만을 가질 때 그것은 지름길이 된다. (그것은 마치 C++에서 괄호없이 한개의 서술문을 지정하는 것과 비슷하다.) 이러한 구문을 사용할 수 있으며, 또는 이어지는 줄에 들여쓰기된 코드를 가질 수도 있다. 그러나 그 둘 다를 같은 블록에 사용할 수는 없다.
Note
자바와 파워빌더는 인자 리스트에 의한 함수 덮어쓰기를 지원한다. 다시 말하면 하나의 클래스는 서로 다른 개수의 인자, 혹은 서로 다른 형의 인자를 가지지만 같은 이름을 가진 여러개의 메쏘드를 가질 수 있다. 다른 언어에서는 (PL/SQL에서 대단히 현저한데) 심지어 인자 이름에 의한 함수 덮어쓰기를 지원한다; 즉, 하나의 클래스는 서로 다른 인자 이름을 가지지만, 같은 개수의 같은 형의 인자를 가지고 같은 이름을 가지는 여러개의 메쏘드를 가질 수 있다. 파이썬은 이 둘 모두를 지원하지 않는다; 함수 덮어쓰기와 같은 등등의 어떤 형태도 가지지 않는다. 그의 인자에 상관없이, __init__ 메쏘드는 그냥 __init__ 메쏘드일 뿐이다. 클래스당 __init__ 메쏘드는 오직 하나만 있을 수 있다. 자손 클래스가 __init__ 메쏘드를 가진다면, 그 자손이 서로 다른 인자 리스트로 정의할 지라도, 언제나 그 조상의 __init__ 메쏘드를 덮어쓸 것이다.
Note
__init__ 메쏘드에 있는, 실체의 모든 데이타 속성들에 대하여 초기화 값을 항상 할당하라. 그렇게 하면 앞으로 디버깅 시간을 절약할 수 있을 것이다.

Example 3.12. UserDict normal methods

    def clear(self): self.data.clear()          1
    def copy(self):                             2
        if self.__class__ is UserDict:          3
            return UserDict(self.data)
        import copy                             4
        return copy.copy(self)
    def keys(self): return self.data.keys()     5
    def items(self): return self.data.items()
    def values(self): return self.data.values()
1 clear는 정상적인 클래스 메쏘드이다; 언제든지 누구나 호출해서 공개적으로 사용가능하다. 다른 모든 클래스 메쏘드처럼 clear는 그의 첫번째 인자로 self를 가진다. (기억하라. 메쏘드를 호출할 때 self를 포함하지 않는다; 그것은 파이썬이 여러분 대신에 더해주는 것이다.) 또한 이 포장 클래스의 기본 테크닉에 주목하라: 실제 사전(data) 을 데이타 속성으로 저장하고, 실제 사전이 가진 모든 메쏘드를 정의하며, 그리고 각 클래스 메쏘드를 그 실제 사전에서 상응하는 메쏘드로 방향전환 시킨다. (혹시 잊어 버리더라도 사전의 clear 메쏘드가 그의 모든 키들과 그들과 연관된 값들을 알아서 삭제한다.)
2 실제 사전의 copy 메쏘드는 새로운 사전을 반환하기 때문에 원래의 사전과 (키-값 쌍 모두가) 정확히 똑 같은 복사본이다. 그러나 UserDict는 단순하게 self.data.copy로 방향전환 될 수 없다. 왜냐하면 그 메쏘드는 실제 사전을 반환하고, 그리고 우리가 원하는 것은 self와 똑 같은 클래스인 새로운 실체를 반환하는 것이기 때문이다.
3 __class__ 속성을 사용하여 selfUserDict인가 확인한다; 만약 그렇다면, 적중한 것이다. 왜냐하면 UserDict를 복사하는 방법을 알기 때문이다: 새로운 UserDict를 만들고 그것에다 self.data에 열심히 모아온 그 진짜 사전을 건네라.
4 만약 self.__class__UserDict가 아니라면, 그러면 self는 틀림없이 UserDict의 어떤 하부클래스이다 (아마도 FileInfo마찬가지로), 이 경우에는 좀 더 꼼수가 필요하다. UserDict는 그의 자손중의 하나를 정확히 복사하는 방법을 알지 못한다; 예를 들어, 그 하부클래스에 다른 데이타 속성들이 있을지도 모른다. 그래서 그것들을 회돌이 하여 확실하게 그들 모두를 복사해야할 필요가 있을 것이다. 다행스럽게도, 파이썬에는 이것을 정확히 수행하는 모듈 하나가 따라온다. 그것은 copy라고 불리운다. 여기에서 세부적으로 들어가지는 않겠다 (그렇지만 혼자서 다이빙해 들어가고자 한다면 그것은 기가막히게 사악한 모듈이다). copy는 임의적인 파이썬 객체들을 복사할 수 있다고 충분하게 말할수 있으며, 그리고 그것이 여기에서 사용하고 있는 방법이다.
5 그 나머지 메쏘드들은 가뿐하게 호출들을 self.data에 있는 내장 메쏘드들로 방향 전환한다.

더 읽어야 할 것

3.6. 특별한 클래스 메쏘드

클래스 메쏘드에 부가해서, 파이썬 클래스가 정의할 수 있는 수 많은 특수한 메쏘드들이 있다. (다른 정상적인 메쏘드처럼) 직접적으로 호출되는 대신에, 특수 메쏘드는 특수한 상황 혹은 특별한 구문이 사용될 때 여러분을 대신하여 파이썬이 호출한다.

이 전의 섹션에서 보았듯이, 정상 메쏘드는 사전을 클래스로 싸기 위하여 긴 여행을 한다. 그러나 정상 메쏘드만으로는 충분하지 않다. 왜냐하면 그들에 대한 호출 메쏘드 외에도 사전으로 할 수 있는 일이 많기 때문이다. 초보자라면 명시적으로 메쏘드를 요청하는 것을 포함하지 않는 구문으로 요소를 획득할 수 있고 설정할 수 있다. 이곳이 바로 특수 클래스 메쏘드가 출현하는 곳이다: 비-메쏘드-호출 구문을 메쏘드 호출에 짝짓기 하는 방법을 제공한다.

Example 3.13. The __getitem__ special method

    def __getitem__(self, key): return self.data[key]
>>> f = fileinfo.FileInfo("/music/_singles/kairo.mp3")
>>> f
{'name':'/music/_singles/kairo.mp3'}
>>> f.__getitem__("name") 1
'/music/_singles/kairo.mp3'
>>> f["name"]             2
'/music/_singles/kairo.mp3'
1 __getitem__ 특수 메쏘드는 너무 단순하게 보인다. 정상 메쏘드 clear, keys, 그리고 values처럼, 단지 사전에 방향전환되어 그의 값을 반환한다. 그러나 어떻게 호출되는가? 음, __getitem__을 직접적으로 호출할 수 있지만, 실제로 그렇게 하지는 않을 것이다; 단지 여기에서는 어떻게 작동하는가를 보여주고 있을 뿐이다. __getitem__의 정확한 사용법은 파이썬이 여러분을 대신하여 호출하는 것이다.
2 이것은 사전 값을 획득하기 위하여 사용할 구문과 똑 같이 보인다. 그리고 실제로 예상한 값을 반환한다. 그러나 여기에는 빠진 것이 있다: 뚜껑을 열어보면, 파이썬은 이 구문을 메쏘드 호출 'f.__getitem__("name")'로 변환한다. 그것이 바로 __getitem__가 특수 클래스 메쏘드인 이유이다; 여러분이 직접 그것을 호출할 수 있을 뿐만 아니라, 그 정확한 구문을 사용하여 파이썬에게 여러분 대신 그것을 호출하도록 시킬 수 있다.

Example 3.14. The __setitem__ special method

    def __setitem__(self, key, item): self.data[key] = item
>>> f
{'name':'/music/_singles/kairo.mp3'}
>>> f.__setitem__("genre", 31) 1
>>> f
{'name':'/music/_singles/kairo.mp3', 'genre':31}
>>> f["genre"] = 32            2
>>> f
{'name':'/music/_singles/kairo.mp3', 'genre':32}
1 __getitem__ 메쏘드 처럼, __setitem__은 단순하게 실제 사전 self.data 으로 방향전환되어 자신의 일을 한다. __getitem__처럼, 보통 이런식으로 직접 호출하지는 않을 것이다; 정확한 구문을 사용하면 파이썬이 대신 __setitem__을 호출한다.
2 f는 사전처럼 보이려고 열심히 노력하는 실제의 클래스이며, 그리고 __setitem__은 그 위장의 핵심 부분이라는 점만 물론 제외하면, 이것은 평범한 사전 구문같이 보인다. 이 코드 줄은 뚜껑을 열어보면 실제로 f.__setitem__("genre", 32)를 호출한다.

__setitem__은 여러분 대신에 호출되기 때문에 특수 클래스 메쏘드이다. 그러나 여전히 클래스 메쏘드이다. UserDict에서 __setitem__메쏘드가 쉽게 정의되듯이, 그것을 자손 클래스에 재정의 하여 그 조상 메쏘드를 덮어쓸 수 있다. 이렇게 하면 어떤 면으로 사전처럼 행동하는 클래스들을 정의할 수 있으나 내장 사전을 넘어서는 자신만의 행위를 정의할 수 있다.

이러한 개념은 이 장에서 공부하고 있는 전체 작업틀의 기본토대이다. 각 파일 유형은 특별한 형태의 파일로부터 메타데이타를 획득하는 법을 알고 있는 처리 클래스를 가진다. (파일의 이름과 위치 같은) 어떤 속성들을 알게 되면, 처리 클래스는 다른 속성들을 자동으로 파생시키는 방법을 안다. 이것은 __setitem__ 메쏘드를 덮어쓰고, 특별한 키들을 점검하며, 그리고 그들이 발견될 때 부가적인 처리를 추가함으로써 수행된다.

예를 들어, MP3FileInfoFileInfo의 자손이다. MP3FileInfo이름(name)이 설정될 때, (그 조상 FileInfo가 그러한 것처럼) 단순히 name 키를 설정하지는 않는다; 그것은 또한 MP3 태그를 위한 파일 그 자체를 들여다 보고 전체 키들을 만들어 낸다.

Example 3.15. Overriding __setitem__ in MP3FileInfo

    def __setitem__(self, key, item):         1
        if key == "name" and item:            2
            self.__parse(item)                3
        FileInfo.__setitem__(self, key, item) 4
1 주목할 것은 __setitem__ 메쏘드가 정확하게 그 조상 메쏘드와 똑 같은 방식으로 정의된다는 것이다. 이것은 중요하다. 왜냐하면 파이썬이 우리를 대신하여 그 메쏘드를 호출할 것이기 때문이다. 그리고 파이썬은 그것이 일정개수의 인자로 정의될 것이라고 예상한다. (기술적으로 이야기 해서, 그 개수처럼 인자들의 이름도 문제가 되지 않는다.)
2 다음은 전체 MP3FileInfo 클래스의 핵심이다: 값 하나를 name 키에 할당하고 나면, 따로 더 어떤 일을 해 주기를 원한다.
3 name에 대하여 따로 더 처리할 일은 __parse 메쏘드에 캡슐화하는 것이다. 이 메쏘드는 MP3FileInfo에 정의된 또 다른 클래스 메쏘드이다. 이 메쏘드를 호출할 때, self로 권한을 부여한다. 단순히 __parse로 호출하면 그 클래스 밖에 정의한 정상적인 함수를 찾으려고 할 텐데, 그것은 원한 바가 아니다; self.__parse로 호출해야 그 클래스 안에 정의된 클래스 메쏘드를 찾을 것이다. 이것은 별로 새로운 것이 아니다; 같은 방식으로 데이타 속성들을 참조한다.
4 따로 더 처리를 한 후에, 그 조상의 메쏘드를 호출하기를 원한다. 기억하라. 이것은 파이썬에서 여러분을 대신하여 이루어지지 않는다; 수동으로 그렇게 할 필요가 있다. 주목할 것은 그 직접적인 조상이 __setitem__ 메쏘드를 가지고 있지 않을 지라도, FileInfo을 호출하고 있다는 것이다. 하지만 괜찮다. 왜냐하면 파이썬은 그 조상의 트리를 거슬러 올라가 우리가 호출하고 있는 클래스를 찾을 것이고, 그래서 이 코드 줄은 결국 UserDict에 정의된 __setitem__을 찾아서 호출할 것이기 때문이다.
Note
한 클래스 안에서 데이타 속성에 접근할 때, 속성의 이름에 권한을 줄 필요가 있다: self.attribute로 말이다. 한 클래스 안에서 다른 메쏘드들을 호출할 때, 메쏘드 이름에 권한을 줄 필요가 있다: self.method로 말이다.

Example 3.16. Setting an MP3FileInfo's name

>>> import fileinfo
>>> mp3file = fileinfo.MP3FileInfo()                   1
>>> mp3file
{'name':None}
>>> mp3file["name"] = "/music/_singles/kairo.mp3"      2
>>> 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" 3
>>> mp3file
{'album': '', 'artist': 'The Cynic Project', 'genre': 18, 'title': 'Sidewinder',
'name': '/music/_singles/sidewinder.mp3', 'year': '2000',
'comment': 'http://mp3.com/cynicproject'}
1 먼저, 파일이름을 주지 않고 MP3FileInfo의 실체를 생성한다. (이것을 생략할 수 있는데 __init__ 메쏘드의 filename 인자가 선택적이기 때문이다.) MP3FileInfo는 그 자신만의 아무런 __init__ 메쏘드를 가지지 않으므로, 파이썬은 그 조상의 트리를 거슬러 올라가 FileInfo__init__ 메쏘드를 찾는다. 이러한 __init__ 메쏘드는 수동으로 UserDict__init__ 메쏘드를 호출한다. 그리고나서 name 키를 filename에 설정한다. 그것은 None인데 파일 이름을 넘져주지 않았기 때문이다. 그리하여, 드디어 mp3file는 사전처럼 보이는데 name 키에 값을 None으로 한다.
2 이제부터 진짜 재미가 시작된다. mp3filename 키를 설정하면 (UserDict가 아니라) MP3FileInfo에 있는 __setitem__ 메쏘드를 촉발시킨다. 그것은 우리가 name 키에 실제 값을 설정한다는 것을 알아채고 self.__parse를 호출한다. __parse 메쏘드를 아직 추적하지 않았음에도 불구하고, 그 출력을 보면 몇 개의 다른 키들을 설정하는 것을 볼 수 있다: album, artist, genre, title, year, 그리고 comment.
3 name 키를 변경하면 같은 처리과정을 또 다시 밟게 될 것이다: 파이썬은 __setitem__을 호출한다. 이 메쏘드는 self.__parse를 호출하고, 이 메쏘드는 모든 다른 키들을 설정한다.

3.7. 고급의 특수 클래스 메쏘드

단지 __getitem__ 그리고 __setitem__ 말고도 더욱 많은 특수 메쏘드가 있다. 이들을 이용하면 이제껏 듣도 보도 못한 기능을 흉내낼 수 있다.

Example 3.17. More special methods in UserDict

    def __repr__(self): return repr(self.data)     1
    def __cmp__(self, dict):                       2
        if isinstance(dict, UserDict):
            return cmp(self.data, dict.data)
        else:
            return cmp(self.data, dict)
    def __len__(self): return len(self.data)       3
    def __delitem__(self, key): del self.data[key] 4
1 __repr__repr(instance)을 호출할 때 호출되는 특별한 메쏘드이다. repr 함수는 객체의 문자열 표현을 반환하는 내장 함수이다. 단지 클래스 실체 뿐만 아니라, 모든 객체에 작동한다. 그것에 대하여 알고 있지 못함에도 불구하고 이미 repr에 매우 친숙하다. 상호대화적인 창에서 그냥 변수 이름을 타자하고 ENTER를 치면, 파이썬은 repr을 사용하여 그 변수의 값을 출력한다. 약간의 데이타를 가진 사전 d를 작성하여 여러분 스스로 print repr(d) 하여 알아보라.
2 클래스 실체를 비교할 때 __cmp__가 호출된다. 일반적으로 클래스 실체뿐만 아니라, 어떠한 두 개의 파이썬 객체도, ==을 이용하여 비교할 수 있다. 내장 데이타형이 같다고 간주될 때에 정의하는 규칙이 있다; 예를 들어, 사전은 모두 같은 키와 값들을 가질 때 동일하며, 그리고 문자열은 같은 길이이고 같은 순서의 문자들을 가질 때 동일하다. 클래스 실체에 대하여, __cmp__ 메쏘드를 정의할 수 있고 여러분 스스로 비교 논리를 코드할 수 있다. 그러면 ==을 사용하여 클래스의 실체를 비교할 수 있고 파이썬은 __cmp__ 특수 메쏘드를 여러분을 대신하여 호출할 것이다.
3 len(instance)를 호출할 때 __len__이 호출된다. len 메쏘드는 객체의 길이를 반환하는 내장 메쏘드이다. 어떤 객체든지 길이를 가지고 있다고 충분히 간주되면 작동한다. 문자열에서 len은 문자의 개수를 가진다; 사전에서 len은 키의 개수를 가진다; 리스트 혹은 터플의 len은 요소의 개수를 돌려준다. 클래스 실체에 __len__ 메쏘드를 정의하고 손수 길이 계산 코드를 만들자. len(instance)를 호출하면, 파이썬이 여러분이 만든 __len__ 특수 메쏘드를 호출할 것이다.
4 __delitem__del instance[key]를 호출할 때 호출된다. 아마도 사전으로부터 항목들을 하나하나 제거하는 방법으로서 기억할 것이다. del을 클래스 실체에 사용할 때, 파이썬은 여러분 대신 __delitem__ 특수 메쏘드를 호출한다.
Note
자바에서는 두 개의 문자열 변수가 같은 물리적 메모리 위치를 참조하는지를 str1 == str2사용하여 결정한다. 이것을 객체 자기확인 (object identity)이라고 부른다. 파이썬에서는 'str1 is str2'로 씌여진다. 자바에서 문자열을 비교하려면, str1.equals(str2)을 사용할 것이고; 파이썬이라면, str1 == str2을 사용할 것이다. 자바에서의 '=='는 값보다는 자기확인에 의해서 비교를 하기 때문에 자신의 세상이 가장 좋은 곳이라고 믿도록 배워온 자바 프로그래머들은 파이썬에 그러한 “훌륭한 것 (gotchas)”이 없다는 것에 대하여 아마도 시간을 두고 곤혹스런 적응이 필요할 지도 모른다.

이 시점에서, 이렇게 생각하고 있을지도 모르겠다. “하나의 클래스에서 무언가를 해야한다면 이 모든 일들을 내장 데이타형으로 처리할 수 있다”고 말이다. 사실 사전과 같은 내장 데이타형으로부터 상속받을 수 있다면 삶이 좀 더 편해질 수 있다 (전체 UserDict 클래스는 불필요하게 될 것이다). 그러나 그렇게 할 수 있다고 해도, 특수 메쏘드는 여전히 쓸모가 있다. 왜냐하면 UserDict와 같은 감싸개 클래스 뿐만 아니라, 어떠한 클래스에도 사용될 수 있기 때문이다.

특수 메쏘드란 단지 __setitem__메쏘드를 정의함으로써, 어떠한 클래스도 사전과 같은 키-값 쌍을 저장할 수 있다는 것을 뜻한다. 어떠한 클래스도 그냥 __getitem__메쏘드를 정의해 주면 연속열처럼 행동할 수 있다. __cmp__가 정의된 클래스는 모두 ==로 비교될 수 있다. 클래스가 길이를 가진 어떤 것을 나타낸다면, GetLength 메쏘드를 정의하지 마라; __len__ 메쏘드를 정의하고 그리고 len(instance)를 사용하라.

Note
다른 객체-지향 언어에서는 겨우 객체의 물리적 모델을 정의할 수 있을 뿐이지만 (“이 객체는 GetLength 메쏘드를 가진다 ”), 반면에 __len__과 같은 파이썬의 특수 클래스 메쏘드는 객체의 논리적 모델을 정의하도록 하여 준다 (“이 객체는 길이를 가진다”).

다른 많은 특수 메쏘드들이 있다. 클래스를 숫자들처럼 행동하도록 하여 주는 메쏘드의 집합이 준비되어 있어서, 클래스 실체에 대하여 더하고, 빼고 그리고 다른 모든 수치 처리를 할 수있다 (모범적인 예는 실수부와 허수부를 가진 복소수를 나타내는 클래스이다.) __call__ 메쏘드는 클래스를 마치 함수처럼 행동하도록 하여 주어서, 클래스 실체를 직접적으로 호출할 수 있다. 그리고 다른 특수 메쏘드들이 있어서 클래스들이 읽기-전용 그리고 쓰기-전용 데이타 속성을 가지도록 해준다; 그것들에 관하여 이 후의 장에서 더 자세히 논의하겠다.

더 읽어야 할 것

3.8. 클래스 속성

이미 데이타 속성에 관하여 배웠다. 그것은 클래스의 특별한 실체가 소유하는 변수들이다. 또한 파이썬은 클래스 속성을 지원한다. 그것은 그 클래스 자체가 소유하고 있는 변수들이다.

Example 3.18. Introducing class attributes

class MP3FileInfo(FileInfo):
    "store ID3v1.0 MP3 tags"
    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            1
<class fileinfo.MP3FileInfo at 01257FDC>
>>> fileinfo.MP3FileInfo.tagDataMap 2
{'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()      3
>>> 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>)}
1 MP3FileInfo는 클래스의 어떠한 특별한 실체가 아니라, 클래스 그 자체이다.
2 tagDataMap은 클래스 속성이다: 문자적으로 이야기해서, 클래스의 속성이다. 클래스의 어떠한 실체도 생성하기전에 그 속성을 사용할 수 있다.
3 클래스 속성은 클래스에 대하여 직접 참조를 통하여 그리고 그 클래스의 모든 실체를 통하여 모두 사용가능하다.
Note
자바에서 (파이썬에서는 클래스 속성이라 부르는) 정적 변수들과 (파이썬에서는 데이타 속성이라고 부르는) 실체 변수들은 모두 그 클래스 정의 뒤에 바로 (하나는 static키워드로, 또 하나는 없이) 정의된다. 파이썬에서는 오직 클래스 속성만이 여기에 정의될 수 있다; 데이타 속성은 __init__ 메쏘드에 정의된다.

클래스 속성은 클래스-수준의 상수들처럼 사용된다 (MP3FileInfo에서 사용하는 방법이다). 그러나 실제로는 상수가 아니다.[4] 그것들을 변경할 수도 있다.

Example 3.19. Modifying class attributes

>>> class counter:
...     count = 0                     1
...     def __init__(self)
...         self.__class__.count += 1 2
...     
>>> counter
<class __main__.counter at 010EAECC>
>>> counter.count                     3
0
>>> c = counter()
>>> c.count                           4
1
>>> counter.count
1
>>> d = counter()                     5
>>> d.count
2
>>> c.count
2
>>> counter.count
2
1 countcounter 클래스의 클래스 속성이다.
2 __class__는 (모든 클래스의) 모든 클래스 실체가 가진 내장 속성이다. self를 그의 실체로 하는 클래스에 대한 참조점이다 (이 경우에는 counter 클래스임).
3 count는 클래스 속성이므로, 클래스의 어떤 실체를 생성하기 전이라도, 클래스에 대하여 직접 참조를 하면 이용할 수 있다.
4 클래스의 실체를 하나 생성하면 __init__ 메쏘드를 호출하는데, 클래스 속성 count1 만큼 증가된다. 이것은 새로 생성된 실체뿐만 아니라, 클래스 자신에도 영향을 미친다.
5 두 번째 실체를 하나 생성하면 또 다시 그 클래스 속성 count를 증가시킬 것이다. 어떻게 클래스 속성을 그 클래스와 그의 모든 실체들이 공유하는지를 주목하라.

3.9. 사적 함수

대부분의 언어들처럼 파이썬은 다음의 개념들을 가진다; 사적 함수는 모듈 밖으로부터 호출될 수 없다; 사적 클래스 메쏘드는 클래스 밖으로부터 호출될 수 없다; 그리고 사적 속성은 클래스 밖으로부터 접근할 수 없다. 대부분의 언어들과는 다르게 파이썬의 함수와 메쏘드, 또는 속성들이 사적인가 혹은 공적인가 하는 것은 전적으로 이름에 의해 결정된다.

MP3FileInfo에는 두 개의 메쏘드가 있다: __parse 그리고 __setitem__이 그것으로서 이미 논의한 바와 같이, __setitem__특수 메쏘드이다; 보통이라면, 클래스 실체에 관한 사전 구문을 사용하여 간접적으로 호출할 것이다. 그러나 이 메쏘드는 공적이므로, 진짜 충분한 이유가 있다면 (심지어 fileinfo모듈 밖에서도) 직접적으로 호출할 수 있을 것이다. 그렇지만, __parse는 사적이다. 왜냐하면 이름 시작부에 두 개의 밑줄문자를 가지기 때문이다.

Note
파이썬의 함수와 클래스 메쏘드 또는 속성의 이름들이 두 개의 밑줄문자로 시작한다면 (그러나 두 개의 밑줄 문자로 끝나지 않음), 그것은 사적이다; 다른 모든 것은 공적이다.
Note
파이썬에서 (__setitem__과 같은) 모든 특수 메쏘드 그리고 (__doc__과 같은) 내장 속성들은 표준적인 이름짓기 관례를 따른다: 모두 두 개의 밑줄문자로 시작하고 끝난다. 메쏘드와 속성들을 이런식으로 이름짓지 마라; 그렇게 되면 나중에 (다른 사람들에게) 혼란을 가중시킬 뿐이다.
Note
파이썬에는 (자신만의 클래스와 자손 클래스에만 접근가능한) 보호 클래스 메쏘드라는 개념이 전혀 없다. 클래스 메쏘드는 사적이거나 (자신의 클래스에만 접근가능함) 또는 공적이다 (어디에서나 접근가능함).

Example 3.20. Trying to call a private method

>>> import fileinfo
>>> m = fileinfo.MP3FileInfo()
>>> m.__parse("/music/_singles/kairo.mp3") 1
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'MP3FileInfo' instance has no attribute '__parse'
1 사적 메쏘드를 호출하고자 한다면, 파이썬은 그 메쏘드가 존재하지 않는다고 약간은 잘못된 예외를 발생시킨다. 물론 그것은 존재하지 않는다. 그러나 그것은 사적이기 때문에, 그 클래스 밖에서는 접근할 수 없는 것이다.[5]

더 읽어야 할 것

3.10. 예외를 다루기

많은 객체-지향 언어들처럼 파이썬에는 try...except 블록을 통한 예외 처리가 있다.

Note
파이썬은 try...except을 사용하여 예외를 처리한다. 그리고 raise를 사용하여 예외를 발생시킨다. 자바와 C++ 은 try...catch 를 사용하여 예외를 다룬다. 그리고 throw를 사용하여 예외를 일으킨다.

이미 예외에 관하여 모든 것을 안다면, 이 섹션을 건너 뛰어도 좋다. 예외 처리를 가지고 있지 않은 저열한 언어로 프로그래밍하는데 익숙하다면, 또는 예외를 사용하지 않고 실제로 언어를 사용해 왔다면, 이 섹션은 대단히 중요하다.

파이썬에서 예외는 어디에나 있다; 실제로 표준 파이썬 라이브러리에 있는 모듈은 모두 예외를 사용한다. 그리고 파이썬 그 자체는 많은 다른 환경하에서 예외를 발생시킬 것이다. 여러분은 이미 그것들을 반복적으로 이 책을 통하여 보아 왔다.

이러한 경우 각각에 대하여, 파이썬 IDE에서는 그저 재미있게 놀기만 하면 된다: 에러가 일어나면, 그 예외가 출력된다. (IDE에 따라서, 내부적으로 경고성 빨간색으로 경고하기는 하지만) 그게 다다. 이것은 처리되지 못한(unhandled) 예외라고 불리운다; 예외가 발생할 때, 명시적으로 그것을 감지하고 처리하는 어떤 코드도 없다. 그래서 예외는 파이썬에 내장된 기본 행위로 다시 부글부글 흘러 넘쳐 돌아온다. 파이썬은 약간의 디버깅 정보를 뱉어내고 실행을 포기한다. IDE에서는 그렇게 큰일은 아니지만, 실제 프로그램이 실행되는 동안에 그런 상황이 발생한다면, 전체 프로그램이 급제동으로 끝나게 될 것이다.[6]

그렇지만, 예외 때문에 프로그램이 완전하게 충돌을 야기해서는 안된다. 예외는 발생되더라도 처리될(handled)수 있다. 예외가 실제로는 코드에 (존재하지 않는 변수에 접근하는 것과 같은) 버그가 있기 때문인 경우도 있지만, 많은 경우에 예외는 대비할 수 있는 어떤 것이다. 파일 하나를 열고자 한다면, 그것이 존재하지 않을지도 모르며; 데이타베이스에 연결하고자 한다면, 그것이 이용불가능할 수도 있거나, 또는 그것에 접근하기 위한 정확한 보안 허가를 가지고 있지 않을 수도 있다. 한 줄의 코드가 예외를 일으킬지도 모른다는 것을 안다면, try...except 블록을 사용하여 그 예외를 처리해야만 한다.

Example 3.21. Opening a non-existent file

>>> fsock = open("/notthere", "r")      1
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
IOError: [Errno 2] No such file or directory: '/notthere'
>>> try:
...     fsock = open("/notthere")       2
... except IOError:                     3
...     print "The file does not exist, exiting gracefully"
... print "This line will always print" 4
The file does not exist, exiting gracefully
This line will always print
1 내장된 open 함수를 사용하면 파일을 열어 읽기를 시도할 수 있다 ( open에 관하여는 다음 섹션에서 더 자세히 다룸). 그러나 그 파일은 존재하지 않는다. 그래서 이것은 IOError 예외를 발생시킨다. IOError 예외에 관하여 아무런 명시적인 점검부를 제공하지 않았으므로, 파이썬은 단지 무슨일이 일어났는지에 관하여 약간의 디버깅 정보를 출력하고는 실행을 포기한다.
2 똑같이 존재하지-않는 파일을 열려고 시도하고 있다. 그러나 이번에는 try...except 블록안에서 수행하고 있다.
3 open 메쏘드가 IOError 예외를 발생시키더라도, 그에 대한 준비가 되어 있다. 'except IOError:' 줄은 예외를 나포하고, 준비된 코드 블록을 실행한다. 이 경우에 그냥 더 상괘한 에러 메시지를 출력할 뿐이다.
4 예외가 처리되기만 하면, 처리는 try...except블록 뒤의 첫 번째 줄에서 정상적으로 진행된다. 주목할 것은 이 줄은 예외가 일어나든 아니든 항상 출력될 것 이라는 것이다. 실제로 notthere라고 불리우는 파일이 디렉토리에 있다면, open을 호출하는 것은 성공할 것이고, except 절은 무시될 것이다. 그리고 이 줄은 여전히 실행될 것이다.

예외는 친숙해 보이지는 않지만, (어쨋든 예외를 나포하지 않는다면, 프로그램 전체가 충돌할 것이므로) 그 대안을 고려해보자. 존재하지-않는 파일에 사용불가능한 파일 객체를 돌려 보내는 편이 좋겠는가? 어쨌든 어떻게 해서든지 유효성을 점검해야만 한다. 잊고 점검하지 않았다면, 프로그램 어디에선가 괴이한 에러들을 보여 줄것이며 그 줄 위에서 소스로 다시 추적해 들어가야만 할 것이다. 분명히 이런 경험이 있으리라고 확신한다; 그것은 재미수준의 문제가 아니다. 예외와 더불어 에러는 직접적으로 함께 일어난다. 그리고 문제를 가진 소스에서 표준적인 방식으로 처리할 수 있다.

실제적인 에러 조건들을 처리하는 것 외에도 예외에는 다른 사용법들이 많이 있다. 표준적인 파이썬 라이브러리의 일반적인 사용법은 모듈을 반입하려고 시도해 보고, 그리고 그것이 작동하나 알아 보는 것이다. 존재하지 않는 모듈을 반입하면 ImportError 예외를 발생시킬 것이다. 이것을 이용하여 실행-시에 어느 모듈이 사용가능한지에 기초하여 여러 수준의 기능을 정의하거나 여러 플랫폼을 지원할 수 있다 (플랫폼-종속적인 코드는 다른 모듈로 따로따로 나뉘어 들어간다).

Example 3.22. Supporting platform-specific functionality

이 코드는 사용자로부터 암호를 획득하기 위한 감싸개 모듈인 getpass 모듈로부터 유래한다. 암호를 획득하는 일은 유닉스와 윈도우즈 그리고 맥OS 플랫폼에서 서로 다르게 달성된다. 이 코드는 그러한 모든 차이를 캡슐화 한다.

  # Bind the name getpass to the appropriate function
  try:
      import termios, TERMIOS                     1
  except ImportError:
      try:
          import msvcrt                           2
      except ImportError:
          try:
              from EasyDialogs import AskPassword 3
          except ImportError:
              getpass = default_getpass           4
          else:                                   5
              getpass = AskPassword
      else:
          getpass = win_getpass
  else:
      getpass = unix_getpass
1 termios는 유닉스-의존적인 모듈로서 입력 터미날에 대한 저-수준의 제어를 제공한다. (시스템에 없거나, 또는 시스템이 그것을 지원하지 않기 때문에) 이 모듈을 사용할 수 없다면, 반입은 실패하고 파이썬은 ImportError를 발생시키며, 그 예외를 나포한다.
2 좋다. termios가 없다. 그래서 msvcrt를 시도해 보자. 이것은 윈도우즈-종속적인 모듈로서 마이크로소프트사의 비쥬얼 C++ 실행시간 서비스에 있는 많은 유용한 함수들에 대한 API 를 제공해준다. 이 모듈이 실패하면, 파이썬은 ImportError에러를 발생시킬 것이고, 그것을 나포한다.
3 앞에서 두개가 작동하지 않으면, EasyDialogs로부터 함수를 하나 반입하려고 한다. 그것은 맥 OS-종속적인 모듈로서 다양한 종류의 대화박스를 팝업하는 함수를 제공한다. 또 다시 이 모듈이 실패하면, 파이썬은 ImportError를 발생시킬 것이고, 그것을 나포한다.
4 이렇게 플래폼-종속적인 모듈 모두가 사용가능하지 않다 (파이썬은 다른 많은 플랫폼으로 이식되어졌기 때문에 있을 수 있는 일이다). 그래서 기본 암호 입력 함수에 의존해야 할 필요가 있다 (다른 곳 getpass 모듈에 정의되어 있다). 여기에서 무엇을 하는지를 주목하라: 함수 default_getpass를 변수 getpass에 할당하고 있다. 공식적인 getpass 문서를 읽어 본다면, 거기에서 getpass 모듈이 getpass 함수를 정의한다는 것을 알 수 있다. 이것이 바로 수행되는 방법이다: getpass를 플랫폼에 적절한 함수와 묶음으로써 말이다. 그러면 getpass 함수를 호출할 때, 실제로는 이 코드가 준비해 둔 플랫폼-종속적인 함수를 호출하고 있는 것이다. 어떤 플랫폼에서 코드가 실행될지 알 필요도 없으며 걱정할 필요도 없다; 그냥 getpass를 호출하기만 하면, 언제나 정확한 작업을 수행할 것이다.
5 try...except 블록은, if 서술문처럼, else 절을 가질 수 있다. 만약 If try 블록을 실행하는 동안에 아무런 예외도 발생하지 않는다면, else 절이 이후에 실행된다. 이 경우, 그 의미는 from EasyDialogs import AskPassword가 반입되어 작동하고, 그래서 getpassAskPassword 함수에 묶어 주어야 한다는 것을 뜻한다. 다른 try...except 블록들 각각은 비슷한 else 절을 가져서 작동하는 import를 발견하면 getpass를 그 적절한 함수에 묶어준다.

더 읽어야 할 것

3.11. 파일 객체

파이썬은 디스크에서 파일을 열기 위한 내장 open 함수가 있다. open 함수는 파일 객체를 반환하는데. 이 객체는 열려진 파일에 대한 정보를 획득하고 처리하기 위한 메쏘드와 속성들을 가진다.

Example 3.23. Opening a file

>>> f = open("/music/_singles/kairo.mp3", "rb") 1
>>> f                                           2
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.mode                                      3
'rb'
>>> f.name                                      4
'/music/_singles/kairo.mp3'
1 open 메쏘드는 매개변수를 세 개 취할 수 있다: 파일 이름, 모드, 그리고 버퍼 매개변수가 그것으로서 오직 첫 번째 파일이름 만이 필수적이다; 다른 두 개는 선택적이다. 지정되지 않는다면, 파일은 텍스트 모드 읽기로 열린다. 여기에서는 파일을 이진 모드 읽기로 열고 있다. (print open.__doc__ 을 타자하면 가능한 모든 모드에 대하여 훌륭한 설명을 볼 수 있다.)
2 open 함수는 객체를 반환한다 (지금까지는 이 때문에 감탄하지는 못할 것이다). 파일 객체는 약간의 유용한 속성들을 가진다.
3 파일의 모드(mode) 속성으로 파일이 어떤 모드로 열렸는지 알 수 있다.
4 파일의 이름(name) 속성으로 파일 객체가 연 파일의 이름을 알 수 있다.

Example 3.24. Reading a file

>>> f
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.tell()              1
0
>>> f.seek(-128, 2)       2
>>> f.tell()              3
7542909
>>> tagData = f.read(128) 4
>>> tagData
'TAGKAIRO****THE BEST GOA         ***DJ MARY-JANE***            Rave Mix                      2000http://mp3.com/DJMARYJANE     \037'
>>> f.tell()              5
7543037
1 파일 객체는 자신이 연 파일에 대하여 상태정보를 유지한다. 파일 객체의 tell 메쏘드로 열려진 파일에서 현재 위치를 알 수 있다. 이 파일에 대하여 아직은 어떤 처리도 행하지 않았으므로, 현재 위치는 0이며, 파일의 처음이다.
2 파일 객체의 seek 메쏘드는 개방된 파일에서 다른 위치로 이동한다. 두 번째 매개변수는 첫 번째 매개변수가 무엇을 뜻하는지를 지정한다; 0 은 (파일의 처음으로부터 세어서) 절대적인 위치로 이동하라는 뜻이고, 1 은 (현재 위치로부터 세어서) 상대적인 위치로 이동하라는 뜻이며, 그리고 2 는 그 파일의 뒤에서부터 상대적인 위치로 이동하라는 뜻이다. 찾고 있는 MP3 태크는 파일의 뒤에 저장되어 있으므로, 2를 사용하고 파일 객체에게 파일의 뒤에서부터 128의 위치로 이동하도록 명령한다.
3 tell 메쏘드는 파일의 현재 위치가 이동한 것을 확인한다.
4 read 메쏘드는 지정된 개수의 바이트를 개방된 파일로부터 읽어서, 그 데이타를 문자열로 돌려준다. 선택적 매개변수는 읽어야할 최대 바이트 개수를 지정한다. 아무런 매개변수도 지정되지 않는다면, read는 파일의 끝까지 읽을 것이다. (여기에서 read()를 편안하게 논의할 수 있다. 왜냐하면 파일에서 정확히 어디에 위치하는지 알고 있으며 사실 마지막 128바이트를 읽고 있는 중이기 때문이다.) 읽혀진 데이타는 tagData 변수에 할당된다. 그리고 현재 위치는 얼마나 많은 바이트가 읽혔느냐에 따라서 갱신된다.
5 tell 메쏘드는 현재 위치가 이동한 것을 확인한다. 계산 해보면 128 바이트를 읽은 후에 그 위치가 128만큼 증가했다는 것을 알 수 있을 것이다.

Example 3.25. Closing a file

>>> f
<open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.closed  1
0
>>> f.close() 2
>>> f
<closed file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988>
>>> f.closed
1
>>> f.seek(0) 3
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.tell()
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.read()
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: I/O operation on closed file
>>> f.close() 4
1 파일 객체의 closed 속성은 파일 객체가 파일을 개방시키고 있는지 아닌지를 가리킨다. 이 경우에 파일은 여전히 열려 있다 (closed0이다). 파일을 여는 것은 시스템 자원을 소모한다. 그리고 파일의 모드에 따라서, 다른 프로그램들이 파일에 접근하지 못할 수도 있다. 처리를 끝내자 마자 최대한 빨리 파일을 닫는 것이 중요하다.
2 파일을 닫기 위해서는 파일 객체의 close 메쏘드를 호출하라. 이렇게 해야 (만약 있다면) 그 파일에 대하여 유지하고 있는 잠금장치를 해제하여 주고, (만약 있다면) 시스템이 아직 실제로 쓰지 않고서 버퍼에 쓴 것들을 강제저장하여 주며, 그리고 시스템 자원을 풀어준다. closed 속성은 파일이 닫혀 있는지 확인하여 준다.
3 단지 파일이 닫혔다고 해서 그 파일 객체가 존재하기를 멈춘다는 것을 뜻하지는 않는다. 변수 f범위를 벗어 날 때까지 혹은 수작업으로 삭제될 때까지 존재할 것이다. 그렇지만, 파일이 닫혀지기만 하면 열려진 파일을 처리하는 메쏘드는 어떤 것도 작동을 멈출 것이다; 그들 모두는 예외를 발생시킨다.
4 파일이 이미 닫혀진 파일 객체에 대해 close를 호출하면 예외를 발생시키지 않는다; 조용히 실패할 뿐이다.

Example 3.26. File objects in MP3FileInfo

        try:                                1
            fsock = open(filename, "rb", 0) 2
            try:
                fsock.seek(-128, 2)         3
                tagdata = fsock.read(128)   4
            finally:                        5
                fsock.close()
            .
            .
            .
        except IOError:                     6
            pass                           
1 파일을 열고 읽는 것은 위험하기 때문에 예외를 발생시킬 수도 있다. 이러한 모든 코드는 try...except 블록에 포장된다. (표준적인 들여쓰기, 정말 대단하지 않은가? 여기에서 여러분은 감사하기 시작한다.)
2 open 함수는 IOError를 발생시킬 수도 있다 (파일이 존재하지 않을 수도 있다.)
3 seek 메쏘드는 IOError를 발생시킬 수도 있다. (파일이 128바이트보다 작을 수도 있다.)
4 read 메쏘드는 IOError를 발생시킬 수도 있다. (디스크에 배드섹터가 있다든가, 또는 파일이 네트워크 드라이브에 있고 그 네트워크가 작동하지 않을 수도 있다.)
5 새로운 구문이다: try...finally 블록을 살펴보자. 파일이 open 함수에 의하여 성공적으로 열렸을 지라도, seek 메쏘드나 read 메쏘드에 의해서 예외가 발생하면, 확실하게 그것이 닫혔는지 확인하고 싶어한다. 그것이 바로 try...finally 블록이 사용되는 용도이다: try 블록 안에서 어떤 것이 예외를 발생시킨다고 할 지라도, finally 블록의 코드는 항상 실행될 것이다. 무슨일이 일어난다고 할지라도 “끝까지” 실행되는 코드라고 생각하면 된다.
6 마침내, IOError 예외를 처리한다. 이것은 open, seek, 혹은 read를 호출하여 발생된 IOError 예외일 수 있다. 여기에서는 실제로 고민거리가 아닌데, 왜냐하면 그냥 조용히 무시하고 계속 진행하고자 하기 때문이다 (기억하라. pass는 파이썬의 서술문으로 아무것도 하지 않는다). 완전하게 적법하다; 예외를 “처리하는 것”은 명시적으로 아무것도 하지 않는다는 것을 뜻할 수 있다. 여전히 처리된 것으로 간주되며, 처리는 try...except 블록 뒤의 바로 그 다음 줄에서 정상적으로 계속 진행될 것이다.

더 읽어야 할 것

3.12. for 회돌이

대부분의 다른 언어처럼 파이썬에는 for 회돌이가 있다. 지금까지 그것을 보지 못했던 유일한 이유는 파이썬이 다른 많은 일들에 능통하여 그렇게 자주 필요하지 않기 때문이다.

대부분의 다른 언어에는 파이썬과 같은 강력한 리스트 데이타형이 없다. 그래서 수 많은 수작업을 해야한다. 처음, 마지막, 그리고 띔값을 지정하여 정수의 범위를 정의하거나 혹은 다른 반복적인 개체들을 정의한다. 그러나 파이썬에서는, for 회돌이가 리스트를 반복해 주며, 같은 방식으로 리스트 통합이 작동한다.

Example 3.27. Introducing the for loop

>>> li = ['a', 'b', 'e']
>>> for s in li:         1
...     print s          2
a
b
e
>>> print "\n".join(li)  3
a
b
e
1 for 회돌이를 위한 구문은 리스트 통합과 비슷하다. li는 리스트이다. s는 첫 번째 요소로부터 시작하여 각 요소의 값을 차례대로 취할 것이다.
2 if 서술문이나 다른 모든 들여쓰기 블록처럼 for 회돌이는 자신 안에 줄을 얼마든지 가질 수 있다.
3 이것이 바로 지금까지 for 회돌이를 보지 못했던 이유이다: 아직까지는 그것이 필요하지 않다. 실제로 원하는 것이 join 혹은 리스트 통합인 경우에 다른 언어로는 얼마나 많은 for 회돌이를 사용하는지를 보면 놀라울 것이다.

Example 3.28. Iterating through a dictionary

>>> for k, v in os.environ.items(): 1 2
...     print "%s=%s" % (k, v)
USERPROFILE=C:\Documents and Settings\mpilgrim
OS=Windows_NT
COMPUTERNAME=MPILGRIM
USERNAME=mpilgrim

[...snip...]
>>> print "\n".join(["%s=%s" % (k, v) for k, v in os.environ.items()]) 3
USERPROFILE=C:\Documents and Settings\mpilgrim
OS=Windows_NT
COMPUTERNAME=MPILGRIM
USERNAME=mpilgrim

[...snip...]
1 os.environ는 시스템에 관하여 정의된 환경 변수들을 담고 있는 사전이다. 윈도우즈에서 이것은 도스로부터 접근할 수 있는 시스템 변수와 사용자 변수이다. 유닉스에서는 쉘의 시작 스크립트에 보내어진 변수들이다. 맥 OS에는 환경변수에 대한 개념이 없기 때문에, 이 사전은 비어 있다.
2 os.environ.items()은 터플의 리스트: [(key1, value1), (key2, value2), ...]를 반환한다. for 회돌이는 이 리스트를 반복한다. 첫 번째 회전에서는 key1k에 할당하고 value1v에 할당한다. 그래서 k = USERPROFILE이고 v = C:\Documents and Settings\mpilgrim이다. 두 번재 회전에서 k는 두번째 키 OS를 획득하고, 그리고 v는 그에 상응하는 값 Windows_NT를 획득한다.
3 여러-변수 할당리스트 통합으로, 전체 for 회돌이를 서술문 하나로 대치할 수 있다. 실제 코드에서 이렇게 진짜로 할지의 여부는 개인적인 코딩 스타일의 문제이다; 나는 그것을 좋아한다. 왜냐하면 우리가 하고 있는 것이 사전을 리스트에 짝을 짓고, 그리고나서 그 리스트를 한 개의 문자열로 결합하는 것이라는 것을 명확하게 하여 주기 때문이다. 다른 프로그래머들은 이것을 for 회돌이로 작성하기를 더 좋아한다. 주목할 것은 어느 경우에나 그 출력은 같다는 것이다. 그렇지만 이 버전이 약간 더 빠른데, 왜냐하면 많은 서술문 대신에 단 한개의 print 서술문만 있기 때문이다.

Example 3.29. for loop in MP3FileInfo

    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)} 1
    .
    .
    .
            if tagdata[:3] == "TAG":
                for tag, (start, end, parseFunc) in self.tagDataMap.items(): 2
                    self[tag] = parseFunc(tagdata[start:end])                3
1 tagDataMap클래스 속성으로서 MP3 파일에서 우리가 찾고 있는 태그들을 정의한다. 태그들은 고정-길이 필드로 저장되어 있다; 파일의 마지막 128 바이트를 읽으면, 그 중의 3에서부터 32바이트까지는 항상 노래 타이틀이고, 33-62은 항상 아티스트 이름이며, 63-92 는 앨범 이름, 등등이다. 주목할 것은 tagDataMap은 터플로 이루어진 사전이며, 각 터플은 두개의 정수와 함수 참조점을 포함하고 있다는 것이다.
2 복잡하게 보이지만, 그렇지 않다. for 변수들의 구조는 items이 반환한 리스트에 포함된 요소들의 구조와 일치한다. 기억하라. items(key, value)의 형태인 터플의 리스트를 돌려준다. 그 리스트의 첫 번째 요소는 ("title", (3, 33, <function stripnulls>))이다. 그래서 첫 번째 회돌이에서, tag"title"을, start3을, end33을, 그리고 parseFuncstripnulls함수를 획득한다.
3 이제 MP3 태그 하나를 위하여 모든 매개변수들을 추출하였으므로, 그 태그 데이타를 저장하는 것은 어렵지 않다. tagdatastart로부터 end까지 쪼개어 이 태그를 위한 실제의 데이타를 획득하고, parseFunc을 호출하여 그 데이타를 후-처리한다. 그리고 이것을 의사-사전 self에 있는 키 tag의 값으로 할당한다. tagDataMap에 있는 모든 요소들을 반복하고 나면, self 는 모든 태그에 대하여 값을 가진다. 이제 그것이 어떻게 보일지를 이해할 것이다.

3.13. 모듈에 대하여 더 자세히

다른 모든 것과 마찬가지로 파이썬에서 모듈은 객체이다. 반입되면, 전역 사전 sys.modules를 통하여 모듈에 대한 참조점을 획득할 수 있다.

Example 3.30. Introducing sys.modules

>>> import sys                          1
>>> print '\n'.join(sys.modules.keys()) 2
win32api
os.path
os
exceptions
__main__
ntpath
nt
sys
__builtin__
site
signal
UserDict
stat
1 sys 모듈은 실행하고 있는 파이썬의 버전 (sys.version 또는 sys.version_info)과 같은 시스템-수준의 정보를 보유한다. 그리고 되부름의 최대 허용 깊이 (sys.getrecursionlimit()sys.setrecursionlimit())와 같은 시스템-수준의 선택사항을 보유한다.
2 sys.modules는 파이썬이 시작된 이후로 반입된 모든 모듈을 담고 있는 사전이다; 키는 모듈 이름이고, 값은 모듈 객체이다. 이것은 여러분의 프로그램이 반입한 모듈들 이상임을 주목하라. 파이썬은 시작할 때 어떤 모듈들을 미리 읽어 들인다. 파이썬 IDE에 있다면, sys.modules은 IDE안에서 여러분이 실행한 모든 프로그램들이 반입한 모듈을 모두 담고 있다.

Example 3.31. Using sys.modules

>>> import fileinfo         1
>>> print '\n'.join(sys.modules.keys())
win32api
os.path
os
fileinfo
exceptions
__main__
ntpath
nt
sys
__builtin__
site
signal
UserDict
stat
>>> fileinfo
<module 'fileinfo' from 'fileinfo.pyc'>
>>> sys.modules["fileinfo"] 2
<module 'fileinfo' from 'fileinfo.pyc'>
1 새로운 모듈이 반입되면, sys.modules에 추가된다. 이것은 같은 모듈을 두 번 반입하면 대단히 빠른 이유를 설명해 준다: 파이썬은 이미 읽어 들여졌고 그 모듈을 sys.modules에 저장한다. 그래서 두 번째 반입하는 것은 단순히 사전을 탐색하는 것일 뿐이다.
2 이전에-반입된 어떠한 모듈의 이름이라도 (문자열로서) 주어진다면, sys.modules 사전을 통하여 그 모듈 자체에 대한 참조점을 획득할 수 있다.

Example 3.32. The __module__ class attribute

>>> from fileinfo import MP3FileInfo
>>> MP3FileInfo.__module__              1
'fileinfo'
>>> sys.modules[MP3FileInfo.__module__] 2
<module 'fileinfo' from 'fileinfo.pyc'>
1 모든 파이썬 클래스는 내장 클래스 속성 __module__을 가진다. 이는 클래스가 정의된 모듈의 이름이다.
2 이것을 sys.modules 사전과 결합하면, 클래스가 정의된 모듈에 대한 참조점을 획득할 수 있다.

Example 3.33. sys.modules in fileinfo.py

    def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]):       1
        "get file info class from filename extension"
        subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:]        2
        return hasattr(module, subclass) and getattr(module, subclass) or FileInfo 3
1 이것은 두 개의 인자를 가지는 함수이다; filename은 필수적이지만, module선택적이다. 그리고 FileInfo 클래스를 담고 있는 모듈이 기본값이 된다. 이것은 비효율적으로 보인다. 왜냐하면 함수가 호출될 때마다 파이썬이 sys.modules 표현식을 평가할 것이라고 예상되기 때문이다. 파이썬은 사실 기본 표현식을 모듈이 반입될 때 처음으로 한번만 평가한다. 나중에 보겠지만, 이 함수를 module 인자로 호출하지 않는다. module은 함수-수준 상수로 기능을 한다.
2 os모듈에 다이빙해 들어간 다음, 이 줄을 깊이 연구해 보겠다. 지금 당장은 subclassMP3FileInfo처럼 클래스의 이름으로 끝난다고 믿도록 하자.
3 이미 getattr 함수에 관하여 배웠는데, 이 함수는 객체에 대한 참조점을 이름으로 획득한다. hasattr는 보조적인 함수로서 한 객체가 특별한 속성을 가지고 있는지 점검한다; 이 경우에는 모듈에 특정한 클래스가 있는지를 점검한다 (그렇지만 getattr 처럼 어떤 객체 그리고 어떤 속성에도 작동한다). 풀어 이야기하면, 이 코드 줄의 뜻은 “ 만약 이 모듈이 subclass에 의하여 지명된 클래스를 가지고 있다면 그것을 반환하고, 그렇지 않으면 기본 클래스 FileInfo를 반환하라”는 뜻이다.

더 읽어야 할 것

3.14. os 모듈

os 모듈은 많은 유용한 함수를 가지고 있어서 파일과 처리과정들을 다룬다. 그리고 os.path에는 파일과 디렉토리 경로를 위한 함수들이 있다.

Example 3.34. Constructing pathnames

>>> import os
>>> os.path.join("c:\\music\\ap\\", "mahadeva.mp3") 1 2
'c:\\music\\ap\\mahadeva.mp3'
>>> os.path.join("c:\\music\\ap", "mahadeva.mp3")   3
'c:\\music\\ap\\mahadeva.mp3'
>>> os.path.expanduser("~")                         4
'c:\\Documents and Settings\\mpilgrim\\My Documents'
>>> os.path.join(os.path.expanduser("~"), "Python") 5
'c:\\Documents and Settings\\mpilgrim\\My Documents\\Python'
1 os.path는 모듈에 대한 참조점이다; 어느 모듈이냐 하는 것은 어떤 플랫폼에서 실행하고 있느냐에 달려 있다. getpassgetpass를 플랫폼-종속적 함수에 설정함으로써 플랫폼사이의 차이를 캡슐화 하는 것과 마찬가지로 똑 같이 ospath를 플랫폼-종속적 함수에 설정함으로써 플랫폼사이의 차이를 캡슐화 한다.
2 os.pathjoin 함수는 여러 경로이름 조각을 모아 경로이름을 구성한다. 이런 간단한 경우에는 그저 문자열을 연결할 뿐이다. (주목할 것은 윈도우즈에서 경로이름을 다루는 것은 번거롭다는 것이다. 왜냐하면 역사선 문자를 피해야만 하기 때문이다.)
3 이런 약간 복잡한 정도는 파일 이름에 합치기 전에 join이 역사선을 따로 더 경로이름에 덧붙일 것이다. 이 사실을 발견했을 때 나는 너무 기뻤는데, 왜냐하면 새로운 언어로 나의 도구함을 만들 때 작성해야만 했던 바보같은 함수들중의 하나가 언제나 필요시-사선-추가(addSlashIfNecessary) 함수였기 때문이다. 파이썬에서는 이런 바보같은 자잘한 함수들을 사용하지 마라; 이미 현명한 사람들이 주의를 기울이고 있음을 명심하자.
4 expanduser는 현재 사용자의 홈 디렉토리를 나타내기 위하여 ~를 사용하는 경로이름을 확장할 것이다. 이것은 윈도우즈와 유닉스 그리고 맥 OS X 처럼 사용자가 홈 디렉토리를 가지는 어떠한 플랫폼에도 작동한다; 물론 Mac OS에는 영향을 미치지 않는다.
5 이러한 테크닉을 결합하면 쉽게 홈 디렉토리 아래에 파일과 디렉토리를 위한 경로이름을 구축할 수 있다.

Example 3.35. Splitting pathnames

>>> os.path.split("c:\\music\\ap\\mahadeva.mp3")                        1
('c:\\music\\ap', 'mahadeva.mp3')
>>> (filepath, filename) = os.path.split("c:\\music\\ap\\mahadeva.mp3") 2
>>> filepath                                                            3
'c:\\music\\ap'
>>> filename                                                            4
'mahadeva.mp3'
>>> (shortname, extension) = os.path.splitext(filename)                 5
>>> shortname
'mahadeva'
>>> extension
'.mp3'
1 split 함수는 완전한 경로이름을 쪼개서 경로와 파일이름을 가진 터플을 반환한다. 여러-변수 할당을 사용하면 함수로부터 여러개의 값을 반환할 수 있다고 말한 적이 있다. 자, split이 그런 함수이다.
2 split 함수가 반환한 값을 두 개의 변수를 가진 터플에 할당한다. 각 변수는 반환된 터플에 상응하는 요소의 값을 접수한다.
3 첫 번째 변수 filepathsplit으로부터 반환된 터플의 첫 번째 요소의 값인 파일 경로를 받는다.
4 두 번째 변수 filenamesplit으로부터 돌려받은 터플의 두 번째 요소의 값인 파일이름을 받는다.
5 os.path에는 splitext라는 함수도 있는데, 이는 파일이름을 쪼개어 파일이름과 확장자를 포함하는 터플로 반환한다. 같은 테크닉을 사용하여 그것들을 변수에 따로따로 할당한다.

Example 3.36. Listing directories

>>> os.listdir("c:\\music\\_singles\\")                                          1
['a_time_long_forgotten_con.mp3', 'hellraiser.mp3', 'kairo.mp3',
'long_way_home1.mp3', 'sidewinder.mp3', 'spinning.mp3']
>>> dirname = "c:\\"
>>> os.listdir(dirname)                                                          2
['AUTOEXEC.BAT', 'boot.ini', 'CONFIG.SYS', 'cygwin', 'docbook',
'Documents and Settings', 'Incoming', 'Inetpub', 'IO.SYS', 'MSDOS.SYS', 'Music',
'NTDETECT.COM', 'ntldr', 'pagefile.sys', 'Program Files', 'Python20', 'RECYCLER',
'System Volume Information', 'TEMP', 'WINNT']
>>> [f for f in os.listdir(dirname) if os.path.isfile(os.path.join(dirname, f))] 3
['AUTOEXEC.BAT', 'boot.ini', 'CONFIG.SYS', 'IO.SYS', 'MSDOS.SYS',
'NTDETECT.COM', 'ntldr', 'pagefile.sys']
>>> [f for f in os.listdir(dirname) if os.path.isdir(os.path.join(dirname, f))]  4
['cygwin', 'docbook', 'Documents and Settings', 'Incoming',
'Inetpub', 'Music', 'Program Files', 'Python20', 'RECYCLER',
'System Volume Information', 'TEMP', 'WINNT']
1 listdir 함수는 경로이름을 취하여 디렉토리의 내용을 포함하는 리스트를 반환한다.
2 listdir은 어떤 것이 어떤 건지 구분하지 않고 파일과 폴더를 모두 돌려준다.
3 리스트 여과하기os.path모듈의 isfile 함수를 사용하면 파일을 폴더와 가를 수 있다. isfile은 경로이름을 취하여 경로가 파일을 가리키면 1을, 그렇지 않으면 0을 반환한다. 여기에서는 os.path.join을 사용하여 완전한 경로이름을 확인한다. 그러나 isfile은 또한 현재 작업 디렉토리에 상대적인, 부분적 경로와도 작동한다. os.path.getcwd()을 사용하면 현재의 작업 디렉토리를 얻을 수 있다.
4 os.path에도 isdir 함수가 있는데, 경로가 디렉토리를 가리키면 1을 그렇지 않으면 0을 돌려준다. 이것을 이용하면 디렉토리안에 있는 하부디렉토리의 리스트를 얻을 수 있다.

Example 3.37. Listing directories in fileinfo.py

def listDirectory(directory, fileExtList):
    "get list of file info objects for files of particular extensions"
    fileExtList = [ext.upper() for ext in fileExtList]
    fileList = [os.path.join(directory, f) for f in os.listdir(directory) \
                if os.path.splitext(f)[1].upper() in fileExtList]                 

이 마지막 코드 줄은 지금까지 os모듈에 관하여 배워온 거의 모든 것들이 결합되어 있다.

  1. os.listdir(directory)디렉토리(directory)안에 있는 폴더와 파일 모두를 담은 리스트를 돌려준다.
  2. f로 그 리스트를 회돌이 하면서 os.path.splitext(f)을 사용하여 각각의 파일이름을 이름과 확장자로 가른다.
  3. 확장자를 취하여 대문자로 강제변환하고 (os.path.splitext(f)[1].upper()), 해당 파일 확장자의 목록에 그것이 있는지 살펴본다 (listDirectory함수에 건네어진 fileExtList) (주목할 것은 fileExtList와 각 파일의 확장자를 대문자로 변환하면 윈도우즈와 같은 저급 플랫폼에서 결점없이 작동할 수 있다는 것이다. 윈도우즈는 mahadeva.mp3mahadeva.MP3를 같은 것으로 생각한다.)
  4. 해당 각 파일에 대하여 os.path.join(directory, f)를 사용하여 파일의 전체 경로를 구성하고 전체 경로이름을 담은 리스트를 돌려준다.
Note
될 수 있으면 언제나 파일, 디렉토리, 그리고 경로 처리에 osos.path에 있는 함수들을 사용해야 한다. 이러한 모듈들은 플랫폼-종속적 모듈을 위한 포장자이다. 그래서 os.path.split와 같은 함수들은 유닉스, 윈도우, 맥 OS, 그리고 지원되는 다른 모든 파이썬 플랫폼에서 작동한다.

더 읽어야 할 것

3.15. 모두 하나로 합치기

또 다시 모든 도미노가 제자리에 놓였다. 각 줄의 코드가 어떻게 작동하는지 알아 보았다. 이제 뒤로 돌아가 어떻게 그 모든 것들이 맞추어 지는지 살펴보자.

Example 3.38. listDirectory

def listDirectory(directory, fileExtList):                                         1
    "get list of file info objects for files of particular extensions"
    fileExtList = [ext.upper() for ext in fileExtList]
    fileList = [os.path.join(directory, f) for f in os.listdir(directory) \
                if os.path.splitext(f)[1].upper() in fileExtList]                  2
    def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]):       3
        "get file info class from filename extension"
        subclass = "%sFileInfo" % os.path.splitext(filename)[1].upper()[1:]        4
        return hasattr(module, subclass) and getattr(module, subclass) or FileInfo 5
    return [getFileInfoClass(f)(f) for f in fileList]                              6
1 listDirectory는 이 모듈 전체에서 가장 매력적인 부분이다. (나의 경우에 c:\music\_singles\와 같은) 디렉토리와 (['.mp3']와 같은) 재미있는 파일 확장자들을 담은 리스트를 취해서, 사전처럼 행동하는 클래스 실체들을 담은 리스트를 돌려준다. 사전에는 디렉토리에 있는 재미있는 파일에 관한 각각의 메타정보들이 담겨 있다. 단지 몇줄의 깔금한 코드로 그 일을 해내고 있다.
2 이전 섹션에서 본 바와 같이, 이 코드 줄은 디렉토리(directory)에 있는 모든 파일들에 대한 전체 경로를 담은 리스트를 얻는다. 디렉토리는 (fileExtList에 의해서 지정된) 흥미로운 파일 확장자를 가지고 있다.
3 예전-학교의 파스칼 프로그래머들은 아마도 이것에 익숙할 것이다. 그러나 파이썬은 내포된 함수 (nested functions)를 지원한다고 말하면 대부분의 사람들은 나를 멍하니 쳐다 본다 -- 문자 그대로, 함수안의 함수를 말한다. 내포된 함수 getFileInfoClass는 오직 그것이 정의된 함수 listDirectory로부터만 호출될 수 있다. 다른 어떤 함수와 마찬가지로 인터페이스 선언이나 또는 상상되는 어떤 것도 필요가 없다; 그냥 함수를 정의하고 코딩(작성)하면 된다.
4 이제 os 모듈을 본 바 있으므로, 이 줄은 더욱 이해가 잘 되리라 믿는다. 파일의 확장자를 얻어서 (os.path.splitext(filename)[1]), 대문자로 변환하고 (.upper()), 점을 썰어 내 버린다 ([1:]). 그리고 문자열 형식화로 클래스 이름을 구성한다. 그래서 c:\music\ap\mahadeva.mp3.mp3가 되고 또 .MP3가 되며 또 MP3가 되고 다시 MP3FileInfo가 된다.
5 이 파일을 처리할 처리 클래스의 이름을 구성하고서 그 처리 클래스가 실제로 이 모듈에 존재하는지 점검한다. 존재한다면 그 클래스를 반환하고, 그렇지 않으면 기본 클래스 FileInfo를 돌려준다. 이것은 대단히 중요한 점이다: 이 함수는 클래스를 반환한다. 클래스의 실체가 아니라, 클래스 그 자체를 돌려준다.
6 “관심의” 목록(fileList)에 있는 각 파일에 대하여 파일이름(f)으로 getFileInfoClass를 호출한다. getFileInfoClass(f)를 호출하면 클래스를 돌려준다; 정확하게 어떤 클래스인지 모르지만, 상관하지 않는다. 그리고 이 클래스의 (그것이 무엇이든) 실체를 생성한다. 그 파일이름(역시 f)을 __init__ 메쏘드에 건네준다. 이장의 초반부에서 본 바와 같이, FileInfo__init__ 메쏘드는 self["name"]를 설정하는데, 그렇게 하면 __setitem__이 촉발되고, 이것은 그 자손 (MP3FileInfo)에 덮어씌여져서 파일을 적절히 분해하여, 그 파일의 메타데이타를 끌어낸다. 각각의 관심 파일들에 대하여 모든 일을 하고, 결과로 나온 실체들의 리스트를 돌려준다.

listDirectory는 완전하게 일반적이라는 사실에 주목하라. 미리 어떤 형태의 파일을 가지게 될지 알지 못하며, 또는 충분히 그러한 파일을 다룰 수 있도록 어떤 클래스가 정의되어 있는지도 미리 알지 못한다. 디렉토리를 뒤져서 처리해야 할 파일들을 찾아서, 그 자신의 모듈을 검사하여 (MP3FileInfo와 같은) 어떤 특수한 처리 클래스들이 정의되어 있는지 살펴본다. 이 프로그램을 확장해서 그냥 적절하게-이름을 붙여 클래스를 정의하면 다른 종류의 파일들을 처리할 수 있다: HTMLFileInfo는 HTML 파일을 처리하고, DOCFileInfo는 워드의 .doc 파일을 처리하는, 등등. listDirectory는 어떤 변경도 필요없이 실제 작업을 적절한 클래스에 떠 넘기고 그 결과를 대조하여 짝맞춤으로써, 그들 모두를 처리할 것이다.

3.16. 요 약

fileinfo.py가 이제 완전히 이해되었으리라 믿는다.

Example 3.39. fileinfo.py

"""Framework for getting filetype-specific metadata.

Instantiate appropriate class with filename.  Returned object acts like a
dictionary, with key-value pairs for each piece of metadata.
    import fileinfo
    info = fileinfo.MP3FileInfo("/music/ap/mahadeva.mp3")
    print "\\n".join(["%s=%s" % (k, v) for k, v in info.items()])

Or use listDirectory function to get info on all files in a directory.
    for info in fileinfo.listDirectory("/music/ap/", [".mp3"]):
        ...

Framework can be extended by adding classes for particular file types, e.g.
HTMLFileInfo, MPGFileInfo, DOCFileInfo.  Each class is completely responsible for
parsing its files appropriately; see MP3FileInfo for example.
"""
import os
import sys
from UserDict import UserDict

def stripnulls(data):
    "strip whitespace and nulls"
    return data.replace("\00", "").strip()

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

class MP3FileInfo(FileInfo):
    "store ID3v1.0 MP3 tags"
    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):
        "parse ID3v1.0 tags from MP3 file"
        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):
    "get list of file info objects for files of particular extensions"
    fileExtList = [ext.upper() for ext in fileExtList]
    fileList = [os.path.join(directory, f) for f in os.listdir(directory) \
                if os.path.splitext(f)[1].upper() in fileExtList]
    def getFileInfoClass(filename, module=sys.modules[FileInfo.__module__]):
        "get file info class from filename extension"
        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

다음 장으로 다이빙해 들어가기 전에, 이러한 모든 것들을 편안하게 느끼는지 확인하라:



[4] 파이썬에는 상수가 없다. 열심히 노력만 하면 모든 것을 변경할 수 있다. 이것은 파이썬의 핵심 원리중의 하나와 맞아 떨어진다: 나쁜 행위는 금지되기 보다는 권장되지 말아야 한다. 진짜로 None값을 변경하기를 원한다면, 그렇게 할 수 있다. 그러나 코드가 디버깅이 불가능하게 되더라도 나에게 책임을 묻지 마시라.

[5] 엄밀히 이야기 해서, 사적인 메쏘드도 클래스 밖에서 접근가능하다. 단 쉽게 접근가능하지 않을 따름이다. 파이썬에서는 어떤 것도 진짜로 사적인 것은 없다; 내부적으로 사적인 메쏘드와 속성의 이름은 그때그때 조립되고 분해되어서 주어진 이름으로는 접근불가능한 것 같이 보이게 만든다. MP3FileInfo클래스의 __parse메쏘드에 _MP3FileInfo__parse라는 이름으로 접근할 수 있다. 이것이 흥미있다는 것을 인정하자. 그리고 절대로, 절대로, 실제코드에서 그렇게 하지 않겠다고 약속하라. 사적인 메쏘드는 이유가 있기 때문에 사적이다. 그러나 다른 많은 것들처럼 파이썬에서는 사적비밀이라는 것은 결론적으로 관례의 문제이지 강제의 문제가 아니다.

[6] 그렇지 않으면, 상품상담원들이 선전하듯이, 어쨌거나 여러분의 프로그램은 불법적인 행위를 수행할 것이다.


 제 2 장 검사의 힘 목 차 제 4 장 HTML 처리하기 >>