방금 소개해 드린 while 서술문 외에도 파이썬은 다른 언어에 있는 일반적인 실행 흐름을 이해합니다. 약간 다르기는 하지만 말입니다.
가장 널리 알려진 서술문 유형은 아마도 if 서술문일 것입니다. 예를 들어:
>>> x = int(raw_input("Please enter an integer: "))
>>> if x < 0:
... x = 0
... print 'Negative changed to zero'
... elif x == 0:
... print 'Zero'
... elif x == 1:
... print 'Single'
... else:
... print 'More'
...
0개 이상의 elif 부분이 더 있을 수 있으며, else 부분은 선택적입니다. 키워드 `elif'는 `else if'의 약자로서, 과도한 들여쓰기를 피하는데 쓸모가 있습니다. if ... elif ... elif ... 가 연속되면 다른 언어에서의 switch 또는 case 서술문에 대응합니다.
파이썬에서 for 서술문은 C나 Pascal에서 사용하던 것과 다릅니다. (파스칼에서처럼) 언제나 늘어 선 숫자를 반복하거나, 또는 (C처럼) 사용자에게 중지 조건과 반복 간격을 정의하도록 해주기 보다, 파이썬의 for 서술문은 연속열(리스트나 문자열) 안의 원소들에 대하여 나타난 순서대로 반복합니다. 예를 들어 (농담이 아님):
>>> # 문자열의 길이 측정: ... a = ['cat', 'window', 'defenestrate'] >>> for x in a: ... print x, len(x) ... cat 3 window 6 defenestrate 12
회돌이 안에서 반복되고 있는 연속열을 수정하는 것은 안전하지 않습니다 (리스트 같은 수정가능 연속열에 이런 일이 일어날 수 있습니다). 회돌이하고 있는 연속열을 수정하고 싶다면 (예를 들어, 선택된 원소들을 복제해 하나 더 만들고 싶다면) 반드시 사본을 회돌이해야 합니다. 이렇게 하는 일은 조각썰기 표기법 덕분에 특히 편리합니다:
>>> for x in a[:]: # 전체 리스트를 조각썰어 사본을 만든다 ... if len(x) > 6: a.insert(0, x) ... >>> a ['defenestrate', 'cat', 'window', 'defenestrate']
숫자 연속열을 반복할 필요가 있다면, 내장 함수 range()가 편리합니다. 이 함수는 숫자가 늘어선 리스트를 만들어 줍니다:
>>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
주어진 마지막 점은 생성된 리스트에 포함되지 않습니다;
range(10)은 10개의 값을 가진 리스트를 만들어 주는데, 이는 길이가 10인 연속열의 원소에 대하여 합법적인 지표입니다. 범위를 다른 숫자에서 시작시키거나, 다르게 증가값을 지정하는 것이 가능합니다 (심지어 음수도 가능합니다; 종종 이를 `step'이라고 부릅니다):
>>> range(5, 10) [5, 6, 7, 8, 9] >>> range(0, 10, 3) [0, 3, 6, 9] >>> range(-10, -100, -30) [-10, -40, -70]
연속열의 지표를 반복하려면, range()와 len()을 다음과 같이 조합해서 사용하세요:
>>> a = ['Mary', 'had', 'a', 'little', 'lamb'] >>> for i in range(len(a)): ... print i, a[i] ... 0 Mary 1 had 2 a 3 little 4 lamb
break 서술문은 C처럼 for 회돌이나 while 회돌이 바로 바깥 테두리로 빠져 나옵니다.
continue 서술문도 역시 C에서 빌려 왔는데, 회돌이의 다음 반복을 계속합니다.
회돌이 서술문에는 else 절이 있을 수 있습니다; (for에서) 리스트가 소진되고 나서 회돌이가 종료되거나 또는 (while에서) 조건이 거짓이면 실행됩니다. 그러나 회돌이가 break 서술문에 의해서 종료되면 실행되지 않습니다. 이것을 다음 회돌이에서 예제로 보여줍니다. 다음 회돌이는 소수를 찾습니다:
>>> for n in range(2, 10): ... for x in range(2, n): ... if n % x == 0: ... print n, 'equals', x, '*', n/x ... break ... else: ... # 회돌이는 인수를 찾지 않고 빠져나간다 ... print n, 'is a prime number' ... 2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3
pass 서술문은 아무것도 하지 않습니다. 서술문에 문법적으로 요구되지만 프로그램에게는 아무 조치도 요구하지 않을 경우 사용할 수 있습니다. 예를 들어:
>>> while True: ... pass # 바쁨-키보드 인터럽트를 기다림 ...
피보나치 수열을 임의의 경계까지 작성하는 함수를 만들 수 있습니다:
>>> def fib(n): # 피보나치 수열을 n까지 작성한다 ... """Print a Fibonacci series up to n.""" ... a, b = 0, 1 ... while b < n: ... print b, ... a, b = b, a+b ... >>> # 이제 방금 정의한 함수를 호출한다: ... fib(2000) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
키워드 def는 함수 정의(definition)를 이끕니다. 다음에는 반드시 함수 이름 그리고 공식 매개변수들의 리스트가 반괄호로 둘러싸여 제공되어야 합니다. 함수의 몸체를 형성하는 서술문들은 그 다음 줄에서부터 시작하며, 반드시 들여쓰기 되어야 합니다. 함수 몸체의 첫 서술문은 선택적으로 문자열 기호상수가 될 수 있습니다; 이 문자열 기호상수는 그 함수의 문서화 문자열, 즉 docstring입니다.
문서화 문자열을 사용하여 자동으로 온라인 문서 또는 인쇄 문서를 생산하는 도구들이 있습니다. 또는 사용자가 상호대화적으로 코드를 열람할 수 있도록 해 줍니다; 코드에 문서화문자열을 포함하는 것이 좋은 습관입니다. 그래서 습관을 들이도록 노력하세요.
함수가 실행되면 그 함수의 지역 변수들을 위해 새로운 심볼 테이블이 도입됩니다. 보다 정확하게 말하면, 함수 안에서 일어나는 변수 할당은 모두 값을 지역 심볼 테이블에 저장합니다; 반면에 변수 참조는 먼저 지역 심볼 테이블을 찾아보고, 다음 전역 심볼 테이블 그리고 다음으로 내장 이름 테이블에서 찾습니다. 그리하여, 전역 변수는 (global 서술문 안에 이름이 없는 한) 함수 안에서 바로 할당할 수 없습니다. 그렇지만 참조는 할 수 있습니다.
함수 호출에 대한 실제 매개변수들 (인자들)은 함수가 호출될 때 호출된 함수의 지역 심볼 테이블에 도입됩니다; 그리하여, 인자는 값으로 호출 방법(call by value)을 사용하여 건네집니다 (여기에서 value는 언제나 객체 참조이지, 그 객체의 값이 아닙니다). 4.1 한 함수가 또다른 함수를 호출하면, 새로운 심볼 테이블이 그 호출에 대하여 생성됩니다.
함수를 정의하면 함수 이름이 현재 심볼 테이블 안에 도입됩니다. 함수 이름의 값은 유형을 가지는데 이 유형을 파이썬은 사용자-정의 함수로 인지합니다. 이 값은 또다른 이름에 할당할 수 있습니다. 그러면 이 이름도 또한 함수로 사용할 수 있습니다. 다음은 일반적인 이름바꾸기 메커니즘으로 기여합니다:
>>> fib <함수 fib at 10042ed0> >>> f = fib >>> f(100) 1 1 2 3 5 8 13 21 34 55 89
어쩌면 fib가 함수가 아니라 프로시저라고 이의를 제기하실지도 모르겠습니다. 파이썬에서 프로시저는 C 처럼 그저 값을 돌려주지 않는 함수일 뿐입니다. 사실, 기술적으로 말해, 프로시저는 값을 돌려줍니다. 약간 따분한 값이긴 하지만 말입니다. 이 값은 이른바 None이라고 부릅니다 (내장 이름이지요). 값 None을 인쇄하는 것은 보통 써야할 유일한 값이라면 파이썬에서 억제합니다. 정말 보고 싶다면 볼 수 있습니다:
>>> print fib(0) None
피보나치 수열의 리스트를 돌려주는 함수를 작성하는 것은 간단합니다. 인쇄하는 대신에:
>>> def fib2(n): # 피보나치 수열을 n까지 돌려준다 ... """피보나치 수열을 n까지 포함한 리스트를 돌려준다.""" ... result = [] ... a, b = 0, 1 ... while b < n: ... result.append(b) # 아래 참조 ... a, b = b, a+b ... return result ... >>> f100 = fib2(100) # 호출한다 >>> f100 # 결과를 인쇄한다 [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
이 예제는 예와 같이 몇가지 새로운 파이썬의 특징을 보여줍니다:
None을 돌려줍니다. 프로시저의 끝에 도달해도 None을 반환합니다.
result.append(b) 서술문은 리스트 객체 result의 메쏘드(method)를 호출합니다. 메쏘드란 객체에 '속한' 함수이며 이름이 obj.methodname로서, obj는 객체이고 (이는 표현식일 수도 있음) methodname은 메쏘드의 이름으로서 그 객체의 유형에 의하여 정의됩니다. 유형이 다르면 메쏘드도 다릅니다. 유형이 다른 메쏘드는 아무 혼란 없이 이름이 같을 수 있습니다 (자신만의 객체 유형과 메쏘드를 정의할 수 있습니다. 나중에 이 자습서에서 언급하겠지만 classes를 사용하면 됩니다). 예제에서 보여준 메소드 append()는 리스트 객체에 정의되어 있습니다; 이 메쏘드는 새로운 원소를 리스트에 끝에 추가하여 줍니다. 이 예제에서는 "result = result + [b]"와 동등하지만, 이 메쏘드가 더 효율적입니다.
가변 갯수의 인자를 가진 함수를 정의하는 것도 가능합니다. 세 가지 형태가 있는데, 조합해서 사용할 수 있습니다.
가장 유용한 형태는 하나 이상의 인자에 대하여 기본 값을 지정하는 것입니다. 이렇게 함수를 만들면 허용된 것보다 더 적은 갯수의 인자로 호출이 가능합니다. 예를 들어:
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
while True:
ok = raw_input(prompt)
if ok in ('y', 'ye', 'yes'): return True
if ok in ('n', 'no', 'nop', 'nope'): return False
retries = retries - 1
if retries < 0: raise IOError, 'refusenik user'
print complaint
이 함수는 다음 두 방식중 하나로 호출할 수 있습니다:
ask_ok('Do you really want to quit?') 또는 다음과 같이 호출합니다:
ask_ok('OK to overwrite the file?', 2).
이 예제는 또한 in 키워드를 소개합니다. 이 키워드는 연속열에 특정 값이 들어 있는지 테스트합니다.
기본 값은 함수 정의시에 정의 영역 안에서 평가됩니다. 그러므로 다음은
i = 5
def f(arg=i):
print arg
i = 6
f()
5를 인쇄합니다.
경고: 기본 값은 오직 한 번 평가됩니다. 이 때문에 기본 인자가 리스트나 사전 또는 실체와 같은 변경가능 객체일 때 차이가 생깁니다. 예를 들어, 다음 함수는 호출 때마다 자신에게 건네지는 인자들을 축적합니다:
def f(a, L=[]):
L.append(a)
return L
print f(1)
print f(2)
print f(3)
This will print
[1] [1, 2] [1, 2, 3]
기본 값이 잇다르는 호출 사이에 공유되지 않도록 하려면 대신에 함수를 다음과 같이 작성하면 됩니다:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
함수는 "keyword = value" 형태의 키워드 인자를 사용하여 호출할 수 있습니다. 예를 들면, 다음 함수는:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print "-- This parrot wouldn't", action,
print "if you put", voltage, "volts through it."
print "-- Lovely plumage, the", type
print "-- It's", state, "!"
다음 방식들중의 하나로 호출이 가능합니다:
parrot(1000)
parrot(action = 'VOOOOOM', voltage = 1000000)
parrot('a thousand', state = 'pushing up the daisies')
parrot('a million', 'bereft of life', 'jump')
그러나 다음과 같은 호출은 모두 유효하지 않습니다:
parrot() # 필요한 인자 없음 parrot(voltage=5.0, 'dead') # 비-키워드 인자 다음에 키워드가 나옴 parrot(110, voltage=220) # 인자 값이 중복 parrot(actor='John Cleese') # 모르는 키워드
일반적으로 인자 리스트는 위치 인자 다음에 키워드 인자가 나와야 하며, 키워드는 반드시 공식 매개변수 이름에서 뽑아야 합니다. 공식 매개변수가 값을 가질지 말지는 중요하지 않습니다. 어떤 인자도 한 번 이상 값을 받지 못합니다 -- 위치 인자에 상응하는 공식 매개변수 이름은 같은 호출에서 키워드로 사용할 수 없습니다. 다음은 이런 제한 때문에 실패하는 예입니다:
>>> def 함수(a): ... pass ... >>> 함수(0, a=0) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: 함수() got multiple values for keyword 인자 건네기 'a'
마지막에 공식 매개변수가 **name의 형태로 존재하면, 공식 매개변수에 상응하는 키워드 인자들을 제외한 모든 인자들이 담긴 사전을 받습니다. 이는 *name 형태의 공식 매개변수와 결합해도 됩니다. 공식 매개변수 리스트 다음의 위치 인자들을 담은 터플을 받습니다(다음 섹션에서 기술함). (*name은 반드시 **name 앞에 나타나야 합니다.) 예를 들어, 함수를 다음과 같이 정의하면:
def cheeseshop(kind, *인자 건네기s, **keywords):
print "-- Do you have any", kind, '?'
print "-- I'm sorry, we're all out of", kind
for arg in 인자 건네기s: print arg
print '-'*40
keys = keywords.keys()
keys.sort()
for kw in keys: print kw, ':', keywords[kw]
다음과 같이 호출할 수 있습니다:
cheeseshop('Limburger', "It's very runny, sir.",
"It's really very, VERY runny, sir.",
client='John Cleese',
shopkeeper='Michael Palin',
sketch='Cheese Shop Sketch')
물론 다음과 같이 인쇄됩니다:
-- Do you have any Limburger ? -- I'm sorry, we're all out of Limburger It's very runny, sir. It's really very, VERY runny, sir. ---------------------------------------- client : John Cleese shopkeeper : Michael Palin sketch : Cheese Shop Sketch
키워드 인자 리스트의 sort() 메쏘드를 먼저 호출하고나서 keywords 사전의 내용물을 인쇄하는 것에 주의하세요; 이렇게 하지 않으면, 인쇄되는 인자의 순서가 정의되지 않습니다.
마지막으로, 별로 잘 사용하지 않는 옵션은 함수가 임의의 갯수의 인자로 호출되도록 지정하는 것입니다. 이런 인자들은 터플 안에 포장됩니다. 가변 갯수의 인자 앞에, 0개 이상의 정상 인자가 나타날 수 있습니다.
def fprintf(file, format, *args):
file.write(format % args)
인자가 이미 리스트나 터플 속에 있을 경우 반대상황이 일어납니다. 따로따로 위치 인자를 요구하는 함수 호출에는 풀어줄 필요가 있습니다. 예를 들면, 내장 range() 함수는 따로 start와 stop 인자를 예상합니다. 따로 얻을 수 없을 경우, *-연산자를 가지고 함수를 호출하면 리스트나 터플로부터 인자를 풀어낼 수 있습니다:
>>> range(3, 6) # 바로 인자를 제공한 정상적인 호출 [3, 4, 5] >>> args = [3, 6] >>> range(*args) # 리스트로부터 풀어서 인자를 제공한 호출 [3, 4, 5]
같은 방식으로, 사전은 키워드 인자를 **-연산자로 건넬 수 있습니다:
>>> def parrot(voltage, state='a stiff', action='voom'):
... print "-- This parrot wouldn't", action,
... print "if you put", voltage, "volts through it.",
... print "E's", state, "!"
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
많은 사람들의 요구로 인하여, 리스프(Lisp)같은 함수형 프로그래밍 언어에서 흔히 나타나는 몇가지 특징이 파이썬에 추가되었습니다. lambda 키워드로 소형 익명 함수를 만들 수 있습니다. 다음은 두 인자의 합을 돌려주는 함수입니다: "lambda a, b: a+b". 람다 형태는 함수 객체가 요구될 때마다 사용할 수 있습니다. 람다는 구문적으로 하나의 표현식으로 제한됩니다. 의미구조적으로 람다는 그냥 보통의 함수 정의에 대한 사탕 구문일 뿐입니다. 내포된 함수 정의처럼, 람다 형태는 자신의 영역 안에서 변수를 참조합니다:
>>> def make_incrementor(n): ... return lambda x: x + n ... >>> f = make_incrementor(42) >>> f(0) 42 >>> f(1) 43
문서화 문자열의 형식화와 내용에 관하여 새로운 관례가 떠 오르고 있습니다
첫 줄은 언제나 그 객체의 목적에 관하여 짧고 간결하게 요약해야 합니다. 간결하게 하기 위하여, 명시적으로 그 객체의 이름이나 유형을 기술하지 않습니다. 왜냐하면 다른 방법으로 얻을 수 있기 때문입니다 (그 이름이 어쩌다가 그 함수의 연산을 기술하는 동사인 경우를 제외하고 말입니다). 이 줄은 대문자로 시작해야 하고 마침표로 끝나야 합니다.
문서화 문자열이 여러 줄이면 두 번째 줄은 빈 줄이어서 시각적으로 요약을 나머지 설명과 구분시켜 주어야 합니다. 다음 줄들은 하나 이상의 문단이 와서 그 객체의 호출 관례, 부작용 등등을 기술해야 합니다.
파이썬 해석기는 여러-줄 문자열 기호상수에서 들여쓰기의 공백을 정리하지 않습니다. 그래서 문서화를 처리하는 도구들은 필요하다면 들여쓰기의 공백을 정리해야 합니다. 이것은 다음의 관례를 사용하여 완수합니다. 문서화문자열의 첫 줄 다음에 처음으로 나오는 줄이 전체 들여쓰기 양을 결정합니다 (첫 줄은 사용할 수 없습니다. 왜냐하면 일반적으로 첫 줄은 문자열의 따옴표에 바로 붙어 있기 때문에 그의 들여쓰기가 명쾌하지 않기 때문입니다.) 이 들여쓰기에 해당하는 공백문자들은 줄마다 앞에서부터 모두 제거됩니다. 들여쓰기가 덜 된 줄이 나타나면 안되겠지만, 혹 나타나면 모두 앞의 공백문자들을 제거해야 합니다. 탭을 (보통 8개의 공간문자로) 확장한 후에 공백문자와 동등문자들을 테스트해야 합니다.
다음은 여러-줄 문서화문자열의 예입니다
>>> def my_함수():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print my_함수.__doc__
Do nothing, but document it.
No, really, it doesn't do anything.