简答 (TL;DR)
这是因为在第一次测试中,dict 的 CPython 实现将从列表中创建一个新的字典,但第二次只复制字典。复制比解析列表花费的时间更少。
附加信息
考虑这段代码:
import dis
dis.dis("dict([('foo', 1), ('bar', 'bar'), ('baz', 100)])", depth=10)
print("------------")
dis.dis("dict({'foo': 1, 'bar': 'bar', 'baz': 100})", depth=10)
在哪里
dis 模块支持通过以下方式解析 CPython 字节码
拆开它。
这让我们可以看到执行的字节码操作。输出显示
1 0 LOAD_NAME 0 (dict)
2 LOAD_CONST 0 (('foo', 1))
4 LOAD_CONST 1 (('bar', 'bar'))
6 LOAD_CONST 2 (('baz', 100))
8 BUILD_LIST 3
10 CALL_FUNCTION 1
12 RETURN_VALUE
------------
1 0 LOAD_NAME 0 (dict)
2 LOAD_CONST 0 (1)
4 LOAD_CONST 1 ('bar')
6 LOAD_CONST 2 (100)
8 LOAD_CONST 3 (('foo', 'bar', 'baz'))
10 BUILD_CONST_KEY_MAP 3
12 CALL_FUNCTION 1
14 RETURN_VALUE
从输出中可以看到:
- 两个调用都需要加载将要调用的
dict 名称。
- 之后,第一个方法将列表加载到内存 (
BUILD_LIST),而第二个方法构建一个字典 (BUILD_CONST_KEY_MAP)(请参阅 here)
- 因此,当调用 dict 函数时(
CALL_FUNCTION 步骤(参见here)),在第二种情况下所需的时间要短得多,因为字典已经创建,所以它只是简单地复制不必遍历列表以创建哈希表。
注意:使用字节码,您不能最终确定 CALL_FUNCTION 会这样做,因为它的实现是用 C 编写的,只有通过阅读它才能真正知道(参见 Martijn Pieters'回答有关这部分如何工作的准确解释)。但是,它有助于了解字典对象是如何在外部 dict() 创建的(在示例中是逐步的,而不是语法上的),而对于列表,情况并非如此。
编辑
要清楚,当你说
在python中有几种构造字典的方法
确实是这样:
dkwargs = {'foo': 1, 'bar': 'bar', 'baz': 100}
您正在创建字典,解释器将表达式转换为存储在内存中的字典对象,并使变量dkwargs 指向它。但是,通过执行:dict(**kwargs) 或者如果您更喜欢 dict(kwargs),您并没有真正创建字典,而只是复制一个已经存在的对象(重要的是强调复制):
>>> dict(dkwargs) is dkwargs
False
dict(kwargs) 强制 Python 创建一个新对象;但是,这并不意味着它必须重新构建对象。事实上,这个操作是没有用的,因为实际上它们是相等的对象(尽管不是同一个对象)。
>>> id(dkwargs)
2787648914560
>>> new_dict = dict(dkwargs)
>>> id(new_dict)
2787652299584
>>> new_dict == dkwargs
True
>>> id(dkwargs) is id(new_dict)
False
身份证号:
返回对象的“身份”。这是一个整数,保证在其生命周期内对于该对象是唯一且恒定的 [...]
CPython 实现细节:这是对象在内存中的地址。
当然,除非您想专门复制对象以修改一个对象,这样编辑就不会链接到另一个引用。