wtf python 알아두면 좋은 내용 정리

    목차
반응형

wtf python이란?

What the f*ck Python?으로, python을 쓰다보면 헷갈리거나 이해되지 않는 연산자나 내장 함수들이 있다. 그런 것들을 소개하고 왜 그런 결과가 나오는지 알려주는 github 문서다.

영문: GitHub - satwikkansal/wtfpython: What the f*ck Python? 😱

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

일부는 엄청 깊게 파고들어서 찐 개발자들에겐 도움이 될 수 있으나 나 같은 데이터 분석 업무 위주로 하는 준개발자에게는 불필요한 내용도 좀 있다. 그래서 긴 wtf python 문서 중에서 나에게 필요한 내용을 정리해보았다.

:= 연산자

(a := 3) # 3


(a := 6, 9) # (6, 9)
print(a) # 6


a, b = 6, 9 # 언패킹
print(a) # 6


(a, b := 16, 19) # (6, 16, 19)
print(a, b) # 6, 16

:= 연산자는 대입과 동시에 다른 연산을 할 수 있도록 돕는다.
4 (a := 6, 9)라는 것은 ((a := 6), 9)와 같다.
12 := 연산자는 언패킹이 되지 않기 때문에 (a, b := 16, 19)를 하면 (a, (b := 16), 19)를 하는 것과 같다.

연결된 비교 연산자 주의

(False == False) in [False] # False

False == (False in [False]) # False

False == False in [False] # True

비교 연산자가 여러 개가 연속해서 나온다면, 앞에서부터 차례대로 계산이 되는 게 아니라 & 조건으로 묶인다. 예를 들어 a op1 b op2 c라는 식이 있다면 이는 곧 a op1 b and b op2 c와 같다.

1 True in [False]이기 때문에 False
2 False == True이기 때문에 False이지만
3 False == False in [False]는 다시 쓰면 (False == False) and (False in [False])이므로 True and TrueTrue가 나오게 된다.

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

여기서도
1 1 > 0 < 1(1 > 0) and (0 < 1)True
2 True < 1에서 True는 1로 취급받아서 False
3 2와 같은 이유로 False

is==의 차이점

is는 양쪽이 같은 객체를 참조하고 있는지를 확인 (같은 메모리를 사용하는지)
==는 양쪽의 “값”이 같은지를 확인
그렇기 때문에 일반적으로 값이 None인지를 체크할 때 a == None 대신 a is None을 쓰는 것이 관례이다.

함수에서는 finally를 조심해서 쓰기

def some_func():
    try:
        print(1)
        return 'from_try'
    finally:
        print(2)
        return 'from_finally'

some_func()
# 1
# 2
# 'from_finally'

some_func()에서 finally가 오기 전에 return이 실행됐어도 finally는 그를 무시하고 반드시 실행된다.

is not ...is (not ...)은 서로 다른 연산자

'something' is not None # True

'something' is (not None) # False

is not은 한 개의 연산자임을 주의해야 한다. (절대 isnot이 합쳐진 연산자가 아니다.)

그렇기 때문에 'something' is (not None)'something' is True라는 뜻이 되고 False가 된다.

==not의 우선순위를 주의

x = True
y = False

not x == y # True


x == not y # error!

not보다 ==의 우선순위가 높기 때문에, ==가 먼저 실행된다.
4 not x == ynot (x == y)True가 되고
7 x == not y(x == not) y가 되어 에러가 난다.
올바르게 고치려면 x == (not y)가 되어야 한다.

반복문에서 리스트 아이템 제거하기

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]

list_1del item은 네임스페이스에서의 item을 지우는 거라 list_1에는 어떤 영향도 주지 않는다.
리스트를 돌면서 아이템을 체크, 삭제하려면 list_3[:]처럼 리스트를 복사해주어야 한다.
list_2list_4는 첫 for에서 첫 번째 item이 지워져 [2, 3, 4]가 되고 두 번째 for에서 idx=2가 되어 두 번째 item이 지워져 [2, 4]가 된다.

+=의 연산 속도

# 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

s1 += s2 + s3s1 = s1 + s2 + s3보다 빠르다.

list와 tuple

x = 6, 7, 8

y = sorted(x)

x == y # False

() == [] # True

x는 tuple이고 sorted 결과는 list이기 때문에 x == yFalse가 맞다.
그러나 빈 tuple과 빈 list를 비교하면 같다.

덧셈과 뺄셈

a = 5

++a # 5
--a # 5

a -=- 1 # a = 6
a +=+ 1 # a = 7

++a+(+a)와 같고 --a-(-a)와 같다.
a -=- 1은 일종의 말장난 같은 것으로 사실 a -= -1이며 a = a - (-1)과 같다.

변수 디버깅

>>> some_string = "wtfpython"
>>> f'{some_string=}'
"some_string='wtfpython'"

python 3.8 이상에서는 f-string으로 변수 디버깅을 빠르게 할 수 있다.

이건 진짜 wtf

a, b = a[b] = {}, 5

a # {5: ({...}, 5)}

표현식은 가장 우측의 {}, 5 하나이며 나머지는 다 대입이다.
대입의 경우 가장 왼쪽부터 실행되어 a, b = {}, 5가 실행되고 그 이후 a[b] = a, b가 실행된다.
...은 순환 참조를 의미하여 a[b][0] is a를 하면 True가 나온다.
참고로 이 문제는 24년 1월 기준, GPT-4도 맞추지 못한 문제다.

728x90
반응형