Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 342
Darryl Lickt Avatar asked Feb 21 '13 17:02

Darryl Lickt


1 Answers

__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'>)
like image 144
Chris Morgan Avatar answered Oct 03 '22 11:10

Chris Morgan