IT_Programming/Python

[펌] 파이썬 팁과 트릭 그리고 핵

JJun ™ 2014. 4. 13. 13:38

 

파이썬 팁과 트릭 그리고 핵

 

 

한글판 johnsonj 2008.06.20 수
Submited by David
Last updated by David on 2008-05-22 19:28 (ver. 9)
Author(s): David, david AT icapsid DOT net

 

 


짧고 깔끔한 코드를 작성하고 싶습니까? 되도록이면 한 가지 표현식에 맞추어야 할 상황에 처해 있습니까? 문서를 읽느라 기나긴 인생을 허비하지 말고 신속하게 핵 처방을 받기를 원하십니까? 바로 이곳이 여러분이 원하는 곳입니다.

 

 

 

 


 


짧고 깔끔한 코드를 작성하고 싶습니까? 되도록이면 한 가지 표현식에 맞추어야 할 상황에 처해 있습니까? 문서를 읽느라 기나긴 인생을 허비하지 말고 신속하게 핵 처방을 받기를 원하십니까?

바로 이곳이 여러분이 원하는 곳입니다. 몇가지 신속한 트릭을 가지고 시작합니다.

파이썬을 가지고 시간을 들여 놀아보셨다면 알만한 것들이지만, 약속하건대 아래로 내려갈수록

더 매력적인 것들로 가득합니다.

 

모든 조각 코드들은 그 자체로 실행되도록 만들기 위해 노력하였습니다.

원하신다면, 파이썬 쉘에 잘라 붙여 넣고 시험해 보실 수 있습니다.

예제 안에 주석 하나만 제거하면 'true example'과 'false example'를 시험해 보실 수 있습니다. 자유롭게 주석을 지워가며 무슨 일이 일어나는지 살펴보세요.

 

빈 주석들이 코드에 떠돌아 다니는 것이 보일 겁니다. 이것들은 읽기 편하게 하기 위하여 빈 줄을 추가한 것인데 여전히 파이썬 인터프리터는 코드를 해석할 수 있습니다. 이 주석들은 '실제' (붙여넣기-하지 않은) 프로그램에서는 불필요합니다.

 

이 글에서 true vs. True를 잠시 구별하자면: 본인이 객체가 'true'라고 말하는 경우 그 의미는, if 서술문 같은 것 안에서 불리언 값으로 변환된다면, 그 객체가 False가 아니고 True로 변환된다는 뜻입니다.

 

그 객체가 True와 반드시 같다거나 동등하다는 뜻이 아닙니다. 같은 방식으로, 객체가 'false'라고 말하는 경우는 그 객체가 False로 변환된다는 뜻이지, 반드시 False와 같다거나 동일하다는 뜻이 아닙니다.

 

1   신속한 트릭

1.1   네가지 종류의 인용

여러분이 알만한 트릭부터 시작해 보겠습니다. 다른 언어로부터 오셨다면, 아마도 한 곳에서는 홑따옴표를 사용하고 다른 곳에서는 겹따옴표를 사용해 오셨을 것입니다. 파이썬에서는 둘 모두를 사용할 수 있습니다. 물론 서로 교환해 쓸수는 없습니다 (그 중 하나로 시작했다면 끝날 때도 같은 것을 사용해야 합니다). 파이썬은 두 가지 유형의 인용방식을 더 지원합니다. 삼중 홑따옴표(''')는 세개의 홑따옴표를 타자하여 만듭니다. 삼중-겹따옴표(""")는 세개의 겹따옴표를 타자하여 만듭니다. 그래서 인용문자를 피신시키는데 신경쓸 필요 없이 여러 겹으로 인용할 수 있습니다. 예를 들어, 다음은 유효한 파이썬 구문입니다:

 

1print """I wish that I'd never heard him say, '''She said, "He said, 'Give me five dollars'"'''"""

 

1.2   다양한 객체의 진위

어떤 프로그래밍 언어와는 다르게( Javascript ) 파이썬의 유형은 비어 있으면 거짓입니다. 그렇지 않으면 참입니다. 즉 문자열이나 터플 또는 리스트나 사전의 길이가 0인지, 즉 비어 있는지 점검할 필요가 없다는 뜻입니다. 그냥 그 객체의 진위만 점검해도 충분합니다.

예상하시겠지만, 숫자 0도 거짓입니다. 반면에 다른 숫자는 모두 참입니다.

예를 들어, 다음 표현식은 동등합니다. 여기에서, 'my_object'는 문자열이지만, (동등성 테스트에 맞게 적절하게 수정하면) 쉽게 또다른 파이썬 유형이 될 수 있습니다.

 1my_object = 'Test' # True example
2# my_object = '' # False example
3
4if len(my_object) > 0:
5 print 'my_object is not empty'
6
7if len(object): # 0 will evaluate to False
8 print 'my_object is not empty'
9
10if object != '':
11 print 'my_object is not empty'
12
13if object: # an empty string will evaluate to False
14 print 'my_object is not empty'

결론적으로 오직 그 객체가 비어있는지 아닌지만 관심 사항이라면 실제로 길이나 동등성을 점검할 필요가 없습니다.

1.3   문자열에 부분문자열이 들어 있는지 점검하기

다음은 확실하게 보이는 힌트이지만, 본인은 이해하는데 일년이 넘게 걸렸습니다.

아마도 리스트나 터플 또는 사전에 항목이 있는지 점검할 수 있다는 것을 아실 겁니다.
다음 '
item in list' 또는 'item not in list' 표현식을 테스트해 보면 됩니다.
본인은 이것이 문자열에도 작동한다는 것을 알지 못했습니다.
언제나 다음과 같이 코딩을 하고 있었습니다
:

1string = 'Hi there' # True example
2# string = 'Good bye' # False example
3if string.find('Hi') != -1:
4 print 'Success!'

코드가 보기 흉하지요. 'if substring in string'과 완전히 똑 같습니다:

1string = 'Hi there' # True example
2# string = 'Good bye' # False example
3if 'Hi' in string:
4 print 'Success!'

훨씬 깔끔하고 간단합니다. 누구나 이해할 만큼 쉽지만, 저는 좀 더 일찍 알았더라면 하는 아쉬움이 있습니다.

1.4   리스트 예쁘게-출력하기

리스트는 인쇄가 깔끔하지 못합니다. 물론 리스트가 무엇인지는 알지만, 일반 사용자는 원소마다 덕지덕지 붙은 따옴표가 보기 싫을 겁니다. 이를 해결하는 간단한 방법이 있습니다. 문자열의 'join' 메쏘드를 사용하는 것입니다:

1recent_presidents = ['George Bush', 'Bill Clinton', 'George W. Bush']
2print 'The three most recent presidents were: %s.' % ', '.join(recent_presidents)
3# prints 'The three most recent presidents were: George Bush, Bill Clinton, George W. Bush.

 

join 메쏘드는 리스트를 문자열로 바꾸어 줍니다.

각 원소를 문자열로 강제 형변환하여 그들을 join이 호출된 문자열에 결합합니다.

아주 똑똑해서 마지막 원소 다음에는 문자열을 더 붙이지 않습니다.

또 장점이 있다면, 이것은 아주 빨라서 실행시간이 선형입니다. 문자열을 만들 때 절대로 for 회돌이 안에서 '+'를 사용하여 리스트 원소들을 더하지 마세요: 보기 흉할 뿐아니라, 제곱 시간이 걸리기 때문입니다!

1.5   람다 함수

가끔 함수를 인자로 건네야 할 경우가 있습니다. 또는 짧지만 복잡한 연산을 여러 번 하고 싶을 경우가 있습니다. 함수를 일반적인 방식으로 정의해도 되며, 람다 함수를 만들어도 됩니다. 람다 함수는 단 하나의 표현식의 결과를 돌려주는 미니-함수입니다. 두 정의는 완전히 동일합니다:

1def add(a,b): return a+b
2
3add2 = lambda a,b: a+b

람다 함수의 이점은 그 자체로 표현식입니다. 그래서 또다른 서술문 안에 사용될 수 있습니다. 다음은 map 함수를 사용하는 예인데, 리스트의 매 원소마다 함수를 호출하여, 그 결과를 리스트에 담아 돌려줍니다. (아래 지능형 리스트(List Comprehensions) 섹션에서 map이 별로 쓸모없다는 좋은 사례를 보여줍니다. 그렇지만, 훌륭한 한줄짜리 예제입니다.)

1squares = map(lambda a: a*a, [1,2,3,4,5])
2# squares is now [1,4,9,16,25]

람다가 없다면 따로 함수를 정의해야 합니다. 방금 한 줄의 코드와 변수 이름을 절약했습니다.

구문: 람다 함수

람다 함수는 다음과 같은 구문을 갖습니다: lambda variable(s) : expression

variable(s) 함수가 받을 수 있는 쉼표로-가른 리스트 변수(들). 키워드를 사용하면 안되며, 괄호 안에 이 변수(들)을 넣으면 안됩니다. (본인도 처음 파이썬을 배울 때 실수를 해서 두달 동안이나 왜 람다가 작동하지 않는지 고민했었습니다).
expression 인라인 파이썬 표현식. 지역 영역과 variable(s)가 영역이다. 이것이 바로 함수가 돌려주는 것입니다.

 

 

 

2   리스트

2.1   지능형 리스트

파이썬을 아주 오랫동안 사용해 보셨다면, 적어도 지능형 리스트에 관하여 들어 보셨을 겁니다.

한 줄에 for 회돌이와 if 서술문 그리고 모든 할당을 짜맞춘 리스트입니다.

다른 말로 해서, 리스트에 mapfilter를 한 표현식 안에서 적용할 수 있습니다.

 

2.1.1   리스트 짝짓기

아주 쉬운 것부터 시작하겠습니다. 리스트의 매 요소마다 제곱으로 만들고 싶을 경우, 초보 파이썬 프로그래머라면 다음과 같이 코드를 작성할 것입니다:

1numbers = [1,2,3,4,5]
2squares = []
3for number in numbers:
4 squares.append(number*number)
5# Now, squares should have [1,4,9,16,25]

 

효과적으로 한 리스트를 다른 리스트에 '짝지었습니다'.

map 함수도 사용할 수 있으며, 다음과 같은 일을 할 수 있습니다:

1numbers = [1,2,3,4,5]
2squares = map(lambda x: x*x, numbers)
3# Now, squares should have [1,4,9,16,25]

 

(3줄이 아니라 1줄이므로) 이 코드가 확실히 더 짧지만 매우 보기가 안 좋습니다. 한 번 보고 대번에 map 함수가 무슨 일을 하고 있는지 말하기가 어렵습니다 (함수와 리스트를 받아서, 그 함수를 리스트의 각 원소에 적용합니다). 게다가, 좀 산만해 보이는 함수를 건네야 합니다. 더 깔끔한 방법이 있다면...아마도 지능형 리스트일 겁니다:

1numbers = [1,2,3,4,5]
2squares = [number*number for number in numbers]
3# Now, squares should have [1,4,9,16,25]

 

이는 앞의 두 예제와 똑 같은 일을 합니다. 그러나 (첫 예제와 다르게) 더 짧으며 (두 번째 예제와 다르게) 깨끗합니다. 무슨 일을 하고 있는지 이해하지 못하는 사람은 아무도 없을 겁니다. 비록 파이썬을 모른다고 할지라도 말입니다.

 

2.1.2   리스트 여과

리스트를 여과하는데 관심이 더 있다면 어떻게 할까? 값이 10을 넘어가는 원소는 제거하고 싶다면? (좋습니다. 그래서 예제가 별로 현실적이지 못합니다. 어쨌거나...) 파이썬 초보라면 다음과 같이 작성할 겁니다:

1squares = [1,4,9,16,25]
2squares_under_10 = []
3for square in squares:
4 if square < 10:
5 squares_under_10.append(square)
6# Now, squares_under_10 contains [1,4,9]

 

아주 간단하지요? 그러나 4 줄이 들고 두 단계로 내포되며 그리고 별거 아닌 일에 append 메쏘드가 소비되었습니다. filter 함수로 코드의 크기를 줄일 수 있습니다:

1squares = [1,4,9,16,25]
2squares_under_10 = filter(lambda x: x < 10, squares)
3# Now, squares_under_10 contains [1,4,9]

 

위에서 언급한 map 함수와 비슷하게, 이는 코드의 크기를 줄여주지만 실제로는 보기가 안 좋습니다. 도데체 무슨 일이 진행되고 있는 건지 모르겠어요?

map처럼, filter 함수는 함수와 리스트를 받습니다. 리스트의 매 요소를 평가하여 참으로 평가되면 그 원소는 최종 리스트에 포함됩니다. 물론, 지능형 리스트로도 이렇게 할 수 있습니다:

1squares = [1,4,9,16,25]
2squares_under_10 = [square for square in squares if square < 10]
3# Now, squares_under_10 contains [1,4,9]

 

역시, 지능형 리스트를 사용하면 코드가 더 짧고 깨끗하며 이해하기 쉽습니다.

 

2.1.3   단번에 Map과 Filter 사용하기

이제 지능형 리스트의 진정한 힘을 맛보게 되었습니다. 아직까지도 mapfilter가 일반적으로 시간 낭비라는데 동감하지 못하시겠다면, 모쪼록 다음이 도움이 되기를 바랍니다.

리스트를 동시에 짝짓고 여과하고 싶다고 해보겠습니다. 다시 말해, 리스트의 각 원소를 제곱하고 싶습니다. 제곱된 값이 10을 넘지 않아야 합니다. 역시, 파이썬 초보의 눈으로 작성해 보면:

1numbers = [1,2,3,4,5]
2squares_under_10 = []
3for number in numbers:
4 if number*number < 10:
5 squares_under_10.append(number*number)
6# squares_under_10 is now [1,4,9]

 

코드는 이제 수평선 저 쪾에서 확대되기 시작합니다! 아, 코드를 간단하게 만들려면 어떻게 해야 할까요? mapfilter의 사용을 시도해 볼 수 있지만, 그러고 싶은 생각이 들지 않습니다...

1numbers = [1,2,3,4,5]
2squares_under_10 = filter(lambda x: x < 10, map(lambda x: x*x, numbers))
3# squares_under_10 is now [1,4,9]

 

앞에서 mapfilter는 보기가 않좋았지만, 이제 읽기에도 버겁습니다. 이것이 좋은 생각이 아닌 것 만큼은 분명합니다. 역시, 지능형 리스트가 시간을 절약해 줍니다:

1numbers = [1,2,3,4,5]
2squares_under_10 = [number*number for number in numbers if number*number < 10]
3# squares_under_10 is now [1,4,9]

 

이는 앞의 지능형 리스트보다 약간 더 깁니다. 그러나 생각건대 읽기에는 아주 좋습니다.

for 회돌이 또는 mapfilter를 사용하는 것보다는 확실히 좋습니다.

 

2.1.4   발생자 표현식

지능형 리스트에는 단점이 있습니다: 전체 리스트가 메모리에 한꺼번에 저장되어야 합니다. 이는 위의 예제에서와 같이 작은 리스트라면 문제가 되지 않습니다. 그러나 수천 수만 배가 넘는 더 큰 리스트라면 결국 이는 아주 비효율적이 됩니다.

 

 

발생자 표현식은 파이썬 2.4에서 도입되었습니다. 그리고 아직 파이썬에 관하여 멋진 것임에도 불구하고 잘 알려지지 않았습니다. 역시, 본인도 얼마 전에 알았습니다. 발생자 표현식은 전체 리스트를 메모리에 한 번에 적재하지 않습니다. 그러나 대신에 '발생자 객체'를 만들어서 언제든지 오직 하나의 원소만 적재되도록 합니다.

 

물론, 실제로 무언가에 리스트를 사용할 필요가 있다면, 이는 실제로 별 도움이 되지 않습니다.

그러나 -- for 회돌이 같이 -- 무언가 반복자 객체를 받는 것에다 리스트를 건네려고 한다면,

발생자 함수를 사용하는 편이 좋을 것입니다.

 

발생자 표현식은 지능형 리스트와 구문이 같지만, 각괄호가 아니라 반괄호로 둘러싸여 있습니다:

1numbers = (1,2,3,4,5) # Since we're going for efficiency, I'm using a tuple instead of a list ;)
2squares_under_10 = (number*number for number in numbers if number*number < 10)
3# squares_under_10 is now a generator object, from which each successive value can be gotten by calling .next()
4
5for square in squares_under_10:
6 print square,
7prints '1 4 9'

 

지능형 리스트를 사용하는 것보다 이것이 좀 더 효율적입니다.

그래서, 원소 개수가 엄청나게 많으면 발생자 표현식을 사용합니다. 어떤 이유로 전체 리스트가 한 번에 필요하다면 언제나 지능형 리스트를 사용합니다. 어느 쪽도 아니라면, 그냥 마음대로 하세요. 발생자 표현식을 사용하는 것이 아마도 좋은 관례일 겁니다. 특별히 그래야 할 이유가 없지 않는 한 말입니다. 그러나 리스트가 아주 방대하지 않는 한 그 효과의 실제적인 차이는 거의 없습니다.

 

마지막 주의 사항으로서, 발생자 표현식은 한 쌍의 반괄호만 주위에 둘러야 합니다.

그래서, 발생자 표현식만 가지고 함수를 호출하려면 오직 한 쌍의 반괄호만 있으면 됩니다.

다음은 유효한 파이썬 구문입니다: some_function(item for item in list).

 

2.1.5   맺는 말

이렇게 말하고 싶지는 않지만, 지금까지 지능형 리스트와 발생자 표현식이 할 수 있는 일들의 겉모습만을 보았을 뿐입니다. for 회돌이와 if 서술문의 진정한 힘을 맛볼 수 있습니다. 이 둘이면 (생각건대) 무엇이든 할 수 있습니다. 리스트의 리스트를 포함하여, (기타 반복가능 객체) 리스트로 시작하고 (발생자) 리스트로 끝내고 싶은 것이면 무엇이든 처리할 수 잇습니다.

구문: 지능형 리스트와 발생자 표현식

 

지능형 리스트의 구문은 다음과 같습니다:

[ element for variable(s) in list if condition ]

 

발생자 표현식의 구문은 다음과 같습니다:

( element for variable(s) in list if condition )

list 리스트나 반복자로 간주되면 무엇이든 된다
variable(s) 현재 리스트 요소가 할당되는 변수(들). 일반적인 for 회돌이와 똑같다
condition 인라인 파이썬 표현식. 역시 지역 영역과 variable(s)가 영역이다. 이것이 참으로 평가되면, 그 원소는 결과 리스트에 포함된다.
element 인라인 파이썬 표현식. 역시 지역 영역과 variable(s)가 영역이다. 이것이 결과 리스트에 포함될 실제 원소이다.

 

2.2   리스트 줄이기

불행하게도 아직 전체 프로그램을 지능형 리스트로 작성할 수는 없습니다. (농담입니다... 물론 그럴 수 있습니다.) 짝짓기와 여과는 할 수 있지만, 지능형 리스트로 리스트를 축약하는 방법은 간단하지 않습니다. 함수를 첫 두 원소에 적용한다음, 그 결과와 다음 리스트 원소에 적용하고 등등 단 하나의 값에 도달할 때까지 적용한다는 뜻입니다. 예를 들어, 리스트의 모든 값들의 합을 구하고 싶다면, for 회돌이를 사용할 수 있습니다:

1numbers = [1,2,3,4,5]
2result = 1
3for number in numbers:
4 result *= number
5# result is now 120

 

또는 내장 함수 reduce를 사용할 수 있는데, 이 함수는 인자를 두 개 취하는 함수 하나와 리스트를 받습니다:

1numbers = [1,2,3,4,5]
2result = reduce(lambda a,b: a*b, numbers)
3# result is now 120

 

이제 지능혈 리스트만큼 예쁘지는 않지만, for 회돌이보다 더 짧습니다.

꼭 기억해 둘 가치가 있습니다.

 

2.3   리스트 회돌이하기: range와 xrange 그리고 enumerate

C로 프로그램할 때가 기억 나십니까? (어쩌면 기억나시지 않을 수도 있겠군요) C에서 for 회돌이는 원소가 아니라 지표 숫자를 세어 회돌이합니다. 파이썬에서도 rangexrange를 사용하여 이 행위를 그대로 복제하는 법을 이미 아시고 계실 겁니다. 값을 range에 건네면 0에서부터 그 값에서 1을 뺀수까지 세어서 정수 리스트를 돌려줍니다. 다시 말해, 리스트의 길이만큼 지표 값을 돌려줍니다. xrange도 같은 일을 합니다. 단 약간 더 효율적일 뿐입니다: 전체 리스트를 한 번에 메모리에 적재하지 않습니다.

 

 

다음은 예제입니다:

1strings = ['a', 'b', 'c', 'd', 'e']
2for index in xrange(len(strings)):
3 print index,
4# prints '0 1 2 3 4'

 

여기에서 문제는 보통 어찌되었건 리스트 원소가 필요하다는 것입니다. 그냥 지표 값만 있으면 무슨 소용이 있습니까? 파이썬은 실제로 기가 막힌 내장 함수 enumerate를 가지고 있습니다. 리스트를 열거(enumerate)-하면 indexvalue 쌍을 반복자로 돌려줍니다:

1strings = ['a', 'b', 'c', 'd', 'e']
2for index, string in enumerate(strings):
3 print index, string,
4# prints '0 a 1 b 2 c 3 d 4 e'

 

추가적인 혜택은 enumeratexrange(len()) 보다 훨씬 더 깨끗하고 가독성이 좋다는 것입니다. 이 때문에, rangexrange는 아마도 어떤 이유로 처음부터 새로 리스트를 만들 필요가 있을 경우에나 유용할 겁니다.

 

2.4   리스트 원소 조건 점검하기

리스트의 어떤 원소든지 일정 조건을 만족하는지 알아보고 싶습니다.

(예를 들어, 10 미만인지 말입니다). 파이썬 2.5에서는, 다음과 같이 할 수 있었습니다:

1numbers = [1,10,100,1000,10000]
2if [number for number in numbers if number < 10]:
3 print 'At least one element is over 10'
4# Output: 'At least one element is over 10'

 

어떤 원소도 조건을 만족시키지 못하면, 지능형 리스트는 거짓으로 평가되는 빈 리스트를 만듭니다. 그렇지 않으면, 비어있지 않은 리스트가 만들어지므로, 참으로 평가됩니다. 엄밀히 말해, 리스트에서 원소마다 평가할 필요가 없습니다; 조건을 만족하는 첫 원소 이후로는 그만 두어도 됩니다. 그러므로, 위의 방법은 별로 효율적이지 못합니다. 그러나 파이썬 2.5에만 의존할 입장이 아니라면 위의 방법이 유일한 선택이며 이 모든 로직을 한 표현식 안에 모두 구겨 넣을 필요가 있습니다.

 

새롭게 any 내장 함수가 파이썬 2.5에 도입되었습니다. 같은 일을 깨끗하고 효율적으로 할 수 잇습니다. any는 실제로 똑똑해서 조건을 만족하는 첫 원소 이후로는 일을 끝내고 True를 돌려줍니다. 다음은 각 원소에 대하여 TrueFalse 값을 돌려주는 발생자 표현식을 사용하고, 그것을 any 함수에 건넵니다. 발생자 표현식은 이 값들이 필요할 때마다 계산하고, any는 필요한 경우에만 그 값들을 요청합니다:

1numbers = [1,10,100,1000,10000]
2if any(number < 10 for number in numbers):
3 print 'Success'
4# Output: 'Success!'

 

단순하지만 우아한 해결책을 제시해 주신 nostrademons님에게 감사의 말씀을 전합니다.

비슷하게, 조건을 만족하는지 원소마다 점검할 수 있습니다. 파이썬 2.5가 없다면, 다음과 같이 할 필요가 있겠습니다:

1numbers = [1,2,3,4,5,6,7,8,9]
2if len(numbers) == len([number for number in numbers if number < 10]):
3 print 'Success!'
4# Output: 'Success!'

 

여기에서 지능형 리스트로 여과한 다음 여전히 원소의 개수가 같은지 점검합니다. 그렇다면, 모든 원소들이 조건을 만족하는 것입니다. 역시, 이는 별로 효율적이지 못합니다. 왜냐하면 조건을 만족하지 않는 첫 원소 이후로는 점검을 계속할 이유가 없기 때문입니다. 역시, Python 2.5가 아니라면 유일한 선택은 모든 로직을 하나의 표현식 안에 맞추어 넣는 것입니다.

 

파이썬 2.5라면, 물론 좀 쉬운 방법이 있습니다: 내장 all 함수가 있습니다. 예상하시다시피, 이 함수는 똑똑하게도 조건에 부합하지 않는 첫 원소 이후로는 일을 마치고 False를 돌려줍니다. 이 메쏘드는 위에 기술한 any 메쏘드와 똑 같이 작동합니다.

1numbers = [1,2,3,4,5,6,7,8,9]
2if all(number < 10 for number in numbers):
3 print 'Success!'
4# Output: 'Success!'

2.5   여러 리스트를 원소끼리 조합하기

내장 zip 함수를 사용하면 리스트를 함께 묶을 수 있습니다. 터플이 담긴 리스트를 돌려주는데, n번째 터플에는 각 리스트에서 n번째 원소가 들어 있습니다. 이러한 예는 간단한 예를 보여주는 것이 가장 좋습니다:

1letters = ['a', 'b', 'c']
2numbers = [1, 2, 3]
3squares = [1, 4, 9]
4
5zipped_list = zip(letters, numbers, squares)
6# zipped_list contains [('a', 1, 1), ('b', 2, 4), ('c', 3, 9)]

종종 이런 종류의 일은 for 회돌이 같은 반복자를 이용하여, 세 개의 값을 한 번에 뽑아냅니다 ('for letter, number, squares in zipped_list').

2.6   리스트 연산자에 대하여 더 자세히

다음은 모두 리스트나 반복자에 호출되는 내장 함수입니다

max
리스트에서 가장 큰 원소를 돌려준다
min
리스트에서 가장 작은 원소를 돌려준다
sum
리스트의 모든 원소들의 합을 돌려준다. 선택적으로 두 번째 인자를 받는데, 합계를 시작할 지표를 인자로 받습니다 (기본 값은 0입니다).

 

2.7   고급 집합 논리

실제로는 리스트에 관한 섹션에 속하지 않습니다. 그러나 집합을 별로 많이 다루어 보지 않았지만, 종종 펼쳐 놓은 리스트에 집합 논리를 적용해야 할 경우가 있습니다. 집합은 원소가 중복되지 않으며 (같은 원소를 2개 이상 담을 수 없습니다) 순서가 없다는 점에서 리스트와 다릅니다. 집합을 또한 수 많은 다양한 논리 연산을 지원합니다.

가장 흔한 연산은 리스트에 원소가 중복되어 있지 않은지 확인하는 것입니다. 아주 쉽습니다; 그냥 집합으로 변환한 다음 길이가 같은지 알아보면 됩니다:

1numbers = [1,2,3,3,4,1]
2set(numbers)
3# returns set([1,2,3,4])
4
5if len(numbers) == len(set(numbers)):
6 print 'List is unique!'
7# In this case, doesn't print anything

물론, 집합을 다시 리스트로 돌릴 수 있지만, 순서가 보존되지 않음을 기억하세요.

집합이 지원하는 연산에 관하여 좀 더 자세한 정보는 파이썬 문서를 참고하세요.

앞으로 언젠가는 이 연산들을 리스트나 집합에 사용하고 싶은 날이 올 것입니다.

 

3   사전

3.1   키워드 인자로 사전 구성하기

처음 파이썬을 배울 때, 본인은 이렇게 사전을 반드는 방법을 전혀 몰랐습니다. dict 구성에 건네는 키워드 인자는 모두 새로 생성된 사전에 추가된 다음 반환됩니다. 물론, 키에 제한이 있어서 키워드 인자로 만들 수 있어야 합니다: 유효한 파이썬 변수 이름이어야 합니다. 다음은 예제입니다:

1dict(a=1, b=2, c=3)
2# returns {'a': 1, 'b': 2, 'c': 3}

이는 코드에 따라 '보통의' 사전 생성 방식보다 좀 더 깨끗합니다;

따옴표도 별로 떠 다니지 않습니다. 본인은 이 방법을 주로 사용합니다.

 

3.2   사전을 리스트로 변환하기

 

사전을 리스트나 반복자로 쉽게 변환시킬 수 있습니다. 키 리스트를 얻으려면, 그냥 사전을 리스트로 바꾸면 됩니다. 그렇지만 사전에 .keys()를 호출하여 키 리스트를 얻거나, .iterkeys()를 호출하여 반복자를 얻는 것이 더 깨끗합니다. 비슷하게, .values().itervalues()를 호출하면 사전 값을 리스트나 반복자로 얻을 수 있습니다. 그렇지만 사전은 본질적으로 순서가 없으며 그래서 이 값들은 순서에 특별한 의미가 없다는 사실을 꼭 기억하세요.

 

 

키와 값을 보존하려면, .items().iteritems()를 사용하여 사전을 2-원소 터플로 구성된 리스트나 반복자로 바꾸면 됩니다. 이는 앞으로 많이 사용할 것입니다. 그리고 별로 특별한 것은 없습니다:

1dictionary = {'a': 1, 'b': 2, 'c': 3}
2dict_as_list = dictionary.items()
3#dict_as_list now contains [('a', 1), ('b', 2), ('c', 3)]

 

3.3   리스트를 사전으로 변환하기

 

 

처리 과정을 역으로 하여, 2-원소 리스트나 터플을 사전으로 바꿀 수 있습니다:

1dict_as_list = [['a', 1], ['b', 2], ['c', 3]]
2dictionary = dict(dict_as_list)
3# dictionary now contains {'a': 1, 'b': 2, 'c': 3}

 

이 방법을 위에 언급한 사전을 만드는 '키워드 인자' 방법과 조합하여 사용해도 됩니다:

1dict_as_list = [['a', 1], ['b', 2], ['c', 3]]
2dictionary = dict(dict_as_list, d=4, e=5)
3# dictionary now contains {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

 

사전을 리스트로 바꿀 수 있으면 편리합니다. 그러나 정말 멋진 방법은 다음 트릭입니다.

3.4   '지능형 사전(Dictionary Comprehensions)'

파이썬은 지능형 사전이 내장되어 있지 않지만, 코드를 별로 어지럽히지 않고서도 거의 비슷하게 할 수 있습니다. 그냥 .iteritems()을 사용하여 사전을 리스트로 바꾼 다음 그것을 발생자 표현식 (또는 지능형 리스트)에 던져 넣으세요. 그리고 그 리스트를 다시 사전으로 바꾸면 됩니다.

예를 들어, name:email 쌍으로 구성된 사전이 있고, name:is_email_at_a_dot_com 쌍으로 구성된 사전을 만들고 싶다면:

1emails = {'Dick': 'bob@example.com', 'Jane': 'jane@example.com', 'Stou': 'stou@example.net'}
2
3email_at_dotcom = dict( [name, '.com' in email] for name, email in emails.iteritems() )
4
5# email_at_dotcom now is {'Dick': True, 'Jane': True, 'Stou': False}

 

눈이 아픕니다. 물론, 사전으로 시작하고 사전으로 끝날 필요가 없습니다.

리스트를 던져 넣어도 됩니다.

직관적인 지능형 리스트보다 약간 가독성은 떨어지지만, 셀수 없이 많은 for 회돌이 보다는

훨씬 좋다고 생각합니다.

 

 

4   값 선택하기

4.1   올바른 방법

이 글을 쓰면서 값을 인라인에서 선택하는 올바른 방법과 마주쳤습니다. 파이썬 2.5에 새로 도입된 방법입니다 (등장이 아주 화려했을 것 같지요!). 파이썬은 이제 'value_if_true if test else value_if_false' 구문을 지원합니다. 그래서, 한 줄에서 간단하게 값을 선택할 수 있습니다. 기이한 구문이나 커다란 함정도 없습니다:

1test = True
2# test = False
3result = 'Test is True' if test else 'Test is False'
4# result is now 'Test is True'

 

좋습니다. 아직도 약간 보기 좋지는 않습니다. 여러 테스트를 한 줄에 사슬처럼 엮을 수 있습니다:

1test1 = False
2test2 = True
3result = 'Test1 is True' if test1 else 'Test1 is True, test2 is False' if test2 else 'Test1 and Test2 are both False'

첫 번째 if/else가 먼저 평가됩니다. 그리고 test1이 거짓이면 두 번째 if/else가 평가됩니다.

좀 복잡하게 할 수도 있습니다. 특히 괄호를 집어 넣으면 말입니다.

개인적 평가

이는 무대에 갓 등장한 것인데, 나는 어떻게 반응해야 할지 몰랐습니다. 정말로 올바른 방법이며, 더 깨끗하고, 마음에 들지만, 여전히 보기 흉합니다. 특히 if/else가 여러개 내포되어 있다면 말입니다.

물론, 값 선택 트릭 구문은 모두 보기에 안 좋습니다.

아래에 가볍게 and/or 트릭을 다루었습니다. 본인은 이 트릭이 실제로 아주 직관적이라고 생각하며 이제 어떻게 작동하는지 이해했습니다. 또한, 올바른 방법으로 일을 하는 것에 비해 효율성이 별로 떨어지지 않습니다.

어떻게 생각하십니까? 자유롭게 아래에 논평을 해 주세요.

비록 인라인 if/else 구문이 새롭고 올바른 방법이기는 하지만, 아래에 있는 트릭을 고려해 보는 것이 더 좋겠습니다. 오로지 파이썬 2.5에서만 프로그래밍을 할 생각일지라도, 예전 코드에서는 이런 것을 만나게 될테니까요. 물론, 하위 호환성이 필요하거나 파이썬 2.5가 없다면, 당연히 아래의 트릭을 살펴 보시는 것이 좋습니다.

 

4.2    and/or 트릭

파이썬에서 'and'와 'or'는 복잡한 놈입니다. 두 표현식을 and-연산하면 두 값이 모두 참이면 True를 돌려준다거나 두 값이 모두 거짓이면 False를 돌려주는 것이 아닙니다. 대신에, 'and'는 첫번째 거짓 값을 돌려주거나, 또는 모두 참이면 가장 마지막 값을 돌려줍니다. 다시 말해, 첫 값이 거짓이면 그것이 반환되지만, 그렇지 않으면 마지막 값이 반환됩니다. 이 결과는 예상하는 바입니다: 둘 모두 참이면, 마지막 값이 반환됩니다. 이 값은 참이며 불리언 테스트에서 (예를 들어 'if' 서술문에서) True로 평가됩니다. 그 중 하나가 거짓이면 그 값이 반환되며 불리언 테스트에서 False로 평가됩니다.

 

두 표현식을 or-연산하는 것도 비슷합니다. 'or'은 첫 번째 참값을 돌려주거나, 또는 모두 거짓이면 마지막 값을 돌려줍니다. 다른 말로 하면, 첫 값이 참이면 그 값이 반환되고, 그렇지 않으면 마지막 값이 반환됩니다. 그래서, 두 값 모두 거짓이면, 마지막 값이 반환됩니다. 이는 거짓이며 불리언 테스트에서 False로 평가됩니다. 그 중 하나가 참이면, 그 값이 반환되고 불리언 테스트에서 True로 평가됩니다.

 

그저 진위를 테스트할 경우라면 별로 도움이 되지 않습니다. 그러나 파이썬에서 다른 목적으로 'and'와 'or'를 사용할 수 있습니다; 내가 좋아하는 방법은 값 중에서 선택하는 것입니다.

C의 삼항 조건 할당 연산자 'test ? value_if_true : value_if_false'와 비슷한 방식으로 말입니다:

 

1test = True
2# test = False
3result = test and 'Test is True' or 'Test is False'
4# result is now 'Test is True'

 

어떻게 작동하는 것일까요? test가 참이라면, and 서술문은 그것을 건너 뛰고 오른쪽 반을 돌려줍니다. 즉, 여기에서는 'Test is True' or 'Test is False'를 돌려주는 거지요. 왼쪽에서 오른쪽으로 처리해 가면서 or 서술문은 첫 참값, 'Test is True'를 돌려줍니다.

 

test가 거짓이라면, and 서술문은 test를 돌려줍니다. 왼쪽에서 오른쪽으로 처리해 가면서, 나머지 서술문은 test or 'Test is False'가 됩니다. test는 거짓이므로, or 서술문은 그것을 건너 뛰고 그의 오른쪽 절반, 'Test is False'을 돌려줍니다.

경고

가운데 (if_true) 값은 절대로 거짓이 될 수 없음에 주의하세요. 거짓이라면, 'or' 서술문은 언제나 그 값을 건너 뛰고 제일 오른쪽 값(if_false)을 돌려줍니다. test 값이 무엇이든 상관없이 없습니다.

이 방법에 이미 익숙해진 고로 본인은 (위의) '올바른 방법'이 별로 직관적으로 보이지 않습니다. 하위 호환성에 걱정이 없다면, 두 방법 모두를 시험해 보고 어느 것이 더 좋은지 알아보시기를 바랍니다. 일단 뒤에 숨은 로직을 이해했다면 아주 쉽게 and/or 트릭을 내포시킬 수 있습니다. 또는 추가로 andor를 넣을 수도 있습니다. 결정을 못하겠거나 둘 모두를 배우고 싶은 마음이 없다면 and/or 방법을 사용하지 마세요. 올바른 방법으로 일을 하세요.

 

물론, 파이썬 2.5 이하의 버전을 지원해야 한다면, '올바른 방법'은 작동하지 않습니다. (마음 속으로는 '잘못된 방법'이라고 부르고 싶네요). 그런 경우 and/or 트릭은 분명히 대부분의 상황에 최고의 선택이 될 것입니다.

 

모쪼록 이 모든 것이 이해가 되셨기를 바랍니다; 설명하기 힘드네요. 지금은 아주 복잡해 보이지만, 몇 번 'and' 그리고 'or'를 가지고 놀아보면 조만간 이해가 가고 보다 복잡한 'and' 그리고 'or' 트릭을 스스로 만들어 처리하실 수 있을 것입니다.

4.3   True와 False를 지표로 사용하기

값을 선택하는 또다른 방법은 True와 False를 리스트 지표로 사용하는 것입니다.

False == 0 그리고 True == 1이라는 사실을 활용합니다:

1test = True
2# test = False
3result = ['Test is False','Test is True'][test]
4# result is now 'Test is True'

 

이는 and/or 트릭보다 더 직관적입니다. 그리고 value_if_true가 그 자체로 반드시 참이어야 할 경우도 문제가 없습니다.

 

그렇지만, 심각한 결점도 있습니다: 두 리스트 모두 평가된 다음 진위가 결정됩니다. 문자열 또는 기타 간단한 원소라면, 이는 큰 문제가 아닙니다. 그러나 각 원소가 심각한 계산 또는 I/O에 관련되어 있다면, 해야 할 일의 두배나 일을 하고 싶지는 않을 것입니다. 이런 이유로 본인은 보통 '올바른 방법'이나 and/or 트릭을 사용합니다.

 

 

5   함수

5.1   기본 인자 값은 오직 한 번만 평가된다

이 섹션을 경고로 시작하겠습니다. 다음은 나 자신을 포함하여, 많은 초보 파이썬 프로그래머들을 곤경에 빠트리는 문제입니다. 반복적으로, 심지어 문제를 인식한 이후로도 실수를 합니다.... 이에 관해서는 모르는 것이 속 편합니다 (이 예제가 최고의 예제는 아니지만, 요점은 예시한다는 사실에 주목하세요):

1def function(item, stuff = []):
2 stuff.append(item)
3 print stuff
4
5function(1)
6# prints '[1]'
7
8function(2)
9# prints '[1,2]' !!!

 

함수 인자에 대한 기본 값은 함수가 정의될 때 오직 한 번만 평가됩니다.

파이썬은 함수가 호출될 때 단순히 이 값을 올바른 변수 이름에 할당합니다.

 

파이썬은 그 그 값이 (메모리의 위치가) 바뀌었는지 점검하지 않습니다. 그저 계속해서 그 값을 필요로 하는 호출자에게 할당할 뿐입니다. 그래서, 값이 변하면, 그 변화는 함수 호출 전체에 걸쳐서 영향을 미칩니다. 위에서 값을 stuff로 대표되는 리스트에 더하면, 실제로는 기본 값이 영원히 바뀝니다. 다시 기본 값을 찾는 함수를 호출하면, 수정된 기본 인자가 반환됩니다.

 

해결책: 변경가능 객체를 함수 기본인자로 사용하지 마세요. 바꾸지만 않는다면 문제를 피할지도 모르겠지만, 여전히 좋은 생각은 아닙니다.

위의 코드를 개선한다면 다음과 같습니다:

 1def function(item, stuff = None):
2 if stuff is None:
3 stuff = []
4 stuff.append(item)
5 print stuff
6
7function(1)
8# prints '[1]'
9
10function(2)
11# prints '[2]', as expected

 

None는 변경불능입니다 (그리고 어쨌든 그 값을 수정하지 않도록 할 생각입니다).

그래서 기본 값을 실수로 변경하게 되지 않으므로 안전합니다.

좋은 면은 똑똑한 프로그래머라면 아마도 이를 트릭으로 바꾸어서, 그 효과상 C-스타일의 '정적 변수'를 만들어 낼 수 있을 겁니다.

 

5.2   인자의 개수

파이썬은 함수에 인자의 개수를 얼마든지 사용할 수 있습니다. (혹 있다면) 먼저 필요한 인자를 정의한 다음, 앞에 '*'를 두고 변수를 사용합니다. 파이썬은 나머지 비-키워드 인자들을 취해서, 그 인자들을 리스트나 터플에 넣고 그리고 다음 이 변수에 할당합니다:

1def do_something(a, b, c, *args):
2 print a, b, c, args
3
4do_something(1,2,3,4,5,6,7,8,9)
5# prints '1, 2, 3, (4, 5, 6, 7, 8, 9)'

 

왜 이렇게 하고 싶을까요? 일반적인 이유는 함수가 수 많은 원소를 받아 그 모든 원소에 같은 일을 하기 때문입니다 (즉, 합계를 냅니다). sum_all([1,2,3])과 같이 사용자가 리스트를 건네도록 강제할 수 있습니다:

또는 sum_all(1,2,3)과 같이 가변 개수의 인자를 사용하도록 허용할 수도 있습니다. 이 편이 코드가 더 깨끗합니다:

키워드 인자를 얼마든지 가질 수도 있습니다. 다른 인자를 모두 정의한 후에, 앞에 '**'를 두고 변수를 사용하세요. 파이썬은 나머지 키워드 인자를 받아서, 그 인자들을 사전에 넣고 다음 이 변수에 할당합니다:

1def do_something_else(a, b, c, *args, **kwargs):
2 print a, b, c, args, kwargs
3
4do_something_else(1,2,3,4,5,6,7,8,9, timeout=1.5)
5# prints '1, 2, 3, (4, 5, 6, 7, 8, 9), {"timeout": 1.5}'

이렇게 하고 싶을까요? 가장 흔한 이유는 다음과 같다고 생각합니다. 함수가 다른 함수(들)의 포장자일 경우, 사용한 키워드 인자는 무엇이든 사전으로부터 꺼낼 수 있고 나머지 인자들은 다른 함수에 건넬 수 있기 때문입니다 (아래의 사전이나 리스트를 인자로 건네기 참조)

 

5.2.1   경고

한 함수 안에 가변 개수의 비-키워드 인자와 고정 개수의 이름붙은-키워드 인자를 모두 건네는 일은 불가능해 보입니다. 이 때문에 이름붙은 키워드 인자는 함수 정의 안에서 반드시 '*' 매개변수 앞에 정의되어야 하며, '*' 매개변수가 채워지기 전에 채워져야 합니다. 예를 들면, 이런 함수를 생각할 수 있습니다:

1def do_something(a, b, c, actually_print = True, *args):
2 if actually_print:
3 print a, b, c, args

이제 문제가 있습니다: 가변 개수의 비-키워드 인자를 제공하면서 동시에 'actually_print'를 이름붙은 키워드 인자로 지정할 방법이 없습니다. 다음 두 예제는 에러를 일으킵니다:

1do_something(1, 2, 3, 4, 5, actually_print = True)
2# actually_print is initially set to 4 (see why?) and then re-set,
3# causing a TypeError ('got multiple values for keyword argument')
4
5do_something(1, 2, 3, actually_print = True, 4, 5, 6)
6# This is not allowed as keyword arguments may not precede non-keyword arguments. A SyntaxError is raised.

이런 상황에서 'actually_print'를 건네는 유일한 방법은 그것을 비-키워드 인자로 건네는 것입니다:

1do_something(1, 2, 3, True, 4, 5, 6)
2# Result is '1, 2, 3, (4, 5, 6)'

 

5.3   리스트나 사전을 인자로 건네기

리스트나 사전을 인자로 받을 수 있으므로, 리스트나 사전으로부터 인자를 함수에 건넬 수 있다고 해도 그렇게 놀랄 일은 아닙니다. 구문은 정확하게 위와 같습니다.

리스트를 비-키워드 인자로 보내려면, 그냥 앞에다 '*'를 두면 됩니다:

1args = [5,2]
2pow(*args)
3# returns pow(5,2), meaning 5^2 which is 25

 

물론, 사전을 키워드 인자로 보내려면 (이것이 보통 많은 일인데), 앞에다 '**'를 붙이면 됩니다:

 1def do_something(actually_do_something=True, print_a_bunch_of_numbers=False):
2 if actually_do_something:
3 print 'Something has been done'
4 #
5 if print_a_bunch_of_numbers:
6 print range(10)
7
8kwargs = {'actually_do_something': True, 'print_a_bunch_of_numbers': True}
9do_something(**kwargs)
10
11# prints 'Something has been done', then '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]'

역사적 각주: 예전 파이썬 버전(pre-2.3)에서는 임의의 인자를 가지고 함수를 호출하기 위해 내장 apply (function, arg_list, keyword_arg_dict)' 함수를 사용했습니다.

 

5.4   장식자

함수 장식자는 아주 단순하지만, 이전에 본적이 없다면 무슨 일이 진행되는지 알지 못할 겁니다. 대부분의 파이썬과는 다르게 구문은 별로 깨끗하지 못합니다. 장식자는 또다른 함수를 감싼 함수입니다: 주 함수가 호출되면 그의 반환값이 장식자에게 건네집니다. 그러면 장식자는 포장된 함수로 교체하여 함수를 돌려줍니다.

 

더 시간을 끌 것없이, 다음이 바로 그 구문입니다:

 1def decorator1(func):
2 return lambda: func() + 1
3
4def decorator2(func):
5 def print_func():
6 print func()
7 return print_func
8
9@decorator2
10@decorator1
11def function():
12 return 41
13
14function()
15# prints '42'

 

이 예제에서, 'function'는 'decorator1'에 건네집니다. 'decorator1'은 'function'을 호출하는 함수를 돌려주고 1을 더해줍니다. 이 함수는 'decorator2'에 건네집니다.
이제 '
decorator1'이 돌려준 함수를 호출하는 함수를 돌려주고 그 결과를 인쇄합니다.
이 마지막 함수가 바로 '
function'를 호출할 때 실제로 호출하고 있는 함수입니다.

이 예제는 똑같은 일을 하지만, 좀 더 상세하고 장식자가 없습니다:

 1def decorator1(func):
2 return lambda: func() + 1
3
4def decorator2(func):
5 def print_func():
6 print func()
7 return print_func
8
9def function():
10 return 41
11
12function = decorator2(decorator1(function))
13
14function()
15# prints '42'

 

전형적으로 장식자는 함수에 능력을 추가하는 데 사용됩니다 (아래의 클래스 메쏘드 만들기 참조). 더 전형적으로 말해, 여기에서 장식자는 전혀 사용되지 않지만, 무엇을 보고 있는지 아는 것이 좋습니다.

 

더 자세한 정보는 Dr. Dobbs지에서 파이썬 장식자 또는 파이썬 문서의 "함수 정의"를 참조하세요.

 

5.5   함수 사전을 사용하여 '서술문 전환하기'

전환(switch) 서술문이 그리우십니까? 아마도 아시겠지만, 파이썬은 실제로 동등한 구문이 없습니다. elif를 반복해 사용하지 않는 한 말입니다. 그렇지만 혹 아시는지 모르겠습니다. (깔끔하지는 않지만) switch 서술문의 행위를 복제할 수 있습니다. 전환하고자 하는 값을 키로 하여 함수 사전을 만들면 됩니다.

 

예를 들어, 키눌림을 처리 중이고 각 키 눌림에 따라 함수를 다르게 호출할 필요가 있다고 해보겠습니다. 또 이미 다음 세가지 함수를 정의해 두었다고 해 보겠습니다:

 1def key_1_pressed():
2 print 'Key 1 Pressed'
3
4def key_2_pressed():
5 print 'Key 2 Pressed'
6
7def key_3_pressed():
8 print 'Key 3 Pressed'
9
10def unknown_key_pressed():
11 print 'Unknown Key Pressed'

 

파이썬에서는 전형적으로 elif를 사용하여 함수를 고릅니다:

 1keycode = 2
2if keycode == 1:
3 key_1_pressed()
4elif keycode == 2:
5 key_2_pressed()
6elif number == 3:
7 key_3_pressed()
8else:
9 unknown_key_pressed()
10# prints 'Key 2 Pressed'

그러나 모든 함수를 사전에 던져 넣어도 좋습니다. 그리고 전환하고자 하는 값을 키로 하여 그 함수들을 묶습니다. 그 키가 존재하는지 점검해 보고 존재하지 않으면 어떤 코드를 실행할 수도 있습니다:

1keycode = 2
2functions = {1: key_1_pressed, 2: key_2_pressed, 3: key_3_pressed}
3functions.get(keycode, unknown_key_pressed)()

함수가 수 없이 많다면 elif 예제보다 이 편이 더 깨끗합니다.

 

6   클래스

6.1   손수 'self' 건네기

메쏘드는 그냥 보통의 함수일 뿐입니다. 실체로부터 호출될 때 그 실체를 (보통 'self'라고 불리우는) 첫 인자로 건넵니다. 어떤 이유로 그 함수를 실체로부터 호출하지 않는다면 언제든지 그 실체를 손수 첫 인자로 건넬 수 있습니다. 예를 들어:

 1class Class:
2 def a_method(self):
3 print 'Hey a method'
4
5instance = Class()
6
7instance.a_method()
8# prints 'Hey a method', somewhat unsuprisingly. You can also do:
9
10Class.a_method(instance)
11# prints 'Hey a method'

내부적으로, 이 서술문들은 정확하게 똑 같습니다.

6.2   특성과 메쏘드가 존재하는지 점검하기

특정 클래스나 실체에 특정 특성이나 메소드가 있는지 알 필요가 있다면 어떻게 해야 할까? 내장 'hasattr' 함수를 사용하여 점검할 수 있습니다; 이 함수는 점검해야 할 객체와 속성을 (문자열로) 받습니다. 사전의 'has_key' 메쏘드도 비슷하게 사용합니다 (물론 작동방식은 완전히 다릅니다):

1class Class:
2 answer = 42
3
4hasattr(Class, 'answer')
5# returns True
6hasattr(Class, 'question')
7# returns False

 

내장 'getattr' 함수를 사용하면 단 한 번에 특성이 있는지 점검하고 접근할 수도 있습니다. getattr은 또한 점검할 객체와 속성을 문자열로 받을 수도 있습니다. 선택적으로 세 번째 인자가 있어서, 속성이 발견되지 않으면 기본값으로 돌려줍니다. 아마도 더 친숙할 사전의 'get' 메쏘드와 다르게, 기본값이 주어지지 않고 속성이 발견되지 않으면, AttributeError가 일어납니다:

1class Class:
2 answer = 42
3
4getattr(Class, 'answer')
5# returns 42
6getattr(Class, 'question', 'What is six times nine?')
7# returns 'What is six times nine?'
8getattr(Class, 'question')
9# raises AttributeError

``hasattr``과 getattr을 남용하지 마세요. 특성이 존재하는지 끊임없이 점검하도록 클래스를 작성했다면, 잘못 작성된 것입니다. 언제든지 값이 존재하도록 만들고 사용되지 않는다면 그 값에 (기타 무엇이든) None을 설정하세요. 이 함수들은 다형성을 처리하는 데 알맞습니다.

즉, 함수/클래스/등등 다양한 종류의 객체를 지원할 수 있습니다.

 

6.3   생성후에 클래스 수정하기

클래스가 생성된 이후, 심지어 실체화되고 난 이후에도 클래스 특성이나 메쏘드를 삭제하거나 추가 또는 수정할 수 있습니다. 그냥 Class.attribute로 특성이나 메쏘드에 접근하세요.

언제 생성되었는가에 상관없이, 클래스의 실체는 이런 변화를 존중합니다:

 1class Class:
2 def method(self):
3 print 'Hey a method'
4
5instance = Class()
6instance.method()
7# prints 'Hey a method'
8
9def new_method(self):
10 print 'New method wins!'
11
12Class.method = new_method
13instance.method()
14# prints 'New method wins!'

아주 경이롭습니다. 그러나 습관적으로 기존의 메쏘드를 수정하지 마세요. 나쁜 습관이며 그 클래스를 사용하는 개체들을 몹시 혼란스럽게 만들 수 있습니다. 반면에 메쏘드를 추가하는 것은 덜 (그러나 여전히 약간은) 위험합니다.

6.4   클래스 메쏘드 만들기

종종 클래스를 작성할 때 실체가 아니라 그 클래스로부터 함수를 호출하고 싶은 경우가 있습니다. 어쩌면 이 메쏘드로 새 실체를 만들거나, 또는 개별 실체의 특성에 영향을 받지 않습니다. 파이썬은 실제로 이렇게 하기 위하여 두 가지 방법이 있습니다. 어느 클래스로부터 자신이 호출되었는지 메쏘드가 알 필요가 있는지 없는지에 따라 다릅니다. 두 방법 모두 장식자를 메쏘드에 적용하는 것에 관련됩니다.

 

'클래스 메쏘드'는 그 클래스를 첫 인자로 받습니다. 보통의 실체 메쏘드가 그 실체를 첫 인자로 받는 것 같이 말입니다. 그래서, 이 메쏘드는 클래스 자신으로부터 호출되고 있는지 아니면 서브클래스로부터 호출되고 있는지 인식합니다.

 

'정적 메쏘드'는 어디에서 호출되었는지에 관하여 아무 정보도 받지 않습니다;

본질적으로 정규 함수입니다. 단지 다른 영역에 있을 뿐입니다.

 

클래스 메쏘드와 정적 메쏘드는 Class.method()의 형태로 클래스로부터 직접 호출되거나 또는 Class().method()의 형태로 실체로부터 호출될 수 있습니다.

 

그의 클래스를 제외하고 실체는 무시됩니다.

다음은 정규 실체 메쏘드와 함께, 각각을 보여주는 예입니다:

 1class Class:
2 @classmethod
3 def a_class_method(cls):
4 print 'I was called from class %s' % cls
5 #
6 @staticmethod
7 def a_static_method():
8 print 'I have no idea where I was called from'
9 #
10 def an_instance_method(self):
11 print 'I was called from the instance %s' % self
12
13instance = Class()
14
15Class.a_class_method()
16instance.a_class_method()
17# both print 'I was called from class __main__.Class'
18
19Class.a_static_method()
20instance.a_static_method()
21# both print 'I have no idea where I was called from'
22
23Class.an_instance_method()
24# raises TypeError
25instance.an_instance_method()
26# prints something like 'I was called from the instance <__main__.Class instance at 0x2e80d0>'

7   맺는 말

좀 더 아이디어가 필요하십니까? 볼만한 좋은 곳을 소개한다면 파이썬 내장 함수 페이지입니다.

아마도 들어 보지 못 했을 멋진 함수가 많이 있습니다.

좋은 트릭이나 알아야 할 것이 있다면, 이 글에 자유롭게 추가하셔도 좋습니다.

(Siafoo 계정은 http://www.siafoo.net/main/acct_request에서 얻을 수 있습니다.)

신나는 코딩을 즐겨봅시다.

8   참조서 & 더 읽어야 할 것

파이썬 일반 프로그래밍 FAQ
파이썬의 멋진 트릭
Core Python Programming by Wesley J. Chun
구글에서만 이 책을 보았지만, 파이썬 개관을 이해하기가 쉽게 잘 씌여진 것 같고, 고급 주제를 잘 다룬 듯 보입니다.
롭 나이트(Rob Knight)가 쓴 파이썬 관용구와 효율성
약간 기한이 지났지만 (파이썬 2.2 쯤을 사용하고 있다고 가정), 파이썬 트릭과 팁으로 가득합니다.
Dr. Dobb's: 파이썬 장식자
장식자 함수를 사용하는 것에 관한 예제나 아이디어가 필요합니까? 좋은 글입니다.
파이썬 문서: 함수 정의
함수 구문과 함수 장식자의 작동방식에 관한 요약.
Wikipedia: "?:"
다양한 언어에 대한 삼진 연산자. 거기에서 파이썬의 새 삼항 연산자에 관한 정보를 발견하였습니다.
대니웹 포럼(DaniWeb Forums): 기본 인자 - 파이썬은 어떤 일을 하는가?
기본 함수 매개변수의 작동 방식을 위한 연구. 기본 인자 예제는 여기에서 참조하세요.
Python.org 튜터 메일링 리스트: Class vs. Static Methods
이 쓰레드는 클래스 메쏘드와 정적 메쏘드를 언제 사용할지에 관하여 품위있게 논의한다. 클래스와 정적 메쏘드 예제는 여기에서 참조하세요.