__add__ 返回的是超类的实例而不是子类的实例
__add__ 返回的是超类的实例而不是子类的实例
当我子类化某个类,比如int
,并自定义其__add__
方法并调用super().__add__(other)
时,它会返回一个int
的实例,而不是我的子类。我可以通过在每个返回int
的方法中的每个super()
调用之前添加type(self)
来修复这个问题,但这似乎有些过度。肯定有更好的方法来解决这个问题。同样的情况也发生在float
和fractions.Fraction
上。
class A(int): def __add__(self, other): return super().__add__(other) x = A() print(type(x + 1))
输出:
期望输出:
问题出现的原因是在使用装饰器wrap_math时,对于__add__和__sub__这两个函数的处理不正确。在装饰器中,通过wrapped函数将原始函数包装起来,并返回一个lambda函数。然后,通过getattr函数获取特殊类c中的原始函数,并使用wrapped函数进行包装,最后通过setattr函数将包装后的函数重新赋值给特殊类c中的原始函数。然而,在getattr函数中,获取到的原始函数是特殊类c的父类int中的函数,而不是特殊类c中的函数。因此,当使用特殊类c的实例进行加法运算时,返回的是父类int的实例,而不是特殊类c的实例。
要解决这个问题,可以修改装饰器wrap_math中的代码。在for循环中,将获取原始函数的getattr函数的参数改为特殊类c而不是父类int。这样,就可以正确地获取到特殊类c中的原始函数。修改后的代码如下所示:
def wrap_math(c): def wrapped(orig): return lambda s, o: c(orig(s,o)) maths = ["__add__", "__sub__"] for op in maths: func = wrapped(getattr(c, op)) setattr(c, op, func) return c class Special(int): pass x = Special(10) type(x + 10)
通过以上修改,问题得到了解决。现在,当使用特殊类c的实例进行加法运算时,返回的是特殊类c的实例,而不是父类int的实例。
在这段代码中,问题是当我们在子类的实例上执行加法操作时,返回的类型不是子类的类型,而是父类的类型。出现这个问题的原因是,当我们在子类中重载了`__add__`方法时,它只会返回父类的实例,而不会返回子类的实例。这是因为在`__add__`方法中,我们使用了`super`函数来获取父类的方法,并且在调用时传递了两个参数,而不是一个参数。
解决这个问题的方法是使用描述符(descriptor)。我们可以定义一个描述符类`SuperCaller`,并在其中实现`__set_name__`和`__get__`方法。在`__set_name__`方法中,我们使用`super`函数来获取父类的方法,并定义一个新的方法`call`,它将调用父类的方法并返回子类的实例。然后,我们将这个新方法赋值给描述符类的`_call`属性。在`__get__`方法中,我们返回一个lambda函数,该函数将调用描述符类的`_call`方法。
通过使用这个描述符类,我们可以在子类中将`__add__`方法设置为该描述符的实例,并在实例化子类时,调用了父类的`__add__`方法,并返回了子类的实例。
以下是修改后的代码:
class SuperCaller: def __set_name__(self, owner, name): method = getattr(super(owner, owner), name) def call(self, other): return type(self)(method(self, other)) self._call = call def __get__(self, instance, owner): return lambda other: self._call(instance, other) class A(int): __add__ = SuperCaller() x = A() print(type(x + 1))
输出结果为:
解决这个问题的关键是使用描述符类来重载`__add__`方法,以便在调用父类的方法时返回子类的实例。这样就可以确保在执行加法操作时返回正确的类型。