在Python类中重载__or__运算符

6 投票
2 回答
8859 浏览
提问于 2025-04-17 16:39

假设我在用Python生成一个随机的水果篮子。首先,我创建了这个篮子:

basket = FruitBasket()

现在我想指定一些特定的水果组合可以出现在这个篮子里。假设我这个人很挑剔,篮子里要么全是苹果和石榴,要么是橙子和葡萄柚,或者只放香蕉。

我在研究Python的运算符重载,发现我可以定义 __or____and__ 来实现我想要的功能。我觉得我可以这样做:

basket.fruits = (Apple() & Pomegranate()) | (Banana()) | (Orange() & Grapefruit())

这样做是没问题的,我创建了两个类(OrAnd)。当调用 __or____and__ 时,我只需要返回一个新的 OrAnd 对象:

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 个回答

1

你不能把特殊的方法(也叫钩子方法)作为类的方法添加到类里面,因为这些方法总是根据当前对象的类型来查找的。对于实例来说,是在类上查找;而对于类本身,它们是通过 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
5

__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'>)

撰写回答