使用装饰器链在程序退出时注册类方法

Posted

技术标签:

【中文标题】使用装饰器链在程序退出时注册类方法【英文标题】:Register class method at program exit using chain of decorators 【发布时间】:2019-09-13 22:31:36 【问题描述】:

我有一个类,它的一个属性是我想在程序退出时运行的类方法。这个立竿见影的想法:

import atexit

class Foo:
    @atexit.register
    @classmethod
    def foo(cls):
        pass

引发以下异常:

Traceback (most recent call last):
  File "test.py", line 3, in <module>
    class Foo:
  File "test.py", line 5, in Foo
    @classmethod
TypeError: the first argument must be callable

另一个直接的想法(注意装饰器的评估顺序发生了变化):

import atexit

class Foo:
    @classmethod
    @atexit.register
    def foo(cls):
        pass

引发以下异常:

Error in atexit._run_exitfuncs:
TypeError: foo() missing 1 required positional argument: 'cls'

我对装饰器的概念还很陌生。

是否有任何简单的方法可以修复此代码,同时仍使用装饰器链? 如果不是,您会推荐什么作为替代解决方案?

【问题讨论】:

【参考方案1】:

首先你应该阅读How does a classmethod object work?的答案

但是看看下面代码的输出会很有趣:

def mydeco(func):
    print(repr(func))
    return func

class Foo:
    @mydeco
    @classmethod
    def foo(cls):
        pass

print(repr(Foo.foo))

如果你运行它,你会看到

<classmethod object at 0x6ffffd1dc18>
<bound method Foo.foo of <class '__main__.Foo'>>

所以第一行来自mydeco() 装饰器,第二行来自底部的print() 语句。你会发现它们是不同的。它们不同的原因是因为classmethod 不像您期望的函数装饰器。它不会给你一个函数,而是一个classmethod 对象,它是不可调用的。但是,与此同时,包含它的类Foo 记得在Foo.__dict__ 中,当您调用装饰类方法时,它会返回一个可调用方法。

仅仅因为classmethod 对象不可调用。你不能用atexit.register 包装它。 staticmethod 的类似情况。

所以现在您应该意识到,要将其注册到 atexit,您可以在课堂之外进行,如下所示:

import atexit

class Foo:
    @classmethod
    def foo(cls):
        pass

atexit.register(Foo.foo)

【讨论】:

谢谢!很好解释。 classmethod 对象对我来说有点像黑魔法,但至少我终于明白了“argument is not callable”异常。我不知道装饰器可以返回对象。

以上是关于使用装饰器链在程序退出时注册类方法的主要内容,如果未能解决你的问题,请参考以下文章

使用装饰器自动注册类方法

如何使用 Python 装饰器以便方法使用 functools.lru_cache 并自行注册?

类装饰器不绑定自我

类方法的python装饰器

如何修补由 Python 数据形状中的装饰器注册的方法?

GetIt/Injectable 在抽象类上缺少可注入装饰器?