You are here: Home Dive Into Python 3

Difficulty level: ♦♢♢♢♢

처음 만나는 파이썬 프로그램

Don’t bury your burden in saintly silence. You have a problem? Great. Rejoice, dive in, and investigate.
당신의 짐을 고요한 침묵속에 묻어두지 마라. 문제가 있는가? 좋다. 기뻐하고, 뛰어들어, 탐구하자!
Ven. Henepola Gunaratana

 

Diving In

대부분의 프로그래밍 학습서 진도대로 나가자면, 지루한 자료형부터 시작해서 프로그래밍의 기본 원리라던지, 어떻게 해야 간단한 채팅 프로그램이라도 만들 수 있는지에 대한 순서대로 설명해야 될 겁니다. 하지만, 모두 잠시 접어두고 그냥 한번 파이썬 코드를 쳐다봅시다. 네, 알고 있습니다. 어쩌면 한줄도 이해 못할 수도 있죠. 그래도 너무 걱정 마시고, 손해보는 셈치고 그냥 한번 읽어 보세요. 제가 이제부터 친절하게 한줄 한줄 설명해 드릴꺼니까요.

[download humansize.py]

SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

def approximate_size(size, a_kilobyte_is_1024_bytes=True):
    '''Convert a file size to human-readable form.

    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000

    Returns: string

    '''
    if size < 0:
        raise ValueError('number must be non-negative')

    multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
    for suffix in SUFFIXES[multiple]:
        size /= multiple
        if size < multiple:
            return '{0:.1f} {1}'.format(size, suffix)

    raise ValueError('number too large')

if __name__ == '__main__':
    print(approximate_size(1000000000000, False))
    print(approximate_size(1000000000000))

이제 이 프로그램을 커맨드라인에서 실행해 봅시다. 마이크로소프트 윈도우즈에서는 아래처럼 되겠죠?

c:\home\diveintopython3\examples> c:\python31\python.exe humansize.py
1.0 TB
931.3 GiB

맥 OS X나 Linux에서는, 이렇게 쓰세요

you@localhost:~/diveintopython3/examples$ python3 humansize.py
1.0 TB
931.3 GiB

축하드립니다. 짝짝짝. 방금 여러분들께서는 처음으로 파이썬 프로그램을 실행시킨 겁니다. 명령행에서 파이썬 실행기를 불러왔고, 실행시킬 파이썬 스크립트 파일의 이름을 넘겨줬습니다. 그 스크립트 안에는 함수 하나가 정의되어 있었는데요, 바이트 단위의 파일 크기를 근사치의, 사람이 읽기 편한 포맷으로 변환해주는 approximate_size()라는 함수입니다. (이런 기능을 아마 윈도우 익스플로러나 맥 OS X의 파인더, 아니면 리눅스의 노틸러스나 Dolphon, Thunar등의 어플리케이션을 통해 보신 분도 있을 겁니다. 문서 폴더에서 자세히 보기를 선택하면 문서의 아이콘, 이름, 크기, 형태, 최근 수정일 등이 나타나게 되죠. 만약 폴더에 TODO라는 1093바이트 크기의 파일이 있다면, TODO 1093 bytes 라고 나타나는 대신 TODO 1 KB 라고 나타납니다. 바로 이런 게 approximate_size() 함수가 하는 일입니다.)

스크립트 아랫 부분을 보면 print(approximate_size(arguments)) 라고 되있는 코드를 볼 수 있습니다. 전형적인 함수 호출인데요, — 우선 approximate_size() 함수에 파라미터를 넘겨서 호출하고, 리턴된 결과값을 바로 print() 함수에 넘겨줬습니다. print() 는 기본으로 제공되는 함수라서 따로 선언할 필요가 없습니다. 이런 기본 제공 함수(built-in function)는 그냥 언제 어디서든 사용할 수 있습니다. (파이썬은 다양한 기본 제공 함수들을 가지고 있지만, 훨씬 더 다양한 함수들이 파이썬 모듈들 아래 또아리를 틀고 있습니다. 그럼 어떤 모듈이 있는지 궁금하기겠죠? 자자. 조금만 기다리세요. 먼저 공부하던것 부터 끝내구요.)

그런데 이 스크립트를 실행시키면, 왜 항상 같은 결과가 나올까요? 이제부터 이것에 대해 알아볼 겁니다. 먼저, approximate_size() 함수를 봅시다.

함수 선언

파이썬도 다른 언어들과 마찬가지로 함수가 있습니다. 하지만 C++처럼 함수를 위한 별도의 헤더 파일도 없고, 파스칼처럼 interfaceimplementation이 분리되어 있지도 않습니다. 파이썬에서는 함수가 필요하면 그냥 그 자리에서 선언합니다.:

def approximate_size(size, a_kilobyte_is_1024_bytes=True):

파이썬의 함수 선언은 def라는 키워드로 시작하고, 그 뒤에 함수 이름, 괄호가 차례로 나옵니다. 괄호 안에는 파라미터를 적어주고, 파라미터가 여러 개라면 콤마를 사용해 구분합니다.

한편, 파이썬 함수 선언시 반환 값의 타입을 별도로 지정하지 않는다는 점에 주의하세요. 심지어 어떤 값을 리턴하는지 아닌지도 지정할 필요가 없습니다. (사실, 모든 파이썬 함수는 어떤 값을 반환합니다. 만약 함수내에 return문이 사용되었다면 해당하는 그 값을 돌려주고, return 문이 없다면 None이라고 하는 파이썬에서 지정한 null 값을 돌려줍니다.)

어떤 언어는 값을 돌려주는 함수는 function이라는 키워드로 선언을 시작하고, 값을 반환하지 않는 단순 서브루틴(subroutine) 코드은 sub라는 키워드로 시작합니다. 하지만, 파이썬에는 서브루틴 같은 개념은 없습니다. 모든 것은 함수이고, 모든 함수는 None이건 뭐건 간에 어쨌든 값을 반환합니다. 그리고 모든 함수는 def라는 키워드로 시작합니다.

여기서 approximate_size() 함수는 sizea_kilobyte_is_1024_bytes라는 두개의 파라미터를 받습니다. 두 파라미터 모두 자료형을 명시하지 않았습니다. 파이썬에서는 변수의 자료형을 별도로 선언하지 않습니다. 왜냐하면 파이썬이 변수의 형태를 내부적으로 유추하여 알아낸 후 내부적으로 기억하고 있기 때문입니다.

Java 같은 정적 타입 언어에서는, 함수 리턴 값과 파라미터의 자료형을 반드시 명시해 주어야 합니다. 그러나 파이썬같은 동적 타입 언어에서는 따로 자료형을 명시할 필요가 없습니다. 변수나 함수에 할당된 값을 통해 그 자료형을 알아내고, 또 기억하기 때문입니다.

추가적이거나 이름을 가지는 넘김값

파이썬에서는 함수 선언시 파라미터에 디폴트 값을 넘겨 줄 수 있습니다. 만약 함수가 호출시 파라미터가 없다면, 지정된 디폴트 파라미터로 대체됩니다. 그리고 named argument 를 사용하면 함수 인자의 순서를 꼭 지키지 않아도 됩니다.

approximate_size()함수의 선언부를 다시 한번 살펴보죠:

def approximate_size(size, a_kilobyte_is_1024_bytes=True):

여기서 두번째 파라미터 a_kilobyte_is_1024_bytesTrue라는 기본값을 가지고 있는데, 이렇게 지정하면 이 파라미터는 반드시 지정하지 않아도 됩니다. 함수 호출시 이 파라미터 없이 호출할 수 있다는 뜻이고, 실제로 그렇게 호출하면 파이썬은 두번째 파라미터가 들어갈 자리에 True를 넣고 동작합니다.

이제 스크립트의 맨 아래 부분를 봅시다

if __name__ == '__main__':
    print(approximate_size(1000000000000, False))  
    print(approximate_size(1000000000000))         
  1. 처음으로 approximate_size()를 부를 때는 두 파라미터 모두 전달했습니다. 두번째 파라미터의 값으로 False를 넘겼기 때문에 approximate_size()함수의 a_kilobyte_is_1024_bytesFalse가 됩니다.
  2. 두번째로 approximate_size()함수를 부를 때는, 하나의 파라미터만을 전달했습니다. 두번째 파라미터에 꼭 값을 넘겨줄 필요가 없기 때문에 괜찮습니다. 함수 호출 쪽에서 따로 두번째 파라미터를 지정하지 않았기 때문에, 그 값은 함수 선언에 따라 자연히 True가 됩니다.

한편 이름을 가지고 함수에 값을 전달할수도 있습니다.

>>> from humansize import approximate_size
>>> approximate_size(4000, a_kilobyte_is_1024_bytes=False)       
'4.0 KB'
>>> approximate_size(size=4000, a_kilobyte_is_1024_bytes=False)  
'4.0 KB'
>>> approximate_size(a_kilobyte_is_1024_bytes=False, size=4000)  
'4.0 KB'
>>> approximate_size(a_kilobyte_is_1024_bytes=False, 4000)       
  File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
>>> approximate_size(size=4000, False)                           
  File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
  1. 이번 approximate_size() 함수호출에 첫번째 파라미터인 (size)에 4000을 넣고, a_kilobyte_is_1024_bytes 에는 False를 넣었습니다.
  2. 이번 approximate_size() 함수 호출에는 size 파라미터에 4000을, a_kilobyte_is_1024_bytes 파라미터에는 False를 각각 넣습니다. (함수 선언부에서의 파라미터 순서와 같지만, 순서는 별로 중요하지 않습니다.)
  3. 이번 approximate_size() 함수 호출에는 먼저 a_kilobyte_is_1024_bytesFalse값을 넣고 size4000을 넣습니다. (순서는 중요하지 않다고 말했죠?)
  4. 이 코드는 에러입니다. 한번 파라미터의 이름을 사용했으면, 그 뒤에 등장하는 파라미터에도 파라미터 이름을 사용해야 합니다.
  5. 앞과 같은 이유로 이번 함수호출도 실패입니다. 놀라셨나요? 얼핏보기에 size4000이란 값을 주었고, 그리고 그 뒤에 등장하는 False가 의미하는 것도 분명해 보이지만, 파이썬은 그렇게 동작하지 않습니다. 일단 하나의 파라미터에 이름을 주었으면, 그 뒤에 나오는 모든 파라미터들도 이름을 명시해줘야 합니다.

가독성 좋은 코드 작성하기

코드 문서화의 중요성에 대해 지루하게 설명드리고 싶지는 않습니다. 다만 이것 하나만 명심하세요. 코드는 대부분 한번 작성되고 나면 수정되는 일이 많지 않습니다. 하지만, 동료가 되었건 내가 되었건 간에 작성된 코드는 한동안 읽히기 마련입니다. 혹시 코드를 짜고나서 6 개월 후에 읽어본 적이 있나요? 내가 왜 이렇게 짰는지 기억도 가물가물하지만, 내일 아침까지 당장 버그를 수정해야 하는 그런 상황말입니다. 하지만 걱정마세요. 파이썬엔 코드를 술술 읽힐수 있도록 작성할 수 있는 기능이 탑재되있습니다. 놀랍죠? 그러니 마구 써먹자고요. 아마 곧 제게 감사하게 될겁니다.

documentation string

함수에 커멘트를 달고 싶으면 document string을 추가해주면 됩니다.(이를 줄여서 docstring 이라고 부릅니다). 아래 approximate_size() 이라는 함수는 docstring을 가지고 있습니다.

def approximate_size(size, a_kilobyte_is_1024_bytes=True):
    '''Convert a file size to human-readable form.

    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000

    Returns: string

    '''

여러 줄에 걸쳐 docstring 을 달고 싶으면, 따옴표를 연달아 세 개 쓰면 됩니다. 그 사이에 나오는 모든 문자열 (줄바꿈이나 공백, 그리고 따옴표 등등)은 하나의 docstring 으로 간주됩니다. 이렇게 세겹 따옴표를 이용한 커멘트는 docstring 뿐만 아니라, 어디서든 사용할 수 있지만, 역시 주로 사용되는 곳은 docstring을 정의할 때 입니다.

세겹 따옴표를 이용하면, 홑따옴표와 곁따옴표가 포함된 문자열을 쉽게 정의할 수 있습니다. 펄(Pearl) 5의 qq/.../ 처럼 말이죠.

세겹 따옴표로 정의된 문자열은 함수가 하는 일을 적어둔 docstring입니다. docstring은 반드시 함수 본문 내에서 처음으로 정의되는 객체여야 합니다.(코드 상으로는 함수 선언 바로 다음 줄에 써줘야 합니다). docstring을 정의하는 것이 의무는 아니지만, 가능한 한 달아주세요. 뭐 이런 얘기는 이미 귀가 따갑도록 들으셨으리라 생각합니다. 하지만, 파이썬의 docstring 은 단순한 주석문이 아닙니다.:
놀랍게도, docstring은 함수의 어트리뷰트 가운데 하나입니다. 그러니까, 런타임에 호출할 수 있습니다.

많은 파이썬 통합개발환경(IDE, Integrated Development Environment)들이 docstring을 적극적으로 활용합니다. 가령 특정 함수 이름을 타이핑하기 시작하면, 툴팁으로 함수의 docstring이 보여주기도 합니다. 물론, 얼마나 성의있게 잘 작성해 두었는지에 따라 유용할 수도 있고 아닐 수도 있을겁니다.

import 검색 경로

파이썬 라이브러리 검색 경로에 대해 잠깐 짚어 보겠습니다. 파이썬은 모듈을 import 할 때 몇 가지 경로를 우선적으로 검색합니다. 정확히 말하면 sys.path에 정의되있는 모든 디렉토리를 뒤져봅니다. 이 디렉토리 목록은 리스트(list)로 저장되있기 때문에, 일반적인 리스트 처리 함수들을 통해 쉽게 읽고 쓸 수 있습니다. (리스트에 대해서는 고유 자료형장에서 자세히 알아보겠습니다.)

>>> import sys                                                 
>>> sys.path                                                   
['', 
 '/usr/lib/python31.zip', 
 '/usr/lib/python3.1',
 '/usr/lib/python3.1/plat-linux2@EXTRAMACHDEPPATH@', 
 '/usr/lib/python3.1/lib-dynload', 
 '/usr/lib/python3.1/dist-packages', 
 '/usr/local/lib/python3.1/dist-packages']
>>> sys                                                        
<module 'sys' (built-in)>
>>> sys.path.insert(0, '/home/mark/diveintopython3/examples')  
>>> sys.path                                                   
['/home/mark/diveintopython3/examples', 
 '', 
 '/usr/lib/python31.zip', 
 '/usr/lib/python3.1', 
 '/usr/lib/python3.1/plat-linux2@EXTRAMACHDEPPATH@', 
 '/usr/lib/python3.1/lib-dynload', 
 '/usr/lib/python3.1/dist-packages', 
 '/usr/local/lib/python3.1/dist-packages']
  1. sys 모듈에 있는 함수와 속성을 사용하기 위해서는 먼저 모듈을 import 해야 합니다.
  2. sys.path는 현재 검색 경로로 지정된 디렉토리들이 리스트 형태로 저장되 있습니다. (PC의 OS나 설치한 파이썬 버전, 디렉토리 위치에 따라 예제에서와는 다를 수도 있습니다.) 우리가 모듈을 import 할때, 파이썬 내부에서는 import 모듈과 같은 이름을 가진 .py 파일을 찾기위해 이 디렉토리들을 차례대로 검색합니다.
  3. 죄송합니다. 모든 모듈이 .py 파일로 되있는건 아닙니다. 앞에서 살짝 거짓말을 했습니다. 사실은 좀 더 복잡하거든요. sys 모듈 같은건 내장(Built-in) 모듈인데요, 내장 모듈은 파이썬 내부에 구현되 있어서, 소스 코드를 볼 수 없습니다. 내장 모듈도 import 해야 사용할 수 있는 등, 그 사용법은 다른 모듈들과 크게 다르지 않지만, 차이점은 내장 모듈은 파이썬이 아니라 C 언어로 작성되있고, 파이썬 안에 구현되있다는 것입니다. (파이썬 자체도 C 언어로 작성되었습니다.)
  4. 런타임에 sys.path에 원하는 디렉토리명을 추가하여, 파이썬 검색 경로에 지정한 디렉토리를 설정할 수 있습니다. 이렇게 한 다음부터는 모듈 import시 지정한 디렉토리 내부 역시 라이브러리 검색경로가 됩니다. 이느 파이썬이 실행되는 동안만 유효합니다.
  5. sys.path.insert(0, new_path)라고 작성하면, new_path가 sys.path라는 리스트의 첫번째 인덱스 항목으로 추가됩니다. 파이썬 모듈 검색 경로의 맨 앞에 위치하게 되기 때문에, 이렇게 해주면 실행속도가 조금이라도 더 나을겁니다. 같은 이름의 모듈이 있어서 충돌이 나는 경우(예를 들어, 어떤 라이브러리의 파이썬 2 버전 대신 파이썬 3 버전으로 사용하고 싶을 때), 이렇게 검색경로의 맨 앞에 원하는 디렉토리를 추가해주면, 파이썬이 설치될 때 디폴트로 셋팅된 모듈보다 직접 추가한 모듈을 먼저 찾게 됩니다.

파이썬에선 모든 것이 객체입니다

앞에서 이미 알려 드렸지만, 다시 한번 말씀드립니다. 파이썬 함수는 attribute를 가지고 있고, 이 attribute는 실행 시 사용할 수 있습니다. 네. 맞습니다. 파이썬 함수도 파이썬의 다른 것들처럼 객체입니다. 파이썬에선 모든게 객체라고요.

파이썬 쉘을 열어서 다음 코드를 작성해 보세요:

>>> import humansize                               
>>> print(humansize.approximate_size(4096, True))  
4.0 KiB
>>> print(humansize.approximate_size.__doc__)      
Convert a file size to human-readable form.

    Keyword arguments:
    size -- file size in bytes
    a_kilobyte_is_1024_bytes -- if True (default), use multiples of 1024
                                if False, use multiples of 1000

    Returns: string

  1. humansize 프로그램을 모듈(필요시 꺼내 쓰거나 다른 파이썬 프로그램에서 사용할 수 있는 코드 묶음)로 import 합니다. import 된 모듈은 그 모듈에서 제공하는 모든 퍼블릭 함수나 클래스, 속성 등이 참조될 수 있습니다. 모듈 또한 다른 모듈의 기능을 import 해 사용할 수 있고, 이를 그때 그때 파이썬 쉘을 이용해 테스트 코드를 작성할 수도 있습니다. 이런 컨셉이 얼마나 중요하고, 또 다른 프로그래밍 언어와 어떻게 다른지, 앞으로 더 많은 예를 보여드리겠습니다.
  2. 모듈 안에 정의된 함수를 사용할 때, 모듈의 이름도 같이 써줘야 합니다. approximate_size라고 하면 안되고 humansize.approximate_size와 같이 작성해야 합니다. 자바(Java) 클래스를 써본 적이 있다면 그것과 비슷하다는 걸 알아차리셨을 겁니다.
  3. 함수 호출 대신, 함수의 속성인 __doc__을 호출했습니다.

파이썬의 import는 펄의 require와 비슷합니다. 파이썬에서 모듈을 import 하면 모듈.함수와 같은 문법을 이용해 그 함수를 호출합니다. 펄에서 모듈을 require 하면, 모듈::함수 와 같은 문법을 사용해 그 함수를 호출합니다. 많이 닮았죠?

객체가 뭔가요?

파이썬에서는 모든 것은 객체라고 했기 때문에, 모든 것은 객체의 특징인 속성과 메소드를 가집니다. 예를 들어, 파이썬의 함수는 docstring을 반환하는 __doc__ 이라는 기본 속성(Built-in attribute)을 가지고 있습니다. 한편 sys 모듈도 일종의 객체고, 속성중에는 path라는 속성도 가지고 있습니다. 그외에도 파이썬에는 여러 다른 객체 타입들이 있습니다.

흠. 여전히 아리송하죠? 제가 계속해서 파이썬에선 모든 것이 객체입니다 라고 주구장창 떠들고 있는데요, 그보다 먼저 객체의 정의에 대해 생각해봐야 할것 같습니다. 사실 프로그래밍 언어들 마다 객체에 대한 정의는 조금씩 다릅니다. 어떤 언어는 객체가 무조건 속성과 메소드를 가져야 한다고 정의합니다. 다른 언어에서는 객체가 하위 클래스를 가질 수 있어야 합니다. 이에 반해 파이썬에서 객체에 대해 내리는 정의는 다른 프로그래밍 언어보다 조금 유연합니다. 파이썬의 객체는 속성과 메소드를 가질 수도 있고 아닐 수도 있습니다. 그리고 모든 객체가 하위클래스를 반드시 가져야 하는 것은 아닙니다. 그러나 분명한 건 파이썬에서의 객체란 변수에 할당할 수 있거나, 함수에 인자로 넘길 수 있는, 그런 어떤 모든 것입니다.

다른 프로그래밍 언어를 통해 “first class object” (역자 주: 객체가 할 수 있도록 정의된 모든 일을 제한없이 할 수 있는 객체. 이에 반해 부분적으로만 할 수 있는 객체를 second class object라고 함) 라는 말을 들어본 적이 있을겁니다. 파이썬 함수는 first-class object 입니다. 즉 함수를 다른 함수의 인자로 넘길 수 있습니다. 파이썬의 모듈도 first class object입니다. 함수의 인자로 모듈 전체를 넘기는 일도 가능합니다. 파이썬의 클래스도 first class object고, 클래스 인스턴스도 first class object입니다.

파이썬에선 전부 다 객체다: 이 개념은 매우 중요하기 때문에 한동안 계속 떠들겁니다. string은 객체입니다. 리스트도 객체입니다. 함수도 객체입니다. 클래스도 객체입니다. 클래스 인스턴스도 객체입니다. 심지어 모듈도 객체입니다.

코드 들여쓰기

파이썬의 함수는 시작이나 끝을 지정하는 표시가 없습니다. 다른 언어처럼 괄호({ })도 사용하지 않습니다. 파이썬의 함수는 콜론(:)과 들여쓰기 만을 이용하여 그 시작과 끝을 나타냅니다.

def approximate_size(size, a_kilobyte_is_1024_bytes=True):  
    if size < 0:                                            
        raise ValueError('number must be non-negative')     
                                                            
    multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
    for suffix in SUFFIXES[multiple]:                       
        size /= multiple
        if size < multiple:
            return '{0:.1f} {1}'.format(size, suffix)

    raise ValueError('number too large')
  1. 코드 블록은 들여쓰기에 의해 결정됩니다. 여기서 코드 블록이라 함은, if문, for문, while문 등을 말하는 겁니다. 들여쓰기 시작하면 블록이 시작되고, 다시 내어쓰면 그 블록이 끝납니다. 대 소 괄호나, 키워드를 따로 써주지 않습니다. 이 말은 파이썬에선 빈칸이 특별한 의미를 가지기 때문에, 빈칸이 항상 일관적이어야 한다는 뜻입니다. 이 예제에서 함수 코드는 네칸을 띄워 썼습니다. 반드시 네칸일 필요는 없지만 한번 네칸으로 들어쓰기를 지정했다면 계속 네칸으로 사용해야 합니다. 함수 정의를 끝내기 위해서는 들여쓰기를 끝내면 됩니다.
  2. 파이썬에서 if 구문 뒤에는 들여쓴 코드블럭이 따라옵니다. 만약 if 식에 대한 계산결과가 참이라면, 들여쓴 블록안에 있는 구문들이 실행되고, 만약 계산결과가 거짓이고 else 블럭이 존재한다면 else 블럭 안의 코드가 실행됩니다. if 문에 괄호를 사용하지 않은걸 눈여겨 보세요.
  3. 이 줄은 if 코드 블럭 내부입니다. 만일 size < 0 이 참으로 판명된다면, raise 구문이 ValueError 타입의 예외를 발생시킬 겁니다.
  4. 이 라인은 함수의 끝을 의미하는게 아닙니다. 코드를 좀 더 읽기 쉽게 하기 위해 한 라인을 비워두었을 뿐입니다. 파이썬은 완전히 빈 라인은 코드로 간주하지 않습니다. 함수는 다음 줄로 이어집니다.
  5. for 루프 역시 코드 블럭의 시작을 의미합니다. 같은 크기로 들여쓴다면 한 코드블럭에 여러 줄을 담더라도 문제 없습니다. 이 for 루프는 세 줄의 코드를 담고 있습니다. 코드 블럭에 여러 줄을 담았다 해서 특별히 사용해야 할 문법 같은건 없습니다. 그냥 계속 들여쓰고, 계속 이어나가면 됩니다.

처음엔 약간 어색하기도 하고 포트란을 써본 분들은 포트란과 헷갈리기도 하겠지만, 점점 익숙해지면 그 장점이 드러나기 시작할 겁니다. 파이썬에선 들여쓰기가 코딩 스타일이 아니라 강제적으로 따라야 하는 규칙이기때문에, 어느 누가 작성했건 간에 코드 생김새가 비슷 비슷 합니다. 이는 C++ 나 Java 같은 다른 언어에서 흔하게 겪는 코딩 스타일문제를 파이썬에선 겪지 않아도 됨을 의미합니다. if 문 다음에 대괄호(curly brace)를 같은 라인에 쓸건지 아님 다음 라인에 쓸건지 따위로 신경쓰지 않아도 된다는 거죠. 따라서 다른 파이썬 프로그래머의 코드라도 백이면 백, 코딩 스타일이 비슷할 수밖에 없는거고, 이는 곧 더 빨리 남의 모듈을 이해할 수 있다는 뜻이기도 합니다.

파이썬은 줄바꿈 문자(carriage return)를 사용해 구문을 나누고, 콜론과 들여쓰기를 이용해 코드 블럭을 구분합니다. 이에 반해 C++와 Java는 세미콜론을 사용해 문장을 나누고 대괄호(curly brace)를 써 코드 블럭을 나눕니다.

예외

파이썬은 예외를 적극적으로 활용하고, 모든 표준 라이브러리가 예외를 발생시키고 또 처리합니다. 책 전반에 걸쳐 예외가 자주 등장하기 때문에 이번 장에서 예외에 대해 잘 알아두시길 바랍니다.

예외란 무엇입니까? 대부분의 예외는 에러고, 에러란 뭔가 잘못된 것을 표시하는데 쓰입니다. (모든 예외가 에러는 아니지만, 지금은 일단 넘어갑시다.) 어떤 프로그래밍 언어는 에러처리를 위해 예외를 사용하기 보다는 에러 코드를 반환하는 방식을 권장하기도 합니다. 하지만 파이썬스러운 방식은 에러코드를 반환하는 것보다 예외를 던지고 이를 올바르게 처리하는 것입니다.

파이썬 쉘을 통해 코드를 실행하다 에러가 발생하면, 쉘은 에러 상황에 대한 간략한 설명과 왜 이런 문제가 생겼는지를 화면을 통해 알려줍니다. 그러고는 끝입니다. 그게 다죠. 이런 상황을 파이썬에선 처리되지 않은(unhandled) 예외라고 부릅니다. 예외를 처리할수 있는 모듈이 없는거죠. 보통 이런 상황에서 파이썬은 예외를 처리할 수 있는 예외처리 루틴을 찾아 헤매다, 결국엔 포기하고 약간의 디버깅 정보를 뱉어내고는 제 할 일 끝냈다는 듯이 멈춰버립니다. 사실 이런 상황이 쉘에서 실행되는 상황이라면 별 문제가 아니겠지만, 만약 어느 서버의 데몬 프로세스로 돌고 있는 파이썬 프로그램이었다면요? 운이 좋으면 그 악명 높은 파란 화면만 뜨고, 프로그램이 멈추고 전체 시스템이 종료될 수도 있지만, 최악의 경우엔 서버에서 하얀 연기가 모락모락 피어오를지도 모를 일입니다. 어떤 분은 회사에 억한 심정이 있어서 이걸 원하셨을 수도 있지만, 여러분들은 이런 일을 원하지 않으셨을 거라 굳게 믿습니다.

파이썬 함수는 어떤 예외를 발생시킬지, 함수레벨에서 별도로 정의하지 않습니다. 이점이 자바에서의 예외처리와 다른 점입니다. 파이썬에서는 함수가 어떤 예외상황을 일으킬 수 있을지 프로그래머 스스로 생각해내야 합니다.

예외가 발생했다고 해서 반드시 프로그램이 죽어버리는건 아닙니다. 왜냐면 예외는 잘 처리하면 되니까요. 존재하지도 않는 변수에 값을 넣는 경우처럼, 프로그래머가 진짜 실수를 만들기도 합니다. 그러나, 어떤 경우엔 예외가 발생하리라는 것을 미리 예측할 수 있는 상황도 있습니다. 가령, 이미 삭제된 파일을 열려고 한다거나, import하려는 모듈이 설치되있지 않다거나, 데이터베이스에 연결하려는데 네트워크 문제로 연결할 수 없거나, 보안 세션이 만료되었다던지 등등, 이런 모든 상황들이 미리 예외가 생길것을 예측할 수 있는 상황들입니다. 이처럼, 코드의 어느 부분에서 예외가 생길지를 미리 알 수 있다면, 예외처리를 위해 try...except 구문을 사용하면 됩니다.

파이썬은 예외처리를 위해 try...except 구문을 사용하고, 예외를 던지는데는 raise 구문을 사용합니다. 참고로, 자바와 C++에서는 예외처리 구문이 try...catch이고, 예외를 던질 때는 throw 문을 사용합니다.

approximate_size() 함수는 두 가지 상황아래서 예외를 발생시킵니다. 하나는 size가 처리할 수 없을만큼 큰 값이 들어왔을 때이고, 다른 하나는 0보다 작은 값이 들어왔을 때입니다.

if size < 0:
    raise ValueError('number must be non-negative')

예외를 던지는 방법은 아주 간단합니다. raise 문 뒤에 예외이름을 쓰고 필요하면 디버깅 문자열로 채워주면 됩니다. 디버깅 문자열은 반드시 제공할 필요는 없습니다. 문법이 함수 호출과 꽤나 비슷하죠? (실제로 예외는 클래스로 구현되있고, raise 구문이 하는 역할은 ValueError 클래스의 인스턴스를 생성하고, 인스턴스의 초기화시 'number must be non-negative' 문자열을 인자로 제공해주도록 되어있답니다. 일단은 여기까지만 알아두세요.)

모든 함수에 예외 처리 구문을 만들 필요는 없습니다. 처리되지 않은 예외는 애초에 그 코드를 호출한 함수에게로 돌아 갑니다. 호출한 함수에서도 예외가 처리되지 않으면 다시 그 함수를 호출한 함수에게로 예외가 돌아가고요. 이런 식으로, 계속해서 예외가 처리될 때까지 타고 올라갑니다. 만약 맨 처음 시작한 곳까지 올라갔는데도 예외가 처리되지 않으면, 프로그램은 결국 비정상적으로 종료되고 파이썬은 오류 추적 메시지를 출력하고 끝냅니다. 맞습니다. 이게 끝입니다. 다시 한번 말씀드리지만, 프로그램이 이렇게 동작하기를 원했다면 이렇게 해도 됩니다.

import 에러 처리하기

파이썬에서는 모듈 import 시 발생될 수 있는 예외를 처리하기 위해 ImportError 라는 클래스를 정의해 두었습니다. import 예외는 다양한 상황에서 발생할 수 있지만, 대부분의 경우, import 하려는 모듈을 import 검색 경로에서 발견할 수 없을 때 발생됩니다. 이런 에러를 처리하기 위해, ImportError 예외 클래스를 사용합니다. 예를 들어, 여러분이 chardet 이라고 하는, 문자열 인코딩 자동 감지 라이브러리를 이용해 프로그램을 작성하고 있다고 생각해 봅시다. 하지만 여러분은 이 chardet 라이브러리가 타겟 시스템에 설치되 있는지 없는지 미리 알 수 없습니다. 이런 상황에서 여러분이 할 수 있는 최선은 chardet 라이브러리가 설치되 있지 않더라도, 프로그램이 오동작하지 않고, 적절한 동작을 하도록 하는 것입니다. try..except구문을 사용해 아래처럼 코딩하면 됩니다.

try:
  import chardet
except ImportError:
  chardet = None

if 문으로 chardet 모듈이 존재하는지 여부도 간단히 확인할 수 있습니다:

if chardet:
  # do something
else:
  # continue anyway

ImportError 예외 클래스를 활용하는 다른 예를 하나 더 알려드리죠. 서로 다른 두 개의 모듈이 공통의 API를 구현하고 있다고 생각해봅시다. 만약 한 모듈이 다른 하나보다 성능상에서 더 나은 경우(더 빠르거나, 메모리를 더 적게 사용한다던지)가 있을 수 있습니다. 이런 상황에서는 먼저 성능이 좋은 모듈을 import 해보고, 성공하면 그 모듈을 사용하고, 만약 실패하면 성능이 떨어지는 모듈을 import 하도록 코드를 작성하는게 좋을겁니다. 이런 라이브러리의 예로는 XML 장에서 다뤄질 모듈인 lxml 모듈과 xml.etree.ElementTree이 있습니다. xml.etree.ElementTree는 파이썬3 표준 라이브러리에 포함된 모듈이지만 성능이 거북이 걸음마 수준입니다. 반면 lxml 모듈은 이 라이브러리보다 더 빠르지만, 표준 라이브러리가 아니기 때문에 사용자가 직접 다운로드받아 설치해야 하는 써드 파티 모듈입니다.

try:
    from lxml import etree
except ImportError:
    import xml.etree.ElementTree as etree

코드 맨 마지막에 xml.etree.ElementTree 모듈을 import 한 후 etree라는 별명을 주었습니다. 두 모듈 모두 공통의 API를 구현했기 때문에, 위 코드를 실행하고 난 뒤에 어떤 모듈을 import 했는지 구분할 필요가 없습니다. 그리고 import한 모듈은 try 구문의 성공여부와 관계없이 etree라는 이름으로 사용될 것이므로, 코드의 뒷부분이 if 문 같은 것을 사용해 각각의 모듈에 대해 따로 따로 처리해야 할 필요도 없습니다.

unbound 변수

approximate_size() 함수 코드 중에서 다음 코드를 다시 한번 실눈을 뜨고 쳐다봅시다.

multiple = 1024 if a_kilobyte_is_1024_bytes else 1000

multiple이라는 변수를 미리 선언도 하지 않고, 값을 할당했네요. 하지만 괜찮습니다. 파이썬에서는 이렇게 할 수 있으니까요. 파이썬에선 이런 변수를 unbound 변수라고 부릅니다. 하지만 값을 할당하지 않은 변수를 참조하는건 허용되지 않습니다. 이 때는 NameError 예외가 발생합니다.

>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> x = 1
>>> x
1

별것 아닌것 처럼 보이는 이 기능을 언젠가는 분명 고마워하게 될 겁니다.

대소문자 구분은 확실히

파이썬의 모든 이름은 대소문자를 구분합니다: 변수명, 함수명, 클래스명, 모듈 명, 예외명. 이 모든 이름들은 대소문자를 구분합니다. 이 값들을 다룰 때는 언제나 대소문자에 유의해 사용해야 한다는 점 잊지 마세요.

>>> an_integer = 1
>>> an_integer
1
>>> AN_INTEGER
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'AN_INTEGER' is not defined
>>> An_Integer
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'An_Integer' is not defined
>>> an_inteGer
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'an_inteGer' is not defined

스크립트 실행하기

파이썬에선 모든 것이 객체이고 모듈 또한 그렇습니다. 모듈에는 몇 가지 유용한 속성이 있는데요, 이 속성을 잘 이용하면 여러분이 직접 모듈을 작성해 나가면서 동시에 테스트까지 수행할 수 있습니다. 어떻게 하냐구요? 코드 안에 조금 특별한 코드 블록을 추가하면 됩니다. 추가된 이 구문은 파이썬 파일이 커맨드 라인을 통해 실행될 때 호출됩니다. 예를 보는게 아무래도 낫겠죠? 샘플 코드가 humansize.py 파일의 끝 부분에 잘 나와 있으니 그걸 보면서 설명드리죠.


if __name__ == '__main__':
    print(approximate_size(1000000000000, False))
    print(approximate_size(1000000000000))

C 언어에서 처럼 파이썬도 동일한 값 여부를 비교할 때는 ==를 사용하고, 변수에 값을 할당할 때는 =을 사용합니다. 하지만 C 언어에서와는 달리, 비교하는 줄과 동일한 라인에서 값을 할당하는 코드까지 연달아 작성하면 파이썬은 에러를 냅니다. 파이썬에선 이렇게 함으로써, 값을 비교하려다 실수로 할당해 버리는 사고를 미연에 방지할 수 있습니다.

자, 이 if 문이 대체 뭐가 그리 특별하다는 걸까요? 앞에서 말했지만, 파이썬에서는 모듈도 객체입니다. 그리고, 모든 모듈은 __name__이라는 속성을 가지고 있습니다. __name__ 속성의 값은 모듈을 쓰는 방법에 따라 다르게 셋팅되는데요. 크게 두 가지로 나뉩니다. 첫번째는, 모듈이 다른 모듈로 import 되어 함수 등을 제공하는 형태입니다. 이때 이 모듈의 __name__속성의 값은 모듈이 정의된 파일의 이름입니다. 파일 이름이지만 파일의 전체 경로와 파일의 확장자는 제외된 형태로 반환합니다. 아래 예를 보세요.

>>> import humansize
>>> humansize.__name__
'humansize'

두번째는, 모듈이 독립 프로그램의 형태로 직접 실행되는 경우입니다. 이런 상황에서는 __name__ 속성의 값이 파이썬이 지정한 디폴트 값인 __main__이라는 문자열로 설정됩니다. 파이썬은 이 if 문의 값을 계산해 식이 맞다는걸 확인한 후, if 구문의 내용을 실행합니다. 위의 예에서는 두 개의 값이 출력되겠네요.

c:\home\diveintopython3> c:\python31\python.exe humansize.py
1.0 TB
931.3 GiB

자, 여기까지가 여러분의 첫번째 파이썬 프로그램입니다. 지금까지 수고 많으셨습니다.

더 읽을거리

© 2001–11 Mark Pilgrim