공부를 하든, 일을 하든 사람은 실수를 하게 마련입니다. Python의 구문은 간단하고 유연하지만 여전히 몇 가지 큰 함정이 있습니다. 주의하지 않으면 초보자와 숙련된 Python 프로그래머 모두 넘어질 수 있습니다. 이 글은 10가지 흔한 실수를 공유합니다. 도움이 필요한 친구들이 참고할 수 있습니다
일반적인 실수 1: 표현식을 함수의 기본 매개변수로 잘못 사용
파이썬에서는 function 매개변수는 기본값을 설정하여 매개변수를 선택사항으로 만듭니다. 이는 훌륭한 언어 기능이지만 기본값이 변경 가능한 유형인 경우 혼란스러운 상황을 초래할 수도 있습니다. 다음 Python 함수 정의를 살펴보겠습니다.
>>> def foo(bar=[]): # bar는 선택적 매개변수입니다. bar 값이 제공되지 않으면 기본값은 []입니다. ,
... bar.append("baz") # 하지만 나중에 이 코드 줄이 문제를 일으킬 것이라는 점을 알게 될 것입니다.
... return bar
Python 프로그래머가 저지르는 일반적인 실수는 함수가 호출될 때마다 선택적 매개변수에 대해 값이 전달되지 않으면 이 선택적 매개변수가 지정된 기본값으로 설정됩니다. 위 코드에서 foo() 함수를 반복적으로 호출하면 항상 'baz'가 반환되어야 한다고 생각할 수도 있습니다. 기본적으로 foo() 함수가 실행될 때마다(bar 변수의 값이 지정되지 않음) bar가 반환되기 때문입니다. 변수는 [ ](즉, 새로운 빈 목록)로 설정됩니다.
그러나 실제 실행 결과는 다음과 같습니다.
>>> foo()
["baz"]
>>> >["baz", "baz"]
>>> foo()
["baz", "baz", "baz"]
이상하지 않나요? 새로운 빈 목록을 생성하는 대신 foo() 함수가 호출될 때마다 기본값 "baz"가 기존 목록에 추가되는 이유는 무엇입니까?
답은 선택적 매개변수의 기본값 설정은 Python에서 한 번, 즉 함수가 정의될 때에만 실행된다는 것입니다. 따라서 foo() 함수가 정의된 경우에만 bar 매개변수가 기본값(즉, 빈 리스트)으로 초기화되지만 이후 foo() 함수가 호출될 때마다 bar 매개변수는 계속해서 초기화됩니다. 해당 목록의 원래 값으로 초기화됩니다.
물론 일반적인 해결책은 다음과 같습니다.
>>> def foo(bar=None):
... if bar is None: # 또는 if not bar:
... bar = []
... bar.append("baz")
... return bar
...
>>> foo()
["baz"]
>>> foo()
["baz"]
>>> foo()
["baz"]
FAQ 2: 클래스 변수의 잘못된 사용
다음 예를 살펴보겠습니다.
>>> class A(object):
... x = 1
.
>>> 클래스 B(A):
... 합격
...
>>> 클래스 C(A):
.. 합격
...
>>> print A.x, B.x, C.x
1 1 1
이 결과는 정상입니다.
>>> B.x = 2
>>> print A.x, B.x, C.x
1 2 1
결과는 예상대로입니다.
>>> A.x = 3
>>> print A.x, B.x, C.x
3 2 3
Python 언어에서 클래스 변수는 사전 It을 기반으로 합니다. 형태로 처리되며 메소드 해결 순서(Method Resolution Order, MRO)를 따릅니다. 따라서 위 코드에서는 클래스 C에 x 속성이 없기 때문에 인터프리터는 해당 기본 클래스를 찾습니다(파이썬은 다중 상속을 지원하지만 이 예에서는 C의 기본 클래스는 A뿐입니다). 즉, C에는 A와 독립적이고 실제로 A에 속하는 자체 x 속성이 없습니다. 따라서 C.x를 참조하는 것은 실제로 A.x를 참조하는 것입니다. 여기서 관계를 제대로 처리하지 않으면 예제의 문제가 발생합니다.
흔한 실수 3: 예외 블록(예외 블록)의 매개변수를 잘못 지정했습니다.
다음 코드를 살펴보세요.
>>> try:
... l = ["a", "b"]
... int(l[2])
... Except ValueError, IndexError: # 두 예외를 모두 잡으려면, 그렇죠?
... pass
...
추적(가장 최근 호출 마지막):
파일 "
IndexError: 목록 인덱스가 범위를 벗어났습니다.
이 코드의 문제점은 제외 문이 이러한 방식으로 예외 지정을 지원하지 않는다는 것입니다. Python 2.x에서는 예외를 더 자세히 보려면 변수 e를 사용하여 예외를 선택적 두 번째 매개변수에 바인딩해야 합니다. 따라서 위 코드에서 Except 문은 IndexError 예외를 포착하지 않고 대신 IndexError라는 매개 변수에 발생하는 예외를 바인딩합니다.
예외 문에서 여러 예외를 올바르게 포착하려면 첫 번째 매개변수를 튜플로 지정한 다음 포착하려는 예외 유형을 튜플에 작성해야 합니다. 또한 이식성을 향상하려면 Python 2와 Python 3 모두에서 지원되는 as 키워드를 사용하세요.
>>> 시도:
... l = ["a", "b"]
... int(l[2])
... 제외(ValueError, IndexError) as e:
... pass
...
>>>
일반적인 실수 4: Python의 변수 이름 구문 분석에 대한 오해
Python 이름 확인의 변수 소위 LEGB 원칙을 따릅니다. 즉, "L: 로컬 범위; E: 상위 계층 구조의 def 또는 람다의 로컬 범위; G: 전역 범위; B: 내장 범위"(Local, Enclosing, Global , 내장), 순서대로 검색하세요. 간단해 보이지 않나요? 그러나 실제로 이 원칙이 적용되는 방식에는 몇 가지 특별한 기능이 있습니다. 이에 관해 다음과 같은 일반적인 Python 프로그래밍 오류를 언급해야 합니다. 다음 코드를 살펴보세요:
>>> x = 10
>> def foo():
...
...
> >> foo()
추적(가장 최근 호출 마지막):
파일 "
파일 "
UnboundLocalError: 할당 전에 참조된 지역 변수 'x'
무슨 문제가 있나요?
위 오류는 특정 범위의 변수에 값을 할당하면 해당 변수가 Python 인터프리터에 의해 자동으로 해당 범위의 로컬 변수로 간주되어 모든 상위 범위 변수를 대체하기 때문에 발생합니다. 같은 이름으로.
이 때문에 잘 시작된 코드가 나타나는데 함수 내부에 할당문을 추가하면 UnboundLocalError가 발생하여 많은 사람들이 놀라는 것은 당연합니다.
Python 프로그래머는 특히 목록 작업을 할 때 이러한 함정에 빠지기 쉽습니다.
다음 코드 예를 참조하세요.
>>> lst = [1, 2, 3]
>>> def foo1():
.. .lst.append(5) # 문제 없습니다
...
>>> foo1()
>>> ]
>>> lst = [1, 2, 3]
>>> def foo2():
... lst += [5] # ...하지만 여기에 문제가 있습니다!
...
>>> foo2()
추적(가장 최근 호출 마지막):
파일 "
파일 "
UnboundLocalError: 할당 전에 지역 변수 'lst'가 참조되었습니다
응? foo1 함수는 정상적으로 실행되는데 foo2에서는 오류가 발생하는 이유는 무엇입니까?
답은 이전 예와 동일하지만 좀 더 이해하기 어렵습니다. foo1 함수는 lst 변수에 값을 할당하지 않지만 foo2는 할당합니다. 우리는 lst += [5]가 lst = lst + [5]의 약어라는 것을 알고 있습니다. 이것으로부터 foo2 함수가 lst에 값을 할당하려고 한다는 것을 알 수 있습니다(따라서 로컬 변수로 간주됩니다). Python 인터프리터에 의한 함수 범위). 그러나 우리가 lst에 할당하려는 값은 lst 변수 자체를 기준으로 합니다(이때 함수의 로컬 범위에서도 변수로 간주됩니다). 이는 해당 변수가 아직 정의되지 않았음을 의미합니다. 그 때 오류가 발생했습니다.
일반적인 실수 5: 목록을 탐색하는 동안 목록 변경
다음 코드의 문제는 매우 분명합니다.
>>> 홀수 = 람다 x : bool(x % 2 )
>>> 숫자 = [n for n in range(10)]
>>> for i in range(len(numbers)):
... 홀수인 경우 ( 숫자[i]):
... del 숫자[i] # 나쁜: 반복하는 동안 목록에서 항목 삭제
...
추적(가장 최근 호출 마지막):
파일 "
IndexError: 목록 인덱스가 범위를 벗어났습니다
반복하는 동안 목록이나 배열에서 요소를 제거한 경험 경험이 풍부한 Python 개발자라면 알 수 있는 문제입니다. 그러나 위의 예는 분명하지만, 숙련된 개발자는 더 복잡한 코드를 작성할 때 실수로 동일한 실수를 저지를 가능성이 높습니다.
다행히도 Python 언어에는 올바르게 사용하면 코드를 크게 단순화할 수 있는 우아한 프로그래밍 패러다임이 많이 포함되어 있습니다. 코드를 단순화함으로써 얻을 수 있는 또 다른 장점은 목록을 순회할 때 요소를 삭제하는 오류가 발생할 가능성이 낮다는 것입니다. 이를 수행할 수 있는 프로그래밍 패러다임 중 하나는 목록 이해입니다. 게다가, 목록 이해는 이 문제를 피하는 데 특히 유용합니다. 위 코드의 기능을 다시 구현하기 위해 목록 이해를 사용해 보겠습니다.
>>> 홀수 = 람다 x : bool(x % 2)
> >> 숫자 = [n for n in range(10)]
>>> 숫자[:] = [홀수가 아닌 경우 n의 숫자(n)] # 아, 정말 멋지군요. all
>>> 숫자
[0, 2, 4, 6, 8]
일반적인 실수 6: Python이 클로저에서 변수를 바인딩하는 방법을 이해하지 못합니다
보세요. 다음 코드에서:
>>> def create_multipliers():
... create_multipliers()의 승수에 대해 [lambda x : i * x for i in range(5)]
>>>를 반환합니다.
... 승수(2)
를 인쇄합니다. ..
출력 결과는 다음과 같아야 한다고 생각할 수도 있습니다.
그러나 실제 출력 결과는 다음과 같습니다.
깜짝!
이 결과는 주로 Python의 후기 바인딩 메커니즘 때문에 발생합니다. 즉, 클로저의 변수 값은 내부 함수가 호출될 때만 쿼리됩니다. 따라서 위의 코드에서는 create_multipliers() 가 반환하는 함수가 호출될 때마다 변수 i 의 값을 가까운 스코프에서 조회하게 되며(그리고 이때 루프가 종료되므로 최종적으로 변수 i 가 할당됩니다) 값은 4)입니다.
이 일반적인 Python 문제를 해결하려면 몇 가지 해킹을 사용해야 합니다.
>>> def create_multipliers():
... return [lambda x, i=i : i * x for i in range(5)]
...
>>> create_multipliers()의 승수:
... print multiplier(2)
.. .
0
2
4
6
8
참고하세요! 여기서는 기본 매개변수를 사용하여 이 람다 익명 함수를 구현합니다. 어떤 사람은 그것이 우아하다고 생각할 수도 있고, 어떤 사람은 영리하다고 생각할 수도 있고, 또 다른 사람은 비웃을 수도 있습니다. 그러나 Python 프로그래머라면 상관없이 이 솔루션에 대해 알아야 합니다.
일반적인 실수 7: 모듈 간의 순환 종속성
아래와 같이 서로를 참조하는 두 개의 파일 a.py와 b.py가 있다고 가정합니다.
a. py 파일:
import b
def f():
return b.x
print f()
b.py 파일 코드:
import a
x = 1
def g():
print a.f()
먼저 a.py 모듈을 가져오려고 합니다.
코드가 잘 실행됩니다. 어쩌면 이것은 예상치 못한 일일 수도 있습니다. 결국 여기서는 순환 참조 문제가 발생하므로 문제가 있는 것이겠죠?
답은 단순히 순환 참조가 존재하는 것 자체로는 문제를 일으키지 않는다는 것입니다. 모듈이 이미 참조된 경우 Python은 해당 모듈이 다시 참조되는 것을 방지할 수 있습니다. 그러나 각 모듈이 잘못된 시간에 다른 모듈에서 정의한 함수나 변수에 액세스하려고 하면 문제가 발생할 수 있습니다.
다시 예로 돌아가서, a.py 모듈을 가져올 때 b.py 모듈을 참조할 때는 문제가 없습니다. 왜냐하면 Access를 참조할 때는 b.py 모듈이 필요하지 않기 때문입니다. a.py 모듈에 정의된 모든 변수 또는 함수. b.py 모듈에서 모듈 a에 대한 유일한 참조는 모듈 a의 foo() 함수에 대한 호출입니다. 그러나 해당 함수 호출은 g() 함수에서 발생하며 g() 함수는 a.py 또는 b.py 모듈에서 호출되지 않습니다. 따라서 아무런 문제가 발생하지 않습니다.
그러나 b.py 모듈을 가져오려고 하면(즉, 이전에 a.py 모듈을 참조하지 않고):
>>> import b
Traceback(최근 호출 마지막):
파일 "
가져오기 a
파일 "a.py", 6행,
print f()
파일 "a.py", 4행, f
return b.x
AttributeError : '모듈' 객체에는 'x' 속성이 없습니다.
앗. 상황이 좋지 않아요! 여기서 문제는 b.py를 가져오는 과정에서 a.py 모듈을 참조하려고 시도하고 a.py 모듈이 foo() 함수를 호출한 다음 b.x 변수에 액세스하려고 시도한다는 것입니다. 그런데 이때 b.x 변수가 정의되지 않아 AttributeError 예외가 발생했습니다.
이 문제를 해결하는 매우 간단한 방법이 있습니다. 즉, 간단히 b.py 모듈을 수정하고 g() 함수 내에서 a.py만 참조하는 것입니다.
x = 1
def g( ):
import a # 이것은 g()가 호출될 때만 평가됩니다.
print a.f()
이제 b.py 모듈을 가져오면 문제가 없습니다.
>>> import b
>>> b.g()
1 # 모듈 'a'가 마지막에 'print f()'를 호출한 이후 처음으로 인쇄되었습니다.
1 # 두 번째 인쇄된 것은 'g'에 대한 호출입니다.
일반적인 실수 8: 모듈 이름 지정이 Python 표준 라이브러리 모듈 이름과 충돌합니다
Python 언어의 가장 큰 장점 중 하나는 강력한 표준 라이브러리가 함께 제공됩니다. 그러나 이 때문에 주의를 기울이지 않으면 모듈에 Python 자체 표준 라이브러리 모듈과 동일한 이름을 지정할 수 있습니다(예를 들어 코드에 email.py라는 모듈이 있는 경우 충돌이 발생합니다). Python 표준 라이브러리에 있는 동일한 이름의 모듈로)
이는 어려운 문제를 일으킬 가능성이 높습니다. 예를 들어, 모듈 A를 가져올 때 모듈 A가 Python 표준 라이브러리에서 모듈 B를 참조하려고 시도하지만 이미 동일한 이름의 모듈 B가 있기 때문에 모듈 A는 대신 사용자 코드에서 모듈 B를 잘못 참조합니다. Python 표준 라이브러리의 모듈 B. 이는 심각한 실수의 원인이기도 합니다.
따라서 Python 프로그래머는 Python 표준 라이브러리 모듈과 동일한 이름을 사용하지 않도록 각별히 주의해야 합니다. 결국, 업스트림 모듈의 이름을 변경하고 제안이 통과되도록 PEP 제안을 제안하는 것보다 자신의 모듈 이름을 변경하는 것이 훨씬 쉽습니다.
일반적인 실수 9: Python 2와 Python 3의 차이점 해결 실패
다음 코드가 있다고 가정합니다.
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def bad():
e = None
try:
bar(int(sys.argv[1]))
제외 KeyError as e:
print('key error')
제외 ValueError as e:
print('value error')
print(e)
bad()
Python 2라면 코드는 정상적으로 실행됩니다.
$ python foo.py 1
key error
1
$ python foo.py 2
값 오류
2
하지만 이제 Python 3으로 전환하여 다시 실행해 보겠습니다.
$ python3 foo.py 1
키 오류
추적(가장 최근 호출 마지막):
파일 "foo.py", 라인 19, in
bad()
파일 "foo.py", 라인 17, in bad
print(e)
UnboundLocalError: 할당 전에 지역 변수 'e'가 참조되었습니다
대체 무슨 일이 일어나고 있는 걸까요? 여기서 "문제"는 Python 3에서 제외 블록의 범위 외부에서 예외 개체에 액세스할 수 없다는 것입니다. (이렇게 설계한 이유는 그렇지 않으면 가비지 수집기가 실행되고 참조가 메모리에서 지워질 때까지 해당 참조 루프가 스택 프레임에 남아 있기 때문입니다.)
이 문제를 피하는 방법 방법은 유지하는 것입니다. 예외 개체에 액세스할 수 있도록 코드 블록 제외의 범위 외부에 있는 예외 개체에 대한 참조입니다. 다음 코드는 이 메서드를 사용하므로 Python 2와 Python 3의 출력 결과는 일관됩니다.
import sys
def bar(i):
if i == 1:
raise KeyError(1 )
if i == 2:
raise ValueError(2)
def good():
예외 = 없음
try:
bar(int( sys.argv[1] ))
e:
예외 = e
print('key error')
제외 ValueError as e:
예외 = e
print('값 오류')
print(Exception)
good()
Python 3에서 코드를 실행합니다:
$ python3 foo.py 1
key error
1
$ python3 foo.py 2
값 오류
2
좋아요!
일반적인 실수 10: del 메소드의 잘못된 사용
mod.py 파일에 다음 코드를 작성했다고 가정해 보겠습니다.
import foo
class Bar(object):
. .
def __del__(self):
foo.cleanup(self.myhandle)
이후 another_mod.py 파일에서 다음 작업을 수행합니다.
import mod
mybar = mod.Bar()
another_mod.py 모듈을 실행하면 AttributeError 예외가 발생합니다.
왜요? 인터프리터가 종료되면 모듈의 전역 변수가 None으로 설정되기 때문입니다. 따라서 위의 예에서는 __del__ 메서드가 호출되기 전에 foo가 None으로 설정되었습니다.
이 다소 까다로운 Python 프로그래밍 문제를 해결하는 한 가지 방법은 atexit.register() 메서드를 사용하는 것입니다. 이 경우 프로그램이 실행을 완료하면(즉, 프로그램이 정상적으로 종료되면) 인터프리터가 닫히기 전에 지정한 핸들러가 실행됩니다.
위 방법을 적용한 후 수정된 mod.py 파일은 다음과 같습니다.
import foo
import atexit
def cleanup(handle):
foo .cleanup(handle )
class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)
이 구현은 다음을 지원합니다. 정상적인 프로그램 종료 시 필요한 정리 기능을 완전히 호출합니다. 분명히 위의 예에서 foo.cleanup 함수는 self.myhandle에 바인딩된 객체를 처리하는 방법을 결정합니다.
개요
Python은 작업 효율성을 크게 향상시킬 수 있는 다양한 프로그래밍 메커니즘과 패러다임을 제공하는 강력하고 유연한 프로그래밍 언어입니다. 그러나 다른 소프트웨어 도구나 언어와 마찬가지로 언어 기능에 대한 이해나 평가가 제한되어 있으면 때로는 이익을 얻기는커녕 방해를 받을 수도 있습니다. 속담에 따르면, “충분히 안다고 생각하는 것은 자신이나 다른 사람을 위험에 빠뜨릴 수 있습니다.
Python 언어의 일부 미묘함, 특히 이 기사에서 언급한 상위 10가지 실수가 도움이 될 것입니다. 언어를 효과적으로 사용하고 흔히 발생하는 실수를 피하세요