有没有一种Pythonic的方法,在实例超出作用域时执行一些代码?

6 浏览
0 Comments

有没有一种Pythonic的方法,在实例超出作用域时执行一些代码?

上述的__del__(self)会抛出AttributeError异常。我理解在调用__del__()时,Python不保证存在"全局变量"(在这个上下文中是指成员数据)。如果是这种情况导致了异常,我该如何确保对象正确地销毁?

0
0 Comments

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层时就不好了!

0
0 Comments

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上发布一个单独的问题,因为您的用例似乎比较特殊。

0
0 Comments

问题的原因是当一个实例超出作用域时,没有一种直接的方式可以执行一些代码。解决方法是使用`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。

这种方法的好处是可以在垃圾回收和程序退出时都执行清理代码,确保资源的正确释放。

0