The code:
>>> class Negative:
... pass
>>> class Positive:
... @classmethod
... def __neg__(cls):
... return Negative
So I try
>>> -Positive is Negative
TypeError: bad operand type for unary -: 'type'
this works though
>>> -Positive() is Negative
True
The same goes with other unary operators and their related "magic" methods (e.g. ~
and __invert__
, +
and __pos__
, etc).
Why does it work with instances but not with classes? How can I get it to work?
Edit: I have modified the code as suggested to move the magic method in a metaclass.
class Negative: pass
class PositiveMeta(type):
def __neg__(cls):
return Negative
class Positive(metaclass=PositiveMeta): pass
The reason your code does not work as originally written is that you can not define a magic method in an instance. According to the docs:
For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.
This applies to classes (which are instances of some metaclass), just as much as it does to "regular" objects. In that sense, this question is equivalent to any of the following: Overriding special methods on an instance, Why does Python's bool builtin only look at the class-level __bool__ method, Assigning (instead of defining) a __getitem__ magic method breaks indexing.
Decorating your magic method with @classmethod
is analagous to assigning a bound method obtained through __get__
to an instance. In both cases, Python simply ignores any descriptors not defined in the class.
That is also the reason that -Positive() is Negative
works. When you negate an instance of Positive
, the interpreter looks up __neg__
in the class. Decorating with @classmethod
is totally superfluous here since you ignore the input parameters anyway. But now you have a magic method that returns a class object.
To properly define a magic method on your class object, you need to define it on the metaclass:
class MetaPositive(type):
def __neg__(self):
return Negative
class Negative: pass
class Positive(metaclass=MetaPositive): pass
The interesting thing here is that this is not restricted to unary operators. You can define any dunder method on the metaclass and have your classes support the corresponding operations:
class MetaPositive(type):
def __gt__(self, other):
if other is Negative:
return True
return False
Now you can use the >
operator to compare your classes. I'm not implying that you should ever do something like that, but the possibility is definitely there.
The question remains, as it often does, as to why you would want to do something like this in the first place.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With