[英]Python list elements vs converting to tuple for string formatting
我在 codewars 上遇到了一个问题,我不确定这两种可能的解决方案之间有什么区别,一种是将列表转换为元组,另一种是指定输入列表的元素。
问题:将名称(字符串)列表转换为类似于 Facebook 用来显示喜欢的语句:“Alex 喜欢这个”、“Alex 和 John 喜欢这个”、“Alex、John 和其他 2 个喜欢这个”等。
使用 if-elif-etc 语句,这很简单:
if len(names) == 0:
output_string = "no one likes this"
elif len(names) == 1:
output_string = str(names[0]) + " likes this"
但在较长的姓名列表中,您可以选择:
elif len(names) == 2:
output_string = "%s and %s like this" % (names[0], names[1])
或者
elif len(names) == 3:
output_string = "%s, %s and %s like this" % tuple(names)
我的假设是使用names[0]
等计算效率更高,因为您不会在 memory 中为元组创建新的 object - 对吗?
CPython 优化规则通常基于您推送到 C 层(与字节码解释器相比)的工作量以及字节码指令的复杂程度; 对于低级别的绝对工作,解释器的固定开销往往会淹没实际工作,因此来自低级别语言经验的直觉并不适用。
不过,它很容易测试,尤其是使用ipython
的%timeit
魔法(在 WSLv2 下运行的 Alpine Linux 上的 Python 3.8.5 上完成的计时):
In [2]: %%timeit l = [1, 2, 3]
...: tuple(l)
97.6 ns ± 0.303 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [3]: %%timeit l = [1, 2, 3]
...: (l[0], l[1], l[2])
104 ns ± 0.561 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [4]: %%timeit l = [1, 2, 3]
...: (*l,)
78.1 ns ± 0.628 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [5]: %%timeit l = [1, 2]
...: tuple(l)
96 ns ± 0.895 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [6]: %%timeit l = [1, 2]
...: (l[0], l[1])
70.1 ns ± 0.571 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [7]: %%timeit l = [1, 2]
...: (*l,)
73.4 ns ± 0.736 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
所以事实上,你给出的代码示例对每种尺寸都做出了正确的决定(假设性能才是最重要的); 在两个元素处,索引比替代方案更快,在三个元素处,批量转换为tuple
节省了足够多的重复索引来赢得胜利。
只是为了好玩,我在上面包含了一个与tuple(l)
等效的解决方案,它使用额外的解包泛化来使用专用字节码构建tuple
,这表明像用专用优化字节码替换通用构造函数调用这样小的事情可以令人惊讶固定开销的差异量。
这个例子有什么特别有趣的地方:更快的(*l,)
解决方案实际上涉及两个临时对象; BUILD_TUPLE_UNPACK
(实现它的字节码)与BUILD_LIST_UNPACK
共享一个代码路径。 他们俩实际上都构建了一个list
,而BUILD_TUPLE_UNPACK
只是在最后将其转换为tuple
。 所以(*l,)
将另一个副本隐藏到临时数据结构中,但是因为专用字节码比内置查找和通用构造函数代码路径高效得多,所以它仍然胜出。
让我们使用反汇编器来看看 Python 为此生成了什么字节码:
>>> names=['alex', 'ramon', 'carla']
>>> from dis import dis
>>> dis('abc')
1 0 LOAD_NAME 0 (abc)
2 RETURN_VALUE
>>> dis('"%s, %s and %s like this" % tuple(names)')
1 0 LOAD_CONST 0 ('%s, %s and %s like this')
2 LOAD_NAME 0 (tuple)
4 LOAD_NAME 1 (names)
6 CALL_FUNCTION 1
8 BINARY_MODULO
10 RETURN_VALUE
>>> dis('"%s, %s and %s like this" % (names[0], names[1], names[2])')
1 0 LOAD_CONST 0 ('%s, %s and %s like this')
2 LOAD_NAME 0 (names)
4 LOAD_CONST 1 (0)
6 BINARY_SUBSCR
8 LOAD_NAME 0 (names)
10 LOAD_CONST 2 (1)
12 BINARY_SUBSCR
14 LOAD_NAME 0 (names)
16 LOAD_CONST 3 (2)
18 BINARY_SUBSCR
20 BUILD_TUPLE 3
22 BINARY_MODULO
24 RETURN_VALUE
反汇编程序显示第二种方法有更多指令的事实并不一定意味着它更慢。 毕竟, function 调用只是不透明的CALL_FUNCTION
。 因此,您必须使用判断力并知道那在做什么。 但似乎你正在构建一个元组......
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.