动态添加特殊方法到 class 不起作用

Dynamically add special methods to class does not work

考虑这个 class 根据某些参数的值添加方法的地方:

class Something(object):
    def __init__(self, value, op='+'):
        self.x = value
        if op == '+':
            self.add = self._operator
        elif op == '-':
            self.sub = self._operator

    def _operator(self, other):
        return self.x * other
    
x = Something(10)
x.add(3)
# 30

现在,我想使用 +- 运算符来代替 .add().sub() 符号。 为此,我会写:

class Something(object):
    def __init__(self, value, op='+'):
        self.x = value
        if op == '+':
            self.__add__ = self._operator
            self.__radd__ = self._operator
        elif op == '-':
            self.__sub__ = self._operator
            self.__rsub__ = self._operator

    def _operator(self, other):
        return self.x * other           

x = Something(10)
print(x + 3)

但我得到的不是 30

TypeError: unsupported operand type(s) for +: 'Something' and 'int'

尽管:

print(dir(x))
# ['__add__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__radd__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_operator', 'x']

出了什么问题,我该如何解决?

编辑

这与Overriding special methods on an instance的不同之处在于,我不会在创建对象后尝试向对象的实例添加特殊方法,即:

x = Something(10)
x.__add__ = ...

而是在 class.__init__() 期间,尽管不可否认,错误的来源和解决方案都非常相似。

嗯,别那么麻烦,量力而行,就用:

class Something(object):
    def __init__(self, value):
        self.x = value

    def __add__(self, other):
        return self.x * other

    def __sub__(self, other):
        return self.x * other

x = Something(10)
print(x + 3)

输出:

30

它也适用于 -

特殊方法通常会避开属性查找机制。他们不检查实例属性,而是直接查看 class.

3.3.10. Special method lookup

For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.

[...]

Bypassing the __getattribute__() machinery in this fashion provides significant scope for speed optimisations within the interpreter, at the cost of some flexibility in the handling of special methods (the special method must be set on the class object itself in order to be consistently invoked by the interpreter).

但是您仍然可以在特殊方法中遵循实例属性。

扩展您的示例(可能有更优雅的方法):

class Something(object):
    def __init__(self, value, op='+'):
        self.x = value
        if op == '+':
            self._add = self._operator
            self._radd = self._operator
        elif op == '-':
            self._sub = self._operator
            self._rsub = self._operator

    def _operator(self, other):
        return self.x * other

    def __add__(self, other):
        try:
            return self._add(other)
        except AttributeError:
            return NotImplemented

    # etc. for __radd__, __sub__ and __rsub__

x = Something(10)
print(x + 3)