A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/buttercrab/wtfpython-ko below:

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

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

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

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

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

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

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

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

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

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

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

그럼, 시작합니다!

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

# 예제 세팅
# 마법 같은 일을 기대하세요...

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

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

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

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

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

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

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

$ pip install wtfpython -U
$ wtfpython

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

1.

# 파이썬 3.8+

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

>>> a := "wtf_walrus"
File "<stdin>", 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 "<stdin>", 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를 두 번 호출하는 것을 방지할 수 있습니다.

1.

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

2.

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

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

3.

>>> 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를 출력하네요.

4.

출력 결과 (< 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와 같은 표현들은 환상적입니다.

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

1.

>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False

2.

>>> 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

1.

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 "<stdin>", line 1, in <module>
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 "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero

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

출력결과:

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

1.

array = [1, 8, 15]
# 전형적인 제너레이터(generator) 예제입니다
gen = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]

출력 결과:

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

2.

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]

3.

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
# 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 변수를 초기화할 때 메모리에서 어떠한 일이 일어나는지 보여줍니다.

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

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

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

1.

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을 리턴합니다.

2.

>>> 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]

1.

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

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

2.

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

3.

>>> 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 "<stdin>", line 1
    def h(x, **kwargs,):
                     ^
SyntaxError: invalid syntax

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

출력 결과:

>>> print("\"")
"

>>> print(r"\"")
\"

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

>>> r'\'' == "\\'"
True

출력 결과:

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

출력 결과:

>>> print('wtfpython''')
wtfpython
>>> print("wtfpython""")
wtfpython
>>> # 다음 표현식은 `SyntaxError`를 발생시킵니다.
>>> # print('''wtfpython')
>>> # print("""wtfpython")
  File "<input>", line 3
    print("""wtfpython")
                        ^
SyntaxError: EOF while scanning triple-quoted string literal

1.

# 다양한 데이터 타입 속 불린의 개수와 정수의 개수를 세는 간단한 예제입니다.
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

2.

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

3.

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!

1.

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)

2.

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
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 <listcomp> 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! *

1.

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

출력 결과 (> 3.3):

>>> list(some_func(3))
[]

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

2.

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)을 발생시킵니다."

1.

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

2.

>>> 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
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

출력 결과:

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

x = {0: None}

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

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

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

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

출력 결과: 1.

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

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

2.

>>> 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}

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

1.

a = 1
def some_func():
    return a

def another_func():
    a += 1
    return a

2.

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]가 출력되나요?

>>> 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이 어디로 갔을까요?

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

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

2.

# 이번엔 먼저 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

3.

출력 결과 (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 "<input>", line 3
    except IndexError, ValueError:
                     ^
SyntaxError: invalid syntax

1.

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]

2.

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]

1.

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

출력 결과:

>>> list(SomeClass.y)[0]
5

2.

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

출력 결과 (Python 2.x):

출력 결과 (Python 3.x):

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

1.

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

출력 결과:

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

2.

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

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

t = ()
print(t)

출력 결과:

3.

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가 일어나지 않습니다

5.

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

6.

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 "<stdin>", line 1, in <module>
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 또는 "비어있음"인지 확인하는 경우 오류가 발생하기 쉽습니다.

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

자, 여기 있습니다

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

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...
def some_func():
    Ellipsis

출력 결과

>>> some_func()
# 출력도 없고, 에러도 없다

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

>>> Ellipsis
Ellipsis

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

출력 결과 (Python 3.x):

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

1.

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

2.

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

출력 결과:

>>> Yo().bro
True

>>> Yo()._Yo__honey__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Yo' object has no attribute '_Yo__honey__'

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

3.

_A__variable = "Some value"

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

출력 결과:

>>> A().__variable
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
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) == 3*iters

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)
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 "<stdin>", line 1, in <module>
KeyError: 1
>>> %timeit another_dict['5']
38.5 ns ± 0.0913 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

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

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

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

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

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

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

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

© Satwik Kansal

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

Twitter | Linkedin | Facebook

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.


RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4