关于反射:调用super init的python decorator

python decorator that calls super init

我正在尝试创建一个自动调用父类init的修饰符。它使用单继承,但是当我试图链接效果时,会得到一个堆栈溢出错误。有人能解释(a)为什么会发生这种情况,(b)如何实现我想要实现的目标吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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


正确的方法

正确的方法是直接调用代码中的父初始值设定项。在python 3中:

1
super().__init__()

在python 2中:

1
super(Parent, self).__init__()

1
super(Child, self).__init__()

错误

但要回答您的直接问题,无限递归的发生是因为您如何获得父级:

1
self.__class__.__bases__[0]

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

错误的方式

您可能希望找到定义当前调用的__init__方法的类,并获取该方法的父类。@Alexmartelli在他的答案中提供了一个这样或python的函数,我在这里逐字复制:

1
2
3
4
5
6
7
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的版本:

1
2
3
4
5
6
7
8
9
10
11
12
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功能,如下所示:

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

再一次。请不要这样做。可能有许多角落的案件,我没有涵盖,将来咬你,从一切围绕埃多克斯1(6)。用super代替。不管怎么说,这是同一想法的一个经过深思熟虑的版本。