CodeGym /행동 /Python SELF KO /표준 오류, part 3

표준 오류, part 3

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

9.1 모듈의 순환 의존성 생성

가령, a.pyb.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.pyb.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.pyf()를 호출하고, b.x에 접근하려 해. 하지만 b.x는 아직 정의되지 않았어. 그래서 AttributeError 예외가 발생하는 거야.

이 문제의 하나의 해결책은 간단해. b.py에서 a.pyg() 안에서 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에 맡겨져 있지만, 아이디어는 이해했으리라 생각해.

1
Опрос
이터레이터,  20 уровень,  3 лекция
недоступен
이터레이터
이터레이터
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION