在python中何时使用运算符重载的经验法则

时间:2009-10-12 01:00:03

标签: python operator-overloading

从我记得的C ++课程中,教授说运算符重载很酷,但是因为需要相对大量的思考和代码来覆盖所有的最终情况(例如,当重载+时你也可能想要重载+++=,并确保处理最终情况,比如将对象添加到自身等等。),您应该只考虑在此功能会对其产生重大影响的情况下你的代码,比如在数学应用程序中重载矩阵类的运算符。

这同样适用于python吗?你会建议在python中覆盖运算符行为吗?你可以给我什么经验法则?

4 个答案:

答案 0 :(得分:25)

当您创建一个属于现有“抽象基类”(ABC)的新类时,运算符重载非常有用 - 实际上,标准库模块collections中的许多ABC依赖于存在某些特殊方法(和特殊方法,一个名称以双下划线开头和结尾的方式AKA“dunders”,正是你在Python中执行运算符重载的方式)。这提供了良好的入门指导。

例如,Container必须覆盖特殊方法__contains__,即成员资格检查运算符item in container(如,if item in container: - 不要与依赖for! - 的for item in container:语句__iter__混淆。 同样,Hashable必须覆盖__hash__Sized必须覆盖__len__SequenceMapping必须覆盖__getitem__等等。 (此外,ABC可以为您的班级提供mixin功能 - 例如,SequenceMapping都可以根据您提供的__contains__覆盖提供__getitem__,从而自动将您的班级设为Container)。

除了collections之外,如果您的新类“是数字”,您将希望覆盖特殊方法(即提供运算符重载)。存在其他特殊情况,但抵制过度运算符“只是为了酷”的诱惑,没有与“正常”含义的语义连接,因为C ++的流为<<>>以及Python字符串(在Python 2.*,幸运的是不在3.*中;-)为%做 - 当这些运算符不再意味着“位移”或“除法余数”时,你'重新引起混乱。一个语言的标准库可以逃脱它(尽管它不应该;-),但除非你的库像语言的标准库一样广泛,否则混乱会受到伤害! - )

答案 1 :(得分:12)

我编写了大量超载的软件,最近我对此政策感到遗憾。我会这样说:

只有重载操作符,如果它是自然的,预期的事情,并且没有任何副作用。

因此,如果你创建一个新的RomanNumeral类,那么重载加法和减法等是有意义的。但是不要重载它,除非它是自然的:为{{1}定义加法和减法是没有意义的}或Car对象。

另一个经验法则:不要超载Vehicle 。如果两个对象是相同的,实际测试它会非常困难(尽管不是不可能)。我犯了这个错误并付了很长时间。

至于何时超载==+=等,我实际上会说:如果您对该功能有很多需求,则只会使其他运算符超载。有一种方法比五种方法更容易。当然,这意味着有时您必须编写++而不是x = x + 1,但如果更清楚,可以使用更多代码。

一般情况下,就像许多“花哨”的功能一样,很容易认为你想要的东西,当你不真的,实现一堆东西,没有注意到副作用,然后后来弄清楚。在保守派方面犯了错误。

编辑:我想添加关于重载x += 1的解释性说明,因为似乎各种评论者误解了这一点,并且它让我感到震惊。是的,==存在,但这是一个不同的操作。假设我有一个对象is,它来自我的自定义类,或者是一个整数。我想知道x是否为数字500.但如果您设置x,然后再测试x = 500,则会得到x is 500,因为Python缓存数字的方式。使用False,它将返回50。但是您无法使用True,因为如果is是您班级的一个实例,您可能希望x == 500返回True。混乱?当然。但这是成功超载运营商需要了解的细节。

答案 2 :(得分:5)

这是一个使用按位或操作来模拟unix管道的示例。这是大多数经验法则的反例。

我刚发现Lumberjack在实际代码中使用了这种语法



class pipely(object):
    def __init__(self, *args, **kw):
        self._args = args
        self.__dict__.update(kw)

    def __ror__(self, other):
        return ( self.map(x) for x in other if self.filter(x) )

    def map(self, x):
        return x

    def filter(self, x):
        return True

class sieve(pipely):
    def filter(self, x):
        n = self._args[0]
        return x==n or x%n

class strify(pipely):
    def map(self, x):
        return str(x)

class startswith(pipely):
    def filter(self, x):
        n=str(self._args[0])
        if x.startswith(n):
            return x

print"*"*80
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | strify() | startswith(5):
    print i

print"*"*80
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | pipely(map=str) | startswith(5):
    print i

print"*"*80
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | pipely(map=str) | pipely(filter=lambda x: x.startswith('5')):
    print i

答案 3 :(得分:3)

Python的重载通常比C ++更“安全” - 例如,赋值运算符不能重载,+=具有合理的默认实现。

但是,在某些方面,Python中的重载仍然像C ++一样“破坏”。程序员应该限制为不相关的目的“重用”运算符的愿望,例如C ++重新使用位移操作来执行字符串格式化和解析。不要使用与实现不同的语义重载操作符,只是为了获得更漂亮的语法。

现代Python风格强烈反对“流氓”重载,但语言和标准库的许多方面都保留了命名不佳的运算符以实现向后兼容。例如:

  • %:模数和字符串格式
  • +:添加和序列连接
  • *:乘法和序列重复

那么,经验法则?如果您的运营商实施会让人感到惊讶,请不要这样做。