14

我正在尝试找到创建执行以下操作的类装饰器的最佳方法:

  1. 将一些函数注入装饰类
  2. __init__在调用装饰类之后强制调用这些函数之一

目前,我只是保存对“原始”__init__方法的引用,并将其替换为__init__调用原始方法和附加函数的方法。它看起来类似于:

orig_init = cls.__init__

def new_init(self, *args, **kwargs):
    """
    'Extend' wrapped class' __init__ so we can attach to all signals
    automatically
    """

    orig_init(self, *args, **kwargs)
    self._debugSignals()

cls.__init__ = new_init

有没有更好的方法来“增强”原件__init__或在其他地方注入我的电话?我真正需要的只是self._debugSignals()在创建对象后的某个时间调用我。我也希望它自动发生,这就是为什么我认为 after__init__是一个好地方。

额外的杂项。装饰笔记

值得一提的是这个装饰器的一些背景。你可以在这里找到完整的代码。装饰器的重点是自动附加到任何 PyQt 信号并在它们发出时打印。当我装饰自己的子类时,装饰器工作正常QtCore.QObject,但是我最近一直在尝试自动装饰所有 QObject 子类。

我想在应用程序中有一个“调试”模式,我可以自动打印所有信号,以确保事情按照我的预期进行。我确信这会导致大量调试,但我仍然想看看发生了什么。

问题是我当前版本的装饰器在替换QtCore.QObject.__init__. 我试过调试这个,但代码都是 SIP 生成的,我没有太多经验。

所以,我想知道是否有一种更安全、更 Python 的方式来在之后注入函数调用__init__并希望避免段错误。

4

3 回答 3

15

根据这篇文章这个答案,另一种方法是通过自定义元类。这将按如下方式工作(在 Python 2.7 中测试):

# define a new metaclass which overrides the "__call__" function
class NewInitCaller(type):
    def __call__(cls, *args, **kwargs):
        """Called when you call MyNewClass() """
        obj = type.__call__(cls, *args, **kwargs)
        obj.new_init()
        return obj


# then create a new class with the __metaclass__ set as our custom metaclass
class MyNewClass(object):
    __metaclass__ = NewInitCaller
    def __init__(self):
        print "Init class"
    def new_init(self):
        print "New init!!"

# when you create an instance
a = MyNewClass()
>>> Init class
>>> New init!!

基本思想是:

  1. 当你调用MyNewClass()它搜索元类时,发现你已经定义了NewInitCaller

  2. 元类 __call__函数被调用。

  3. 此函数MyNewClass使用创建实例type

  4. 该实例运行它自己的__init__(打印“Init class”)。

  5. 然后元类调用new_init实例的函数。

于 2013-07-05T20:37:32.097 回答
1

这是 Python 3.x 的解决方案,基于这篇文章接受的答案。另请参阅PEP 3115以供参考,我认为其基本原理很有趣。

上面示例中的更改以注释显示;唯一真正的变化是定义元类的方式,所有其他的都是微不足道的 2to3 修改。

# define a new metaclass which overrides the "__call__" function
class NewInitCaller(type):
    def __call__(cls, *args, **kwargs):
        """Called when you call MyNewClass() """
        obj = type.__call__(cls, *args, **kwargs)
        obj.new_init()
        return obj

# then create a new class with the metaclass passed as an argument
class MyNewClass(object, metaclass=NewInitCaller):  # added argument
    # __metaclass__ = NewInitCaller  this line is removed; would not have effect
    def __init__(self):
        print("Init class")  # function, not command
    def new_init(self):
        print("New init!!")  # function, not command

# when you create an instance
a = MyNewClass()
>>> Init class
>>> New init!!
于 2017-10-19T11:30:38.660 回答
0

__post_init__这是在非数据类上实现的 jake77 示例的通用形式。这使得子类能够在基类和子类完成configure()后以正确的顺序自动调用。__init__

# define a new metaclass which overrides the "__call__" function
class PostInitCaller(type):
    def __call__(cls, *args, **kwargs):
        """Called when you call BaseClass() """
        print(f"{__class__.__name__}.__call__({args}, {kwargs})")
        obj = type.__call__(cls, *args, **kwargs)
        obj.__post_init__(*args, **kwargs)
        return obj


# then create a new class with the metaclass passed as an argument
class BaseClass(object, metaclass=PostInitCaller):
    def __init__(self, *args, **kwargs):
        print(f"{__class__.__name__}.__init__({args}, {kwargs})")
        super().__init__()

    def __post_init__(self, *args, **kwargs):
        print(f"{__class__.__name__}.__post_init__({args}, {kwargs})")
        self.configure(*args, **kwargs)

    def configure(self, *args, **kwargs):
        print(f"{__class__.__name__}.configure({args}, {kwargs})")


class SubClass(BaseClass):
    def __init__(self, *args, **kwargs):
        print(f"{__class__.__name__}.__init__({args}, {kwargs})")
        super().__init__(*args, **kwargs)

    def configure(self, *args, **kwargs):
        print(f"{__class__.__name__}.configure({args}, {kwargs})")
        super().configure(*args, **kwargs)

# when you create an instance
a = SubClass('a', b='b')

运行给出:

PostInitCaller.__call__(('a',), {'b': 'b'})
SubClass.__init__(('a',), {'b': 'b'})
BaseClass.__init__(('a',), {'b': 'b'})
BaseClass.__post_init__(('a',), {'b': 'b'})
SubClass.configure(('a',), {'b': 'b'})
BaseClass.configure(('a',), {'b': 'b'})
于 2021-08-26T13:37:21.260 回答