【问题标题】:How to prove that parameter evaluation is "left to right" in Python?如何证明参数评估在 Python 中是“从左到右”的?
【发布时间】:2012-09-26 18:52:42
【问题描述】:

例如,在 JavaScript 中,我们可以编写这样的程序:

var a = 1;
testFunction(++a, ++a, a);
function testFunction(x, y, z){
      document.writeln("<br />x = " + x);
      document.writeln("<br />y = " + y);
      document.writeln("<br />z = " + z);
}

我们会得到一个输出:

x = 2
y = 3
z = 3

这意味着在 JavaScript 中参数是真正从左到右计算的。在 C 中我们会得到输出

x = 3
y = 3
z = 3

我想知道我们是否可以在 Python 中做同样的事情,还是因为它是按值传递参考语言所以不可能?

我编写了一个简单的程序,但我认为这并不能证明什么:

x = 2
def f(x, y, z):
    print(x, y, z)

f(x*2, x*2, x**2)
print(x)
4 4 4
2

当我调用函数参数时,Python 不允许我在函数参数中进行任何新的赋值(例如 f(x=4, x, x) 或类似的东西)。

【问题讨论】:

  • 实际上,在 C 中,你会得到 UB(因此输出可能因环境而异)。
  • “这意味着在 JavaScript 中参数是真正从左到右计算的。” – 不,这意味着对于此特定环境中此特定代码的此特定运行的此特定实现的此特定版本,参数的评估距离如此之远,以至于 似乎 他们被评估为左-向右。例如,它没有说明在不同 CPU 上的不同操作系统上运行的不同实现。
  • “在 C 中我们会得到输出”——不,我们不会。您展示的代码在 C 中具有未定义的行为,这意味着实现字面上允许任何它想要的。它可以打印11 7 5,它可以打印4 4 4,它什么都不打印,它可以格式化你的硬盘。所有这些都是规范允许的。

标签: python parameter-passing operator-precedence


【解决方案1】:
>>> def f(x, y): pass
...
>>> f(print(1), print(2))
1
2

【讨论】:

  • 短小精悍——非常好。
【解决方案2】:

反汇编函数调用。

>>> def foo():
...   bar(x+1, x+2, x+3)
... 
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (bar)
              3 LOAD_GLOBAL              1 (x)
              6 LOAD_CONST               1 (1)
              9 BINARY_ADD          
             10 LOAD_GLOBAL              1 (x)
             13 LOAD_CONST               2 (2)
             16 BINARY_ADD          
             17 LOAD_GLOBAL              1 (x)
             20 LOAD_CONST               3 (3)
             23 BINARY_ADD          
             24 CALL_FUNCTION            3
             27 POP_TOP             
             28 LOAD_CONST               0 (None)
             31 RETURN_VALUE        

【讨论】:

  • 哇,这是一个很酷的答案,但它更像是令人困惑。这是某种通用的参数评估顺序吗?
  • 不能说这行得通。你有什么要进口的吗?我得到NameError: name 'dis' is not defined
  • 没有忘记导入... O.O
  • 好吧,当我输入 dis.dis(foo) 时,我得到了这个 Traceback (most recent call last): File "&lt;pyshell#16&gt;", line 1, in &lt;module&gt; dis.dis(foo) NameError: name 'dis' is not defined。不知道为什么。
  • 即使稍作理解也能理解?想了想,欢迎解释……
【解决方案3】:

使用 Python 3:

>>> a = []
>>> f = print(
    a.append(1), a[:],
    a.append(2), a[:],
    a.append(3), a[:]
)
None [1] None [1, 2] None [1, 2, 3]

存档:

>>> a = []
>>> f = print(a.append(1), a, a.append(2), a, a.append(3), a)

奇怪的是(起初),这段代码产生了:

None [1, 2, 3] None [1, 2, 3] None [1, 2, 3]

不过,dis(f) 更清楚地说明了这一点:

>>> dis(f)

  1           0 LOAD_NAME                0 (print) #Loads the value of 'print' into memory. Precisely, the value is pushed to the TOS (Top of Stack)
    -->       3 LOAD_NAME                1 (a) #Loads the value of object 'a' 
              6 LOAD_ATTR                2 (append) #Loads the append attribute (in this case method)
              9 LOAD_CONST               0 (1) #Loads the constant 1
             12 CALL_FUNCTION            1 #a.append(1) is called
             15 LOAD_NAME                1 (a) #for print(...,a,...)
             18 LOAD_NAME                1 (a) #for the next a.append()
             21 LOAD_ATTR                2 (append) 
             24 LOAD_CONST               1 (2) 
             27 CALL_FUNCTION            1 #a.append(2)
             30 LOAD_NAME                1 (a) 
             33 LOAD_NAME                1 (a) 
             36 LOAD_ATTR                2 (append) 
             39 LOAD_CONST               2 (3) 
             42 CALL_FUNCTION            1 #a.append(3)
             45 LOAD_NAME                1 (a) #loads a to be used thrice by print
             48 CALL_FUNCTION            6 #calls print
             51 PRINT_EXPR                 #prints TOS and clears it
             52 LOAD_CONST               3 (None) #Loads None
             55 RETURN_VALUE             #Returns None

dis(f) 的输出是我们所期望的——从左到右的评估。本质上,这种“差异”是 print() 最后评估的结果。到那时,a 的值已更改为[1, 2, 3],并且相同的最终对象被打印三次。

如果我们将a 替换为a[:],我们会得到预期的结果。

【讨论】:

  • @Geo Papas:请记住,在 Python 2 中,print 不是函数调用。所以使用 Python 3 的 print() 函数。
  • 这个例子是 Python 2,检查你使用 Python 3 得到相同的结果(很确定你会)。
  • 对不起,但它不起作用我猜python 3有变化。我得到输出None [1, 2, 3] None [1, 2, 3] None [1, 2, 3]
  • @AnujGupta +1 非常好的答案我会接受这个,但 larsmans 有一个更简单和多汁的例子。嗯,我不能接受 2 个答案吗? :o
  • 如果您第一次使用from __future__ import print_function +1,这适用于 Python 2,很好的答案
【解决方案4】:

自定义类可以在这里提供帮助:

class Tester(object):
    "test object to reveal left to right evaluation"
    def __init__(self, value):
        self.value = value
    def __add__(self, value):
        print("adding ", value)
        return Tester(self.value + value)
    def __repr__(self):
        return repr(self.value)

当运行时:

--> t = Tester(7)
--> t
7
--> t = t + 7
adding  7
--> t
14
--> def blah(a, b, c):
...   print(a, b, c)
... 
--> blah(t+1, t+2, t+3)
adding  1
adding  2
adding  3
15 16 17

【讨论】:

    【解决方案5】:

    恕我直言,这也表明了这一点:

    >>> '{} {} {}'.format(x,x+1,x+2)
    '1 2 3'
    

    编辑:

    >>> def f(t):   return time.time()-t 
    ...         
    
    >>> t1=time.time(); '{:.4} {:.4} {:.4}'.format(f(t1),f(t1),f(t1))
    '5.007e-06 7.868e-06 9.06e-06'
    

    【讨论】:

    • 这个例子没有说明x+2x之前没有被评估。
    【解决方案6】:

    简答:从左到右

    示例: 由于这是我在进行算术运算时总是在脑海中浮现的问题(我应该转换为浮点数以及转换为哪个数字),因此提供了一个这方面的示例:

    >>> a = 1/2/3/4/5/4/3
    >>> a
    0
    

    当我们对整数进行除法时,它会得到更低的四舍五入也就不足为奇了。

    >>> a = 1/2/3/4/5/4/float(3)
    >>> a
    0.0
    

    如果我们将最后一个整数类型转换为浮点数,我们仍然会得到零,因为当我们的数字除以浮点数时,由于整数除法,我们已经变成了 0。

    >>> a = 1/2/3/float(4)/5/4/3
    >>> a
    0.0
    

    与上面相同的场景,但将浮点类型转换移动到更靠近左侧的位置。

    >>> a = float(1)/2/3/4/5/4/3
    >>> a
    0.0006944444444444445
    

    最后,当我们将第一个整数类型转换为浮点数时,结果就是所需的整数,因为从第一个除法开始,即最左边的除法,我们使用浮点数。

    额外 1:如果你想回答这个问题以提高算术评估,你应该检查this

    额外2:请注意以下情况:

    >>> a = float(1/2/3/4/5/4/3)
    >>> a
    0.0
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-06
      • 2019-10-26
      • 1970-01-01
      • 2014-03-26
      • 2014-12-02
      • 2021-02-20
      • 2011-06-09
      • 2016-08-30
      相关资源
      最近更新 更多