this question之后的讨论让我感到疑惑,所以我决定运行一些测试,比较set((x,y,z)){x,y,z}在Python中创建集合的创建时间(我使用的是Python 3.7).

我用timetimeit比较了这两种方法.

test1 = """
my_set1 = set((1, 2, 3))
"""
print(timeit(test1))

Result: 0.30240735499999993

test2 = """
my_set2 = {1,2,3}
"""
print(timeit(test2))

Result: 0.10771795900000003

因此,第二种方法比第一种方法高almost 3 times faster.

* Note: I only show the results of the timeit tests since they are averaged over many samples, and thus perhaps more reliable, but the results when testing with time showed similar differences in both cases.


Edit:我知道this similar question,虽然它回答了我最初问题的某些方面,但它并没有涵盖所有方面.问题中没有提到集合,而且由于empty sets在python中没有文字语法,我很好奇使用文字创建集合与使用set()方法有什么不同(如果有的话).此外,我想知道set((x,y,z)tuple parameter的处理是如何在幕后进行的,以及它对运行时的可能影响.

推荐答案

(这是对已从初始问题中编辑出来的代码的回应)在第二种情况下,您忘记调用函数.进行适当修改后,结果如预期:

test1 = """
def foo1():
     my_set1 = set((1, 2, 3))
foo1()
"""    
timeit(test1)
# 0.48808742000255734

test2 = """
def foo2():
    my_set2 = {1,2,3}
foo2()
"""    
timeit(test2)
# 0.3064506609807722

现在,造成计时差异的原因是,set()是一个函数调用,需要查找符号表,而{...}集的构造是语法的产物,速度要快得多.

当观察反汇编的字节码时,差异是明显的.

import dis

dis.dis("set((1, 2, 3))")
  1           0 LOAD_NAME                0 (set)
              2 LOAD_CONST               3 ((1, 2, 3))
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

dis.dis("{1, 2, 3}")
  1           0 LOAD_CONST               0 (1)
              2 LOAD_CONST               1 (2)
              4 LOAD_CONST               2 (3)
              6 BUILD_SET                3
              8 RETURN_VALUE

在第一种情况下,由指令CALL_FUNCTION对元组(1, 2, 3)进行函数调用(这也带来了它自己的开销,尽管它是通过LOAD_CONST作为常量加载的),而在第二条指令中只是BUILD_SET调用,这更有效.

Re:关于元组构建所需时间的问题,我们发现这实际上可以忽略不计:

timeit("""(1, 2, 3)""")
# 0.01858693000394851

timeit("""{1, 2, 3}""")
# 0.11971827200613916

元组是不可变的,因此编译器通过将其作为常量加载来优化此操作,这称为constant folding(您可以从上面的LOAD_CONST指令中清楚地看到这一点),因此所花费的时间可以忽略不计.这在集合中是看不到的,因为它们是可变的(感谢@user2357112指出这一点).


对于较大的序列,我们可以看到类似的行为.{..}语法在使用集合理解构造集合方面比set()语法更快,后者必须从生成器生成集合.

timeit("""set(i for i in range(10000))""", number=1000)
# 0.9775058150407858

timeit("""{i for i in range(10000)}""", number=1000)
# 0.5508635920123197

作为参考,您还可以在较新的版本上使用iterable Unpackaging:

timeit("""{*range(10000)}""", number=1000)
# 0.7462548640323803

然而,有趣的是,直接在range上调用set()会更快:

timeit("""set(range(10000))""", number=1000)
# 0.3746800610097125

这恰好比集合构造要快.对于其他序列(例如lists),您将看到类似的行为.

我的建议是在构造集合文字时使用{...}集合理解,并作为将生成器理解传递给set()的替代方法;而是使用set()将现有的序列/iterable转换为集合.

Python-3.x相关问答推荐

Django 5.0.2和django_rest_framework

使用Polars阅读按日期键分区的最新S3镶木地板文件

如何使用PySide6创建切换框架?

如何在matplotlib中显示次要刻度标签

visual studio代码窗口中未激活虚拟环境11

无法导入名称';核心';来自部分初始化的模块';tensorflow_datasets';(很可能是由于循环导入)

检测点坐标 - opencv findContours()

Python - 根据条件附加 NULL 值

Python 列表求和所有出现的保留顺序

Python BeautifulSoup:在 Select 语句中排除其他标签

SqlAlchemy - 从 oracle db 中检索长文本

Python 3 `str.__getitem__` 的计算复杂度是多少?

使用正确的数据类型时,使用 Cerberus 验证 JSON 架构会引发错误

Semaphore信号量 Python 的工作原理

使用带有多线程的 win32com

Django 2 个字段之一不能为空

Python:pprint的模块错误,打印没有错误

aiohttp+sqlalchemy:在回滚无效事务之前无法重新连接

为什么在 Python 中不推荐使用 MutableString?

十六进制字符串到 Python 3.2 中的带符号整数?