Python中字典类的动态运算符重载
我有一个类,它可以动态地重载一些基本的算术运算符,像这样...
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)
或者其他的东西,才能让它正常工作。为什么会这样呢?
4 个回答
对于新式类,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
一般来说,别在实例上设置 __ 方法——这些方法只支持在类上使用。(在这个例子中,问题在于它们在旧式类上能工作。别用旧式类)。
你可能想用元类,而不是你现在这样奇怪的做法。
这里有一个关于元类的教程: http://www.voidspace.org.uk/python/articles/metaclasses.shtml
答案在于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
而且,这种语义上的差异意味着在第一个例子中,我可以用一个参数定义我的本地加法方法(它使用的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
我有时候会想到元类,而简单的解决方案可能更好 :)