Darryl Lickt Darryl Lickt - 3 months ago 15
Python Question

Overriding __or__ operator on python classes

As a contrived example, suppose I'm generating a random fruit basket in python. I create the basket:

basket = FruitBasket()


Now I want to specify specific combinations of fruit that can occur in the basket. Suppose I'm a very picky dude, and the basket either has to be full of apples and pomegranates, oranges and grapefruit, or only bananas.

I was reading up on python operator overloading, and it seems like I could define
__or__
and
__and__
to get the behavior I want. I think I could do something like this:

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


This works just fine making two classes (
Or
and
And
). When
__or__
or
__and__
get called, I just have return a new
Or
or
And
object:

def __or__(self, other):
return Or(self, other)

def __and__(self, other):
return And(self, other)


What I'm trying to figure out is how do I do this without having to instantiate the fruits first? Why can't I use a static
__or__
method on the base
Fruit
class? I've tried this but it doesn't work:

class Fruit(object):
@classmethod
def __or__(self, other):
return Or(self, other)


and assigning the fruit:

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


I get an error like this:

TypeError: unsupported operand type(s) for |: 'type' and 'type'


Any thoughts on how to make this work?

Answer

__or__ is looked up on the type of the object; for a Fruit instance, that'll be Fruit; for Fruit, that is type. You can change the type of Fruit, though, by using a metaclass:

class FruitMeta(type):

    def __or__(self, other):
        return Or(self, other)


class Fruit(object):
    __metaclass__ = FruitMeta

(For Python 3, the syntax is class Fruit(metaclass=FruitMeta): instead.)

This then does all that you want. Apple | Banana (assuming these two to be subclasses of Fruit) will produce Or(Apple, Banana).

Be very careful with this sort of design, though. It's tending into the realm of magic and may easily cause confusion.

(Complete demonstration, in 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'>)