2009-05-14 4 views
340
class Package: 
    def __init__(self): 
     self.files = [] 

    # ... 

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

__del__(self)은 AttributeError 예외와 함께 실패합니다. 내가 Python doesn't guarantee__del__()가 호출 될 때 "전역 변수"(이 컨텍스트의 멤버 데이터)의 존재를 이해합니다. 이것이 사실인데 이것이 이것이 예외의 이유라면, 어떻게 객체가 제대로 파괴되는지 확인해야합니까?어떻게 파이썬 객체를 올바르게 정리합니까?

+3

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

+3

예외는 프로그램이 종료되기 전에 오래 버려집니다. AttributeError 예외는 Python이 self.files를 Package의 속성으로 인식하지 않는다는 것을 나타냅니다. 내가 잘못 될 수 있습니다. 그러나 "전역 변수"에 의해 변수에 대한 전역 변수가 메서드에 대해 (그러나 클래스에 로컬 일 수도 있음) 의미하지 않는다면이 예외의 원인을 모릅니다. Google 힌트 Python은 __del __ (self)가 호출되기 전에 구성원 데이터를 정리할 권한을 보유합니다. – wilhelmtell

+1

게시 된 코드가 나를 위해 작동하는 것 같습니다 (Python 2.5). 오류가있는 실제 코드를 게시 할 수 있습니까? 아니면 단순화 된 것입니까? (오류가 더 좋은 버전 일수록 더 간단합니다.) – Silverfish

답변

468

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

는 다음과 같은 방법으로 클래스를 만듭니다 with 문을 사용하려면 : 당신이 그런

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) 

사용하십시오, 위의 예에서

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

를, 누군가가 당신의 클래스를 사용하고자 할 때 그들은 다음을 수행합니다.

with Package() as package_obj: 
    # use package_obj 

변수 package_obj는 Package 유형의 인스턴스 (__enter__ 메소드에서 반환 된 값)입니다. 예외가 발생하는지 여부에 관계없이 해당 __exit__ 메서드가 자동으로 호출됩니다.

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

with PackageResource() as package_obj: 
    # use package_obj 
+25

기술적으로 말하자면, PackageResource() .__ enter __()를 명시 적으로 호출하여 완성되지 않는 패키지를 만들 수는 있지만 실제로 코드를 해독해야합니다. 아마도 걱정할 것이 없습니다. –

+3

그런데 파이썬 2.5를 사용한다면 with 문을 사용할 수 있으려면 __future__ import with_statement 에서 을 수행해야합니다. –

+2

__del __()이 컨텍스트 관리자 솔루션을 사용하는 방식대로 작동하는 이유를 보여주는 데 도움이되는 기사를 찾았습니다. http://www.andy-pearce.com/blog/posts/2013/Apr/python- 소멸자 - 단점/ – eikonomega

6

try/except 문으로 소멸자를 래핑하면 전역 변수가 이미 삭제 된 경우 예외가 throw되지 않습니다.

편집

이 시도 :

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 

이 호출시 존재하는 보장 된 기능의 파일 목록을 채울 것입니다. weakref 프록시는 파이썬을 막거나 스스로 self.files 변수를 삭제하지 못하게하는 것입니다 (삭제 된 경우 원래 파일 목록에는 영향을주지 않습니다). 변수에 대한 참조가 더 많아도 삭제되지 않는 경우 프록시 캡슐화를 제거 할 수 있습니다.

+2

문제는 회원 데이터가 없어지면 너무 늦었다는 것입니다. 그 데이터가 필요해. 위의 코드를 참고하십시오. 제거 할 파일을 알기 위해서는 파일 이름이 필요합니다. 내 코드를 간소화했지만 직접 정리해야하는 다른 데이터가 있습니다 (즉, 통역사는 청소 방법을 알 수 없습니다). – wilhelmtell

+0

이 솔루션은 필자의 경우 매우 훌륭하게 작동했다. – gaborous

4

이 작업을 수행하는 관용적 인 방법은 close() 방법 (또는 이와 유사한 방법)을 제공하고 명시 적으로 호출하는 것 같습니다.

+18

이것은 이전에 사용한 방법 이었지만 다른 문제가 발생했습니다. 다른 라이브러리에 의해 예외가 던져지기 때문에 오류가 나는 경우 엉망을 제거하는 데 Python의 도움이 필요합니다. 특히, 나를 위해 소멸자를 호출하는 Python이 필요합니다. 그렇지 않으면 코드가 빠르게 관리하기 어려워지기 때문에 .close() 호출이 있어야하는 종료 점을 잊어 버릴 것입니다. – wilhelmtell

16

나는 인스턴스 멤버 가능하다고 생각하지 않는다 다음과 같이

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 문을 사용하지 않고 패키지의 클래스를 인스턴스화 수 없었다 결코 __del__이 호출되기 전에 제거되어야합니다. 내 생각 엔 특정 AttributeError에 대한 이유가 어딘가에있다. 어쩌면 실수로 self.file을 다른 곳에서 삭제할 수도있다.

그러나 다른 사람들이 지적했듯이 __del__은 피해야합니다. 주된 이유는 __del__의 인스턴스가 가비지 수집되지 않기 때문입니다 (refcount가 0에 도달 할 때만 해제됩니다). 따라서 인스턴스가 순환 참조에 포함되어 있으면 응용 프로그램이 실행되는 동안 메모리에 살게됩니다. (나는이 모든 것에 대해 잘못 생각할 수도 있지만, 나는 gc 문서를 다시 읽어야 할 것이다. 그러나 나는 오히려 그것이 이렇게 작동한다는 것을 확신한다.)

+4

'__del__'을 가진 객체는 '__del__'을 가진 다른 객체의 참조 카운트가 0이고 도달 할 수없는 경우 가비지 수집 될 수 있습니다. 즉, '__del__'이 (가)있는 개체 사이에 참조주기가 있으면 그 중 하나도 수집되지 않습니다. 그러나 다른 모든 경우는 예상대로 해결해야합니다. – Collin

10

표시된 것보다 더 많은 코드가있을 경우 문제가 __init__에있을 수 있다고 생각하십니까?

__del____init__이 제대로 실행되지 않았거나 예외가 발생한 경우에도 호출됩니다.

Source

+2

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

21

Clint's answer의 부록, 당신은 contextlib.contextmanager 사용 PackageResource를 단순화 할 수 있습니다으로 : 아마 파이썬, 당신은 Package.__new__ 대체 할 수 있지만, 다른 방법으로

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

을 :

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 이름을 지정하고 그 __new__ 간단한

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

with contextlib.closing(Package(...))를 통해 수정되지 않은 Package 클래스를 사용하거나 오버라이드 (override) 할 수 있습니다이 경우 contextlib.closing을, 사용이 생성자는 상속하려면 , 그래서 당신은 단순히 상속받을 수 있습니다.

# 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의 생성 된 모든 인스턴스를 유지 것이라는 점을 명심해야합니다

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

** 이것은 대단합니다. ** 나는 특히 마지막 예를 좋아합니다. 그러나'Package .__ new __()'메소드의 네 줄짜리 상용구를 피할 수 없다는 것은 유감 스럽다. 아마도 우리는 할 수있을 것입니다. _ 우리는 아마도 클래스 데코레이터 또는 메타 클래스 일반화를 정의 할 수 있습니다. Pythonic 생각을위한 음식. –

+0

@CecilCurry 감사합니다. 좋은 지적입니다. 'Package'로부터 상속받은 클래스도이 작업을 수행해야합니다 (아직 테스트하지는 않았지만). 따라서 메타 클래스가 필요하지 않습니다. 나는 과거에 메타 클래스를 사용하는 호기심이 많은 방법을 찾았지만 ... –

+0

@CecilCurry 사실, 생성자는 상속되므로 클래스 부모 클래스 대신에'Package' (또는'Closing' 클래스)를 사용할 수 있습니다. '개체'. 그러나 다중 상속이 이것을 어떻게 망쳐 놓았는지 나에게 묻지 말아라. –

6

표준 방법은 atexit.register을 사용하는 것입니다. 위의 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... 
3

더 나은 대안은 weakref.finalize를 사용하는 것입니다. Finalizer ObjectsComparing finalizers with __del__() methods의 예를 참조하십시오.

+0

오늘이 도구를 사용하면 다른 솔루션보다 완벽하게 작동한다. 시리얼 포트를 연 멀티 프로세싱 기반의 커뮤니케이터 클래스가 있고 포트를 닫고 프로세스를'join()'하는'stop()'메소드가 있습니다. 그러나 프로그램이 예기치 않게 종료되면 'stop()'이 호출되지 않습니다. 나는 finalizer를 사용하여이를 해결했습니다. 하지만 어쨌든 stop 메소드에서'_finalizer.detach()'를 호출하여 두 번 호출하는 것을 막습니다 (수동으로 그리고 나중에 파이널 라이저에 의해 다시 호출 됨). –

관련 문제