list() 与 Python 3.5+ 中的可迭代拆包
list() vs iterable unpacking in Python 3.5+
list(iterable)
和 [*iterable]
在支持后者的 Python 版本中有什么实际区别吗?
您可以使用标准库模块dis
来研究函数生成的字节码。在这种情况下:
import dis
def call_list(x):
return list(x)
def unpacking(x):
return [*x]
dis.dis(call_list)
# 2 0 LOAD_GLOBAL 0 (list)
# 2 LOAD_FAST 0 (x)
# 4 CALL_FUNCTION 1
# 6 RETURN_VALUE
dis.dis(unpacking)
# 2 0 LOAD_FAST 0 (x)
# 2 BUILD_LIST_UNPACK 1
# 4 RETURN_VALUE
所以有一个区别,它不仅是加载全局定义的名称list
,不需要在解包时发生。所以它归结为内置 list
函数的定义方式以及 BUILD_LIST_UNPACK
究竟做了什么。
请注意,与为此编写标准列表理解相比,两者实际上代码要少得多:
def list_comp(x):
return [a for a in x]
dis.dis(list_comp)
# 2 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f65356198a0, file "<ipython-input-46-dd71fb182ec7>", line 2>)
# 2 LOAD_CONST 2 ('list_comp.<locals>.<listcomp>')
# 4 MAKE_FUNCTION 0
# 6 LOAD_FAST 0 (x)
# 8 GET_ITER
# 10 CALL_FUNCTION 1
# 12 RETURN_VALUE
list(x)
是一个函数,[*x]
是一个表达式。您可以重新分配 list
,并使其执行其他操作(但您不应该这样做)。
谈到 cPython,b = list(a)
转换为这个字节码序列:
LOAD_NAME 1 (list)
LOAD_NAME 0 (a)
CALL_FUNCTION 1
STORE_NAME 2 (b)
相反,c = [*a]
变为:
LOAD_NAME 0 (a)
BUILD_LIST_UNPACK 1
STORE_NAME 3 (c)
所以你可以争辩说 [*a]
可能 稍微更有效率,但略微如此。
由于 [*iterable]
正在解包,它接受 类赋值 语法,这与 list(iterable)
:
不同
>>> [*[]] = []
>>> list([]) = []
File "<stdin>", line 1
SyntaxError: can't assign to function call
您可以阅读更多相关内容 here(虽然没有用)。
您也可以使用 list(sequence=iterable)
,即使用关键字参数:
>>> list(sequence=[])
[]
再次not useful。
做同样事情的两个构造之间总是会有一些差异。问题是,我不会说这种情况下的差异实际上 实用 。两者都是采用可迭代对象的表达式,遍历它,然后从中创建一个列表。
契约相同:输入是一个可迭代的输出是一个由可迭代元素填充的列表。
是的,list
可以重新绑定到不同的名称; list(it)
是函数调用,[*it]
是列表显示; [*it]
对于较小的迭代更快,但通常对于较大的迭代执行相同。哎呀,甚至可以说 [*it]
少了 3 次击键。
这些实用吗?在尝试从可迭代对象中获取列表时,我会想到它们吗?好吧,也许击键是为了保持在 79 个字符以下并让 linter 关闭它。
显然在 CPython 中存在性能差异,其中 [*a] 过度分配而 list() 没有:
list(iterable)
和 [*iterable]
在支持后者的 Python 版本中有什么实际区别吗?
您可以使用标准库模块dis
来研究函数生成的字节码。在这种情况下:
import dis
def call_list(x):
return list(x)
def unpacking(x):
return [*x]
dis.dis(call_list)
# 2 0 LOAD_GLOBAL 0 (list)
# 2 LOAD_FAST 0 (x)
# 4 CALL_FUNCTION 1
# 6 RETURN_VALUE
dis.dis(unpacking)
# 2 0 LOAD_FAST 0 (x)
# 2 BUILD_LIST_UNPACK 1
# 4 RETURN_VALUE
所以有一个区别,它不仅是加载全局定义的名称list
,不需要在解包时发生。所以它归结为内置 list
函数的定义方式以及 BUILD_LIST_UNPACK
究竟做了什么。
请注意,与为此编写标准列表理解相比,两者实际上代码要少得多:
def list_comp(x):
return [a for a in x]
dis.dis(list_comp)
# 2 0 LOAD_CONST 1 (<code object <listcomp> at 0x7f65356198a0, file "<ipython-input-46-dd71fb182ec7>", line 2>)
# 2 LOAD_CONST 2 ('list_comp.<locals>.<listcomp>')
# 4 MAKE_FUNCTION 0
# 6 LOAD_FAST 0 (x)
# 8 GET_ITER
# 10 CALL_FUNCTION 1
# 12 RETURN_VALUE
list(x)
是一个函数,[*x]
是一个表达式。您可以重新分配 list
,并使其执行其他操作(但您不应该这样做)。
谈到 cPython,b = list(a)
转换为这个字节码序列:
LOAD_NAME 1 (list)
LOAD_NAME 0 (a)
CALL_FUNCTION 1
STORE_NAME 2 (b)
相反,c = [*a]
变为:
LOAD_NAME 0 (a)
BUILD_LIST_UNPACK 1
STORE_NAME 3 (c)
所以你可以争辩说 [*a]
可能 稍微更有效率,但略微如此。
由于 [*iterable]
正在解包,它接受 类赋值 语法,这与 list(iterable)
:
>>> [*[]] = []
>>> list([]) = []
File "<stdin>", line 1
SyntaxError: can't assign to function call
您可以阅读更多相关内容 here(虽然没有用)。
您也可以使用 list(sequence=iterable)
,即使用关键字参数:
>>> list(sequence=[])
[]
再次not useful。
做同样事情的两个构造之间总是会有一些差异。问题是,我不会说这种情况下的差异实际上 实用 。两者都是采用可迭代对象的表达式,遍历它,然后从中创建一个列表。
契约相同:输入是一个可迭代的输出是一个由可迭代元素填充的列表。
是的,list
可以重新绑定到不同的名称; list(it)
是函数调用,[*it]
是列表显示; [*it]
对于较小的迭代更快,但通常对于较大的迭代执行相同。哎呀,甚至可以说 [*it]
少了 3 次击键。
这些实用吗?在尝试从可迭代对象中获取列表时,我会想到它们吗?好吧,也许击键是为了保持在 79 个字符以下并让 linter 关闭它。
显然在 CPython 中存在性能差异,其中 [*a] 过度分配而 list() 没有: