我有一个动态重载基本算术运算符的类,如...
import operator
class IshyNum:
def __init__(self, n):
self.num=n
self.buildArith()
def arithmetic(self, other, o):
return o(self.num, other)
def buildArith(self):
map(lambda o: setattr(self, "__%s__"%o,lambda f: self.arithmetic(f, getattr(operator, o))), ["add", "sub", "mul", "div"])
if __name__=="__main__":
number=IshyNum(5)
print number+5
print number/2
print number*3
print number-3
但是,如果我将类更改为继承自字典(class IshyNum(dict):
),则它不起作用。我需要明确def __add__(self, other)
或其他什么才能使它工作。为什么呢?
答案 0 :(得分:4)
答案可以在Python的两种类型中找到。
你提供的第一个代码片段使用了一个遗留的“旧式”类(你可以告诉它,因为它没有任何子类 - 冒号前没有任何内容)。它的语义是特殊的。特别是,您可以向实例添加特殊方法:
class Foo:
def __init__(self, num):
self.num = num
def _fn(other):
return self.num + other.num
self.__add__ = _fn
并获得有效回复:
>>> f = Foo(2)
>>> g = Foo(1)
>>> f + g
3
但是,子类化dict
意味着您正在生成一个新式的类。并且运算符重载的语义不同:
class Foo (object):
def __init__(self, num):
self.num = num
def _fn(other):
return self.num + other.num
self.__add__ = _fn
>>> f = Foo(2)
>>> g = Foo(1)
>>> f + g
Traceback ...
TypeError: unsupported operand type(s) for +: 'Foo' and 'Foo'
为了使这种方法适用于新式类(包括dict
的子类或几乎所有其他类型),您必须确保在类上定义了特殊方法。您可以通过元类执行此操作:
class _MetaFoo(type):
def __init__(cls, name, bases, args):
def _fn(self, other):
return self.num + other.num
cls.__add__ = _fn
class Foo(object):
__metaclass__ = _MetaFoo
def __init__(self, num):
self.num = num
>>> f = Foo(2)
>>> g = Foo(1)
>>> f+g
3
此外,语义差异意味着在第一种情况下,我可以使用一个参数定义我的本地add方法(它使用的self
是从定义它的周围范围捕获的),但是新的样式类,Python期望显式传递两个值,因此内部函数有两个参数。
正如之前提到的那样,最好尽可能避免使用旧式类,并坚持使用新式类(旧式类在Python 3+中删除)。不幸的是,在这种情况下,旧式的课程恰好适合你,新式课程需要更多的代码。
编辑:
您还可以通过在类而不是实例上设置方法,以最初尝试的方式执行此操作:
class Foo(object):
def __init__(self, num):
self.num = num
setattr(Foo, '__add__', (lambda self, other: self.num + other.num))
>>> f = Foo(2)
>>> g = Foo(1)
>>> f+g
3
我担心我有时会在Metaclasses中思考,更简单的解决方案会更好:)
答案 1 :(得分:3)
通常,永远不要在实例上设置__方法 - 它们仅在类上受支持。 (在这种情况下,问题是它们碰巧在旧式类上工作。不要使用旧式类。)
你可能想要使用元类,而不是你在这里做的奇怪事情。
这是一个元类教程:http://www.voidspace.org.uk/python/articles/metaclasses.shtml
答案 2 :(得分:2)
我不明白你想要完成什么,但我几乎肯定你会以错误的方式解决它。我的一些观察结果:
我不明白你为什么要动态生成这些算术方法。你没有做任何特定于实例的事情,所以我不明白为什么你不会在课堂上定义它们。
他们工作的唯一原因是因为IshyNum
是一个旧式的课程;这不是一件好事,因为旧式类已被长期弃用,并不像新式类那样好。 (我稍后会解释为什么你应该对此特别感兴趣。)
如果你想自动化为多个方法做同样事情的过程(在这种情况下可能不值得),你可以在类定义块之后立即执行此操作。
map
来执行此操作。 map
用于列表;用它来副作用是愚蠢的。只需使用正常的for循环。如果您想使用合成在使用合成时自动将大量方法引用到同一属性,请使用__getattr__
并重定向到该属性的方法。
不要继承dict
。继承内置类型没有什么好处。事实证明它比它的价值更令人困惑,而且你不会重复使用它。
dict
。如果不是,请尝试发布您的真实用例。这是你真正想知道的:
当您继承dict
时,您正在制作new-style class。 IshyNum
是旧式类,因为它不继承object
(或其子类之一)。
新风格的课程已经成为Python十年来的旗舰类,并且是你想要使用的。在这种情况下,它们实际上导致您的技术不再起作用。但这很好,因为您发布的代码中没有任何理由在每个实例级别上设置魔术方法,而且几乎没有理由要这样做。
答案 3 :(得分:2)
对于新式类,Python在执行添加时不检查实例的__add__
方法,而是检查类。问题是您将__add__
方法(以及所有其他方法)绑定到实例作为绑定方法,而不是作为未绑定方法绑定到类。 (这也适用于其他特殊方法,您只能将它们附加到类,而不是实例)。因此,您可能希望使用元类来实现此功能(尽管我认为这是一件非常尴尬的事情,因为明确说明这些方法更具可读性)。无论如何,这是一个元类的例子:
import operator
class OperatorMeta(type):
def __new__(mcs, name, bases, attrs):
for opname in ["add", "sub", "mul", "div"]:
op = getattr(operator, opname)
attrs["__%s__" % opname] = mcs._arithmetic_func_factory(op)
return type.__new__(mcs, name, bases, attrs)
@staticmethod
def _arithmetic_func_factory(op):
def func(self, other):
return op(self.num, other)
return func
class IshyNum(dict):
__metaclass__ = OperatorMeta
def __init__(self, n):
dict.__init__(self)
self.num=n
if __name__=="__main__":
number=IshyNum(5)
print number+5
print number/2
print number*3
print number-3