我正在尝试制作一个自动调用父类 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
最佳答案
正确的方法
执行此操作的正确方法是直接在代码中调用父初始值设定项。在 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
即可。无论如何,这是同一想法的一个经过深思熟虑的更好版本。
关于调用 super init 的 python 装饰器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47397998/