如果运算符有副作用会出什么问题?
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
连接到 b
,b
连接到 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 >> a
和 a >> b
),因为无论最终是否使用它,都会计算默认参数。
再举一个例子,当使用 try
/except
时,任何部分完成的评估都会导致一组不完整的副作用,无法倒带。
try:
a >> b >> c
except NameError:
# do something else
这里,错误处理可能需要处理 a >> b
仍然被评估的事实。
我不建议用户执行这些操作。我只是声称,如果用户这样做,那么他们可能会遇到令人惊讶、意外或难以调试的行为。
我遇到了一个 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
连接到 b
,b
连接到 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 >> a
和 a >> b
),因为无论最终是否使用它,都会计算默认参数。
再举一个例子,当使用 try
/except
时,任何部分完成的评估都会导致一组不完整的副作用,无法倒带。
try:
a >> b >> c
except NameError:
# do something else
这里,错误处理可能需要处理 a >> b
仍然被评估的事实。
我不建议用户执行这些操作。我只是声称,如果用户这样做,那么他们可能会遇到令人惊讶、意外或难以调试的行为。