代码之家  ›  专栏  ›  技术社区  ›  wilhelmtell

如何正确清理Python对象?

  •  399
  • wilhelmtell  · 技术社区  · 15 年前
    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__() 被调用。如果是这种情况,这就是异常的原因,那么如何确保对象正确销毁?

    9 回复  |  直到 6 年前
        1
  •  533
  •   Victor Silva    9 年前

    我建议使用python的 with 用于管理需要清理的资源的语句。使用显式 close() 声明是你必须担心人们忘记打电话或忘记把它放在 finally 阻止以防止发生异常时发生资源泄漏。

    使用 具有 语句,使用以下方法创建类:

      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类型的实例(它是 __enter__ 方法)。它的 __exit__ 无论是否发生异常,都将自动调用方法。

    您甚至可以进一步采用这种方法。在上面的示例中,仍有人可以使用其构造函数实例化包,而不使用 具有 条款。你不想这样。您可以通过创建定义 三叶草 第二代 方法。然后,包类将严格定义在 三叶草 方法并返回。这样,调用方就永远无法在不使用 具有 声明:

    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
    
        2
  •  26
  •   Community Neeleshkumar S    7 年前

    作为附录 Clint's answer ,您可以简化 PackageResource 使用 contextlib.contextmanager :

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

    或者,尽管可能不是蟒蛇,但你可以覆盖 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
    
        3
  •  25
  •   ostrokach    8 年前

    标准的方法是使用 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 直到python终止。

    使用上述代码演示另存为 包装袋 :

    $ 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...
    
        4
  •  16
  •   Virgil Dupras    15 年前

    我不认为有可能像以前那样把成员移除 __del__ 被称为。我的猜测是,您的特定属性错误的原因是在其他地方(可能您在其他地方错误地删除self.file)。

    但是,正如其他人指出的,您应该避免使用 埃尔德勒斯 . 其主要原因是 埃尔德勒斯 不会被垃圾收集(只有当refcount达到0时才会释放它们)。因此,如果您的实例涉及循环引用,那么它们将在应用程序运行期间一直存在于内存中。(不过,我可能对这一切都弄错了,我必须再次阅读GC文档,但我确信它是这样工作的)。

        5
  •  11
  •   mfitzp    8 年前

    我想问题可能出在 __init__ 如果代码比显示的多?

    __del__ 即使在 爱因斯坦 未正确执行或引发异常。

    Source

        6
  •  7
  •   SCGH Eser Aygün    7 年前
        7
  •  6
  •   Unknown    15 年前

    只需用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
    

    它会将文件列表填充到 德尔 保证在调用时存在的函数。weakref代理是为了防止python或您自己以某种方式删除self.files变量(如果删除了该变量,则不会影响原始文件列表)。如果没有这种情况,即使对变量有更多的引用,也要删除它,那么您可以删除代理封装。

        8
  •  4
  •   Bastien Léonard    15 年前

    似乎惯用的方法是提供 close() 方法(或类似方法),并明确地调用它。

        9
  •  2
  •   user2394284    6 年前

    这里是一个最小的工作框架:

    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 (部分) Clint Miller's correct answer )你会盯着这胡说八道:

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

    我为此花了半天时间。希望它能帮助下一个人。