让Python类与一元运算符配合使用

5

代码如下:

>>> class Negative: 
...      pass

>>> class Positive:
...    @classmethod
...    def __neg__(cls):
...        return Negative

所以我尝试一下。

>>> -Positive is Negative
TypeError: bad operand type for unary -: 'type'

这个可以正常工作。

>>> -Positive() is Negative
True

同样,对于其他一元运算符及其相关的“魔术”方法(例如~__invert__+__pos__等),也是如此。
它为什么适用于实例而不适用于类?我该如何让它工作?
编辑:我已按建议修改代码,将魔术方法移动到元类中。
class Negative: pass

class PositiveMeta(type):
    def __neg__(cls):
        return Negative

class Positive(metaclass=PositiveMeta): pass

2
一个类的类是它的元类。在自定义元类中定义方法,而不是在类方法中定义。只有当魔术方法在类中定义时才被识别。 - Mad Physicist
@Jean-FrançoisFabre为什么不呢?它们只需要进入元类即可生效。 - Mad Physicist
不错。Python面向对象的威严总是让我惊叹不已。来点疯狂的操作:为你的类定义一些二元运算符。让它们相互除和指数运算。 - Mad Physicist
@MadPhysicist 是的,那听起来像是猜测,但是我自己想不到。 - Jean-François Fabre
@Jean-FrançoisFabre 这只是猜测。我相信 Python 的一致性和普遍的强大功能 :) - Mad Physicist
显示剩余6条评论
3个回答

5
您的代码无法按照最初编写的方式工作,原因是您不能在实例中定义魔术方法。根据文档所述:

对于自定义类,只有在对象的类型上定义的特殊方法才能保证隐式调用特殊方法的正确性,而不是在对象的实例字典中定义。

这适用于类(它们是某些元类的实例)以及“常规”对象。从这个意义上讲,这个问题与以下任何一个等价:覆盖实例上的特殊方法为什么Python的bool内置函数只查看类级别的__bool__方法分配(而不是定义)__getitem__魔术方法会破坏索引
使用@classmethod装饰您的魔术方法类似于将通过__get__获取的绑定方法分配给实例。在这两种情况下,Python仅忽略未在类中定义的任何描述符。
这也是-Positive() is Negative有效的原因。当您对Positive的实例取反时,解释器会在类中查找__neg__。在这里装饰使用@classmethod是完全多余的,因为您无论如何都会忽略输入参数。但现在您有了一个返回类对象的魔术方法。
要在类对象上正确定义魔术方法,您需要在元类上定义它。
class MetaPositive(type):
    def __neg__(self):
        return Negative

class Negative: pass

class Positive(metaclass=MetaPositive): pass

有趣的是,这不仅限于一元运算符。您可以在元类上定义任何双下划线方法,并使您的类支持相应的操作:

class MetaPositive(type):
    def __gt__(self, other):
        if other is Negative:
            return True
        return False

现在你可以使用>运算符来比较你的类。我并不是在暗示你应该做这样的事情,但这个可能性绝对存在。
问题依旧存在,就像经常发生的那样,为什么你首先要做这样的事情。

感谢更完整的解释。我移动了已接受的答案,因为您的评论最初让我回到正确的轨道上。实际上,我已经尝试通过元类来做到这一点,但是由于对Python3的新手,我没有意识到旧语法(__metaclass__ = PositiveMeta)没有起作用,而错误消息使我失去了方向(错误的操作类型)。您的评论证实了我的某些假设是正确的,而其他假设需要重新检查。您发现了错误并表示感谢。 - Michael Ekoka
@mike_e。好的,我的答案只适用于Python 3。感谢您选择它。虽然我同意它提供了更详细的解释,但我也必须给其他答案以信用。它们证实了最初只是我个人的非常有教养的猜测,这是在没有访问解释器的移动设备上进行的。 - Mad Physicist

3
这似乎是有效的:
class Negative: pass

class PositiveMeta(type):
    def __neg__(self):
        return Negative

class Positive(metaclass=PositiveMeta):
    pass

print(-Positive is Negative)  # prints True

你的示例中为什么要使用classmethod? - Mad Physicist
我的意思是,你在展示定义__neg__的正确方式之后,为什么还要费心定义__pos__的方式? - Mad Physicist
它是从原帖的代码复制而来的。然而,它并不必要且可能会让未来的读者感到困惑,因此我编辑了我的回答。 - asherbret
这是完全适得其反的。回答问题的整个目的是修复原始帖子的代码,而不是使其更加复杂化。 - Mad Physicist

3
尝试这个:
class Negative:
    pass

class meta(type):
    def __neg__(cls):
        return Negative

class Positive(metaclass=meta):
    pass

-Positive
#output __main__.Negative

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接