作者:手机用户2602934713 | 来源:互联网 | 2023-05-21 20:43
我在 codewars 上遇到了一个问题,我不确定这两种可能的解决方案之间有什么区别,一种是将列表转换为元组,另一种是指定输入列表的元素。问题:将姓名(字符串)列表转换为类似于 Facebook 用来
我在 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]
etc 的计算效率更高,因为您没有在内存中为元组创建新对象 - 是吗?
回答
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,)
躲在另一个拷贝到临时数据结构,但由于特殊的字节码比这么多有效的内置的查找加通用构造函数的代码路径,它仍然获胜。