Proper use of Python inheritance super and __init__ of class decorator, decorating classes.

huangapple go评论69阅读模式
英文:

Proper use of Python inheritance super and __init__ of class decorator, decorating classes

问题

以下是您要翻译的代码部分:

I have several classes that are inheriting from a base Widget class from a third-party package.  I have two separate set of behaviors that I want to optionally apply to some of these classes (one, the other, or both).  Setting this behavior up with classic/standard inheritance would result in a inheritance diamond:

class NotMyWidget:
  pass

class OptionalBehavior1(NotMyWidget):
  pass

class OptionalBehavior2(NotMyWidget):
  pass

class MySpecialWidget(OptionalBehavior1, OptionalBehavior2):
  pass

class MySpecialWidget2(OptionalBehavior2):
  pass
Maybe I'm being dumb, and this is actually the preferred solution, and the diamond isn't actually an issue.

I need to figure out a good implementation in Python 2.7.5 (forced by other packages that depend on it and RedHat 7 and have not been fixed for Python 3, I wish I had a choice here).

For my own edification, I'd like to see Python 3 solutions as well.

What I have tried, using the decorator concept:

import functools

class NotMyWidget:
  def __init__(self, parent):
    pass

def optional_behavior1(cls):
  if not issubclass(cls, NotMyWidget):
    raise TypeError('Decorated class must inherit from NotMyWidget')

  @functools.wraps(cls, updated=())
  class Optional1(cls):
    def __init__(self, *args, **kwargs):
      super(Optional1, self).__init__(*args, **kwargs)
      #special init needs of optional_behavior1 here

    #define overloaded methods here to modify NotMyWidget base behavior
  return Optional1

def optional_behavior2(cls):
  if not issubclass(cls, NotMyWidget):
    raise TypeError('Decorated class must inherit from NotMyWidget')

  @functools.wraps(cls, updated=())
  class Optional2(cls):
    def __init__(self, parent, second_arg, *args, **kwargs):
      super(Optional2, self).__init__(parent, second_arg, *args, **kwargs)
      #special init needs of optional_behavior2 here, including logic based off of second_arg

    #define overloaded methods here to modify NotMyWidget base behavior and MySpecialWidget* behavior (second_arg)
  return Optional2

@optional_behavior2
@optional_behavior1
class MySpecialWidget(NotMyWidget):
  def __init__(self, parent, second_arg, third_arg):

    #attempt 1
    super(MySpecialWidget, self).__init__(parent)
    # -> TypeError: __init__() takes exactly 4 arguments (2 given)

    #attempt 2
    super(MySpecialWidget, self).__init__(parent, second_arg, third_arg)
    # -> RuntimeError: maximum recursion depth exceeded while calling a Python object (Optional2 __init__ run once, Optional1 __init__ run recursively until exception thrown, reversing decorators reverses which class is called once or until error)

    #attempt 3
    #no super/__init__ invocation included
    # -> RuntimeError: super-class __init() of type MySpecialWidget was never called

    #attempt 4
    #no super/__init__ invocation included here
    #call to super(cls, self).__init(parent) in both __init__ methods of Optional1 and Optional2, before their own super/__init__ invocations
    # -> TypeError: __init() takes exactly 4 arguments (2 given)

    #attempt 5
    #no super/__init__ invocation included here
    #call to super(cls, self).__init()(<same args as Optional1 or Optional2 super/__init__ invocation) in __init__ method of Optional1 and Optional2, before their own super/__init__ invocations
    # -&gt; RuntimeError: super-class __init() of type MySpecialWidget was never called

如果您需要更多翻译,请告诉我。

英文:

I have several classes that are inheriting from a base Widget class from a third-party package. I have two separate set of behaviors that I want to optionally apply to some of these classes (one, the other, or both). Setting this behavior up with classic/standard inheritance would result in a inheritance diamond:

class NotMyWidget:
pass
class OptionalBehavior1(NotMyWidget):
pass
class OptionalBehavior2(NotMyWidget):
pass
class MySpecialWidget(OptionalBehavior1, OptionalBehavior2)
pass
class MySpecialWidget2(OptionalBehavior2):
pass

Maybe I'm being dumb, and this is actually the preferred solution, and the diamond isn't actually an issue.

I need to figure out a good implementation in Python 2.7.5 (forced by other packages that depend on it and RedHat 7 and have not been fixed for Python 3, I wish I had a choice here).

For my own edification, I'd like to see Python 3 solutions as well.

What I have tried, using the decorator concept:

import functools
class NotMyWidget:
def __init__(self, parent):
pass
def optional_behavior1(cls):
if not issubclass(cls, NotMyWidget):
raise TypeError(&#39;Decorated class must inherit from NotMyWidget&#39;)
@functools.wraps(cls, updated=())
class Optional1(cls):
def __init__(self, *args, **kwargs):
super(Optional1, self).__init__(*args, **kwargs)
#special init needs of optional_behavior1 here
#define overloaded methods here to modify NotMyWidget base behavior
return Optional1
def optional_behavior2(cls):
if not issubclass(cls, NotMyWidget):
raise TypeError(&#39;Decorated class must inherit from NotMyWidget&#39;)
@functools.wraps(cls, updated=())
class Optional2(cls):
def __init__(self, parent, second_arg, *args, **kwargs):
super(Optional2, self).__init__(parent, second_arg, *args, **kwargs)
#special init needs of optional_behavior2 here, including logic based off of second_arg
#define overloaded methods here to modify NotMyWidget base behavior and MySpecialWidget* behavior (second_arg)
return Optional2
@optional_behavior2
@optional_behavior1
class MySpecialWidget(NotMyWidget):
def __init__(self, parent, second_arg, third_arg):
#attempt 1
super(MySpecialWidget, self).__init__(parent)
# -&gt; TypeError: __init__() takes exactly 4 arguments (2 given)
#attempt 2
super(MySpecialWidget, self).__init__(parent, second_arg, third_arg)
# -&gt; RuntimeError: maximum recursion depth exceeded while calling a Python object (Optional2 __init__ run once, Optional1 __init__ run recursively until exception thrown, reversing decorators reverses which class is called once or until error)
#attempt 3
#no super/__init__ invocation included
# -&gt; RuntimeError: super-class __init__() of type MySpecialWidget was never called
#attempt 4
#no super/__init__ invocation included here
#call to super(cls, self).__init__(parent) in both __init__ methods of Optional1 and Optional2, before their own super/__init__ invocations
# -&gt; TypeError: __init__() takes exactly 4 arguments (2 given)
#attempt 5
#no super/__init__ invocation included here
#call to super(cls, self).__init__(&lt;same args as Optional1 or Optional2 super/__init__ invocation) in __init__ method of Optional1 and Optional2, before their own super/__init__ invocations
# -&gt; RuntimeError: super-class __init__() of type MySpecialWidget was never called

I had some variant of atempt 4 or 5 that inexplicably seemed to work, but I thought it was extremely odd to not have a super/init call in the init method of MySpecialWidget, so I jumped back down the rabbit hole and have not seen a functioning verison since. I'm obvioulsy misunderstanding the behavior of Python inheritance and super/init.

答案1

得分: 2

使用菱形继承

我相信菱形继承,这是多重继承的一种形式,绝对是在这里的首选解决方案,因为在类上使用装饰器可能会给您的代码增加更多复杂性。除非您的问题实际上更复杂,而菱形继承对您不起作用,否则继承应该可以解决问题。您还可以查看此StackOverflow答案,了解如何处理菱形继承的更多信息。

class Widget():
  def __init__(self, parent):
    pass

class BehaviorA(Widget):
  def __init__(self, parent):
    super(BehaviorA, self).__init__(parent)
    # 在这里执行操作
  # 也可以在这里添加方法或属性

class BehaviorB(Widget):
  def __init__(self, parent):
    super(BehaviorB, self).__init__(parent)
    # 在这里执行操作
  # 也可以在这里添加方法或属性
  # 确保不要使用与BehaviorA冲突的名称

class MyWidget(BehaviorA, BehaviorB):
  def __init__(self, parent):
    super(MyWidget, self).__init__(self, parent)
    # 在这里执行操作

这个菱形的执行顺序非常简单。如果您在所有的__init__方法中都调用print,您将看到以下顺序:

  • MyWidget
  • BehaviorB(B之所以在A之前运行的原因是B是最后继承的,继承有点像堆栈)
  • BehaviorA
  • Widget

如果您对Python查找方法的顺序感到困惑,您可以查看MyWidget.__mro__

使用装饰器时为什么会出现无限递归?

出现无限递归的原因是您正在使用装饰器创建一个新类,同时还在一起使用了旧的super(ClassName, self).__init__(...)语法。

class Widget():
  def __init(self, parent):
    pass

def behavior_a(cls):
  class BehaviorA(cls):
    def __init__(self, *args, **kwargs):
      super(BehaviorA, self).__init__(*args, **kwargs)
  return BehaviorA

@behavior_a
class MyWidget(Widget):
  def __init__(self, parent):
    super(MyWidget, self).__init__(parent) # 这就是我们出现无限递归的地方,因为现在MyWidget实际上是BehaviorA

MyWidget('test') # 无限递归

发生的情况是,您正在将MyWidget的值替换为装饰器生成的类BehaviorA;因此,当您运行super(MyWidget, self)时,您将得到BehaviorA的父类,即原始的MyWidget。从那里,您调用原始MyWidget__init__,循环重复。

为避免此问题,您可以使用新的super()语法(如果可用),或者可以直接使用Widget.__init__,因为在使用这些装饰器时,您有一个单一继承方案。最符合Python风格的方法是使用新的super()语法。

@behavior_a
@behavior_b
class MyWidget(Widget):
  def __init__(self, parent):
    super().__init(parent)

# 或

@behavior_a
@behavior_b
class MyWidget(Widget):
  def __init__(self, parent):
    Widget.__init__(self, parent)
英文:

Use Diamond Inheritance

I believe diamond inheritance, which is a form of multiple inheritance, is definitely the preferred solution here because using decorators on classes can add more complexity to your code. Unless your issue is actually more complicated and diamond inheritance doesn't work for you, inheritance should do the trick. You can also check out this StackOverflow answer for more on how to handle diamond inheritance.

class Widget():
  def __init__(self, parent):
    pass

class BehaviorA(Widget):
  def __init__(self, parent):
    super(BehaviorA, self).__init__(parent)
    # Do stuff here
  # Maybe add methods or properties here

class BehaviorB(Widget):
  def __init__(self, parent):
    super(BehaviorB, self).__init__(parent)
    # Do stuff here
  # Maybe add methods or properties here
  # Make sure not to use names that conflict with BehaviorA

class MyWidget(BehaviorA, BehaviorB):
  def __init__(self, parent):
    super(MyWidget, self).__init__(self, parent)
    # Do stuff here

The execution order for this diamond is pretty simple. If you were to call print in all of the __init__ methods, you would see the following order:

  • MyWidget
  • BehaviorB (the reason why B runs before A is that B was inherited last and inheritance is kind of like a stack)
  • BehaviorA
  • Widget

If you're ever confused about the order of where Python will look for methods first, you can look at MyWidget.__mro__.

Why the infinite recursion when using decorators?

The reason why you get infinite recursion is that you're using decorators to create a new class and also using the old super(ClassName, self).__init__(...) syntax together.

class Widget():
  def __init__(self, parent):
    pass

def behavior_a(cls):
  class BehaviorA(cls):
    def __init__(self, *args, **kwargs):
      super(BehaviorA, self).__init__(*args, **kwargs)
  return BehaviorA

@behavior_a
class MyWidget(Widget):
  def __init__(self, parent):
    super(MyWidget, self).__init__(parent) # This is where we hit infinite recursion becuase MyWidget is now actually BehaviorA

MyWidget(&#39;test&#39;) # Infinite Recursion

What's happening is that you're replacing the value of MyWidget with the class generated by the decorator, BehaviorA; therefore, when you run super(MyWidget, self) you're getting back the parent class of BehaviorA which is the original MyWidget. From there, you call __init__ on the original MyWidget, and the loop repeats.

To avoid this problem, you can either use the newer super() syntax if it's available, or you can use Widget.__init__ directly since you have a single inheritance scheme when using these decorators. The most pythonic way would be to use the newer super() syntax.

@behavior_a
@behavior_b
class MyWidget(Widget):
  def __init__(self, parent):
    super().__init__(parent)

# or

@behavior_a
@behavior_b
class MyWidget(Widget):
  def __init__(self, parent):
    Widget.__init__(self, parent)

huangapple
  • 本文由 发表于 2023年4月11日 13:00:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/75982537.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定