如果运算符有副作用会出什么问题?

What can go wrong if operators have side-effects?

我遇到了一个 Python 库,它定义了一个类似于以下内容的 class,其中 >> 运算符被重载以产生全局副作用:

from collections import defaultdict

class V(object):
    """A Vertex in a graph."""

    graph = defaultdict(list)  # accessed globally

    def __init__(self, label):
        self.label = label

    def __rshift__(self, other):
        V.graph[self].append(other)
        return other

    def __repr__(self):
        return 'V(%r)' % (self.label,)

图书馆这样做是因为 "it gives the user some nice syntax" 通过写下简单的表达式来象征性地建立网络,例如:

a = V('a')
b = V('b')
c = V('c')
a >> b >> c >> c

为了建立一个网络,其中 a 连接到 bb 连接到 c,后者循环连接到它自己。

+*@ 运算符中也有类似的机制。所有这些行为都记录在案并符合用户的预期。

但是,是否会发生任何奇怪的事情,您认为表达式不应该被求值——但实际上是——或者反之亦然?也就是说,Python 的执行模型是否假设某些表达式没有任何副作用?我想知道有没有先例,或者需要用户采取什么特别的预防措施。

您专门询问了意外评估或非评估。 Python 不假定任何用户定义的运算符都是无副作用的,因此它不会跳过 a >> b 错误的假设,即它什么都不做。当人们假设某些函数不做某些事情时,他们偶尔会遇到问题 - 例如,假设一个接受迭代的函数不会尝试 len - 但大多数此类问题同样适用于 >> 或一种方法。

您可能会在替代交互式环境做出假设时遇到问题 - 例如,我可以想象 Tab 补全会遇到问题,试图评估它不应该评估的东西 - 但在实际的 Python 程序中使用该模块应该没有这样的问题。

我期望在实际程序中最接近此类问题的是优先级问题。例如,C++使用<<进行打印会导致类似下面代码的问题:

std::cout << true ? "a" : "b";

which prints 1 而不是 a 因为 <<?: 结合得更紧。我可以想象这个库会出现类似的问题。

我不知道有任何其他 Python 图书馆这样做。

对我来说,一个奇怪或意想不到的例子如下:

a = V('a')
b = V('b')

def do_something(c=a>>b):
    return c>>c

do_something(a)

这可能会被认为会给出一个奇怪的结果(a >> aa >> b),因为无论最终是否使用它,都会计算默认参数。


再举一个例子,当使用 try/except 时,任何部分完成的评估都会导致一组不完整的副作用,无法倒带。

try:
    a >> b >> c
except NameError:
    # do something else

这里,错误处理可能需要处理 a >> b 仍然被评估的事实。


我不建议用户执行这些操作。我只是声称,如果用户这样做,那么他们可能会遇到令人惊讶、意外或难以调试的行为。