GitHub - buttercrab/wtfpython-ko: 놀라운 예제들을 통해서 파이썬을 탐험하고 이해해보세요! (original) (raw)

번역

번역에 참여하고 싶으시면 Github Discussion을 방문하세요!

What the f*ck Python! 😱

놀라운 예제들을 통해서 파이썬 탐험하고 이해하기

영어 English(원문) | 중국어 中文

다른 읽는 방법: 인터랙티브 | CLI

설계가 잘된 고급 인터프리터 언어인 파이썬에는 프로그래머를 위한 편의 기능이 많습니다. 하지만 간혹 어떤 파이썬 코드들은 실행 결과가 이상해보일 때도 있습니다.

이 문서는 직관적이지 않거나 덜 알려진 기능을 사용하는 예제들에 대해 그 이면의 동작을 정확하게 설명합니다.

아래의 예제들이 WTF 까지는 아닐 수도 있지만, 파이썬의 잘 몰랐던 부분을 보여드릴 수는 있을 것입니다. 이런 식으로 공부하는 것이 프로그래밍 언어의 내부를 알게되는데 효과적이라고 생각합니다. 여러분도 동의하게 되실거라 믿습니다!

만약 파이썬의 고인물이라면 첫눈에 예제의 의미를 파악해보세요. 아마 이미 경험한 적이 있는 코드일 수도 있겠습니다. 그렇다면 옛 추억을 떠으로게 해드린 셈이 되겠네요! 😅

추신: 예전에 읽었는데 다시 오신 분이라면 여기서 바뀐 부분을 확인할 수 있습니다.

추신 2: 옮긴이의 말을 읽는 것을 추천합니다.

그럼, 시작합니다!

목차

예제의 구성

모든 예제는 아래와 같은 구조로 이루어져 있습니다.

▶ 빛나는 제목

예제 세팅

마법 같은 일을 기대하세요...

출력 결과 (유효한 파이썬 버전들):

놀라운 결과에 대한 한 줄 설명이 있을 수도 있습니다.

💡 설명:

출력 결과 (유효한 파이썬 버전들):

입력 # 놀라운 결과의 이해를 돕기 위한 예제

이해 가능한 결과

참고: 여기에 있는 모든 예제는 파이썬 3.5.2 인터렉티브 인터프리터에서 테스트 되었고 추가적으로 명시되어 있지 않은 이상 모든 버전에서 작동할 것입니다.

사용방법

예제들을 순서대로 읽어내려가는 것을 권장하고 예제마다:

추신: pypi 패키지를 사용하면 command line에서도 이 문서를 읽을 수 있습니다.

$ pip install wtfpython -U $ wtfpython


👀 예제

"머리가 아플수도 있어요!" 단원

▶ 먼저 처음 것들부터 *

어떤 이유에서인지, 파이썬 3.8의 Walrus 연산자 (:=) 가 꽤 알려지게 되었습니다. 확인해봅시다.

파이썬 3.8+

a = "wtf_walrus" 'wtf_walrus' a 'wtf_walrus'

a := "wtf_walrus" File "", line 1 a := "wtf_walrus" ^ SyntaxError: invalid syntax

(a := "wtf_walrus") # 이건 잘 작동하네요 a 'wtf_walrus'

2 .

파이썬 3.8+

a = 6, 9 a (6, 9)

(a := 6, 9) a 6

a, b = 6, 9 # 전형적인 언패킹 a, b (6, 9) (a, b = 16, 19) # 이런 File "", line 1 (a, b = 6, 9) ^ SyntaxError: invalid syntax

(a, b := 16, 19) # 이것은 이상한 3-튜플을 출력합니다. (6, 16, 19)

a # a가 아직도 안 바뀌었네요? 6

b 16

💡 설명

간단한 walrus 연산자 설명

walrus 연산자 (:=) 는 파이썬 3.8에서 소개되었으며, 변수에 할당하면서 연산을 하고 싶을 때 유용하게 쓰일 수 있습니다.

def some_func(): # 많은 계산을 하는 함수라고 가정합시다. # time.sleep(1000) return 5

그래서

if some_func(): print(some_func()) # 같은 계산이 두 번 이루어지므로 안 좋은 방법입니다.

또는

a = some_func() if a: print(a)

대신에 이렇게 간단하게 쓸 수 있습니다.

if a := some_func(): print(a)

출력 결과 (> 3.8):

이 연산자는 한 줄의 코드를 아끼고 some_func를 두 번 호출하는 것을 방지할 수 있습니다.


▶ 문자열은 가끔 헷갈려요

a = "some_string" id(a) 140420665652016 id("some" + "_" + "string") # 두 id가 같네요 140420665652016

a = "wtf" b = "wtf" a is b True

a = "wtf!" b = "wtf!" a is b False

a, b = "wtf!", "wtf!" a is b # 3.7.x 버전을 제외하고 모든 버전에서 이렇게 작동합니다. True

a = "wtf!"; b = "wtf!" a is b # 어디서 실행시키는지에 따라 True 혹은 False가 출력될 것입니다. (파이썬 쉘 / ipython / 파이썬 스크립트) False

이번에는 some_file.py 파일에서 실행시켜봅시다.

a = "wtf!" b = "wtf!" print(a is b)

모듈을 실행시키면 True를 출력하네요.

출력 결과 (< 3.7 )

'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' True 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa' False

말이 되는 거 같죠?

💡 설명:


▶ 연결된 연산들을 조심하세요

(False == False) in [False] # 말이 되네요 False False == (False in [False]) # 이것도 말이 됩니다 False False == False in [False] # 이건 뭐죠? True

True is False == False False False is False is False True

1 > 0 < 1 True (1 > 0) < 1 False 1 > (0 < 1) False

💡 설명:

https://docs.python.org/2/reference/expressions.html#not-in 에 따라서

형식적으로, a, b, c, ..., y, z가 표현식이고 op1, op2, ..., opN이 비교 연산자라면, 각 식이 한번에 평가된다는 점을 제외하고 a op1 b op2 c ... y opN z는 a op1 b and b op2 c and ... y opN z에 해당합니다.

위의 예시와 같은 결과들은 이상해 보일지도 모르지만, a == b == c0 <= x <= 100와 같은 표현들은 환상적입니다.


is 연산자를 안 쓰는 방법

아래 예제는 인터넷에서 매우 유명한 예제로 퍼져있습니다.

a = 256 b = 256 a is b True

a = 257 b = 257 a is b False

a = [] b = [] a is b False

a = tuple() b = tuple() a is b True

3.출력 결과

a, b = 257, 257 a is b True

출력 결과 (파이썬 3.7.x)

a, b = 257, 257 a is b False

💡 설명:

is==의 차이점

256은 존재하는 객체이지만 257은 아닙니다

파이썬을 시작하게 되면, -5부터 256까지의 수들은 할당됩니다. 이 수들은 많이 사용되기 때문에 미리 준비하는 것입니다.

https://docs.python.org/3/c-api/long.html 에서 인용한 글입니다.

현 구현은 -5부터 256까지의 정수들을 담는 배열을 만듭니다. 만약 이 범위 안에 있는 정수를 만들게 되면 이미 존재하는 객체의 참조를 반환합니다. 그래서 1의 값을 바꾸는 것이 가능할 것입니다. 아마도 이 경우는 파이썬의 행동은 정의되지 않을 것입니다. :-)

id(256) 10922528 a = 256 b = 256 id(a) 10922528 id(b) 10922528 id(257) 140084850247312 x = 257 y = 257 id(x) 140084850247440 id(y) 140084850247344

여기서 인터프리터는 y = 257을 실행할 때 위에서 벌써 257을 가지는 정수를 만들었다는 것을 알 정도로 똑똑하지 않아서 메모리에 새로운 객체를 만들게 됩니다.

빈 튜플과 같이 다른 변하지 않는 객체에 대해서도 비슷한 최적화가 적용됩니다. 배열은 변할 수 있어서, [] is []는 항상 False를 반환하고 () is ()는 항상 True를 반환합니다. 이는 두 번째 예제를 성명합니다. 이제 세 번쨰로 넘어가볼까요?

같은 줄에서 같은 값으로 초기화할 때 ab 둘 다 같은 객체를 참조합니다.

출력 결과

a, b = 257, 257 id(a) 140640774013296 id(b) 140640774013296 a = 257 b = 257 id(a) 140640774013392 id(b) 140640774013488


▶ 해시 브라우니

some_dict = {} some_dict[5.5] = "JavaScript" some_dict[5.0] = "Ruby" some_dict[5] = "Python"

출력 결과:

some_dict[5.5] "JavaScript" some_dict[5.0] # "Python"이 "Ruby"를 사라지게 했네요. "Python" some_dict[5] "Python"

complex_five = 5 + 0j type(complex_five) complex some_dict[complex_five] "Python"

그래서, 왜 파이썬이 여기저기서 발견되나요?

💡 설명


▶ 깊이 들어가면 우리는 다 똑같아.

출력 결과:

WTF() == WTF() # 두 인스턴스는 같을 수 없습니다 False WTF() is WTF() # 메모리 위에서도 다르네요 False hash(WTF()) == hash(WTF()) # 해시는 당연히 같아야 합니다 True id(WTF()) == id(WTF()) True

💡 설명:


▶ 질서 속의 무질서 *

from collections import OrderedDict

dictionary = dict() dictionary[1] = 'a'; dictionary[2] = 'b';

ordered_dict = OrderedDict() ordered_dict[1] = 'a'; ordered_dict[2] = 'b';

another_ordered_dict = OrderedDict() another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';

class DictWithHash(dict): """ hash 마법을 구현하는 dict """ hash = lambda self: 0

class OrderedDictWithHash(OrderedDict): """ hash 마법을 구현하는 OrderedDict """ hash = lambda self: 0

출력 결과

dictionary == ordered_dict # 만약 a == b 이고, True dictionary == another_ordered_dict # b == c 이면 True ordered_dict == another_ordered_dict # 왜 c == a 가 아닐까요? False

집합(set)은 유일한 원소들만 가지고 있으므로,

위의 딕셔너리로 집합을 만들고 어떤 일이 일어나는지 알아봅시다.

len({dictionary, ordered_dict, another_ordered_dict}) Traceback (most recent call last): File "", line 1, in TypeError: unhashable type: 'dict'

딕셔너리는 __hash__가 구현되어있지 않으므로 그런것 같네요.

그러면 위에서 만든 래퍼(wrapper) 클래스를 써봅시다.

dictionary = DictWithHash() dictionary[1] = 'a'; dictionary[2] = 'b'; ordered_dict = OrderedDictWithHash() ordered_dict[1] = 'a'; ordered_dict[2] = 'b'; another_ordered_dict = OrderedDictWithHash() another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a'; len({dictionary, ordered_dict, another_ordered_dict}) 1 len({ordered_dict, another_ordered_dict, dictionary}) # 순서를 바꿔봅시다. 2

무슨 일이 벌어지고 있는거죠?

💡 설명:


▶ 계속 시도해 보세요... *

def some_func(): try: return 'from_try' finally: return 'from_finally'

def another_func(): for _ in range(3): try: continue finally: print("Finally!")

def one_more_func(): # 알았다! try: for i in range(3): try: 1 / i except ZeroDivisionError: # 여기서 에러를 발생시키고 반복문 밖에서 다뤄보도록 하죠 raise ZeroDivisionError("A trivial divide by zero error") finally: print("Iteration", i) break except ZeroDivisionError as e: print("Zero division error ocurred", e)

출력 결과:

some_func() 'from_finally'

another_func() Finally! Finally! Finally!

1 / 0 Traceback (most recent call last): File "", line 1, in ZeroDivisionError: division by zero

one_more_func() Iteration 0

💡 설명:


▶ 무엇을 위해서(for)?

some_string = "wtf" some_dict = {} for i, some_dict[i] in enumerate(some_string): i = 10

출력결과:

some_dict # 딕셔너리가 나타나네요 {0: 'w', 1: 't', 2: 'f'}

💡 설명:

for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]  

exprlist는 할당되는 부분입니다. 이는 {exprlist} = {next_value}와 같다는 말이고 **각각의 원소에 대해 실행됩니다.**아래에 재미있는 예제가 있습니다.
for i in range(4):
print(i)
i = 10
출력 결과:
반복문이 한 번만 돌기를 기대했나요?
💡 설명:


▶ 실행되는 시간의 차이

array = [1, 8, 15]

전형적인 제너레이터(generator) 예제입니다

gen = (x for x in array if array.count(x) > 0) array = [2, 8, 22]

출력 결과:

print(list(gen)) # 다른 값들은 어디 갔나요? [8]

array_1 = [1,2,3,4] gen_1 = (x for x in array_1) array_1 = [1,2,3,4,5]

array_2 = [1,2,3,4] gen_2 = (x for x in array_2) array_2[:] = [1,2,3,4,5]

출력 결과:

print(list(gen_1)) [1, 2, 3, 4]

print(list(gen_2)) [1, 2, 3, 4, 5]

array_3 = [1, 2, 3] array_4 = [10, 20, 30] gen = (i + j for i in array_3 for j in array_4)

array_3 = [4, 5, 6] array_4 = [400, 500, 600]

출력 결과:

print(list(gen)) [401, 501, 601, 402, 502, 602, 403, 503, 603]

💡 설명


is not ...is (not ...)이 아니다

'something' is not None True 'something' is (not None) False

💡 설명


▶ X가 첫 번째 시도에서 승리하는 틱택토!

row를 초기화합니다

row = [""] * 3 #row i['', '', '']

board를 만듭니다

board = [row] * 3

출력 결과:

board [['', '', ''], ['', '', ''], ['', '', '']] board[0] ['', '', ''] board[0][0] '' board[0][0] = "X" board [['X', '', ''], ['X', '', ''], ['X', '', '']]

우리는 세 개의 "X"를 할당하지 않았습니다. 그랬나요?

💡 설명:

다음 시각화 자료는 row 변수를 초기화할 때 메모리에서 어떠한 일이 일어나는지 보여줍니다.

image

그리고 다음은 row를 곱하여 board를 초기화할 때, 메모리에서 일어나는 일입니다. (각각의 원소 board[0], board[1] 그리고 board[2]row가 참조한 동일한 리스트의 참조자입니다.)

image

위와 같은 현상은 row를 사용하지 않고 board를 생성하여 해결할 수 있습니다. (이 이슈에서 질문되었습니다).

board = [['']*3 for _ in range(3)] board[0][0] = "X" board [['X', '', ''], ['', '', ''], ['', '', '']]


▶ 달라붙는 출력 함수

funcs = [] results = [] for x in range(7): def some_func(): return x funcs.append(some_func) results.append(some_func()) # 함수를 호출하고 있다는 것을 놓치지 마세요.

funcs_results = [func() for func in funcs]

출력 결과:

results [0, 1, 2, 3, 4, 5, 6] funcs_results [6, 6, 6, 6, 6, 6, 6]

funcssome_func를 추가하기 전의 x값은 항상 달랐는데도, 모든 함수가 6을 리턴합니다.

powers_of_x = [lambda x: x**i for i in range(10)] [f(2) for f in powers_of_x] [512, 512, 512, 512, 512, 512, 512, 512, 512, 512]

💡 설명


▶ 닭이 먼저일까, 달걀이 먼저일까 *

isinstance(3, int) True isinstance(type, object) True isinstance(object, type) True

그래서, "궁극적인" 기본 클래스는 뭘까요? 혼란스러운 점은 이게 끝이 아닙니다.

class A: pass isinstance(A, A) False isinstance(type, type) True isinstance(object, object) True

issubclass(int, object) True issubclass(type, object) True issubclass(object, type) False

💡 설명


▶ 서브 클래스의 관계

출력 결과:

from collections import Hashable issubclass(list, object) True issubclass(object, Hashable) True issubclass(list, Hashable) False

서브 클래스의 관계는 삼단논법을 따라야 하지 않나요? (즉 AB의 서브 클래스이고 BC의 서브 클래스이면 AC의 서브 클래스 이여야만 합니다)

💡 설명:


▶ 메소드의 같음과 동일함

class SomeClass: def method(self): pass

@classmethod
def classm(cls):
    pass

@staticmethod
def staticm():
    pass

*출력 결과:

print(SomeClass.method is SomeClass.method) True print(SomeClass.classm is SomeClass.classm) False print(SomeClass.classm == SomeClass.classm) True print(SomeClass.staticm is SomeClass.staticm) True

classm을 두 번 접근했을 때, 동등한 객체이지만 같지는 않은 객체가 되네요?SomeClass의 인스턴스는 어떻게 되는지 한 번 볼까요?:

여기서 동등하다는 것은 메모리 상 같은 위치에 있다는 것이고, 같다는 것은 단순하게 값이 같다는 의미입니다.

o1 = SomeClass() o2 = SomeClass()

출력 결과:

print(o1.method == o2.method) False print(o1.method == o1.method) True print(o1.method is o1.method) False print(o1.classm is o1.classm) False print(o1.classm == o1.classm == o2.classm == SomeClass.classm) True print(o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm) True

SomeClass의 같은 인스턴스는 classmmethod를 두 번 접근했을 때, 동등하지만 같지는 않은 객체를 생성합니다.

💡 설명

o1.method <bound method SomeClass.method of <__main__.SomeClass object at ...>>

SomeClass.method <function SomeClass.method at ...>

o1.classm <bound method SomeClass.classm of <class '__main__.SomeClass'>>

SomeClass.classm <bound method SomeClass.classm of <class '__main__.SomeClass'>>

o1.staticm <function SomeClass.staticm at ...> SomeClass.staticm <function SomeClass.staticm at ...>


▶ 참 거짓의 반복 *

all([True, True, True]) True all([True, True, False]) False

all([]) True all([[]]) False all([[[]]]) True

왜 이런 참 거짓의 반복이 일어날까요?

💡 설명:


▶ 놀라운 콤마

출력 결과 (< 3.6):

def f(x, y,): ... print(x, y) ... def g(x=4, y=5,): ... print(x, y) ... def h(x, **kwargs,): File "", line 1 def h(x, **kwargs,): ^ SyntaxError: invalid syntax

def h(*args,): File "", line 1 def h(*args,): ^ SyntaxError: invalid syntax

💡 설명:


▶ 문자열과 백슬래시

출력 결과:

print(""") "

print(r""") "

print(r"") File "", line 1 print(r"") ^ SyntaxError: EOL while scanning string literal

r''' == "\'" True

💡 설명


▶ 매듭이 아니야!

출력 결과:

not x == y True x == not y File "", line 1 x == not y ^ SyntaxError: invalid syntax

💡 설명:


▶ 반쪽 3중 따옴표 문자열

출력 결과:

print('wtfpython''') wtfpython print("wtfpython""") wtfpython

다음 표현식은 SyntaxError를 발생시킵니다.

print('''wtfpython')

print("""wtfpython")

File "", line 3 print("""wtfpython") ^ SyntaxError: EOF while scanning triple-quoted string literal

💡 설명:

>>> print("wtf" "python")  
wtfpython  
>>> print("wtf" "") # 또는 "wtf"""  
wtf  

▶ 불린의 문제점이 뭐야?

다양한 데이터 타입 속 불린의 개수와 정수의 개수를 세는 간단한 예제입니다.

mixed_list = [False, 1.0, "some_string", 3, True, [], False] integers_found_so_far = 0 booleans_found_so_far = 0

for item in mixed_list: if isinstance(item, int): integers_found_so_far += 1 elif isinstance(item, bool): booleans_found_so_far += 1

출력 결과:

integers_found_so_far 4 booleans_found_so_far 0

some_bool = True "wtf" * some_bool 'wtf' some_bool = False "wtf" * some_bool ''

def tell_truth(): True = False if True == False: print("I have lost faith in truth!")

출력 (< 3.x):

tell_truth() I have lost faith in truth!

💡 설명:


▶ 클래스 속성과 인스턴스 속성

class A: x = 1

class B(A): pass

class C(A): pass

Output:

A.x, B.x, C.x (1, 1, 1) B.x = 2 A.x, B.x, C.x (1, 2, 1) A.x = 3 A.x, B.x, C.x # C.x 는 바뀌었지만, B.x 는 바뀌지 않았습니다. (3, 2, 3) a = A() a.x, A.x (3, 3) a.x += 1 a.x, A.x (4, 3)

class SomeClass: some_var = 15 some_list = [5] another_list = [5] def init(self, x): self.some_var = x + 1 self.some_list = self.some_list + [x] self.another_list += [x]

Output:

some_obj = SomeClass(420) some_obj.some_list [5, 420] some_obj.another_list [5, 420] another_obj = SomeClass(111) another_obj.some_list [5, 111] another_obj.another_list [5, 420, 111] another_obj.another_list is SomeClass.another_list True another_obj.another_list is some_obj.another_list True

💡 설명:


▶ yielding None

some_iterable = ('a', 'b')

def some_func(val): return "something"

출력 결과 (<= 3.7.x):

[x for x in some_iterable] ['a', 'b'] [(yield x) for x in some_iterable] <generator object at 0x7f70b0a4ad58> list([(yield x) for x in some_iterable]) ['a', 'b'] list((yield x) for x in some_iterable) ['a', None, 'b', None] list(some_func((yield x)) for x in some_iterable) ['a', 'something', 'b', 'something']

💡 설명:


▶ Yielding from... return! *

def some_func(x): if x == 3: return ["wtf"] else: yield from range(x)

출력 결과 (> 3.3):

list(some_func(3)) []

"wtf"이 어디로 갓나요? yield from의 특수한 효과 때문일까요? 확인해봅시다.

def some_func(x): if x == 3: return ["wtf"] else: for i in range(x): yield i

출력 결과:

list(some_func(3)) []

같은 결과입니다. 이것도 효과가 없습니다.

💡 설명:

"... 제너레이터 내부의 return expr는 제너레이터가 종료될 때 StopIteration(expr)을 발생시킵니다."


▶ Nan-재귀성 *

a = float('inf') b = float('nan') c = float('-iNf') # 이 문자열은 대소문자를 구분하지 않습니다 d = float('nan')

출력 결과:

a inf b nan c -inf float('some_other_string') ValueError: could not convert string to float: some_other_string a == -c # inf==inf True None == None # None == None True b == d # 하지만 nan!=nan 입니다 False 50 / a 0.0 a / a nan 23 + b nan

x = float('nan') y = x / x y is y # 정체성은 유지됩니다 True y == y # y와 같은 값은 아닙니다 False [y] == [y] # 하지만 y를 리스트로 감싸면 같은 값이 됩니다 True

💡 설명:


▶ 불변을 변형하기!

여러분이 파이썬에서 참조가 어떻게 작동하는지 안다면 이건 당연해 보일 수 있습니다.

some_tuple = ("A", "tuple", "with", "values") another_tuple = ([1, 2], [3, 4], [5, 6])

출력 결과:

some_tuple[2] = "change this" TypeError: 'tuple' object does not support item assignment another_tuple[2].append(1000) #이건 에러를 만들지 않습니다 another_tuple ([1, 2], [3, 4], [5, 6, 1000]) another_tuple[2] += [99, 999] TypeError: 'tuple' object does not support item assignment another_tuple ([1, 2], [3, 4], [5, 6, 1000, 99, 999])

하지만 저는 튜플이 변경 불가능하다 생각했습니다...

💡 설명:


▶ 외부 범위에서 사라지는 변수

e = 7 try: raise Exception() except Exception as e: pass

출력 결과 (Python 2.x):

print(e)

아무것도 출력하지 않습니다

출력 결과 (Python 3.x):

print(e) NameError: name 'e' is not defined

💡 설명:

이것은 except 절 후에 참조하려면 예외를 다른 이름에 대입해야 한다는 뜻입니다. 예외를 제거하는 이유는, 그것에 첨부된 트레이스백으로 인해, 스택 프레임과 참조 순환을 형성해서 다음 가비지 수거가 일어나기 전까지 그 프레임의 모든 지역 변수들을 잡아두기 때문입니다.

아무것도 출력되지 않았습니다!


▶ 미스테리한 키 타입 형 변환

class SomeClass(str): pass

some_dict = {'s': 42}

출력 결과:

type(list(some_dict.keys())[0]) str s = SomeClass('s') some_dict[s] = 40 some_dict # 예상: 두개의 다른 key-value 쌍 {'s': 40} type(list(some_dict.keys())[0]) str

💡 설명:


▶ 여러분이 맞출 수 있는지 한번 볼까요?

출력 결과:

💡 설명:

(target_list "=")+ (expression_list | yield_expression)  

그리고

대입문은 표현식 목록 (이것이 하나의 표현식일 수도, 쉼표로 분리된 목록일 수도 있는데, 후자의 경우는 튜플이 만들어진다는 것을 기억하라) 의 값을 구하고, 왼쪽에서 오른쪽으로, 하나의 결과 객체를 타깃 목록의 각각에 대입한다.



"미끄러운 비탈길" 단원

▶ 딕셔너리가 반복 중일 때 수정하기

x = {0: None}

for i in x: del x[i] x[i+1] = None print(i)

출력 결과 (Python 2.7- Python 3.5):

정확히 8번 돌고 멈춥니다.

💡 설명:


▶ 완강한 del 연산자

class SomeClass: def del(self): print("Deleted!")

**출력 결과:**1.

x = SomeClass() y = x del x # "Deleted!"를 출력해야 합니다 del y Deleted!

휴, 드디어 삭제되었습니다. 여러분은 처음의 x 삭제에서 __del__이 호출되지 않은 것을 생각하실 수도 있습니다. 이제 예제를 살짝 비틀어 봅시다.

x = SomeClass() y = x del x y # y가 존재하는지 확인합니다 <__main__.SomeClass instance at 0x7f98a1a67fc8> del y # 이전과 같이, "Deleted!"를 출력해야 합니다 globals() # 오, 그렇지 않네요. 우리의 전역변수를 확인해봅시다 Deleted! {'builtins': <module '__builtin__' (built-in)>, 'SomeClass': <class __main__.SomeClass at 0x7f98a1a5f668>, 'package': None, 'name': 'main', 'doc': None}

좋습니다. 이제 삭제되었습니다 😕

💡 설명:


▶ 범위를 벗어난 변수

a = 1 def some_func(): return a

def another_func(): a += 1 return a

def some_closure_func(): a = 1 def some_inner_func(): return a return some_inner_func()

def another_closure_func(): a = 1 def another_inner_func(): a += 1 return a return another_inner_func()

출력 결과:

some_func() 1 another_func() UnboundLocalError: local variable 'a' referenced before assignment

some_closure_func() 1 another_closure_func() UnboundLocalError: local variable 'a' referenced before assignment

💡 설명:


▶ 반복하는 동안 리스트의 아이템을 삭제하기

list_1 = [1, 2, 3, 4] list_2 = [1, 2, 3, 4] list_3 = [1, 2, 3, 4] list_4 = [1, 2, 3, 4]

for idx, item in enumerate(list_1): del item

for idx, item in enumerate(list_2): list_2.remove(item)

for idx, item in enumerate(list_3[:]): list_3.remove(item)

for idx, item in enumerate(list_4): list_4.pop(idx)

출력 결과:

list_1 [1, 2, 3, 4] list_2 [2, 4] list_3 [] list_4 [2, 4]

왜 출력 결과가 [2, 4]가 나오는지 알 수 있나요?

💡 설명:

del, remove, pop의 차이점

[2, 4]가 출력되나요?


▶ 반복자의 손실되는 zip *

numbers = list(range(7)) numbers [0, 1, 2, 3, 4, 5, 6] first_three, remaining = numbers[:3], numbers[3:] first_three, remaining ([0, 1, 2], [3, 4, 5, 6]) numbers_iter = iter(numbers) list(zip(numbers_iter, first_three)) [(0, 0), (1, 1), (2, 2)]

지금까지는 좋은데, 나머지도 압축해봅시다

list(zip(numbers_iter, remaining)) [(4, 3), (5, 4), (6, 5)]

numbers 리스트에서 요소 3이 어디로 갔을까요?

💡 설명:


▶ 루프 변수가 유출되고 있습니다!

for x in range(7): if x == 6: print(x, ': for x inside loop') print(x, ': x in global')

출력 결과:

6 : for x inside loop 6 : x in global

하지만 x는 루프의 밖에서 선언된 적이 없습니다...

이번엔 먼저 x를 초기화해봅시다

x = -1 for x in range(7): if x == 6: print(x, ': for x inside loop') print(x, ': x in global')

출력 결과:

6 : for x inside loop 6 : x in global

출력 결과 (Python 2.x):

x = 1 print([x for x in range(5)]) [0, 1, 2, 3, 4] print(x) 4

출력 결과 (Python 3.x):

x = 1 print([x for x in range(5)]) [0, 1, 2, 3, 4] print(x) 1

💡 설명:


▶ 기본 가변인수를 조심하세요!

def some_func(default_arg=[]): default_arg.append("some_string") return default_arg

출력 결과:

some_func() ['some_string'] some_func() ['some_string', 'some_string'] some_func([]) ['some_string'] some_func() ['some_string', 'some_string', 'some_string']

💡 설명:


▶ 여러 예외들을 잡기

some_list = [1, 2, 3] try: # IndexError를 일으킵니다 print(some_list[4]) except IndexError, ValueError: print("Caught!")

try: # ValueError를 일으킵니다 some_list.remove(4) except IndexError, ValueError: print("Caught again!")

출력 결과 (Python 2.x):

Caught!

ValueError: list.remove(x): x not in list

출력 결과 (Python 3.x):

File "", line 3 except IndexError, ValueError: ^ SyntaxError: invalid syntax

💡 설명

Caught again!  
list.remove(x): x not in list  

출력 결과 (Python 3.x):
File "", line 4
except (IndexError, ValueError), e:
^
IndentationError: unindent does not match any outer indentation level

Caught again!  
list.remove(x): x not in list  

▶ 같은 피연산자, 다른 이야기!

a = [1, 2, 3, 4] b = a a = a + [5, 6, 7, 8]

출력 결과:

a [1, 2, 3, 4, 5, 6, 7, 8] b [1, 2, 3, 4]

a = [1, 2, 3, 4] b = a a += [5, 6, 7, 8]

출력 결과:

a [1, 2, 3, 4, 5, 6, 7, 8] b [1, 2, 3, 4, 5, 6, 7, 8]

💡 설명:


▶ 이름 확인은 클래스 범위를 무시합니다

x = 5 class SomeClass: x = 17 y = (x for i in range(10))

출력 결과:

list(SomeClass.y)[0] 5

x = 5 class SomeClass: x = 17 y = [x for i in range(10)]

출력 결과 (Python 2.x):

출력 결과 (Python 3.x):

💡 설명


▶ 모래밭에서 바늘찾기 *

다음의 시나리오 중 하나 이상을 접해보지 못한 파이써니스트는 한 번도 만나본 적이 없습니다,

x, y = (0, 1) if True else None, None

출력 결과:

x, y # (0, 1)이 예상됩니다 ((0, 1), None)

t = ('one', 'two') for i in t: print(i)

t = ('one') for i in t: print(i)

t = () print(t)

출력 결과:

ten_words_list = [
    "some",
    "very",
    "big",
    "list",
    "that"
    "consists",
    "of",
    "exactly",
    "ten",
    "words"
]

출력 결과

len(ten_words_list) 9

4. 충분히 강하게 주장하지 않음

a = "python" b = "javascript"

출력 결과:

실패 메세지가 있는 assert 구문.

assert(a == b, "Both languages are different")

AssertionError가 일어나지 않습니다

some_list = [1, 2, 3] some_dict = { "key_1": 1, "key_2": 2, "key_3": 3 }

some_list = some_list.append(4) some_dict = some_dict.update({"key_4": 4})

출력 결과:

print(some_list) None print(some_dict) None

def some_recursive_func(a): if a[0] == 0: return a[0] -= 1 some_recursive_func(a) return a

def similar_recursive_func(a): if a == 0: return a a -= 1 similar_recursive_func(a) return a

출력 결과:

some_recursive_func([5, 0]) [0, 0] similar_recursive_func(5) 4

💡 설명:


▶ 나눠봅시다 *

'a'.split() ['a']

같은 결과입니다

'a'.split(' ') ['a']

하지만

len(''.split()) 0

이건 같지 않네요

len(''.split(' ')) 1

💡 설명:


▶ 제멋대로 가져오기 *

File: module.py

def some_weird_name_func_(): print("works!")

def _another_weird_name_func(): print("works!")

출력 결과

from module import * some_weird_name_func_() "works!" _another_weird_name_func() Traceback (most recent call last): File "", line 1, in NameError: name '_another_weird_name_func' is not defined

💡 설명:


▶ 다 정렬되었나요? *

x = 7, 8, 9 sorted(x) == x False sorted(x) == sorted(x) True

y = reversed(x) sorted(y) == sorted(y) False

💡 설명:


▶ 자정은 존재하지 않나요?

from datetime import datetime

midnight = datetime(2018, 1, 1, 0, 0) midnight_time = midnight.time()

noon = datetime(2018, 1, 1, 12, 0) noon_time = noon.time()

if midnight_time: print("Time at midnight is", midnight_time)

if noon_time: print("Time at noon is", noon_time)

출력 결과 (< 3.5):

('Time at noon is', datetime.time(12, 0))

자정은 출력되지 않습니다.

💡 설명:

파이썬 3.5 이전에, datetime.time 객체의 불리언 값은 UTC 기준으로 자정을 나타내는 경우 False로 간주하였습니다. 이는 if obj: 구문을 사용하우 obj가 null 또는 "비어있음"인지 확인하는 경우 오류가 발생하기 쉽습니다.



"숨겨진 보물들!" 단원

이 단원에는 저 같은 초보자들이 (더 이상은 아니지만) 대부분 모르고 있는 파이썬에 대한 덜 알려지고 흥미로운 것들이 몇 가지 포함되어있습니다.

▶ 파이썬, 날 날게해줄 수 있니?

자, 여기 있습니다

**출력 결과:**쉿... 이건 일급비밀이야.

💡 설명:


goto, 하지만 왜?

from goto import goto, label for i in range(9): for j in range(9): for k in range(9): print("I am trapped, please rescue!") if k == 2: goto .breakout # 깊게 중첩된 루프에서 탈출 label .breakout print("Freedom!")

출력 결과 (Python 2.3):

I am trapped, please rescue! I am trapped, please rescue! Freedom!

💡 설명:


▶ 마음 단단히 먹으세요!

만약 여러분이 파이썬에서 스코프를 나타내기 위해 공백을 사용하는 것을 좋아하지 않는 사람 중 한 명이라면, C-스타일의 {}을 가져와 사용할 수 있습니다.

from future import braces

출력 결과:

File "some_file.py", line 1 from future import braces SyntaxError: not a chance

중괄호? 절대 안돼! 만약 이게 실망스럽다면 자바를 사용하세요. 또 하나 놀라운 것은 __future__모듈에서 발생한 SyntaxError코드의 어디에 있는지 찾을 수 있나요?

💡 설명:


▶ 평생 친근한 아저씨 같은 언어를 만나봅시다

출력 결과 (Python 3.x)

from future import barry_as_FLUFL "Ruby" != "Python" # 이건 의심할 여지가 없습니다 File "some_file.py", line 1 "Ruby" != "Python" ^ SyntaxError: invalid syntax

"Ruby" <> "Python" True

또 시작이군.

💡 설명:


▶ 파이썬 조차 사랑이 복잡하다는 것을 이해합니다

잠깐, this가 뭔가요? this는 사랑입니다 ❤️

출력 결과:

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

이것은 the Zen of Python 입니다!

love = this this is love True love is True False love is False False love is not True or False True love is not True or False; love is love # 사랑은 복잡합니다 True

💡 설명:


▶ 네, 존재합니다!

반복문에 대한 else 조건의 예로 다음과 같은게 있습니다:

def does_exists_num(l, to_find): for num in l: if num == to_find: print("Exists!") break else: print("Does not exist")

출력 결과:

some_list = [1, 2, 3, 4, 5] does_exists_num(some_list, 4) Exists! does_exists_num(some_list, -1) Does not exist

예외 처리에 대한 else 조건의 예는 다음과 같습니다,

try: pass except: print("Exception occurred!!!") else: print("Try block executed successfully...")

출력 결과:

Try block executed successfully...

💡 설명:


▶ Ellipsis *

def some_func(): Ellipsis

출력 결과

some_func()

출력도 없고, 에러도 없다

SomeRandomString Traceback (most recent call last): File "", line 1, in NameError: name 'SomeRandomString' is not defined

Ellipsis Ellipsis

💡 설명


▶ Inpinity

철자는 의도된 것입니다. 이것에 대한 수정사항을 보내지 마세요.

출력 결과 (Python 3.x):

infinity = float('infinity') hash(infinity) 314159 hash(float('-inf')) -314159

💡 설명:


▶ 망쳐봅시다

class Yo(object): def init(self): self.__honey = True self.bro = True

출력 결과:

Yo().bro True Yo().__honey AttributeError: 'Yo' object has no attribute '__honey' Yo()._Yo__honey True

class Yo(object): def init(self): # 이번엔 대칭적으로 해봅시다 self.honey = True self.bro = True

출력 결과:

Yo().bro True

Yo().Yo__honey_ Traceback (most recent call last): File "", line 1, in AttributeError: 'Yo' object has no attribute 'Yo__honey_'

Yo()._Yo__honey가 동작했을까요?

_A__variable = "Some value"

class A(object): def some_func(self): return __variable # 아직 아무것도 초기화되지 않았습니다

출력 결과:

A().__variable Traceback (most recent call last): File "", line 1, in AttributeError: 'A' object has no attribute '__variable'

A().some_func() 'Some value'

💡 설명:



"겉모습은 기만적입니다!" 단원

▶ 줄 건너뛰기?

출력 결과:

value = 11 valuе = 32 value 11

뭐라고요?

참고: 이를 재현하는 가장 쉬운 방법은 위의 코드에서 구문을 복사해서 파일/셸에 붙여넣는 것입니다.

💡 설명

일부 비-서양의 문자들은 영어의 알파벳과 똑같아 보이지만 인터프리터에 의해 별개의 것으로 여겨집니다.

ord('е') # 키릴 문자 'e' (Ye) 1077 ord('e') # 라틴 문자 'e', 영어에 사용되고 표준 키보드를 사용하여 타이핑한 것 101 'е' == 'e' False

value = 42 # 라틴 문자 e valuе = 23 # 키릴 문자 'e', Python 2.x 인터프리터는 SyntaxError를 일으킵니다 value 42

내장된 ord() 함수는 문자의 유니코드 코드 포인트 를 반환하며, 키릴 문자 'e'와 라틴 문자 'e'의 다른 코드 위치는 예제의 동작이 옳음을 보여줍니다.


▶ 순간이동

먼저 pip install numpy를 하세요.

import numpy as np

def energy_send(x): # numpy 배열을 초기화합니다. np.array([float(x)])

def energy_receive(): # 빈 numpy 배열을 반환합니다. return np.empty((), dtype=np.float).tolist()

출력 결과:

energy_send(123.456) energy_receive() 123.456

노벨상은 어디있나요?

💡 설명:


▶ 음, 뭔가 수상한데...

def square(x): """ 숫자의 합으로 제곱을 구하는 간단한 함수. """ sum_so_far = 0 for counter in range(x): sum_so_far = sum_so_far + x return sum_so_far

출력 결과 (Python 2.x):

100이 아니여야 하나요?

참고: 이걸 재현할 수 없는 경우 mixed_tabs_and_spaces.py를 셸에서 실행해보세요.

💡 설명



"기타 등등" 단원

+= 가 더 빨라요

3개의 문자열을 "+"을 사용해서:

timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100) 0.25748300552368164

3개의 문자열을 "+="을 사용해서:

timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100) 0.012188911437988281

💡 설명:


▶ 거대한 문자열을 만들어봐요!

def add_string_with_plus(iters): s = "" for i in range(iters): s += "xyz" assert len(s) == 3*iters

def add_bytes_with_plus(iters): s = b"" for i in range(iters): s += b"xyz" assert len(s) == 3*iters

def add_string_with_format(iters): fs = "{}"iters s = fs.format((["xyz"]iters)) assert len(s) == 3iters

def add_string_with_join(iters): l = [] for i in range(iters): l.append("xyz") s = "".join(l) assert len(s) == 3*iters

def convert_list_to_string(l, iters): s = "".join(l) assert len(s) == 3*iters

출력 결과:

더 좋은 가독성을 위해 %timeit을 사용하여 ipython shell에서 실행했습니다.

파이썬 shell/scriptm= 에서 timeit 모듈을 사용할 수 있습니다. 아래와 같은 방식입니다.

timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())

NUM_ITERS = 1000 %timeit -n1000 add_string_with_plus(NUM_ITERS) 124 µs ± 4.73 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %timeit -n1000 add_bytes_with_plus(NUM_ITERS) 211 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n1000 add_string_with_format(NUM_ITERS) 61 µs ± 2.18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n1000 add_string_with_join(NUM_ITERS) 117 µs ± 3.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) l = ["xyz"]*NUM_ITERS %timeit -n1000 convert_list_to_string(l, NUM_ITERS) 10.1 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

반복 횟수를 10배로 늘렸습니다.

NUM_ITERS = 10000 %timeit -n1000 add_string_with_plus(NUM_ITERS) # Linear increase in execution time 1.26 ms ± 76.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n1000 add_bytes_with_plus(NUM_ITERS) # Quadratic increase 6.82 ms ± 134 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n1000 add_string_with_format(NUM_ITERS) # Linear increase 645 µs ± 24.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit -n1000 add_string_with_join(NUM_ITERS) # Linear increase 1.17 ms ± 7.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) l = ["xyz"]*NUM_ITERS %timeit -n1000 convert_list_to_string(l, NUM_ITERS) # Linear increase 86.3 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

💡 설명


dict 검색 속도 느려지게 하기 *

some_dict = {str(i): 1 for i in range(1_000_000)} another_dict = {str(i): 1 for i in range(1_000_000)}

출력 결과:

%timeit some_dict['5'] 28.6 ns ± 0.115 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) some_dict[1] = 1 %timeit some_dict['5'] 37.2 ns ± 0.265 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

%timeit another_dict['5'] 28.5 ns ± 0.142 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each) another_dict[1] # 존재하지 않는 키에 접근을 해볼까요 Traceback (most recent call last): File "", line 1, in KeyError: 1 %timeit another_dict['5'] 38.5 ns ± 0.0913 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

왜 같은 검색의 속도가 느려질까요?

💡 설명:

dict 인스턴스 부풀리기 *

import sys

class SomeClass: def init(self): self.some_attr1 = 1 self.some_attr2 = 2 self.some_attr3 = 3 self.some_attr4 = 4

def dict_size(o): return sys.getsizeof(o.dict)

출력 결과: (파이썬 3.8, 다른 파이썬 3 버전은 조금 다를 수 있습니다.)

o1 = SomeClass() o2 = SomeClass() dict_size(o1) 104 dict_size(o2) 104 del o1.some_attr1 o3 = SomeClass() dict_size(o3) 232 dict_size(o1) 232

새로운 인터프리터에서 다시 시도해볼까요?:

o1 = SomeClass() o2 = SomeClass() dict_size(o1) 104 # 예상한 대로 나왔네요 o1.some_attr5 = 5 o1.some_attr6 = 6 dict_size(o1) 360 dict_size(o2) 272 o3 = SomeClass() dict_size(o3) 232

무엇이 이 딕셔너리들을 부풀리게 했을까요? 그리고 왜 새롭게 생성된 객체도 부풀려질까요?

💡 설명:


▶ 사소한 것들 *

File some_file.py

import time
print("wtfpython", end="_")
time.sleep(3)
출력 버퍼가 \n 에 도달한 후 또는 프로그램의 실행이 끝날 때 출력 버퍼가 플러시 되기 때문에 end 인자로 인하여 3초 뒤에 wtfpython 을 출력합니다. flush=True 인자를 전달하여 버퍼를 강제로 플러시 할 수도 있습니다.



기여하기

wtfpython에 기여할 수 있는 몇 가지 방법이 있어요,

더 많은 정보는 CONTRIBUTING.md을는보세요. 자유롭게 새로운 issue를 만들어 토론해보세요.

추신: 역링크 요청으로 연락하지 마세요. 프로젝트와 관련이 높지 않으면 링크를 추가하지 않습니다.

감사의 말

이 항목들의 아이디어와 디자인은 Denys Dovhan's 의 멋진 프로젝트 wtfjs 에서 영감을 받았습니다. Pythonista들의 압도적인 지지는 그것의 현재의 모습을 주었습니다.

몇 개의 멋진 링크들!

🎓 License

WTFPL 2.0

© Satwik Kansal

친구들을 놀라게 해보세요!

만약 wtfpython이 마음에 드셨다면, 친구들에게 빠르게 공유하기 위한 퀵 링크들을 사용할 수 있어요.

Twitter | Linkedin | Facebook

Need a pdf version?

I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details here to get them as soon as they are finished.

That's all folks! For upcoming content like this, you can add your email here.