在Python类中重载__or__运算符
假设我在用Python生成一个随机的水果篮子。首先,我创建了这个篮子:
basket = FruitBasket()
现在我想指定一些特定的水果组合可以出现在这个篮子里。假设我这个人很挑剔,篮子里要么全是苹果和石榴,要么是橙子和葡萄柚,或者只放香蕉。
我在研究Python的运算符重载,发现我可以定义 __or__
和 __and__
来实现我想要的功能。我觉得我可以这样做:
basket.fruits = (Apple() & Pomegranate()) | (Banana()) | (Orange() & Grapefruit())
这样做是没问题的,我创建了两个类(Or
和 And
)。当调用 __or__
或 __and__
时,我只需要返回一个新的 Or
或 And
对象:
def __or__(self, other):
return Or(self, other)
def __and__(self, other):
return And(self, other)
我现在想弄明白的是,如何在不先实例化水果的情况下做到这一点?为什么我不能在基础的 Fruit
类上使用静态的 __or__
方法?我试过这样做,但不成功:
class Fruit(object):
@classmethod
def __or__(self, other):
return Or(self, other)
然后给水果赋值:
basket.fruits = (Apple & Pomegranate) | (Orange & Grapefruit) | (Banana)
结果我遇到了这样的错误:
TypeError: unsupported operand type(s) for |: 'type' and 'type'
有没有什么想法可以让这个工作?
2 个回答
你不能把特殊的方法(也叫钩子方法)作为类的方法添加到类里面,因为这些方法总是根据当前对象的类型来查找的。对于实例来说,是在类上查找;而对于类本身,它们是通过 type
来查找的。想了解更多原因,可以看看这个之前的回答。
这意味着你需要在 metaclass上实现这些方法;metaclass就像是类的类型:
class FruitMeta(type):
def __or__(cls, other):
return Or(cls, other)
def __and__(cls, other):
return And(cls, other)
那么对于Python 3:
class Fruit(metaclass=FruitMeta):
或者Python 2:
class Fruit(object):
__metaclass__ = FruitMeta
__or__
是根据对象的类型来查找的;对于一个 Fruit
实例来说,它的类型就是 Fruit
;而对于 Fruit
本身来说,它的类型是 type
。不过,你可以通过使用元类来改变 Fruit
的类型:
class FruitMeta(type):
def __or__(self, other):
return Or(self, other)
class Fruit(object):
__metaclass__ = FruitMeta
(对于 Python 3,语法应该是 class Fruit(metaclass=FruitMeta):
。)
这样做就能实现你想要的效果。比如 Apple | Banana
(假设这两个是 Fruit
的子类)会产生 Or(Apple, Banana)
。
不过,要非常小心这种设计方式。这有点像魔法,容易让人感到困惑。
(完整示例,使用 Python 2.7:)
>>> class Or(object):
... def __init__(self, a, b):
... self.a = a
... self.b = b
... def __repr__(self):
... return 'Or({!r}, {!r})'.format(self.a, self.b)
...
>>> class FruitMeta(type):
... def __or__(self, other):
... return Or(self, other)
...
>>> class Fruit(object):
... __metaclass__ = FruitMeta
...
>>> class Apple(Fruit): pass
...
>>> class Banana(Fruit): pass
...
>>> Apple | Banana
Or(<class '__main__.Apple'>, <class '__main__.Banana'>)