dict()和{}有什么区别?

76

假设我想创建一个字典,我们将其称为d。但是在Python中有多种初始化字典的方式!例如,我可以这样做:

d = {'hash': 'bang', 'slash': 'dot'}

或者我可以这样做:

d = dict(hash='bang', slash='dot')

或者是这个,有趣的是:

d = dict({'hash': 'bang', 'slash': 'dot'})

或者这样:

d = dict([['hash', 'bang'], ['slash', 'dot']])

除此之外,dict() 函数还提供了许多其他方法。显然,dict() 提供的一项功能是语法和初始化的灵活性。但这不是我的问题。

假设我只想创建一个空字典 d。在 Python 解释器背后执行 d = {}d = dict() 时会发生什么?它们只是两种实现相同功能的方式吗?使用 {} 是否会额外调用 dict()?它们两个之间是否有(即使微小的)性能差异?虽然这个问题真的没有什么重要性,但这是我很感兴趣的一个问题。


1
我在这里找到的解释更清晰:https://doughellmann.com/blog/2012/11/12/the-performance-impact-of-using-dict-instead-of-in-cpython-2-7-2/ - theBuzzyCoder
8个回答

83
>>> def f():
...     return {'a' : 1, 'b' : 2}
... 
>>> def g():
...     return dict(a=1, b=2)
... 
>>> g()
{'a': 1, 'b': 2}
>>> f()
{'a': 1, 'b': 2}
>>> import dis
>>> dis.dis(f)
  2           0 BUILD_MAP                0
              3 DUP_TOP             
              4 LOAD_CONST               1 ('a')
              7 LOAD_CONST               2 (1)
             10 ROT_THREE           
             11 STORE_SUBSCR        
             12 DUP_TOP             
             13 LOAD_CONST               3 ('b')
             16 LOAD_CONST               4 (2)
             19 ROT_THREE           
             20 STORE_SUBSCR        
             21 RETURN_VALUE        
>>> dis.dis(g)
  2           0 LOAD_GLOBAL              0 (dict)
              3 LOAD_CONST               1 ('a')
              6 LOAD_CONST               2 (1)
              9 LOAD_CONST               3 ('b')
             12 LOAD_CONST               4 (2)
             15 CALL_FUNCTION          512
             18 RETURN_VALUE        

据称 dict() 是一些 C 内置函数。一个非常聪明或专注的人(不是我)可以查看解释器源代码并告诉您更多信息。我只是想展示一下 dis.dis。:)


1
很遗憾,由于“CALL_FUNCTION 512”,没有人能告诉你“g”背后的原因... - Benjamin Toueg
5
请看Doug Hellmann写的这篇很棒的文章: http://doughellmann.com/2012/11/12/the-performance-impact-of-using-dict-instead-of-in-cpython-2-7-2.html - ezdazuzena
3
更新版本的Python使得{'a': 1, 'b': 2}更加高效。例如,在Python 2.7.10中,反汇编将ROT_THREE; STORE_SUBSCR; DUP_TOP指令替换为STORE_MAP。而在Python 3.5.1中,则完全消除了它们,并只使用了一个单一的BUILD_MAP指令。 - danio

44

就性能而言:

>>> from timeit import timeit
>>> timeit("a = {'a': 1, 'b': 2}")
0.424...
>>> timeit("a = dict(a = 1, b = 2)")
0.889...

对于空字典,我得到了类似的结果:'x = {}' 的时间是0.1微秒,而 'x = dict()' 的时间是0.3微秒。 - John Fouhy
是的,函数调用很耗费资源。但是那种形式更加灵活,即 dict(zip(... - DNS
函数调用在Python中比C语言昂贵80-100倍。 - vartec
1
import timeit t = timeit.Timer("a = {'a': 1, 'b': 2}") t.timeit() t = timeit.Timer("a = dict(a = 1, b = 2)") t.timeit()第二个比第一个慢四倍。 - DrFalk3n
较小的数字更好,对吧?那么C内置的dict()比纯Python {}慢? - Houman

31

@Jacob:对象分配的方式有所不同,但它们并非写时复制(copy-on-write)。Python会分配一个固定大小的“空闲列表”,以便快速分配字典对象(直到填满为止)。通过{}语法(或使用C调用PyDict_New)分配的字典可以来自这个空闲列表。当字典不再被引用时,它将返回到空闲列表中,该内存块可以被重用(尽管字段首先会被重置)。

第一个字典会立即返回到空闲列表中,接下来的字典将重用其内存空间:

>>> id({})
340160
>>> id({1: 2})
340160

如果你保留一个引用,下一个字典将从下一个空闲的插槽中获取:

>>> x = {}
>>> id(x)
340160
>>> id({})
340016

但是我们可以删除对该字典的引用,并再次释放其空间:

>>> del x
>>> id({})
340160

由于{}语法在字节码中处理,因此可以使用上述优化。另一方面,dict()像普通类构造函数一样处理,并且Python使用通用内存分配器,其模式不像上述自由列表那样易于预测。

此外,在查看 Python 2.6 中的 compile.c 时,使用{}语法似乎会根据已知的解析时间存储的项目数来预调大小哈希表。


9

基本上,{} 是语法,由语言和字节码级别处理。dict() 只是另一个内置函数,具有更灵活的初始化语法。请注意,dict() 仅在2.x系列中期添加。


8
更新: 感谢回复。已经删除关于写时复制的猜测。 {}dict 之间的另一个区别是,dict 总是分配一个新的字典(即使内容是静态的),而 {} 并不总是如此(有关何时以及为什么,请参见mgood的答案):
def dict1():
    return {'a':'b'}

def dict2():
    return dict(a='b')

print id(dict1()), id(dict1())
print id(dict2()), id(dict2())

输出结果如下:

$ ./mumble.py
11642752 11642752
11867168 11867456

我并不是在建议您是否应该利用这一点,这取决于具体情况,只是指出它的存在。(如果您理解操作码,从反汇编结果中也可以看出)。


2
这里发生的不是那样的情况。{} 仍然在分配一个新的字典(否则就会非常糟糕),但您不保留它的生命周期意味着在其死亡后可以重新使用其 ID(一旦第一个字典被打印出来)。 - Brian
我怀疑函数调用的行为不同,因为它在更多地使用id()空间,所以第二次调用获得了不同的id。 - Brian
我认为mgood的回答澄清了事情,我已经更新了我的条目。 - Jacob Gabrielson

4

dict()函数用于从可迭代对象创建一个字典,例如:

dict( generator which yields (key,value) pairs )
dict( list of (key,value) pairs )

1
是的。对于可以使用{...}的情况,应该这样做,因为它比调用dict()构造函数更直接和更快(在Python中,函数调用是昂贵的,为什么要调用一个只返回可以通过{...}语法直接构建的东西的函数呢?)。 - Eric O. Lebigot
它在Python 2中运行了相当长的时间。 - Eric O. Lebigot

1
有趣的用法:

def func(**kwargs):
      for e in kwargs:
        print(e)
    a = 'I want to be printed'
    kwargs={a:True}
    func(**kwargs)
    a = 'I dont want to be printed'
    kwargs=dict(a=True)
    func(**kwargs)

输出:

I want to be printed
a

2
不确定你所说的“有趣”是指“哈哈大笑”还是指“错误”。请加上一些评论或解释,因为这个问题已经在8年前得到了回答,这会让新手感到困惑。 - Chris Schaller

-1
为了创建一个空集合,我们应该在前面使用关键字set,即set(),这将创建一个空集合,而在字典中只有花括号可以创建一个空字典。
让我们举个例子。
print isinstance({},dict) 
True 
print isinstance({},set) 
False 
print isinstance(set(),set) 
True

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接