【问题标题】:Injecting function call after __init__ with decorator使用装饰器在 __init__ 之后注入函数调用
【发布时间】:2013-04-07 16:45:33
【问题描述】:

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

  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()。我也希望它自动发生,这就是为什么我认为__init__ 之后是个好地方。

额外的杂项。装修说明

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

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

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

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

【问题讨论】:

  • 不是解决方案,而是一些信息:QtCore.QObject.__init__ 在 Qt 库中实现,即。在 C++ 中,这就是为什么你不能简单地替换 __init__
  • @rainer 这很有道理。我试图查看QtCore.QObject.__init__ 源代码,但我迷失在创建包装器等的所有 sip/C++ 魔法中。这解释了为什么我的装饰器适用于基于 python 的对象(或至少不包装的对象)与 sip/c++ 层密切相关)。但是,我在想也许有一种更简洁的方法可以在不劫持__init__ 的情况下实现这种行为,因为我也不太喜欢基于 python 的对象的这种行为。但是,对于 python 对象,“它只是有效”。

标签: python pyqt4 decorator


【解决方案1】:

基于this postthis answer,另一种方法是通过自定义metaclass。这将按如下方式工作(在 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. 此函数使用type 创建MyNewClass 实例,

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

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

【讨论】:

  • 这不适用于我使用 PyQt 和 QtCore.QObject 的特定场景。这是因为我试图猴子补丁的对象是用 C 实现的,而不是纯 Python。但是,这个解决方案对于纯 Python 对象来说效果很好。
  • 啊……这很痛苦。附带说明一下,您的装饰器最后似乎没有返回cls。我假设您刚刚发布了摘录?
  • 是的,刚刚发布了一个sn-p。真正的装饰器应该返回cls
【解决方案2】:

这里是 Python 3.x 的解决方案,基于 this post's accepted answer。另请参阅PEP 3115 以供参考,我认为其基本原理很有趣。

上面例子中的变化用 cmets 表示;唯一真正的变化是元类的定义方式,所有其他都是微不足道的 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!!

【讨论】:

    【解决方案3】:

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

    # 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'})
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-20
      • 1970-01-01
      相关资源
      最近更新 更多