python - 位置参数和关键字参数的求值顺序

标签 python function arguments python-internals operator-precedence

考虑这个人为的*示例:

def count(one, two, three):
    print one
    print two
    print three

三是你要数的数,数的数 应该是三个。

>>> x = [1, 2, 3]
>>> count(*map(int, x), three=x.pop())
1
2
3

你不可数四,

>>> x = [1, 2, 3, 4]
>>> count(*map(int, x), three=x.pop())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: count() got multiple values for keyword argument 'three'

你也不要数二,除非你接着数三。

>>> x = [1, 2]
>>> count(*map(int, x), three=x.pop())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: count() takes exactly 3 arguments (2 given)

五个就出来了。

>>> x = [1, 2, 3, 4, 5]
>>> count(*map(int, x), three=x.pop())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: count() takes exactly 3 arguments (5 given)

读完 this question 后, 我实际上认为 x = [1, 2] 是唯一有效的,因为

  • 首先,将评估 map(int, x),将 one 设置为 1 并设置 two2
  • 然后,x 仍然是 [1, 2]x.pop() 将会被求值,并且 三个 也设置为 2

我对 x = [1, 2, 3] 的期望是得到我实际看到的错误 对于 x = [1, 2, 3, 4]

这是怎么回事?为什么这些论点似乎不是从左到右评估的?关键字参数是否首先被评估?


*实际上我的真实代码对应于x = [1, 2, 3],它有效,但我不确定它是否安全,阅读后另一个问题我认为它实际上不应该起作用。

如果重要的话,我正在使用 Python 2.7。

最佳答案

Python 2.7

如果我们查看与为函数调用创建 AST( ast_for_call ) 相关的 CPython 源代码,则参数求值的顺序如下:

return Call(func, args, keywords, vararg, kwarg, func->lineno,
                func->col_offset, c->c_arena);

即。 args --> 关键字 --> vararg --> kwarg

因此,在您的情况下,首先评估关键字参数,然后评估基于星号的表达式(vararg)。

字节码:

>>> dis.dis(lambda: func(1, 2, *('k', 'j', 'l'), z=1, y =2, three=x.pop(), **{kwarg:1}))
  1           0 LOAD_GLOBAL              0 (func)
              3 LOAD_CONST               1 (1)         # arg
              6 LOAD_CONST               2 (2)         # arg
              9 LOAD_CONST               3 ('z')       # keyword
             12 LOAD_CONST               1 (1)
             15 LOAD_CONST               4 ('y')       # keyword
             18 LOAD_CONST               2 (2)
             21 LOAD_CONST               5 ('three')   # keyword
             24 LOAD_GLOBAL              1 (x)
             27 LOAD_ATTR                2 (pop)
             30 CALL_FUNCTION            0
             33 LOAD_CONST               9 (('k', 'j', 'l')) #vararg
             36 BUILD_MAP                1
             39 LOAD_CONST               1 (1)
             42 LOAD_GLOBAL              3 (kwarg)     #kwarg
             45 STORE_MAP
             46 CALL_FUNCTION_V

因此,在您的情况下,将首先发生 pop() 调用,然后是 varargs 评估。

因此,如果 twokwargs 的一部分,那么我们将收到 map 错误:

>>> x = [1, 2, 3]
>>> count(*map(float, x), **{'three': x.pop()})
Traceback (most recent call last):
  File "<ipython-input-133-e8831565af13>", line 1, in <module>
    count(*map(float, x), **{'three': x.pop()})
TypeError: count() got multiple values for keyword argument 'three'

如果我们*懒惰地这样做,它就会起作用:

>>> x = [1, 2, 3]
>>> count(*(float(y) for y in x), **{'three': x.pop()})
1.0, 2.0, 3

*生成器工作而map或列表理解失败的原因在最后解释。


Python 3.5

ast_for_call这里的函数只维护两个列表:argskeywords

这里,varargs 被插入到 args 列表中,而 kwargs 则被插入到 keywords 列表中。因此,最终的调用如下所示:

return Call(func, args, keywords, func->lineno, func->col_offset, c->c_arena);

字节码:

>>> dis.dis(lambda: func(1, 2, *('k', 'j', 'l'), z=1, y =2, three=x.pop(), **{kwarg:1}))
  1           0 LOAD_GLOBAL              0 (func)
              3 LOAD_CONST               1 (1)
              6 LOAD_CONST               2 (2)
              9 LOAD_CONST               9 (('k', 'j', 'l'))
             12 LOAD_CONST               6 ('z')
             15 LOAD_CONST               1 (1)
             18 LOAD_CONST               7 ('y')
             21 LOAD_CONST               2 (2)
             24 LOAD_CONST               8 ('three')
             27 LOAD_GLOBAL              1 (x)
             30 LOAD_ATTR                2 (pop)
             33 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             36 LOAD_GLOBAL              3 (kwarg)
             39 LOAD_CONST               1 (1)
             42 BUILD_MAP                1
             45 CALL_FUNCTION_VAR_KW   770 (2 positional, 3 keyword pair)
             48 RETURN_VALUE

现在,如果生成 varargs 的表达式是惰性的,事情会变得有点令人兴奋:

>> def count(one, two, three):
        print (one, two, three)
...
>>> x = [1, 2, 3]
>>> count(*map(float, x), three=x.pop())  # map is lazy in Python 3
1.0 2.0 3
>>> x = [1, 2, 3]
>>> count(*[float(y) for y in x], three=x.pop())
Traceback (most recent call last):
  File "<ipython-input-25-b7ef8034ef4e>", line 1, in <module>
    count(*[float(y) for y in x], three=x.pop())
TypeError: count() got multiple values for argument 'three'

字节码:

>>> dis.dis(lambda: count(*map(float, x), three=x.pop()))
  1           0 LOAD_GLOBAL              0 (count)
              3 LOAD_GLOBAL              1 (map)
              6 LOAD_GLOBAL              2 (float)
              9 LOAD_GLOBAL              3 (x)
             12 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             15 LOAD_CONST               1 ('three')
             18 LOAD_GLOBAL              3 (x)
             21 LOAD_ATTR                4 (pop)
             24 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             27 CALL_FUNCTION_VAR      256 (0 positional, 1 keyword pair)
             30 RETURN_VALUE
>>> dis.dis(lambda: count(*[float(y) for y in x], three=x.pop()))
  1           0 LOAD_GLOBAL              0 (count)
              3 LOAD_CONST               1 (<code object <listcomp> at 0x103b63930, file "<ipython-input-28-1cc782164f20>", line 1>)
              6 LOAD_CONST               2 ('<lambda>.<locals>.<listcomp>')
              9 MAKE_FUNCTION            0
             12 LOAD_GLOBAL              1 (x)
             15 GET_ITER
             16 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             19 LOAD_CONST               3 ('three')
             22 LOAD_GLOBAL              1 (x)
             25 LOAD_ATTR                2 (pop)
             28 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             31 CALL_FUNCTION_VAR      256 (0 positional, 1 keyword pair)
             34 RETURN_VALUE

延迟调用之所以有效,是因为在实际调用函数之前不会发生解包(也称为生成器的实际评估),因此在这种情况下 pop() 调用将首先删除 3,然后再删除稍后 map 上只会经过1、2。

但是,在列表理解的情况下,列表对象已经包含 3 个项目,然后即使 pop() 稍后删​​除了 3 个项目,我们仍然为第三个参数传递两个值。

关于python - 位置参数和关键字参数的求值顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33244776/

相关文章:

函数的 JavaScript 命名

c# - 传递指向类属性的指针

python - 在 if 语句中为 1 到 50 之间的任何数字制作通配符整数

java - 如何使用深度优先搜索列出电话簿中的号码?

c++ - 具有函数模板的递归函数

javascript - 获取函数中的变量

perl - 命令行参数变量@ARGV

r - 将 "..."参数中的参数赋予 R 中的右侧函数

python - 艰难地学习 Python ex 46. 无法创建工作模块! setup.py 是如何工作的?

python - 转置数据框和排序