Python in 操作在 tuple 和 list 中到底哪个快?

2020-11-17 15:47:45 +08:00
 ztoben

依稀记得在公司刚开始写代码时,大佬看到我写这种语句

if xxx in [xxx, xxx, ...]:
	...

告诉我,遇到这种情况,要用元组替代列表,因为 in 操作在元组中更快,类似这样

if xxx in (xxx, xxx, ...):
	...

后来在网上查资料说是两个的数据结构的不同导致元组会比列表性能更高一点,今天闲来无事,写了一个测试,没想到元组比 list 慢的多?请各位大佬赐教 代码

a = [i for i in range(100000000)]
s = time.time()
if 99999993 in a:
    print('true')
e = time.time()
t = e - s
print(t) --> 1.7924060821533203
c = (i for i in range(100000000))
s = time.time()
if 99999993 in c:
    print('true')
e = time.time()
t = e - s
print(t) --> 8.226477146148682
2618 次点击
所在节点    Python
10 条回复
ruanimal
2020-11-17 15:51:42 +08:00
你这个写法不对,第二部分并不是元祖是生成器
Wincer
2020-11-17 15:55:15 +08:00
首先,圆括号()的表达式不生成元组,而是生成器,你需要先把生成器转成 tuple,再使用 in 来判断:

c = tuple(range(100000000))
s = time.time()
if 99999993 in c:
print('true')
e = time.time()
t = e - s
print(t)

其次,想 in 操作更快,可以换成花括号(集合):{xxx, xxx, xxx}
ztoben
2020-11-17 16:00:18 +08:00
@ruanimal 忘记了这茬,感谢
ztoben
2020-11-17 16:00:55 +08:00
@Wincer 明白了 集合这个我知道的 谢谢
fasionchan
2020-11-17 19:11:36 +08:00
其实两者一样慢,都是 O(n)操作;而且你写的是字面量,n 本来就很小,所以差别更是微乎其微。有些 tuple 字面量可以编译成常量,这样就不用每次都创建 tuple 对象,有一丁点性能优势。另外,考虑 list 和 tuple 对象的内部结构,tuple 也有些优势。具体可以参考 Python 虚拟机内部原理,特别是内建对象部分:

https://www.imooc.com/read/76

这个例子不如这样理解:在什么场景,就用什么数据结构。如果你需要一个不需要变化的列表,那就用 tuple 对象,而不是 list 对象。这样既避免意外修改,也获得一丁点性能优势。单从性能考虑,则有些牵强。
fasionchan
2020-11-17 19:17:05 +08:00
对了,还可以观察字节码,看看不同的写法有什么差别。下面是一个可以编译成常量的 tuple 对象的例子:

```
>>> dis.dis(compile('a = ("a", "b", "c")', '', 'exec'))
1 0 LOAD_CONST 0 (('a', 'b', 'c'))
2 STORE_NAME 0 (a)
4 LOAD_CONST 1 (None)
6 RETURN_VALUE
```

而同样的 list 对象则不能,只能即时创建(BUILD_LIST 字节码):

```
>>> dis.dis(compile('a = ["a", "b", "c"]', '', 'exec'))
1 0 LOAD_CONST 0 ('a')
2 LOAD_CONST 1 ('b')
4 LOAD_CONST 2 ('c')
6 BUILD_LIST 3
8 STORE_NAME 0 (a)
10 LOAD_CONST 3 (None)
12 RETURN_VALUE
```

关于 Python 代码编译过程与字节码,我上面贴的链接里面同样有介绍。
yucongo
2020-11-17 21:16:14 +08:00
In [112]: tuple_ = tuple(range(100000))

In [113]: list_ = [*range(100000)]

In [114]: %timeit 999 in list_
65.2 µs ± 16.8 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [115]: %timeit 999 in tuple_
60.3 µs ± 5.88 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [116]: %timeit 999 in tuple_
60 µs ± 4.19 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [117]: %timeit 999 in list_
54.2 µs ± 1.38 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

其实几乎没有区别
dingwen07
2020-11-17 22:25:55 +08:00
当你 print 的时候,最慢的是 io
keepwalk2020
2020-11-18 08:04:10 +08:00
如果只是判断是否包含,用 set 肯定比 tuple 和 list 都快,
如果集合成员固定的话,用 frozenset 会更快一点,毕竟 frozenset 比 set 少调用了一次源码里的 set_clear_internal 函数。
freakxx
2020-11-19 22:08:11 +08:00
@keepwalk2020 #9

老哥你源码看这么细的吗,

想请教你是特意看过这部分,还是说有什么比较“系统”的一个思路去看到这么细节?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/726235

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX