有没有一种Pythonic的方法,在实例超出作用域时执行一些代码?
Python中有一种更pythonic的方式来管理资源的清理工作,即使用with
语句。使用显式的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
将是一个类型为Package的实例(它是__enter__
方法返回的值)。无论是否发生异常,都将自动调用它的__exit__
方法。
甚至可以更进一步。在上面的示例中,某人仍然可以使用构造函数实例化Package而不使用with
语句。你可以通过创建一个PackageResource类来解决这个问题,该类定义了__enter__
和__exit__
方法。然后,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
严格来说,有人可能会显式地调用PackageResource().__enter__()
,从而创建一个永远不会被销毁的Package... 但他们真的必须试图破坏代码。可能不值得担心。
顺便说一句,如果使用Python 2.5,需要添加from future import with_statement
才能使用with语句。
关于为什么__del__()
的行为是这样的,并且使用上下文管理器解决方案的可信度,我找到了一篇文章:andy-pearce.com/blog/posts/2013/Apr/python-destructor-drawbacks
如果想要传递参数,应该如何使用这种优雅简洁的结构?我想能够这样做:with Resource(param1, param2) as r: # ...
可以给Resource添加一个__init__
方法,在其中将*args和**kwargs存储在self中,并在enter方法中将它们传递给内部类。在使用with语句时,__init__会在enter之前被调用。
这几乎是我最终采取的做法。出于编译型编程的背景,我只喜欢给__init__
方法提供明确的参数,以使我的"接口"更清晰。使用*args
和**kwargs
更"Pythonic"还是有我看不到的优点吗?我知道它只会在解释时失败,但也许对于那些有良好IDE的程序员来说,拥有一个更清晰的__init__
签名的资源类更容易使用?
这取决于你想要做什么,我认为没有一种方式比另一种更pythonic。假设你要包装一个带有许多参数的函数,并且你不想在包装器中复制所有这些参数。这就是我会使用*args和**kwargs的情况,以简化我的编程过程。在你的情况下,我也会更喜欢明确地使用参数。
这个答案是否意味着每次在Python中处理资源(文件、数据库等)时都必须创建一个包装器类而不仅仅是一个析构函数?
如果Package是一个(抽象)基类,这种模式会是什么样子?是否有办法从内部类继承?
好的 🙂 PackageResource可以使用contextlib进行简化,参见我的回答
...或者更好的是,如果清理命名为close,可以直接使用contextlib.closing
__exit__
和__del__
之间有什么区别?
当某个对象的生命周期存在于某个语法框架之外,并与其他事件的发生相关联时,使用with
很困难。但是,如果与某个事件相关联,就很难使用with
。
当你在代码中必须使用多个带参数的对象时,使用with
也很困难。当有多层结构时,with
的分层结构很好,但是当有7层时就不好了!
Python中有一种方法可以在实例销毁时执行一些代码,即使用atexit.register函数。具体的实现方法是,在类的构造函数中调用atexit.register方法,将需要在实例销毁时执行的代码注册到atexit中。这样,当实例销毁时,注册的代码就会被执行。
但需要注意的是,这种方法会将所有创建的实例都持久化,直到Python终止运行。
下面是一个使用上述代码的示例:
from package import * p = Package() q = Package() q.files = ['a', 'b', 'c'] quit()
上述示例中,创建了两个Package的实例p和q,然后将q的files属性设置为['a', 'b', 'c']。当退出Python时,会执行注册的cleanup方法,输出如下:
Running cleanup... Unlinking file: a Unlinking file: b Unlinking file: c Running cleanup...
这种使用atexit.register的方法的好处是,不需要关心类的使用者如何使用该类(是否使用了with语句?是否显式调用了__enter__方法?)。缺点是,如果需要在Python终止之前执行清理操作,则无法满足要求。
还有一种可能的解决方法是使用enter和exit方法,并在exit方法中注册atexit.register(self.__exit__)。但是,这种方法可能并不实用,因为可以在__exit__方法中执行所有的清理逻辑,并使用上下文管理器。此外,__exit__方法接受额外的参数(即__exit__(self, type, value, traceback)),因此需要考虑这些参数。无论如何,如果有需要,可以在Stack Overflow上发布一个单独的问题,因为您的用例似乎比较特殊。
问题的原因是当一个实例超出作用域时,没有一种直接的方式可以执行一些代码。解决方法是使用`weakref.finalize`来创建一个finalizer对象,该对象在实例被垃圾回收或程序退出时执行一些代码。在Python 2.7中没有`weakref.finalize`,所以这个方法在Python 2.7中不可用。
代码示例:
import weakref class MyClass: def __init__(self): self.finalizer = weakref.finalize(self, self.cleanup) def cleanup(self): # Code to execute when instance goes out of scope def stop(self): # Code to stop the instance self.finalizer.detach() # Example usage obj = MyClass() # Do something with obj # When obj goes out of scope, cleanup() will be called automatically
使用`weakref.finalize`创建一个finalizer对象,并将需要在实例超出作用域时执行的代码传递给`cleanup`方法。在`stop`方法中,调用`finalizer.detach()`可以防止重复调用finalizer。
这种方法的好处是可以在垃圾回收和程序退出时都执行清理代码,确保资源的正确释放。