Reputation: 203
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?
Upvotes: 6
Views: 10200
Reputation: 1124110
You cannot add special (hook) methods as class methods to classes, because they are always looked up on the type of the current object; for instances that is on the class, for classes, they are looked up on type
instead. See this previous answer for the motivations as to why that is.
That means you need to implement this on the metaclass instead; a metaclass acts as the class's 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
Upvotes: 1
Reputation: 90852
__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'>)
Upvotes: 5