파이썬 객체를 올바르게 정리하려면 어떻게합니까?


462
class Package:
    def __init__(self):
        self.files = []

    # ...

    def __del__(self):
        for file in self.files:
            os.unlink(file)

__del__(self)위의 AttributeError 예외로 실패합니다. 이해 파이썬은 보장하지 않습니다 때 "전역 변수"(이 맥락에서 구성원 데이터?)의 존재를 __del__()호출됩니다. 그 경우이고 이것이 예외의 원인 인 경우, 객체가 올바르게 파괴되도록하려면 어떻게해야합니까?


3
링크 된 것을 읽으면 전역 변수가 사라지는 것은 프로그램이 종료 될 때 이야기하지 않는 한 여기에 적용되지 않는 것 같습니다.이 동안 링크 된 내용에 따라 os 모듈 자체가 이미 사라 졌을 가능성이 있습니다. 그렇지 않으면 __del __ () 메서드의 멤버 변수에 적용되지 않는다고 생각합니다.
Kevin Anderson

3
내 프로그램이 종료되기 오래 전에 예외가 발생합니다. 내가 얻는 AttributeError 예외는 파이썬이 self.files를 Package의 속성으로 인식하지 못한다고 말합니다. 이 오류가 발생할 수 있지만 "글로벌"에 의해 전역 변수가 메서드에 전역 변수를 의미하지는 않지만 (클래스에 로컬 일 가능성이 있음)이 예외의 원인을 알 수 없습니다. 구글은 파이썬이 __del __ (self)가 호출되기 전에 멤버 데이터를 정리할 권리를 보유하고 있다고 암시합니다.
wilhelmtell

1
게시 된 코드는 Python 2.5와 함께 작동합니다. 실패한 실제 코드를 게시 할 수 있습니까? 아니면 오류를 일으키는 더 간단한 버전 일수록 간단합니까?
Silverfish

@ wilhelmtell 좀 더 구체적인 예를 들어 줄 수 있습니까? 내 모든 테스트에서 소멸자는 완벽하게 작동합니다.
알 수 없음

7
누구나 알고 싶은 경우 : 이 도움말__del__ 은의 대응으로 사용해서는 안되는 이유 를 자세히 설명 합니다 __init__. (즉, __init__생성자 라는 의미에서 "파괴자"가 아닙니다 .
franklin

답변:


619

with정리해야 할 리소스를 관리하기 위해 Python의 문장을 사용하는 것이 좋습니다 . 명시 적 close()문 을 사용할 때의 문제점 finally은 예외가 발생했을 때 리소스 누수를 방지하기 위해 호출을 잊어 버리거나 블록에 배치하는 것을 잊어 버린 사람들에 대해 걱정해야한다는 것 입니다.

with명령문 을 사용하려면 다음 메소드를 사용하여 클래스를 작성하십시오.

  def __enter__(self)
  def __exit__(self, exc_type, exc_value, traceback)

위의 예에서는

class Package:
    def __init__(self):
        self.files = []

    def __enter__(self):
        return self

    # ...

    def __exit__(self, exc_type, exc_value, traceback):
        for file in self.files:
            os.unlink(file)

그런 다음 누군가 수업을 사용하고 싶을 때 다음을 수행합니다.

with Package() as package_obj:
    # use package_obj

변수 package_obj는 패키지 유형의 인스턴스입니다 ( __enter__메소드가 반환 한 값 ). 그 __exit__방법에 관계없이 자동으로 예외가 발생 여부에 호출됩니다.

이 접근법을 한 단계 더 발전시킬 수도 있습니다. 위의 예에서 누군가는 with절 을 사용하지 않고 생성자를 사용하여 패키지를 인스턴스화 할 수 있습니다 . 당신은 그런 일이 일어나기를 원하지 않습니다. __enter____exit__메소드 를 정의하는 PackageResource 클래스를 작성하여이 문제를 해결할 수 있습니다 . 그런 다음 Package 클래스는 __enter__메소드 내부에 엄격하게 정의 되어 리턴됩니다. 이렇게하면 호출자는 with명령문 을 사용하지 않고 Package 클래스를 인스턴스화 할 수 없습니다 .

class PackageResource:
    def __enter__(self):
        class Package:
            ...
        self.package_obj = Package()
        return self.package_obj

    def __exit__(self, exc_type, exc_value, traceback):
        self.package_obj.cleanup()

다음과 같이 사용하십시오.

with PackageResource() as package_obj:
    # use package_obj

35
기술적으로 말하자면 PackageResource () .__ enter __ ()를 명시 적으로 호출하여 절대로 끝나지 않을 패키지를 만들 수는 있지만 실제로 코드를 깨려고 시도해야합니다. 아마도 걱정할 것이 없습니다.
David Z

3
그런데 Python 2.5 를 사용하는 경우 with 문을 사용할 수 있으려면 향후 import with_statement 에서 수행해야 합니다.
클린트 밀러

2
__del __ ()이 수행하는 방식을 설명하고 컨텍스트 관리자 솔루션을 사용하여 신뢰를주는 이유를 보여주는 기사를 찾았습니다. andy-pearce.com/blog/posts/2013/Apr/python-destructor-drawbacks
eikonomega

2
매개 변수를 전달하려면 멋지고 깨끗한 구성을 사용하는 방법은 무엇입니까? 나는 할 수 있기를 with Resource(param1, param2) as r: # ...
원합니다

4
@ snooze92 * Resource에 * args와 ** kwargs를 저장하는 __init__ 메소드를 Resource에 제공 한 다음 enter 메소드의 내부 클래스로 전달합니다. 문에 사용하는 경우, __init__는 __enter__ 전에 호출됩니다
브라이언 SCHLENKER

48

표준 방법은 다음을 사용하는 것입니다 atexit.register.

# package.py
import atexit
import os

class Package:
    def __init__(self):
        self.files = []
        atexit.register(self.cleanup)

    def cleanup(self):
        print("Running cleanup...")
        for file in self.files:
            print("Unlinking file: {}".format(file))
            # os.unlink(file)

그러나 이것은 Package파이썬이 종료 될 때까지 생성 된 모든 인스턴스를 유지한다는 것을 명심해야 합니다.

package.py로 저장된 위의 코드를 사용한 데모 :

$ python
>>> from package import *
>>> p = Package()
>>> q = Package()
>>> q.files = ['a', 'b', 'c']
>>> quit()
Running cleanup...
Unlinking file: a
Unlinking file: b
Unlinking file: c
Running cleanup...

2
atexit.register 접근 방식의 좋은 점은 클래스 사용자가 무엇을 할 것인지 걱정할 필요가 없다는 것입니다 (사용 with했습니까? 명시 적으로 호출 __enter__했습니까?) 파이썬 전에 정리가 필요하다면 단점은 물론입니다 종료되면 작동하지 않습니다. 필자의 경우 객체가 범위를 벗어날 때인 지 또는 파이썬이 종료 할 때까지 있지 않은지 상관하지 않습니다. :)
hlongmore

enter와 exit를 사용하고 추가 할 수 있습니까 atexit.register(self.__exit__)?
myradio

@ myradio 어떻게 유용한 지 모르겠습니까? 내부의 모든 정리 논리를 수행 __exit__하고 컨텍스트 관리자를 사용할 수 없습니까? 또한 __exit__추가 인수 (예 __exit__(self, type, value, traceback):)를 취 하므로 해당 인수에 대해 인정해야합니다. 어느 쪽이든, 유스 케이스가 비정상적으로 보이기 때문에 SO에 별도의 질문을 게시 해야하는 것처럼 들립니다.
ostrokach

33

Clint의 답변에 대한 부록으로 다음을 사용 PackageResource하여 단순화 할 수 있습니다 contextlib.contextmanager.

@contextlib.contextmanager
def packageResource():
    class Package:
        ...
    package = Package()
    yield package
    package.cleanup()

또는 아마도 Pythonic은 아니지만 다음을 재정의 할 수 있습니다 Package.__new__.

class Package(object):
    def __new__(cls, *args, **kwargs):
        @contextlib.contextmanager
        def packageResource():
            # adapt arguments if superclass takes some!
            package = super(Package, cls).__new__(cls)
            package.__init__(*args, **kwargs)
            yield package
            package.cleanup()

    def __init__(self, *args, **kwargs):
        ...

간단하게 사용하십시오 with Package(...) as package.

작업 시간을 줄이려면 정리 함수의 이름을 지정하고을 close사용하십시오 contextlib.closing.이 경우 수정되지 않은 Package클래스를 사용 with contextlib.closing(Package(...))하거나 __new__더 간단한 클래스로 재정의 할 수 있습니다

class Package(object):
    def __new__(cls, *args, **kwargs):
        package = super(Package, cls).__new__(cls)
        package.__init__(*args, **kwargs)
        return contextlib.closing(package)

그리고이 생성자는 상속되기 때문에 간단하게 상속 할 수 있습니다.

class SubPackage(Package):
    def close(self):
        pass

1
대단해. 나는 특히 마지막 예를 좋아합니다. Package.__new__()그러나이 방법 의 4 줄 상용구를 피할 수 없다는 것은 불행한 일입니다 . 아니면 가능할 수도 있습니다. 아마도 보일러 플레이트를 일반화하는 클래스 데코레이터 또는 메타 클래스를 정의 할 수있을 것입니다. 파이썬적인 생각을위한 음식.
Cecil Curry

@CecilCurry 감사합니다. 좋은 지적입니다. 에서 상속받은 모든 클래스 Package도이 작업을 수행해야하지만 (아직 테스트하지는 않았지만) 메타 클래스는 필요하지 않습니다. 나는 비록 과거에 메타 클래스를 사용하는 몇 가지 매우 흥미로운 방법 ... 발견
토비아스 Kienzler을

@CecilCurry 실제로 생성자는 상속되므로 클래스 대신 부모 클래스로 Package(또는 더 나은 클래스를 Closing) 사용할 수 있습니다 object. 그러나 다중 상속이 어떻게이 문제를 해결하는지 묻지 마십시오.
Tobias Kienzler

17

인스턴스 멤버 __del__가 호출 되기 전에 제거 될 수 있다고 생각하지 않습니다 . 내 추측은 특정 AttributeError에 대한 이유가 다른 곳 (아마도 self.file을 다른 곳에서 제거 할 수 있음) 일 것입니다.

그러나 다른 사람들이 지적했듯이을 사용하지 않아야합니다 __del__. 이것의 주된 이유는 __del__가비지 수집되지 않은 인스턴스입니다 (참조 횟수가 0에 도달 할 때만 해제 됨). 따라서 인스턴스가 순환 참조에 관련된 경우 응용 프로그램이 실행되는 한 메모리에 존재합니다. (하지만이 모든 것에 대해 잘못 생각할 수 있습니다 .gc 문서를 다시 읽어야하지만 오히려 다음과 같이 작동한다고 확신합니다).


5
개체를 __del__다른 개체로부터 자신의 참조 카운트는 경우 가비지 수집 될 수 __del__0이고 그들이 연결할 수있다. 이는을 사용하여 객체간에 참조주기가있는 경우 해당주기 __del__가 수집되지 않음을 의미합니다 . 그러나 다른 모든 경우는 예상대로 해결해야합니다.
Collin

"Python 3.4부터 __del __ () 메소드는 더 이상 참조주기가 가비지 수집되는 것을 막지 않으며 인터프리터 종료 중에 더 이상 모듈 전역이 None으로 강제 설정되지 않습니다. 따라서이 코드는 CPython에서 아무런 문제없이 작동합니다." - docs.python.org/3.6/library/...
토마스 Gandor

14

더 나은 대안은 weakref.finalize 를 사용하는 입니다. Finalizer 개체__del __ () 메서드를 사용한 종료 자 비교 의 예제를 참조하십시오 .


1
오늘날 이것을 사용했으며 다른 솔루션보다 완벽하게 작동합니다. 직렬 포트를 여는 다중 처리 기반 커뮤니케이터 클래스가 있고 stop()포트와 join()프로세스 를 닫는 방법 이 있습니다. 그러나 프로그램이 예기치 않게 종료되면 stop()호출되지 않습니다. 파이널 라이저로 해결했습니다. 그러나 어쨌든 나는 _finalizer.detach()stop 메소드를 호출하여 두 번 호출하지 않도록합니다 (마지막으로 나중에 수동으로 다시 호출).
Bojan P.

3
IMO, 이것이 가장 좋은 대답입니다. 가비지 콜렉션에서 정리할 수있는 가능성과 종료시 정리할 수있는 가능성을 결합합니다. 경고 python 2.7에는 weakref.finalize가 없습니다.
hlongmore

12

__init__표시된 것보다 더 많은 코드가 있으면 문제가 발생할 수 있다고 생각 합니까?

__del____init__제대로 실행되지 않았거나 예외를 던진 경우에도 호출됩니다 .

출처


2
가능성이 높습니다. 사용할 때이 문제를 피하는 가장 좋은 방법 __del__은 클래스 수준에서 모든 멤버를 명시 적으로 선언 하여 __init__실패 하더라도 항상 존재하는지 확인하는 것 입니다. 주어진 예에서, files = ()대부분 당신은 단지 할당 할 것이지만 작동합니다 None. 두 경우 모두에 실제 값을 할당해야합니다 __init__.
Søren Løvborg

11

최소한의 작동 골격이 있습니다.

class SkeletonFixture:

    def __init__(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def method(self):
        pass


with SkeletonFixture() as fixture:
    fixture.method()

중요 : 자기 반품


당신이 나와 같고 return self( 클린트 밀러의 정답 중 ) 부분을 간과한다면 , 당신은이 말도 안될 것입니다.

Traceback (most recent call last):
  File "tests/simplestpossible.py", line 17, in <module>                                                                                                                                                          
    fixture.method()                                                                                                                                                                                              
AttributeError: 'NoneType' object has no attribute 'method'

그것이 다음 사람에게 도움이되기를 바랍니다.


8

try / except 문으로 소멸자를 감싸면 전역이 이미 처리 된 경우 예외가 발생하지 않습니다.

편집하다

이 시도:

from weakref import proxy

class MyList(list): pass

class Package:
    def __init__(self):
        self.__del__.im_func.files = MyList([1,2,3,4])
        self.files = proxy(self.__del__.im_func.files)

    def __del__(self):
        print self.__del__.im_func.files

호출시 존재하도록 보장 된 del 함수 에 파일 목록을 채 웁니다 . weakref 프록시는 파이썬이나 자신이 self.files 변수를 어떻게 든 삭제하지 못하게하는 것입니다 (삭제 된 경우 원래 파일 목록에 영향을 미치지 않음). 변수에 대한 참조가 더 있어도 삭제되지 않는 경우 프록시 캡슐화를 제거 할 수 있습니다.


2
문제는 회원 데이터가 사라지면 너무 늦다는 것입니다. 그 데이터가 필요합니다. 위의 코드를 참조하십시오 : 제거 할 파일을 알기 위해서는 파일 이름이 필요합니다. 코드를 단순화했지만 직접 정리해야하는 다른 데이터가 있습니다 (예 : 통역사가 정리 방법을 알지 못함).
wilhelmtell

4

이것을하는 관용적 인 방법은 close()메소드 (또는 유사한) 를 제공 하고 명시 적으로 호출하는 것 같습니다.


20
이것이 내가 전에 사용한 접근법이지만 다른 문제가 발생했습니다. 다른 라이브러리에서 예외가 발생하면 오류가 발생했을 때 혼란을 없애는 데 Python의 도움이 필요합니다. 구체적으로 말하면 파이썬이 소멸자를 호출해야합니다. 그렇지 않으면 코드를 빠르게 관리 할 수 ​​없어지기 때문에 .close ()를 호출 해야하는 종료 지점을 잊어 버릴 것입니다.
wilhelmtell
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.