CodeGym /행동 /Python SELF KO /표준 오류

표준 오류

Python SELF KO
레벨 20 , 레슨 1
사용 가능

7.1 함수 인자의 기본값으로 표현식의 잘못된 사용

많은 것을 배웠으니, 신입생들에게 가장 흔한 실수를 살펴보자 – 다른 사람의 실수를 통해 배우는 것이 자신의 실수를 통해 배우는 것보다 낫지, 안 그래?

Python은 함수에 선택적 인자가 있을 수 있음을 기본값으로 설정하여 지정할 수 있게 해줘. 물론, 이것은 아주 편리한 기능이지만, 이러한 값의 타입이 변경 가능한 경우 좋지 않은 결과를 초래할 수 있어. 예를 들어, 다음과 같은 함수 정의를 보자:


def foo(bar=[]):  # bar는 선택적 인자로 기본값은 빈 리스트야.
    bar.append("baz")  # 이 줄이 문제가 될 수 있어...
    return bar

여기서 흔히 하는 오해는 선택적 인자의 값이 함수가 이 인자에 대한 값 없이 호출될 때마다 기본값으로 설정된다고 생각하는 거야.

위의 코드에서, 예를 들어, foo() 함수를 반복해서 호출할 때 (즉, 인자 bar의 값을 지정하지 않을 때) 항상 ["baz"]를 반환할 것이라 생각할 수 있어. 왜냐하면 foo()가 호출될 때마다 (인자 bar를 지정하지 않을 때) bar[]로 (즉, 새로운 빈 리스트) 설정된다고 가정하기 때문이야.

하지만 실제로 어떤 일이 일어나는지 보자:


result = foo()
print(result)  # ["baz"]
            
result = foo()
print(result)  # ["baz", "baz"]
            
result = foo()
print(result)  # ["baz", "baz", "baz"]

foo()를 호출할 때마다 기존 리스트에 baz 값을 계속 추가하느냐고? 새로운 리스트를 생성하지 않고?

이 질문에 대한 답은 Python의 '내부'를 좀 더 깊이 이해하는 데 있어. 바로, 함수의 기본값은 한 번만 초기화된다, 즉 함수가 정의될 때 단 한 번!

따라서, 인자 bar는 기본값 (즉, 빈 리스트)으로 단 한 번, foo()가 처음 정의될 때 초기화되고, 이후에 foo()를 호출할 때 (즉, 인자 bar를 지정하지 않을 때) 항상 초기 정의 시 생성된 동일한 리스트를 사용하게 되는 거야.

이 실수에 대한 일반적인 "해결책"은 다음과 같은 정의야:


def foo(bar=None):             
    if bar is None:
        bar = []                                          
    bar.append("baz")   
    return bar
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]

7.2 클래스 변수의 잘못된 사용

다음 예제를 보자:


class A(object):
    x = 1
       
class B(A):
    pass
       
class C(A):
    pass
       
print(A.x, B.x, C.x) 
# 1 1 1

모두 잘 되네.

이제 B 클래스의 x 필드에 값을 할당해보자:


B.x = 2
print(A.x, B.x, C.x)
# 1 2 1

예상대로 잘 되었어.

이제 A 클래스의 변수를 변경해보자:


A.x = 3
print(A.x, B.x, C.x)
# 3 2 3

이상하네, 우리는 A.x만 변경했는데, 왜 C.x도 변경됐지?

Python에서는 클래스 변수가 딕셔너리처럼 처리되고, 메서드 해상도 순서(MRO)라고 불리는 규칙을 따르기 때문이야. 위 코드에서, 속성 x클래스 C 안에서는 발견되지 않기 때문에, 그것은 기본 클래스들에서 (위 예제에서는 A만 있지만, Python은 다중 상속을 지원하지) 찾아지게 돼.

다른 말로, CA와 독립적인 x 속성을 가지고 있지 않아. 따라서, C.x에 대한 참조는 사실 A.x에 대한 참조인 거야. 이러한 경우를 적절히 고려하지 않으면 문제가 될 수 있어. Python을 배우면서 클래스 속성과의 작업에 특별한 주의를 기울이자.

7.3 예외 처리 블록의 잘못된 매개변수 지정

다음 코드 조각을 보자:


try:
    l = ["a", "b"]
    int(l[2])
except ValueError, IndexError:  # 두 예외를 모두 잡으려고 했지?
    pass
        
Traceback (most recent call last):
    File "<stdin>", line 3, in <module>
IndexError: list index out of range

여기서 문제는 except 표현식이 그렇게 지정된 예외 목록을 수락하지 않는다는 거야. 결과적으로 위 코드에서는 IndexError 예외가 except 표현식에 의해 잡히지 않고, 대신 IndexError라는 이름의 매개변수에 바인딩돼.

중요! 인터넷 예제에서 이런 코드를 볼 수 있는데, Python 2.x에서 사용되던 방식이야.

except 표현식을 사용하여 여러 예외를 잡는 올바른 방법은 첫 번째 매개변수를 잡고자 하는 모든 예외를 포함하는 튜플로 지정하는 것이야. 또한, 더 읽기 쉽도록 as 키워드를 사용할 수도 있어, 예를 들어 이렇게:


try:
    l = ["a", "b"]
    int(l[2])
except (ValueError, IndexError) as e:  
    pass
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION