Overriding the __or__ operator on python classes

As a contrived example, suppose I create a random fruit basket in python. I create a basket:

basket = FruitBasket() 

Now I want to indicate specific combinations of fruits that may occur in the basket. Suppose I’m a very picky dude, and the basket should be full of apples and pomegranates, oranges and grapefruits, or just bananas.

I read about overloading the python operator, and it looks 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 great by making two classes ( Or and And ). When __or__ or __and__ , I simply 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 understand is how to do this without creating the first results? Why can't I use the static __or__ method in the Fruit base class? I tried this, but it does not work:

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

and fetal appropriation:

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

I get an error message:

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

Any thoughts on how to make this work?

+4
source share
2 answers

__or__ viewed the type of object; for the Fruit instance, which will be Fruit ; for Fruit , i.e. type . You can change the type of Fruit , however, using the 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): )

It does whatever you want. Apple | Banana Apple | Banana (assuming these two are subclasses of Fruit ), you get Or(Apple, Banana) .

Be very careful with this design. He seeks the realm of magic and can easily cause confusion.

(Full demo 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'>) 
+3
source

You cannot add special (hook) methods as class methods to classes because they always look for the type of the current object; for instances that belong to a class, for classes they are looked up instead of type . See this previous answer for motivation as to what it is.

This means that you need to implement this on metaclass ; metaclass acts as a class type:

 class FruitMeta(type): def __or__(cls, other): return Or(cls, other) def __and__(cls, other): return And(cls, other) 

then for Python 3:

 class Fruit(metaclass=FruitMeta): 

or Python 2:

 class Fruit(object): __metaclass__ = FruitMeta 
+1
source

All Articles