[英]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.