【问题标题】:How can I delegate to the __add__ method of a superclass?如何委托给超类的 __add__ 方法?
【发布时间】:2012-10-31 21:14:39
【问题描述】:

假设我有这门课:

class MyString(str):
    def someExtraMethod(self):
        pass

我希望能够做到

a = MyString("Hello ")
b = MyString("World")
(a + b).someExtraMethod()
("a" + b).someExtraMethod()
(a + "b").someExtraMethod()
  1. 按原样运行:

    AttributeError: 'str' object has no attribute 'someExtraMethod'
    
  2. 显然这行不通。所以我添加了这个:

    def __add__(self, other):
        return MyString(super(MyString, self) + other)
    def __radd__(self, other):
        return MyString(other + super(MyString, self))
    
    TypeError: cannot concatenate 'str' and 'super' objects
    
  3. 嗯,好的。 super 似乎不尊重运算符重载。也许:

    def __add__(self, other):
        return MyString(super(MyString, self).__add__(other))
    def __radd__(self, other):
        return MyString(super(MyString, self).__radd__(other))
    
    AttributeError: 'super' object has no attribute '__radd__'
    

仍然没有运气。我应该在这里做什么?

【问题讨论】:

    标签: python oop inheritance overriding delegation


    【解决方案1】:

    我通常使用super(MyClass, self).__method__(other) 语法,在您的情况下这不起作用,因为str 不提供__radd__。但是您可以使用str 将您的类的实例转换为字符串。

    谁说它的第 3 版有效:它没有:

    >>> class MyString(str):
    ...     def __add__(self, other):
    ...             print 'called'
    ...             return MyString(super(MyString, self).__add__(other))
    ... 
    >>> 'test' + MyString('test')
    'testtest'
    >>> ('test' + MyString('test')).__class__
    <type 'str'>
    

    如果你实现__radd__,你会得到一个AttributeError(见下面的注释)。

    无论如何,我会避免使用内置函数作为基本类型。如您所见,一些细节可能很棘手,而且您必须重新定义它们支持的所有操作,否则该对象将成为内置的实例,而不是您的类的实例。 我认为在大多数情况下,使用委托而不是继承更容易。

    另外,如果您只是想添加一个方法,那么您可以尝试在纯字符串上使用函数。


    我在这里添加一些解释,说明为什么str 不提供__radd__,以及当python 执行BINARY_ADD 操作码(执行+ 的操作码)时发生了什么。

    str.__radd__ 的缺失是因为字符串对象实现了序列的连接运算符,而不是数字加法运算。对于listtuple 等其他序列也是如此。这些在“Python 级别”是相同的,但实际上在 C 结构中有两个不同的“槽”。

    数字+ 运算符,在python 中由两种不同的方法(__add____radd__)定义,实际上是一个使用交换参数调用的单个C 函数,以模拟对__radd__ 的调用。

    现在,您可能认为只实现 MyString.__add__ 可以解决您的问题,因为 str 没有实现 __radd__,但事实并非如此:

    >>> class MyString(str):
    ...     def __add__(self, s):
    ...             print '__add__'
    ...             return MyString(str(self) + s)
    ... 
    >>> 'test' + MyString('test')
    'testtest'
    

    如您所见,MyString.__add__ 未被调用,但如果我们交换参数:

    >>> MyString('test') + 'test'
    __add__
    'testtest'
    

    被调用了,怎么回事?

    答案在documentation 中,其中指出:

    对于对象xy,首先尝试x.__op__(y)。如果这不是 实现或返回NotImplemented,尝试y.__rop__(x)。如果这 也没有实现或返回NotImplemented,一个TypeError 引发异常。但请参阅以下异常:

    上一项的例外:如果左操作数是 内置类型或新型类,右操作数是 该类型或类的适当子类的实例并覆盖 base 的__rop__() 方法,尝试右操作数的__rop__() 方法 在左操作数的__op__() 方法之前。

    这样做是为了让子类可以完全覆盖二进制文件 运营商。否则,左操作数的__op__() 方法将始终 接受正确的操作数:当给定类的实例是 预期,该类的子类的实例总是 可以接受。

    这意味着您必须实现所有 str 的方法 __r*__ 方法,否则您仍然会遇到参数顺序问题。

    【讨论】:

      【解决方案2】:

      可能是:

      class MyString(str):
        def m(self):
          print(self)
      
        def __add__(self, other):
          return MyString(str(self) + other)
      
        def __radd__(self, other):
          return MyString(other + str(self))    
      
      a = MyString("Hello ")
      b = MyString("World")
      (a + b).m()
      

      更新:你最后一个带有super 的版本适合我

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-02-13
        • 2020-06-05
        • 1970-01-01
        • 1970-01-01
        • 2018-05-07
        • 2011-06-19
        • 2013-12-25
        相关资源
        最近更新 更多