結論
[*iterable]
が(たぶん)最速。
だけどそれ以前に考えるべきことがあるよね。
はじめに
「Pythonでlistを生成するとき、list()よりも[]の方が早い」ということを知って、これは他オブジェクトのlist化でも同じことが言えるのかと少し気になった。
disモジュールで確認
適当にバイトコードを確認してみる。
>>> import dis
>>> dis.dis(lambda: list(iterable))
1 0 RESUME 0
2 LOAD_GLOBAL 1 (NULL + list)
14 LOAD_GLOBAL 2 (iterable)
26 PRECALL 1
30 CALL 1
40 RETURN_VALUE
>>> dis.dis(lambda: [*iterable])
1 0 RESUME 0
2 BUILD_LIST 0
4 LOAD_GLOBAL 0 (iterable)
16 LIST_EXTEND 1
18 RETURN_VALUE
>>>
たしかに行数は少ないが、内容がだいぶ違う。
もしかしたら実際の処理負荷的にlist(iterable)
の方が早いということも考えられる。
timeitモジュールで確認
参考にした記事でも使っていたtimeitモジュールでも確認してみる。
>>> import timeit
>>>
>>> iterable = '1234567890'*10
>>>
>>> timeit.timeit('list(iterable)', globals=globals(), number=5000000)
1.7787381240050308
>>> timeit.timeit('[*iterable]', globals=globals(), number=5000000)
1.7009898429969326
>>>
ほぼ誤差レベルではあるが、一応早くなってはいる。
結論(というか個人的な意見)
Python的には[*iterable]
が読みやすいらしい。
しかし、今まで数々の可読性が最悪なPythonコードを生み出してきた私個人の感覚では、list(iterable)
の方がわかりやすいと思う。
呼び出し側の*
演算子は正直言って使いどころが少なく、認知度も高くないのではないかと個人的に思っている。
もしあなたがコンピュータと同じ完成の持ち主なら、[*iterable]
を使ったほうが良いかもしれない。
しかし、所詮誤差は誤差なので、それ以外に改善できるところを探したほうが良いと思うよ。
なんか前にJavaScriptでa = -x
よりもa = 0 - x
の方が若干早いって話が議論されてたよね。
参考
pythonでリストを生成するときにlist()と[]どっちを使うか? by inetcpl
https://qiita.com/inetcpl/items/9fd9fb4bfcb39663dcd5
Python の * 演算子 (iterable unpacking operator) の使い方 by eumesy
https://qiita.com/eumesy/items/dda85b70d28da61663cb
おまけ:他の処理系で比べてみる
さっきまでの検証はCPython 3.11.4で行っていたが、処理系によっては他の結果が出るかもしれない。
というわけで手短にIronPython 3.4.1 (.NET 6.0.24)で確認してみる。
>>> # Pythonのバージョンが古いせいか、timeit.timeitの仕様がやや違ったため、面倒なコードを書いて計測した
>>> # 一応CPython上で、timeit.timeitと同様の結果が出ることは確認している。
>>> import time
>>>
>>> iterable = '1234567890'*10
>>>
>>> if True:
... a_start = time.time()
... for _ in range(5000000):
... __ = list(iterable)
... a_end = time.time()
...
>>> a_end - a_start
5.4328689575195312
>>>
>>> if True:
... b_start = time.time()
... for _ in range(5000000):
... __ = [*iterable]
... b_end = time.time()
...
>>> b_end - b_start
28.690162658691406
>>>
普通に終わるかどうか不安になった
遅いとかそういう次元の話じゃないんだけどなにこれ
dis.dis
はNotImplementedError
が出たので使い物にならなかった。
とりあえず処理時間がlist()
> []
であることは覆っていないか確認する。
>>> if True:
... a_start = time.time()
... for _ in range(5000000):
... __ = list()
... a_end = time.time()
...
>>> a_end - a_start
1.338226318359375
>>> if True:
... b_start = time.time()
... for _ in range(5000000):
... __ = []
... b_end = time.time()
...
>>> b_end - b_start
0.6037750244140625
倍以上の差があるのでここは問題なさそう。
Pythonのバージョンによるものかとも思ったがそうでもなく、ちゃんと[*iterable]
の方が早かった。
引数展開の速さは処理系によってかなり違いがあるということなのだろうか。