☜ 제 05 장 객체와 객체지향 | """ Dive Into Python """ 다이빙 파이썬 |
제 07 장 정규표현식 ☞ |
이 장에서는 예외와 파일 객체 그리고 for 회돌이와 os 모듈 그리고 sys 모듈을 연구합니다. 다른 프로그래밍 언어에서 예외를 사용해 보셨다면 첫 섹션을 건너뛰고 파이썬의 구문을 알아보셔도 좋습니다. 파일 처리에 관해서는 꼭 보시기 바랍니다.
다른 많은 프로그래밍 언어처럼 파이썬은 try...except 블록을 통하여 예외를 처리합니다.
☞ | |
파이썬은 try...except를 사용하여 예외를 처리하고 raise로 예외를 만들어 냅니다. 자바와 C++은 try...catch를 사용하여 예외를 처리하고 throw로 예외를 만들어 냅니다. |
예외는 파이썬 곳곳에서 사용됩니다. 사실상 표준 파이썬 라이브러리의 모든 모듈에서 예외를 사용하고 이미 이 책을 통하여 자주 보셨듯이 파이썬 조차도 다른 많은 상황에서 예외를 일으킵니다.
이 경우에는 그냥 단순히 파이썬 IDE 안에서 놀기만 하면 됩니다: 예러가 일어났고 예외가 인쇄되며 (IDE에 따라 의도적으로 빨간색으로 인쇄되기도 합니다) 그걸로 끝입니다. 이것을 처리되지 않은 예외라고 부릅니다. 예외가 일어나더라도 명시적으로 그 예외를 감지해서 처리할 코드가 없으므로 파이썬에 내장된 기본 행위에 되돌아 오고, 파이썬은 약간의 디버깅 정보를 토해내고 포기합니다. IDE 안에서는 예외가 일어나더라도 별거 아니지만, 실제 파이썬 프로그램이 실행되는 동안에 예외가 일어나면 프로그램 전체가 굉음을 내며 멈추어 버릴 겁니다.
그렇지만 예외 때문에 전체 프로그램에 충돌이 일어날 필요는 없습니다. 예외가 일어나더라도 처리할 수 있습니다. (존재하지 않는 변수에 접근하는 것과 같이) 어떤 경우에는 정말로 코드에 버그가 있기 때문에 일어나기도 하지만, 대부분의 경우 에외는 예측할 수 있는 것입니다. 파일을 열려고 한다면 파일이 존재하지 않을 수도 있습니다. 데이터베이스에 접속하려고 할때, 데이터베이스가 존재하지 않을 수도 있고 접근에 필요한 올바른 보안 신용을 갖추지 못하고 있을 수도 있습니다. 코드 한 줄이 예외를 일으킬 가능성이 있다는 사실을 알고 있다면 그 예외를 try...except 블록으로 처리하면 됩니다.
>>> fsock = open("/notthere", "r") ① Traceback (innermost last): File "<interactive input>", line 1, in ? IOError: [Errno 2] No such file or directory: '/notthere' >>> try: ... fsock = open("/notthere") ② ... except IOError: ③ ... print "The file does not exist, exiting gracefully" ... print "This line will always print" ④ The file does not exist, exiting gracefully This line will always print
① | 내장된 open 함수를 사용하면 파일을 열어 읽을 수 있습니다 (open에 대하여 더 자세한 것은 다음 섹션을 참조). 그러나 파일이 존재하지 않고, 그래서 이 때문에 IOError 예외가 일어납니다. 명시적으로 IOError 예외에 대하여 점검 코드를 제공하지 않았기 때문에 파이썬은 그냥 일어난 일에 대하여 약간의 디버깅 정보를 인쇄해 내고 처리를 포기합니다. |
② | 똑 같이 존재하지 않는 파일을 열려고 합니다. 그러나 이 번에는 try...except 블록 안에서 열고 있습니다. |
③ | open 메쏘드가 IOError 예외를 일으키더라도 이미 그에 대하여 준비가 되어 있습니다. except IOError: 줄에서 그 예외를 잡아서 여러분이 준비한 코드 블록을 실행합니다. 이 경우에는 그냥 좀 상쾌한 에러 메시지를 인쇄할 뿐입니다. |
④ | 예외가 처리되면 계속해서 정상적으로 try...except 블록 다음의 첫 줄이 실행됩니다. 이 줄은 언제나 인쇄될 것이라는데 주목합시다. 예외가 일어나든 말든 상관없이 말입니다. 정말로 루트 디렉토리에 notthere라고 부르는 파일이 있다고 확신한다면 open을 호출해도 성공할 것이고 except 절은 무시되며 이 줄은 여전히 실행됩니다. |
예외는 별로 마음에 안 들수도 있지만 (어쨌거나 예외를 잡지 않으면 전체 프로그램이 충돌하기도 합니다), 대안을 고려해 보세요. 존재하지 않는 파일을 가리키는 쓸모없는 파일 객체를 돌려받고 싶습니까? 어떻게든 그의 유효성을 점검할 필요가 있습니다. 그렇지 않다면 줄 어디에선가 프로그램은 이상한 에러를 일으키고 그 어디에선가 소스로 다시 추적해 들어갈 필요가 있습니다. 분명히 이런 경험이 있으리라 믿습니다. 아시다시피 그런 경험은 유괘하지 않습니다. 예외가 있으면 에러가 일어나자 마자 문제의 근원에서 표준적인 방식으로 에러를 다룰 수 있습니다.
실제 에러 조건을 다루는 외에도 예외에는 수 많은 다른 사용법이 있습니다. 표준 파이썬 라이브러리에서 흔히 사용되는 예는 모듈을 반입해 보고, 작동하는지 점검하는 것입니다. 존재하지 않는 모듈을 반입하면 ImportError 예외가 일어납니다. 이를 사용하면 모듈이 실행시간에 사용가능한가에 근거하여 여러 수준의 기능을 정의할 수 있으며, 또는 여러 플랫폼을 지원할 수 있습니다 (플랫폼-종속적 코드는 따로 다른 모듈에 들어갑니다).
자신만의 독자적인 예외도 정의할 수 있는데 내장 Exception 예외를 계승한 클래스를 만들고 raise 명령어로 예외를 일으키면 됩니다. 관심이 있는 분은 더 읽어야 할 것 섹션을 참조하세요.
다음 예제에서는 예외를 사용하여 플랫폼 종속적 기능을 지원하는 법을 보여줍니다. 이 코드는 getpass 모듈에서 가져 왔는데, 이 모듈은 사용자로부터 암호를 얻는 포장 모듈입니다. 암호를 얻는 일은 UNIX와 Windows 그리고 Mac OS 플랫폼마다 다르게 완수되지만 이 코드는 그 모든 차이점들을 캡슐화해 넣습니다.
# getpass라는 이름을 적절한 함수에 묶는다. try: import termios, TERMIOS ① except ImportError: try: import msvcrt ② except ImportError: try: from EasyDialogs import AskPassword ③ except ImportError: getpass = default_getpass ④ else: ⑤ getpass = AskPassword else: getpass = win_getpass else: getpass = unix_getpass
① | termios는 UNIX-종속적 모듈로서 입력 터미널을 낮은 수준에서 제어합니다. (시스템에 없거나 시스템이 지원하지 않기 때문에) 이 모듈을 사용할 수 없으면 반입은 실패하고 파이썬은 ImportError를 일으키는데, 이것을 잡습니다. |
② | 좋습니다. termios가 없으며, 그래서 msvcrt를 시도해 봅니다. 이는 윈도우즈-종속적인 모듈로서 마이크로소프트 Visual C++ 실행시간 서비스의 많은 유용한 함수에 대하여 API를 제공합니다. 이 반입이 실패하면 파이썬은 ImportError를 일으키는데, 이것을 잡습니다. |
③ | 앞의 두 모듈이 작동하지 않으면 EasyDialogs로부터 함수를 하나 반입해 보는데, 이는 Mac OS-종속적 모듈로서 다양한 종류의 대화 상자를 띄워 주는 함수를 제공합니다. 역시, 이 반입이 실패하면 파이썬은 ImportError를 일으키고, 이것을 잡습니다. |
④ | 이 플랫폼-종속적 모듈을 모두 사용할 수 없습니다 (파이썬은 수 많은 다양한 플랫폼에 이식되어 있으므로 이런 일이 일어날 수 있습니다). 그래서 기본 패스워드 입력 함수에 의존할 필요가 있습니다 (이는 getpass 모듈 어느 곳엔가 정의되어 있습니다). 여기에서 무슨 일을 하고 있는지 주목하세요: default_getpass 함수를 getpass 변수에 할당하고 있습니다. 공식적인 getpass 문서를 읽어 보면 getpass 모듈에 getpass 함수가 정의되어 있습니다. getpass 함수를 플랫폼에 맞는 함수에 묶습니다. 다음 getpass 함수를 호출하면 이 코드가 여러분 대신 설정해 둔 플랫폼-종속적 함수를 진짜 호출하게 됩니다. 어느 플랫폼에서 코드가 실행되는지 알아야 한다거나 신경써야 할 필요가 없습니다 -- 그냥 getpass를 호출하면 언제나 올바르게 일을 해 줍니다. |
⑤ | try...except 블록은 else 절을 가질 수 있습니다. 마치 if 서술문처럼 말입니다. try 블록 안에서 아무 예외도 일어나지 않으면 계속해서 else 절이 실행됩니다. 이 경우, 그것은 from EasyDialogs import AskPassword 반입이 작동한다는 뜻이고, 그래서 getpass를 AskPassword 함수에 묶어야 합니다. 다른 try...except 블록들도 비슷하게 import가 작동하면 else 절을 가지고 getpass 함수를 적절한 함수에 묶을 수 있습니다. |
파이썬은 내장 open 함수가 있는데, 디스크에 있는 파일을 여는데 사용됩니다. open 함수는 파일 객체를 돌려줍니다. 이 객체에 있는 메쏘드와 속성으로 그 열린 파일를 조작하고 그에 관한 정보를 얻습니다.
>>> f = open("/music/_singles/kairo.mp3", "rb") ① >>> f ② <open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988> >>> f.mode ③ 'rb' >>> f.name ④ '/music/_singles/kairo.mp3'
① | open 메쏘드는 세 개의 매개 변수를 취할 수 있습니다: 파일이름과 모드 그리고 버퍼링 매개변수가 그것입니다. 오직 첫 번째 매개변수, 즉 파일이름만 필수입니다; 나머지 두 개는 선택적입니다. 지정되지 않으면 파일은 텍스트 모드로 읽기 전용으로 열립니다. 여기에서는 파일을 이진 모드 읽기 전용으로 열고 있습니다. (print open.__doc__를 해 보면 모든 가능한 모드를 멋지게 설명해 보여줍니다.) |
② | open 함수는 객체를 돌려줍니다 (지금까지는 이것이 별로 놀랍지 않을 것입니다). 파일 객체는 여러 유용한 속성이 있습니다. |
③ | 파일 객체의 mode 속성은 파일이 어느 모드로 열렸는지 알려줍니다. |
④ | 파일 객체의 name 속성은 파일 객체가 연 파일의 이름을 알려줍니다. |
파일을 열고 나면 무엇보다도 읽어 보고 싶을 것입니다. 다음 예제에서 보여주는 바와 같이 말입니다.
>>> f <open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988> >>> f.tell() ① 0 >>> f.seek(-128, 2) ② >>> f.tell() ③ 7542909 >>> tagData = f.read(128) ④ >>> tagData 'TAGKAIRO****THE BEST GOA ***DJ MARY-JANE*** Rave Mix 2000http://mp3.com/DJMARYJANE \037' >>> f.tell() ⑤ 7543037
① | 파일 객체는 자신이 연 파일의 상태를 유지관리합니다. 파일 객체의 tell 메쏘드는 열린 파일에서 현재 위치를 알려줍니다. 아직 이 파일에 대하여 아무런 처리도 하지 않았으므로 현재 위치는 0이고, 이는 파일의 처음입니다. |
② | 파일 객체의 seek 메쏘드는 열린 파일에서 또다른 위치로 이동시킵니다. 두 번째 매개변수에 첫 매개변수가 무슨 뜻인지 지정합니다; 0은 절대 위치로 이동한다는 뜻입니다 (파일의 시작에서부터 세어서 이동함). 1은 상대 위치로 이동한다는 뜻입니다 (현재 위치에서부터 세어서 이동함). 그리고 2는 파일의 끝에까지 상대 위치로 이동한다는 뜻입니다. 찾고 있는 MP3 태그는 파일의 끝에 저장되어 있으므로 2를 사용하여 파일 객체에서 파일의 끝에서부터 128 바이트 떨어진 곳으로 이동하라고 알려줍니다. |
③ | tell 메쏘드는 현재 파일 위치가 이동되었는지 확인합니다. |
④ | read 메소드는 지정된 개수의 바이트를 열린 파일로부터 읽어서 그 데이터를 문자열로 돌려줍니다. 선택적인 매개변수에는 읽을 바이트의 최대 개수를 지정합니다. 아무 매개변수도 지정되지 않으면 read는 파일의 끝까지 읽습니다. (여기에서는 그냥 read()로 기술할 수 있는데, 왜냐하면 정확하게 파일 안에 있다는 사실을 알고 있고 실제로 마지막 128 바이트를 읽을 것이기 때문입니다.) 읽은 데이터는 tagData 변수에 할당되고 현재 위치는 얼마나 많은 바이트를 읽었는가에 따라 갱신됩니다. |
⑤ | tell 메쏘드는 위치가 이동되었는지 확인합니다. 계산을 해 보면 128 바이트를 읽고나면 그 위치가 128 만큼 증가되어 있는 것을 보실 수 있습니다. |
파일을 열면 시스템 자원이 소모되고 그 자원은 파일 모드에 따라 다른 프로그램이 접근하지 못할 수도 있습니다. 파일 작업이 끝나면 최대한 신속하게 파일을 닫는 것이 중요합니다.
>>> f <open file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988> >>> f.closed ① False >>> f.close() ② >>> f <closed file '/music/_singles/kairo.mp3', mode 'rb' at 010E3988> >>> f.closed ③ True >>> f.seek(0) ④ 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() ⑤
① | 파일 객체의 closed 속성은 파일이 열렸는지 닫혔는지 나타냅니다. 이 경우, 파일은 여전히 열려 있습니다 (closed가 False입니다). |
② | 파일을 닫으려면 파일 객체의 close 메쏘드를 호출합니다. 이렇게 하면 파일에 대하여 잠구어 두었던 자원을 (혹 있다면) 풀어주고, 버퍼에 시스템이 아직 쓰지 못한 것을 (혹 있다면) 강제로 파일에 쓰고 시스템 자원을 풀어줍니다. |
③ | closed 속성으로 파일이 닫혔는지 확인합니다. |
④ | 단지 파일이 닫혔다고 해서 그것이 곧 파일 객체가 존재하지 않는다는 뜻은 아닙니다. f 변수는 영역을 벗어날 때까지 또는 손수 삭제할 때까지 죽지 않고 존재합니다. 그렇지만, 열린 파일에 대하여 작업하던 메쏘드들은 모두 일단 파일이 닫히면 작동을 멈춥니다; 모두 예외를 일으킵니다. |
⑤ | 이미 닫힌 파일에 대하여 close 메쏘드를 호출하더라도 예외를 일으키지 않습니다; 그냥 조용히 실패합니다. |
앞 장에 있는 fileinfo.py 샘플 코드에서 파일 처리 코드를 이해하기 위한 예들을 충분히 보았습니다. 다음 예제는 파일로부터 안전하게 열고 읽는 법과 우아하게 에러를 처리하는 법을 보여줍니다.
try: ① fsock = open(filename, "rb", 0) ② try: fsock.seek(-128, 2) ③ tagdata = fsock.read(128) ④ finally: ⑤ fsock.close() . . . except IOError: ⑥ pass
① | 파일을 열고 읽는 것은 위험하기 때문에 예외를 일으킬 수 있기 때문에, 이 모든 코드는 try...except 블록에 둘러 싸입니다. (표준화된 들여쓰기 멋지지 않습니까? 이 곳이 바로 그 고마움을 일깨워 주는 곳입니다.) |
② | open 함수는 IOError를 일으킬 수 있습니다. (파일이 존재하지 않을 수도 있기 때문입니다.) |
③ | seek 메쏘드는 IOError를 일으킬 수 있습니다. (파일이 128 바이트보다 작을 수 있기 때문입니다.) |
④ | read 메쏘드는 IOError를 일으킬 수 있습니다. (디스크에 배드 섹터가 있거나 네트워크 드라이브에 있다면 그 네트워크가 맛이 갔을 수도 있기 때문입니다.) |
⑤ | 새로운 것이군요: try...finally 블록입니다. 파일이 open 함수로 성공적으로 열렸다면 확실하게 닫고 싶을 것입니다. seek 메쏘드나 read 메쏘드에 의하여 예외가 일어나더라도 말입니다. 그것이 바로 try...finally 블록이 있는 이유입니다: finally 블록에 있는 코드는 언제나 실행됩니다. 비록 무엇인가 try 블록 안에서 예외를 일으키더라도 말입니다. 이 블록을 이렇게 생각하세요. 앞서 무슨 일이 일어났든 상관없이 나가면서 반드시 실행되는 코드라고 말입니다. |
⑥ | 드디어, IOError 예외를 다룹니다. 이 IOError 예외는 open이나 seek 또는 read를 호출하면 일어날 수 있습니다. 여기에서는 실제로 신경쓰지 않는데 그 이유는 그냥 조용히 무시하고 계속 진행할 것이기 때문입니다. (기억하세요. pass는 아무것도 하지 않는 파이썬 서술문입니다.) 완벽하게 합법적입니다; 예외를 “처리하는 것은” 명시적으로 아무것도 하지 않음을 의미할 수 있습니다. 그래도 여전히 처리되었다고 간주되며, 처리는 정상적으로 try...except 블록 이후 다음 코드 줄로 계속 진행됩니다. |
짐작하시겠지만 읽을 때와 마찬가지 방식으로 파일에 쓸 수도 있습니다. 두 개의 기본적인 파일 모드가 있습니다:
어느 모드든지 파일이 아직 존재하지 않으면 자동으로 생성합니다. 그래서 "로그 파일이 아직 존재하지 않으면 처음으로 그것을 열어 사용할 수 있도록 새로 빈 파일을 만들어라" 와 같은 하찮은 로직이 전혀 필요 없습니다. 그냥 열어서 쓰기 시작하면 됩니다.
>>> logfile = open('test.log', 'w') ① >>> logfile.write('test succeeded') ② >>> logfile.close() >>> print file('test.log').read() ③ test succeeded >>> logfile = open('test.log', 'a') ④ >>> logfile.write('line 2') >>> logfile.close() >>> print file('test.log').read() ⑤ test succeededline 2
① | 대담하게 처음부터 새 파일 test.log를 만들거나 아니면 기존의 파일을 덮어씁니다. 그리고 쓰기를 위해 그 파일을 엽니다. (두 번째 "w" 매개변수는 쓰기용으로 파일을 열라는 뜻입니다.) 그렇습니다. 예상대로 아주 위험합니다. 모쪼록 파일의 이전 내용은 신경쓰지 마시기를 바랍니다. 이제 날아가고 없으니까요. |
② | open 메쏘드가 돌려준 파일 객체의 write 메쏘드로 새로 열린 파일에 데이터를 추가할 수 있습니다. |
③ | file은 open과 동의어입니다. 단-한 줄로 파일을 열고 그 내용을 읽어서 인쇄합니다. |
④ | (방금 쓰기를 마쳤기 때문에) test.log가 존재한다는 사실을 우연히 알고 있습니다. 그래서 그 파일을 열어서 추가할 수 있습니다. ("a" 매개변수는 파일을 추가 모드로 연다는 뜻입니다.) 파일이 존재하지 않더라도 실제로 이렇게 할 수 있습니다. 왜냐하면 파일을 추가하기 위하여 열기 때문에 필요하다면 파일이 생성되기 때문입니다. 그러나 추가한다고 해서 그 파일에 있는 기존의 내용물에는 전혀 해를 끼치지 않습니다. |
⑤ | 보시다시피 이미 쓴 원래 줄과 추가한 두 번째 줄이 이제 test.log에 있습니다. 또 행갈이-문자가 포함되지 않았음을 주목하세요. 그 파일에 명시적으로 행갈이-문자를 쓰지 않았기 때문에 그 파일에 포함되지 않습니다. 행갈이-문자는 "\n" 문자로 쓸 수 있습니다. 이렇게 하지 않았으므로 파일에 쓴 모든 것은 모조리 같은 줄에 붙습니다. |
다른 모든 언어들처럼 파이썬에도 for 회돌이가 있습니다. 아직까지 보지 못한 유일한 이유는 파이썬이 다른 많은 것에 능하므로 그렇게 자주 필요하지 않았기 때문입니다.
다른 언어는 대부분 파이썬과 같은 강력한 리스트 데이터유형이 없습니다. 그래서 시작과 끝 그리고 증분을 지정하여 일정 범위의 정수나 문자를 지정하거나 기타 반복가능한 객체를 정의하려면 손수 수 많은 작업을 해야 합니다. 그러나 파이썬에서, for 회돌이는 단순히 리스트를 반복하며 지능형 리스트(list comprehensions)도 같은 방식으로 작동합니다.
>>> li = ['a', 'b', 'e'] >>> for s in li: ① ... print s ② a b e >>> print "\n".join(li) ③ a b e
① | for 회돌이의 구문은 지능형 리스트(list comprehensions)와 비슷합니다. li는 리스트이며, s는 첫 원소부터 시작하여 순서대로 각 원소의 값을 취합니다. |
② | if 서술문이나 기타 들여쓰기된 블록처럼, for 회돌이는 얼마든지 그 안에 코드가 들어갈 수 있습니다. |
③ | 이 때문에 아직 for 회돌이를 보지 못한 것입니다: 지금까지 필요하지 않았기 때문입니다. 단지 join이나 지능형 리스트를 원할 뿐인데 다른 언어에서 for 회돌이를 얼마나 많이 사용하는지 보면 놀랍습니다. |
for 회돌이에 (비주얼 베이직(Visual Basic)을 기준으로) “보통처럼” 세는 것도 역시 간단합니다.
>>> for i in range(5): ① ... print i 0 1 2 3 4 >>> li = ['a', 'b', 'c', 'd', 'e'] >>> for i in range(len(li)): ② ... print li[i] a b c d e
① | 예제 3.20, “연속적인 값 할당하기”에서 보셨듯이, range는 정수 리스트를 생산합니다. 그러면 이것으로 회돌이를 합니다. 약간 기묘해 보이지만 계수 회돌이를 가지면 유용한 경우가 종종 있습니다 (종종이라는 말을 강조합니다). |
② | 이렇게 하지 마세요. 이것은 비주얼 베이직(Visual Basic)-스타일의 사고방식입니다. 그런 사고방식에서 벗어나세요. 앞 예제에서 보여준 바와 같이 그냥 리스트를 회돌이하면 됩니다. |
for 회돌이는 그냥 단순한 카운터가 아닙니다. 온갖 종류의 것들을 회돌이 할 수 있습니다. 다음은 for 회돌이를 사용하여 사전을 순회하는 예입니다.
>>> import os >>> for k, v in os.environ.items(): ① ② ... print "%s=%s" % (k, v) USERPROFILE=C:\Documents and Settings\mpilgrim OS=Windows_NT COMPUTERNAME=MPILGRIM USERNAME=mpilgrim [...이하 생략...] >>> print "\n".join(["%s=%s" % (k, v) ... for k, v in os.environ.items()]) ③ USERPROFILE=C:\Documents and Settings\mpilgrim OS=Windows_NT COMPUTERNAME=MPILGRIM USERNAME=mpilgrim [...이하 생략...]
① | os.environ은 시스템에 정의된 환경 변수가 담긴 사전입니다. 윈도우즈에서는 MS-DOS로부터 접근할 수 있는 사용자와 시스템 변수입니다. UNIX에서는 쉘의 기동 스크립트에서 반출된 변수들입니다. Mac OS에서는 환경 변수라는 개념이 없습니다. 그래서 이 사전은 비어 있습니다. |
② | os.environ.items()는 터플이 담긴 리스트를 돌려줍니다: [(key1, value1), (key2, value2), ...]. for 회돌이는 이 리스트를 순회합니다. 첫 회전에서 key1을 k에 그리고 value1을 v에 할당합니다. 그래서 k = USERPROFILE 그리고 v = C:\Documents and Settings\mpilgrim. 두 번째 회전에서, k는 두 번째 키 OS를 얻고, v는 그에 상응하는 값 Windows_NT를 얻습니다. |
③ | 다중-변수 할당과 지능형 리스트로 전체 for 회돌이를 단 하나의 서술문으로 교체할 수 있습니다. 실제 코드에서 이렇게 할지 말지 여부는 개인적 코딩 스타일의 문제입니다. 나는 이런 스타일을 좋아하는데, 내가 하고 있는 일이 바로 사전을 리스트로 짝짓고 그 리스트를 한-개짜리 문자열로 결합하는 것이라는 사실이 확실하게 드러나기 때문입니다. 다른 프로그래머라면 이런 코드를 for 회돌이로 작성하기를 더 선호할 수도 있습니다. 그 출력결과는 어느 경우든 같습니다. 물론 이 버전이 약간 더 빠른데, 그 이유는 print 서술문이 오직 하나 밖에 없기 때문입니다. |
이제 for 회돌이를 MP3FileInfo에서 볼 수있습니다. 이 파일은 제 5 장에서 소개한 fileinfo.py 샘플 프로그램에서 빌려 왔습니다.
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)} ① . . . if tagdata[:3] == "TAG": for tag, (start, end, parseFunc) in self.tagDataMap.items(): ② self[tag] = parseFunc(tagdata[start:end]) ③
① | tagDataMap은 MP3 파일에서 찾고 있는 태그들을 정의해 둔 클래스 속성입니다. 태그는 길이가-고정된 필드에 저장됩니다. 일단 파일의 마지막 128 바이트를 읽고 나면 그 중에서 3바이트에서 32바이트까지는 언제나 노래 제목이고, 33에서 62 바이트까지는 언제나 작가 이름이며, 63에서 92까지는 앨범 이름이고, 등등 입니다. tagDataMap은 터플이 담긴 사전이고 각 터플에는 두 개의 정수와 함수 참조점이 담겨 있습니다. |
② | 복잡해 보이지만 그렇지 않습니다. for 변수의 구조는 items가 돌려준 리스트의 원소의 구조에 일치합니다. items는 (key, value) 형태의 터플이 담긴 리스트를 돌려줍니다. 이 리스트의 첫 원소는 ("title", (3, 33, <function stripnulls>))입니다. 그래서 회돌이의 첫 회전 동안 tag는 "title"을 얻고, start는 3을 얻으며, end는 33을 얻고, parseFunc는 stripnulls 함수를 얻습니다. |
③ | 이제 MP3 태그 하나에 대하여 매개변수를 모두 추출하였습니다. 그 태그 데이터를 저장하는 것은 쉽습니다. tagdata를 start에서 end까지 조각을 썰어 이 태그에 대한 실제 데이터를 얻어서, parseFunc를 호출하여 그 데이터를 후-처리하고, 그리고 이를 의사-사전 self의 tag의 키에 대한 값으로 할당합니다. tagDataMap의 모든 원소를 순회한 후, self는 모든 태그에 대하여 값을 가지고 있고, 그 모양이 어떤지 압니다. |
모듈은 파이썬의 다른 모든 것처럼 객체입니다. 모듈이 반입되면 언제나 전역 사전인 sys.modules을 통하여 그 모듈에 대한 참조점을 얻을 수 있습니다.
>>> import sys ① >>> print '\n'.join(sys.modules.keys()) ② win32api os.path os exceptions __main__ ntpath nt sys __builtin__ site signal UserDict stat
① | sys 모듈에는 예를 들어 실행중인 파이썬 버전 같은 시스템-수준의 정보 (sys.version 또는 sys.version_info)가 담겨 있으며, 허용된 최대 재귀 깊이 같은 시스템-수준의 옵션 (sys.getrecursionlimit() 그리고 sys.setrecursionlimit())이 담겨 있습니다. |
② | sys.modules은 파이썬이 기동한 이후로 반입된 모듈이 모두 담긴 사전입니다; 키는 모듈의 이름이고, 값은 모듈 객체입니다. 이는 여러분의 프로그램이 반입한 모듈 그 이상이라는 사실에 주목하세요. 파이썬은 기동시에 어떤 모듈들을 미리 적재합니다. 그리고 파이썬 IDE를 사용한다면 sys.modules에는 IDE에서 실행한 모든 프로그램에 의하여 반입된 모듈이 모두 담겨 있습니다. |
다음 예제는 sys.modules을 사용하는 법을 보여줍니다.
>>> import fileinfo ① >>> 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"] ② <module 'fileinfo' from 'fileinfo.pyc'>
다음 예제는 sys.modules 사전의 __module__ 클래스 속성을 사용하여 클래스에 정의된 그 모듈에 대한 참조점을 얻는 법을 보여줍니다.
>>> from fileinfo import MP3FileInfo >>> MP3FileInfo.__module__ ① 'fileinfo' >>> sys.modules[MP3FileInfo.__module__] ② <module 'fileinfo' from 'fileinfo.pyc'>
이제 fileinfo.py에서 어떻게 sys.modules이 사용되는지 볼 준비가 되었습니다. 이는 제 5 장에서 소개한 샘플 프로그램입니다. 다음 예제는 코드에서 해당 부분을 보여줍니다.
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 ③
① | 이는 인자를 두 개 가진 함수입니다; filename은 필수이지만 module은 선택적이고 FileInfo 클래스를 담은 모듈이 기본값입니다. 이는 비효율적인데, 그 이유는 그 함수가 호출될 때마다 파이썬이 sys.modules 표현식을 평가할 것이기 때문입니다. 사실, 파이썬은 기본 표현식을 오직 한 번만, 바로 그 모듈이 처음 반입될 때만 평가합니다. 나중에 보시겠지만 이 함수를 module 인자로 호출하지 않습니다. 그래서 module은 함수-수준의 함수로 기여합니다. |
② | 나중에 이 줄을 깊이 파헤쳐 보겠습니다. 먼저 os 모듈을 연구해 보겠습니다. 당장은 subclass가 결과적으로 클래스의 이름으로 끝난다고 가정하겠습니다. 예를 들면 MP3FileInfo처럼 말입니다. |
③ | 이미 getattr을 알고 있습니다. 이 메쏘드는 이름으로 한 객체에 대한 참조점을 얻습니다. hasattr은 한 객체에 특정한 속성이 있는지 알아보는 보충 함수입니다; 이 경우, 모듈에 특정 속성이 있는지 알아 봅니다 (물론 getattr과 꼭 마찬가지로 어떤 객체 어떤 속성에도 작동합니다). 영어로 이 줄은 다음과 같이 말합니다 “이 모듈에 이름이 subclass인 클래스가 있으면 그것을 돌려주고, 그렇지 않으면 바탕 클래스 FileInfo를 돌려주시오.” |
os.path 모듈에 파일과 디렉토리 조작을 위한 여러 함수가 있습니다. 여기에서는 디렉토리의 내용을 보여주고 경로 이름을 처리하는 방법을 살펴보겠습니다.
>>> import os >>> os.path.join("c:\\music\\ap\\", "mahadeva.mp3") ① ② 'c:\\music\\ap\\mahadeva.mp3' >>> os.path.join("c:\\music\\ap", "mahadeva.mp3") ③ 'c:\\music\\ap\\mahadeva.mp3' >>> os.path.expanduser("~") ④ 'c:\\Documents and Settings\\mpilgrim\\My Documents' >>> os.path.join(os.path.expanduser("~"), "Python") ⑤ 'c:\\Documents and Settings\\mpilgrim\\My Documents\\Python'
① | os.path는 모듈을 가리키는데 -- 이 모듈은 플랫폼에 따라 다릅니다. getpass를 플랫폼-종속적 함수에 설정하여 getpass가 플랫폼 사이의 차이점을 감싸듯이, os는 path를 플랫폼-종속적 모듈에 설정하여 플랫폼 사이의 차이점을 감쌉니다. |
② | os.path의 join 함수는 하나 이상의 부분적인 경로이름으로 경로이름을 구성합니다. 이 경우, 단순히 문자열을 결합합니다. (윈도우즈에서 경로이름을 다루는 일은 좀 성가신데 그 이유는 역사선 문자를 반드시 피신시켜야 하기 때문입니다.) |
③ | 이렇게 약간 복잡한 사례에서 join은 여분의 역사선을 경로이름에 추가한 다음 그것을 파일이름에 결합합니다. 이를 발견했을 때 너무 기뻤는데, 그 이유는 addSlashIfNecessary가 바보같은 작은 함수가 되어버렸기 때문입니다. 언제나 나는 새로운 언어로 도구함을 만들 때 이 바보같은 작은 함수를 작성해야 했거든요. 파이썬에서는 이런 바보같은 작은 함수를 작성하지 마세요; 똑똑한 사람들이 이미 여러분을 위하여 만들어 두었답니다. |
④ | expanduser 함수는 ~를 사용하여 현재 사용자의 홈 디렉토리를 나타내는 경로이름을 확장합니다. 사용자가 홈 디렉토리에 있다면 어느 플랫폼에서도 작동합니다. 윈도우즈나 UNIX 그리고 Mac OS X와 같이 어느 플랫폼이든 상관없습니다; Mac OS에는 영향을 미치지 않습니다. |
⑤ | 이 테크닉을 조합하면 사용자의 홈 디렉토리 아래에 있는 디렉토리와 파일에 대하여 쉽게 경로이름을 구성할 수 있습니다. |
>>> os.path.split("c:\\music\\ap\\mahadeva.mp3") ① ('c:\\music\\ap', 'mahadeva.mp3') >>> (filepath, filename) = os.path.split("c:\\music\\ap\\mahadeva.mp3") ② >>> filepath ③ 'c:\\music\\ap' >>> filename ④ 'mahadeva.mp3' >>> (shortname, extension) = os.path.splitext(filename) ⑤ >>> shortname 'mahadeva' >>> extension '.mp3'
① | split 함수는 완전한 경로이름을 갈라서 경로와 이름이 담긴 터플을 돌려줍니다. 기억하시는지? 다중-변수 할당을 사용하여 함수로부터 여러 값을 돌려줄 수 있습니다. 자, split이 바로 그런 함수입니다. |
② | split 함수의 반환 값을 두 개의 변수가 담긴 터플에 할당합니다. 각 변수는 반환된 터플에서 상응하는 원소의 값을 받습니다. |
③ | 첫 변수 filepath는 split이 돌려준 터플의 첫 원소의 값을 받습니다. 즉, 파일 경로를 받습니다. |
④ | 두 번째 변수 filename은 split이 돌려준 터플의 두 번째 원소의 값을 받습니다. 즉, 파일이름을 받습니다. |
⑤ | os.path에는 또한 splitext 함수가 들어 있는데, 이 함수는 파일이름을 갈라서 파일이름과 파일 확장자가 담긴 터플을 돌려줍니다. 같은 테크닉을 사용하여 하나하나 따로따로 변수에 할당할 수 있습니다. |
>>> os.listdir("c:\\music\\_singles\\") ① ['a_time_long_forgotten_con.mp3', 'hellraiser.mp3', 'kairo.mp3', 'long_way_home1.mp3', 'sidewinder.mp3', 'spinning.mp3'] >>> dirname = "c:\\" >>> os.listdir(dirname) ② ['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))] ③ ['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))] ④ ['cygwin', 'docbook', 'Documents and Settings', 'Incoming', 'Inetpub', 'Music', 'Program Files', 'Python20', 'RECYCLER', 'System Volume Information', 'TEMP', 'WINNT']
① | listdir 함수는 경로이름을 받아 디렉토리의 내용 목록을 돌려줍니다. |
② | listdir은 파일과 폴더를 모두 돌려주는데, 어느 것이 어느 것인지 구분하지 않습니다. |
③ | 리스트 여과 그리고 os.path 모듈의 isfile 함수를 사용하면 폴더와 파일을 구분할 수 있습니다. isfile 함수는 경로이름을 받아 그 경로가 파일이면 1을 돌려주고 그렇지 않으면 0을 돌려줍니다. 여기에서는 os.path.join을 사용하여 완전한 경로이름을 확인하지만 isfile은 현재 작업 디렉토리에서 상대적인 부분적인 경로에도 작동합니다. os.getcwd()를 사용하면 현재 작업 디렉토리를 얻을 수 있습니다. |
④ | os.path에는 또한 isdir 함수가 있는데 경로가 디렉토리이면 1을 돌려주고 그렇지 않으면 0을 돌려줍니다. 이를 사용하면 디렉토리 안의 하부디렉토리의 목록을 얻을 수 있습니다. |
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] ③ ④ ⑤
① | os.listdir(directory)은 directory 안에 있는 모든 파일과 폴더의 목록을 돌려줍니다. |
② | f로 리스트를 순회하면서 os.path.normcase(f)를 사용하여 운영체제의 기본값에 맞게 대소문자를 정규화합니다. normcase는 대소문자-무시 운영체제를 보상해 주는 유용한 작은 함수입니다. 이런 운영체제에서는 mahadeva.mp3와 mahadeva.MP3를 같은 파일로 간주합니다. 예를 들면 윈도우즈와 Mac OS에서 normcase는 전체 파일이름을 소문자로 변환합니다; UNIX-호환 시스템에서는 파일이름을 그대로 돌려줍니다. |
③ | f로 다시 정규화된 리스트를 순회하면서, os.path.splitext(f)를 사용하여 파일이름을 이름과 확장자로 가릅니다. |
④ | 각 파일에 대하여 확장자가 여러분이 관심을 둔 파일 확장자 리스트에 들어 있는지 알아봅니다 (fileExtList가 그것인데, listDirectory 함수에 건넸습니다). |
⑤ | 해당 파일에 대하여, os.path.join(directory, f)를 사용하여 그 파일의 전체 경로이름을 구성하고, 전체 경로이름이 든 리스트를 돌려줍니다. |
☞ | |
가능하면 파일과 디렉토리 그리고 경로 조작에 os와 os.path에 있는 함수를 사용하는게 좋습니다. 이런 모듈들은 플랫폼-종속적 모듈을 감싼 것이고, 그래서 os.path.split 같은 함수들은 UNIX와 Windows 그리고 Mac OS, 파이썬이 지원하는 기타 다른 플랫폼에 작동합니다. |
디렉토리의 내용을 얻는 다른 방법이 하나 더 있습니다. 아주 강력하고, 와일드카드 종류를 사용하는데 이는 명령어 줄에서 작업하면서 이미 여러분이 많이 보아온 것입니다.
>>> os.listdir("c:\\music\\_singles\\") ① ['a_time_long_forgotten_con.mp3', 'hellraiser.mp3', 'kairo.mp3', 'long_way_home1.mp3', 'sidewinder.mp3', 'spinning.mp3'] >>> import glob >>> glob.glob('c:\\music\\_singles\\*.mp3') ② ['c:\\music\\_singles\\a_time_long_forgotten_con.mp3', 'c:\\music\\_singles\\hellraiser.mp3', 'c:\\music\\_singles\\kairo.mp3', 'c:\\music\\_singles\\long_way_home1.mp3', 'c:\\music\\_singles\\sidewinder.mp3', 'c:\\music\\_singles\\spinning.mp3'] >>> glob.glob('c:\\music\\_singles\\s*.mp3') ③ ['c:\\music\\_singles\\sidewinder.mp3', 'c:\\music\\_singles\\spinning.mp3'] >>> glob.glob('c:\\music\\*\\*.mp3') ④
① | 앞서 보았듯이, os.listdir은 그냥 디렉토리 경로를 받아 그 디렉토리 안에 든 모든 파일과 디렉토리를 나열해 줍니다. |
② | 반면에 glob 모듈은 와일드카드를 취해, 그에 부합하는 모든 파일과 디렉토리의 완전한 경로를 돌려줍니다. 여기에서 와일드카드는 디렉토리 경로에다 "*.mp3"이며, 이는 모든 .mp3 파일에 일치합니다. 반환된 리스트의 각 원소는 이미 그 파일의 전체 경로에 포함되어 있음을 주의하세요. |
③ | 특정 디렉토리에서 "s"로 시작하고 ".mp3"로 끝나는 모든 파일을 찾고 싶다면 역시 그렇게 할 수 있습니다. |
④ | 이제 다음 시나리오를 연구해 보겠습니다: music 디렉토리가 있고, 그 안에 하부디렉토리가 여러개 있으며, 그 하부 디렉토리마다 .mp3 파일이 들어 있다고 생각해 보겠습니다. glob을 한 번만 호출하면 두 개의 와일드카드를 한 번만 사용하면 됩니다. 하나는 "*.mp3"이고 (.mp3 파일에 일치), 다른 하나는 디렉토리 경로 그 자체 안에 있는데, c:\music 안에 있는 하부디렉토리에 일치합니다. 어마어마한 파워가 지독히도 간단해-보이는 단 한개의 함수에 들어 있습니다! |
다시 한 번, 도미노가 모두 준비되었습니다. 코드줄 마다 어떻게 작동하는지 하나하나 보셨습니다. 이제 뒤로 물러서서 모든 것이 어떻게 조립되는지 살펴보겠습니다.
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] ⑥
① | listDirectory가 이 전체 모듈에서 제일 관심을 끕니다. 이 함수는 (이 경우 c:\music\_singles\ 와 같은) 디렉토리와 (['.mp3'] 같은) 관심의 대상인 파일 확장자 리스트를 받아서, 사전처럼 행위하는 클래스 실체가 담긴 리스트를 돌려줍니다. 이 글래스 실체는 그 디렉토리 안에 있는 해당 파일에 관한 메타데이터를 담고 있습니다. 단 몇 줄의 코드에 불과합니다. |
② | 앞 섹션에서 보셨듯이, 이 코드는 directory에서 (fileExtList가 지정한대로) 해당 파일 확장자를 가진 모든 파일의 완전한 경로이름이 담긴 리스트를 얻습니다. |
③ | 예전-학창 시절에 파스칼(Pascal)을 배웠던 프로그래머라면 잘 아시겠지만, 파이썬이 내포된 함수를 지원한다고 말해 주면 대부분의 사람들은 멍하니 저를 쳐다 봅니다 -- 문자 그대로 함수 안의 함수를 말합니다. 내포된 함수인 getFileInfoClass는 자신이 정의된 함수, 즉 listDirectory 안에서만 호출됩니다. 다른 함수에서도 그런 것처럼, 인터페이스 선언이나 기타 다른 어떤 특별한 것도 필요하지 않습니다; 그냥 함수를 정의하고 코드하면 됩니다. |
④ | 이제 os 모듈을 살펴보았으므로, 이 줄은 더 확실하게 이해가 되시리라 믿습니다. 파일의 확장자를 얻어 (os.path.splitext(filename)[1]), 그것을 대문자로 바꾸고 (.upper()), 점에서 조각을 썬다음 ([1:]), 그로부터 문자열 형식화를 사용하여 클래스 이름을 구성합니다. 그래서 c:\music\ap\mahadeva.mp3는 .mp3가 되고 이는 .MP3가 되며 이는 MP3이 되고 이는 MP3FileInfo가 됩니다. |
⑤ | 이 파일을 처리할 처리자 클래스의 이름을 구성한 후, 실제로 이 모듈에 그 처리자가 존재하는지 알아봅니다. 존재한다면 그 클래스를 돌려줍니다. 그렇지 않으면 바탕 클래스 FileInfo를 돌려줍니다. 다음은 아주 중요한 점입니다: 이 함수는 클래스를 돌려줍니다. 클래스의 실체가 아니라, 클래스 그 자체를 돌려줍니다. |
⑥ | “관심 파일” 리스트 (fileList)에 존재하는 각 파일마다 그 파일이름 (f)을 가지고 getFileInfoClass를 호출합니다. getFileInfoClass(f)를 호출하면 클래스를 돌려줍니다; 어느 클래스인지 정확하게 알지 못하지만 신경쓸 필요가 없습니다. 다음 (그것이 무엇이든 상관없이) 이 클래스의 실체를 만들고 그 파일이름 (역시 f)를 __init__ 메쏘드에 건넵니다. 이 장의 전반부에서 보셨듯이 FileInfo의 __init__ 메쏘드는 self["name"]을 설정하는데, 이는 __setitem__을 촉발시키고, 이는 다시 자손 (MP3FileInfo)에 의하여 덮어쓰기 됩니다. 그리하여 적절하게 파일을 해석하여 그 파일의 메타데이터를 추출합니다. 해당 파일마다 그 모든 처리를 거쳐서 결과 실체들을 리스트에 담아 돌려줍니다. |
listDirectory은 완전히 총괄적이라는 사실에 주목하세요. 어느 유형의 파일을 얻을지 미리 알지 못합니다. 즉 어느 클래스에 잠재적으로 그런 파일을 다룰 수 있도록 정의되어 있는지 미리 알지 못합니다. 디렉토리에서 처리할 파일들을 찾아보고, 자신만의 모듈을 들여다보고 (MP3FileInfo 같이) 어떤 특별한 처리자 클래스가 정의되어 있는지 알아봅니다. 이 프로그램을 확장하면 다른 유형의 파일을 다룰 수 있습니다. 그냥 적절하게-이름 붙여 클래스를 정의하면 됩니다: HTML이라면 HTMLFileInfo, 워드 .doc 파일이라면 DOCFileInfo 등등으로 정의하면 됩니다. listDirectory는 하나도 수정하지 않고 그들 모두를 다룰 수 있으며, 그냥 진짜 일은 적절한 클래스에 맡기고 그 결과를 대조해 보기만 하면 됩니다.
제 5 장에서 소개한 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
다음 장으로 들어가기 전에, 다음 사항을 편안하게 처리할 수 있는지 확인하세요:
☜ 제 05 장 객체와 객체지향 | """ Dive Into Python """ 다이빙 파이썬 |
제 07 장 정규표현식 ☞ |