我在 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]
等计算效率更高,因为您不会在内存中为元组创建新对象 - 对吗?
最佳答案
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列表元素与转换为元组以进行字符串格式化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65260928/