9.1 모듈의 순환 의존성 생성
가령, a.py
와 b.py
라는 두 파일이 있고, 각 파일은 다음과 같이 서로를 import한다고 하자:
a.py
에서:
import b
def f():
return b.x
print(f())
b.py
에서:
import a
x = 1
def g():
print(a.f())
먼저 a.py
를 import해보자:
import a
# 1
잘 작동했어. 놀라지 않았어? 결국, 모듈들이 순환적으로 import하니까 문제가 있어야 할 것 같은데 그렇지 않니?
답은, 단순히 순환 import 존재 자체는 Python에서 문제가 되지 않아. 모듈이 이미 import되었다면, Python은 굳이 다시 import하려 하지 않아. 하지만, 각 모듈이 다른 모듈에서 정의된 함수나 변수에 접근하려는 시점에 따라 문제에 직면할 수도 있어.
우리 예시로 돌아가보면, a.py
를 import했을 때, b.py
를 import하는 데 문제가 없었어, 왜냐면 b.py
가 import될 때 a.py
에서 정의된 어떤 것도 필요하지 않았거든. b.py
에서 a
에 대한 유일한 참조는 a.f()
호출이야. 하지만 이 호출은 g()
안에 있고, a.py
나 b.py
에서 g()
를 호출하는 것은 없어. 그래서 모두 잘 작동하는 거야.
하지만 우리가 b.py
를 import하려고 하면 (즉, a.py
를 먼저 import하지 않고), 어떤 일이 일어날까?
import b
Traceback (most recent call last):
File "<stdin>", line 1, in
File "b.py", line 1, in
import a
File "a.py", line 6, in
print(f())
File "a.py", line 4, in f
return b.x
AttributeError: 'module' object has no attribute 'x'
문제는 b.py
를 import하는 과정에서 a.py
를 import하려 하는데, a.py
는 f()
를 호출하고, b.x
에 접근하려 해. 하지만 b.x
는 아직 정의되지 않았어. 그래서 AttributeError
예외가 발생하는 거야.
이 문제의 하나의 해결책은 간단해. b.py
에서 a.py
를 g()
안에서 import하도록 바꾸면 돼:
x = 1
def g():
import a # 이렇게 하면 g()가 호출될 때만 평가될 거야
print(a.f())
이제 우리가 import하면 모든 게 정상 작동해:
import b
b.g()
# 1 모듈 'a'가 마지막에 'print(f())'를 호출해서 먼저 출력됨
# 1 우리가 'g()'를 호출해서 두 번째로 출력됨
9.2 Python 표준 라이브러리 모듈과 이름 충돌
Python의 장점 중 하나는 '박스에서 바로' 사용할 수 있는 많은 모듈이 있다는 거야. 그런데 결과적으로, 신경 쓰지 않으면, 프로젝트 모듈 이름이 Python 표준 라이브러리의 모듈 이름과 충돌할 수 있어 (예를 들어, email.py
라는 모듈이 있을 수 있는데, 이게 표준 라이브러리 모듈 이름과 같아서 충돌이 발생할 수 있어).
이런 충돌은 심각한 문제를 일으킬 수 있어. 예를 들어, 어떤 모듈이 Python 표준 라이브러리 모듈을 import하려고 할 때, 프로젝트에 같은 이름의 모듈이 있으면 잘못해서 프로젝트 모듈을 import하게 될 거야.
그래서 Python 표준 라이브러리 모듈과 같은 이름을 사용하지 않도록 주의해야 해. 프로젝트 모듈 이름을 변경하는 게 표준 라이브러리 모듈 이름을 변경 요청하고 승인 기다리는 것보다 훨씬 쉬워.
9.3 예외 가시성
다음 main.py
파일을 봐봐:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def bad():
e = None
try:
bar(int("1"))
except KeyError as e:
print('key error')
except ValueError as e:
print('value error')
print(e)
bad()
코드는 괜찮아 보이는데, 어떤 출력이 나올지 한번 보자:
$ python main.py 1
Traceback (most recent call last):
File "C:\Projects\Python\TinderBolt\main.py", line 19, in <module>
bad()
File "C:\Projects\Python\TinderBolt\main.py", line 17, in bad
print(e)
^
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value
여기서 무슨 일이 일어난 거지? 문제는 Python에서 예외 블록 내의 객체는 그 블록 바깥에서는 접근할 수 없다는 거야. (그 이유는, 그렇지 않으면 그 블록의 객체들이 가비지 컬렉터가 이를 제거할 때까지 메모리에 남게 될 테니까.)
이 문제를 피하는 한 가지 방법은 이 예외 블록 객체에 대한 참조를 블록 밖에 저장하여, 계속 접근 가능하도록 하는 거야. 이전 예제를 이 기법을 사용해 작동하도록 만든 버전이 여기 있어:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def good():
exception = None
try:
bar(int("1"))
except KeyError as e:
exception = e
print('key error')
except ValueError as e:
exception = e
print('value error')
print(exception)
good()
9.4 __del__
메서드의 잘못된 사용
인터프리터가 객체를 삭제할 때, 이 객체에 __del__
함수가 있는지 확인하여, 있다면 객체를 삭제하기 전에 이를 호출해. 외부 리소스나 캐시를 정리하고 싶을 때 매우 유용해.
가령, mod.py
에 다음과 같은 코드가 있다고 하자:
import foo
class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)
그리고 다른 파일 another_mod.py
에서는 이렇게 시도한다고 해봐:
import mod
mybar = mod.Bar()
그러면 끔찍한 AttributeError
가 발생할 거야.
왜일까? 여기에서 설명한 것처럼, 인터프리터가 종료되면 모든 글로벌 모듈 변수가 None
이 돼. 그래서 위 예제에서는, __del__
호출 시점에 foo
이름이 이미 None
으로 설정된 거야.
이 '별표 문제'의 해결책은 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)
이러한 구현은 프로그램이 정상적으로 종료된 후에 필요한 정리를 간단하고 확실하게 호출할 수 있는 방법을 제공해. self.myhandle
과 연결된 객체를 어떻게 처리할지는 foo.cleanup
에 맡겨져 있지만, 아이디어는 이해했으리라 생각해.
GO TO FULL VERSION