调用超级初始化的python装饰器

Posted

技术标签:

【中文标题】调用超级初始化的python装饰器【英文标题】:python decorator that calls super init 【发布时间】:2018-05-04 00:36:38 【问题描述】:

我正在尝试制作一个自动调用父类 init 的装饰器。它适用于单继承,但是当我尝试链接效果时,出现堆栈溢出错误。谁能解释(a)为什么会发生这种情况以及(b)如何完成我想要完成的事情?

def init(__init):
    def wrapper(self, *args, **kwargs):
        self.__class__.__bases__[0].__init__(self)
        __init(self)
    return wrapper

class Base:
    def __init__(self):
        print("base init")


class Parent(Base):
    @init
    def __init__(self):
        print("parent init")

class Child(Parent):
    @init
    def __init__(self):
        print("child init")

a = Parent() #works
#c = Child() #stack overflow

【问题讨论】:

看看第二种情况下self.__class__是什么。它将永远是Parent,因为self 永远不会停止成为Child 的实例,无论您调用什么方法。 您希望引用包含您正在调用的__init 的类。这将确保您的递归最终终止。 这个装饰器到底有什么意义??? 不要费心让这个装饰器工作。这将是一段充满陷阱的艰难旅程。只需像其他人一样在构造函数中调用父 __init__。无论如何,它只比输入@init 长几个字符。 @Rawing。我希望我在回答中抓住了你的情绪。 【参考方案1】:

正确的方式

执行此操作的正确方法是直接在代码中调用父初始化程序。在 Python 3 中:

super().__init__()

在 Python 2 中:

super(Parent, self).__init__()

super(Child, self).__init__()

错误

不过,要回答您的直接问题,无限递归的发生是因为您获取父级的方式:

self.__class__.__bases__[0]

无论您调用wrapper 函数多少次,self 都不会停止成为Child 的实例。你最终会无限期地调用Parent.__init__(self),因为父类永远不会改变。

错误的方式

您可能想要的是找到定义了您当前正在调用的__init__ 方法的类,并获取它的父类。 @AlexMartelli 在他的answer 中提供了一个执行此操作或 Python 的函数,我在这里逐字复制:

import inspect

def get_class_that_defined_method(meth):
    for cls in inspect.getmro(meth.im_class):
        if meth.__name__ in cls.__dict__: 
            return cls
    return None

如果您使用的是 Python 3,请改用 @Yoel's version:

def get_class_that_defined_method(meth):
    if inspect.ismethod(meth):
        for cls in inspect.getmro(meth.__self__.__class__):
           if cls.__dict__.get(meth.__name__) is meth:
                return cls
        meth = meth.__func__  # fallback to __qualname__ parsing
    if inspect.isfunction(meth):
        cls = getattr(inspect.getmodule(meth),
                      meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0])
        if isinstance(cls, type):
            return cls
    return getattr(meth, '__objclass__', None)  # handle special descriptor objects

您现在可以重新定义您的 init 函数,如下所示:

def init(__init):
    def wrapper(self, *args, **kwargs):
        get_class_that_defined_method(__init).__bases__[0].__init__(self)
        __init(self)
    return wrapper

再次。请不要这样做。从__bases__[0] 周围的所有内容开始,可能有很多我没有涵盖的极端案例会来咬你。只需改用super。无论如何,它是同一个想法的一个更好的深思熟虑的版本。

【讨论】:

以上是关于调用超级初始化的python装饰器的主要内容,如果未能解决你的问题,请参考以下文章

python__高级 : 类当作装饰器

使用 python 装饰器调用递归函数的次数

初识python: 装饰器

Python__new__方法定制属性访问描述符与装饰器

python全栈闯关--11-装饰器初识

python装饰器实现用户密码认证(简单初形)