位置和关键字参数的评估顺序
Evaluation order of positional and keyword arguments
考虑这个人为设计的*示例:
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
,two
设置为 2
- 然后,
x
仍然 [1, 2]
,x.pop()
将被评估,并且 three
也设置为 2
。
我对 x = [1, 2, 3]
的期望是得到我实际看到的错误
x = [1, 2, 3, 4]
.
这是怎么回事?为什么论点似乎不是从左到右评估的?是否首先评估关键字参数?
*实际上我的真实代码对应于x = [1, 2, 3]
,它可以工作,但我不确定它是否安全,在阅读另一个问题后我认为它实际上不应该工作。
我正在使用 Python 2.7,如果有的话。
Python2.7
如果我们查看与为函数调用创建 AST(ast_for_call
) 相关的 CPython 源代码,参数评估的顺序结果为:
return Call(func, args, keywords, vararg, kwarg, func->lineno,
func->col_offset, c->c_arena);
即。参数 --> 关键字 --> 可变参数 --> 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
评估。
因此,如果 three
是 kwargs
的一部分,那么我们将得到 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
或列表理解失败的原因在最后解释。
Python3.5
这里的ast_for_call
函数只维护两个列表:args
和keywords
.
此处 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,然后再删除地图只会通过 1, 2.
但是,在列表理解的情况下,列表对象已经包含 3 个项目,然后即使 pop()
稍后删除了 3 个,我们仍然为第三个参数传递两个值。
考虑这个人为设计的*示例:
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
,two
设置为2
- 然后,
x
仍然[1, 2]
,x.pop()
将被评估,并且three
也设置为2
。
我对 x = [1, 2, 3]
的期望是得到我实际看到的错误
x = [1, 2, 3, 4]
.
这是怎么回事?为什么论点似乎不是从左到右评估的?是否首先评估关键字参数?
*实际上我的真实代码对应于x = [1, 2, 3]
,它可以工作,但我不确定它是否安全,在阅读另一个问题后我认为它实际上不应该工作。
我正在使用 Python 2.7,如果有的话。
Python2.7
如果我们查看与为函数调用创建 AST(ast_for_call
) 相关的 CPython 源代码,参数评估的顺序结果为:
return Call(func, args, keywords, vararg, kwarg, func->lineno,
func->col_offset, c->c_arena);
即。参数 --> 关键字 --> 可变参数 --> 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
评估。
因此,如果 three
是 kwargs
的一部分,那么我们将得到 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
或列表理解失败的原因在最后解释。
Python3.5
这里的ast_for_call
函数只维护两个列表:args
和keywords
.
此处 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,然后再删除地图只会通过 1, 2.
但是,在列表理解的情况下,列表对象已经包含 3 个项目,然后即使 pop()
稍后删除了 3 个,我们仍然为第三个参数传递两个值。