什么是字典视图对象?


158

在python 2.7中,我们获得了可用的字典视图方法

现在,我知道以下内容的优缺点:

  • dict.items()(和valueskeys):返回一个列表,以便您可以实际存储结果,并且
  • dict.iteritems() (等等):返回一个生成器,因此您可以逐个迭代生成的每个值。

有什么用dict.viewitems()(等等)?他们有什么好处?它是如何工作的?到底是什么看法?

我读到该视图始终反映字典中的变化。但是,从性能和内存的角度来看,它的表现如何?优点和缺点是什么?

Answers:


157

字典视图本质上就是它们的名字所说的:视图就像是字典的键和值(或项)上的窗口。这是Python 3 官方文档的摘录:

>>> dishes = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500}
>>> keys = dishes.keys()
>>> values = dishes.values()

>>> # view objects are dynamic and reflect dict changes
>>> del dishes['eggs']
>>> keys  # No eggs anymore!
dict_keys(['sausage', 'bacon', 'spam'])

>>> values  # No eggs value (2) anymore!
dict_values([1, 1, 500])

(Python 2等效项使用dishes.viewkeys()dishes.viewvalues()。)

此示例显示了视图动态特性:按键视图不是给定时间点的按键副本,而是一个简单的窗口,向您显示按键;如果它们被更改,那么您在窗口中看到的内容也会发生更改。此功能在某些情况下很有用(例如,可以在程序的多个部分中使用键视图,而不必每次都需要重新计算当前键列表)—请注意,如果修改了字典键在视图上进行迭代时,迭代器的行为方式未明确定义,这可能会导致错误

一个优点是,例如,查看键仅使用少量且固定的内存,并且需要少量且固定的处理器时间,因为没有创建键列表(另一方面,Python 2,通常会不必要地创建一个新列表,如Rajendran T所引用的那样,该列表占用的内存和时间与列表的长度成比例。要继续进行窗口类比,如果您想查看墙后的风景,只需在其中开一个洞(您就可以建立一个窗口);将关键帧复制到列表中将相当于在墙上绘制风景的副本-该副本需要时间,空间并且不会自我更新。

总而言之,视图只是…词典上的视图(窗口),即使词典发生更改,视图也会显示该词典的内容。它们提供的功能与列表不同:键的列表包含给定时间点的字典键的副本,而视图是动态的并且获取起来要快得多,因为它无需复制任何数据(键或值)以进行创建。


6
+1。好的,这与直接访问内部键列表有何不同?那更快,更慢吗?记忆效率更高?受限制?如果您可以阅读和编辑它,则感觉与引用此列表完全相同。
e-satis 2012年

3
谢谢。问题在于,视图您对“内部键列表”的访问权(请注意,此“键列表”不是Python列表,而是精确的视图)。视图比Python 2的键(或值或项)列表更有效地利用内存,因为视图不复制任何内容。它们的确就像“对键列表的引用”(也请注意,在Python中,“对列表的引用”实际上简称为列表,因为列表是可变对象)。还要注意,您不能直接编辑视图:相反,您仍然可以编辑字典,并且视图会立即反映出您所做的更改。
Eric O Lebigot 2012年

3
好的,我尚不清楚实现方式,但这是迄今为止的最佳答案。
e-satis 2012年

2
谢谢。实际上,这个答案主要是关于视图的语义的。我没有有关它们在CPython中的实现的信息,但是我猜想视图基本上是指向正确结构(键和/或值)的指针,并且这些结构是字典对象本身的一部分。
Eric O Lebigot 2012年

5
我认为值得指出的是,本文中的示例代码来自python3,而不是我在python2.7中得到的。
snth 2012年

21

如前所述,dict.items()返回字典的(键,值)对列表的副本是浪费的,并且dict.iteritems()返回对字典的(键,值)对的迭代器。

现在以以下示例为例,看看dict的插入器和dict的视图之间的区别

>>> d = {"x":5, "y":3}
>>> iter = d.iteritems()
>>> del d["x"]
>>> for i in iter: print i
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration

而视图只是向您显示字典中的内容。不管它是否更改:

>>> d = {"x":5, "y":3}
>>> v = d.viewitems()
>>> v
dict_items([('y', 3), ('x', 5)])
>>> del d["x"]
>>> v
dict_items([('y', 3)])

视图只是字典现在的样子。删除后,条目.items()将是过时的并且.iteritems()将引发错误。


很好的例子,谢谢。虽然,应该是V = d.items()不是V - d.viewitems()
RIX

1
问题是关于Python 2.7的,所以viewitems()实际上是正确的(items()在Python 3中正确给出了一个视图)。
Eric O Lebigot

但是,视图不能在修改字典时用于迭代字典。
Ioannis Filippidis

18

只是通过阅读文档,我得到的印象是:

  1. 视图是“类伪集”,因为它们不支持索引编制,因此您可以对它们进行测试,以测试成员资格并对其进行迭代(因为键是可哈希的且唯一的,因此键和项视图更加“集样”,因为它们不包含重复项)。
  2. 您可以存储它们并多次使用它们,例如列表版本。
  3. 因为它们反映了基础字典,所以字典中的任何更改都会更改视图,并且几乎可以肯定会更改迭代顺序。因此,与列表版本不同,它们不是“稳定的”。
  4. 因为它们反映了基础词典,所以几乎可以肯定它们是小型代理对象;复制键/值/项目将要求他们以某种方式观看原始字典,并在发生更改时将其复制多次,这将是荒谬的实现。因此,我希望内存开销很小,但是访问比直接访问字典要慢一些。

所以我想关键用例是,如果您要保留一个字典并反复修改其键/项/值,并在其间进行修改。你可以只使用一个视图代替,把for k, v in mydict.iteritems():for k, v in myview:。但是,如果只对字典进行一次迭代,我认为迭代版本仍然是可取的。


2
+1用于从我们得到的一些信息中分析利弊。
e-satis 2012年

如果我在视图上创建迭代器,则每当字典更改时,迭代器仍然无效。这与在字典本身上进行迭代的麻烦相同(例如iteritems())。那么这些观点有什么意义呢?我什么时候乐意拥有它们?
Alfe

@Alfe你是对的,这是字典迭代的问题,而视图根本无法解决问题。假设您需要将字典的值传递给函数。您可以使用.values(),但这涉及将整个副本制成列表,这可能会很昂贵。有,.itervalues()但是您不能多次使用它们,因此它不适用于所有功能。视图不需要昂贵的副本,但它们作为独立值比迭代器更有用。但是,它们仍然不打算同时进行迭代和修改(确实需要复制)。

17

view方法返回一个列表(与和相比.keys(),不是列表的副本),因此它更轻巧,但反映了字典的当前内容。.items().values()

Python 3.0开始-dict方法返回视图-为什么?

主要原因是,在许多用例中,返回完全分离的列表是不必要且浪费的。这将需要复制整个内容(可能很多,也可能很多)。

如果只想遍历键,则无需创建新列表。如果确实需要将其作为单独的列表(作为副本),则可以从视图轻松创建该列表。


6
视图方法返回不符合列表接口的视图对象。
马修·特雷弗

5

视图使您可以访问底层数据结构,而无需复制它。除了动态而不是创建列表外,in测试最有用的用途之一是测试。假设您要检查dict中是否包含一个值(它是键还是值)。

选项一是使用创建键列表dict.keys(),这可以工作,但显然会占用更多内存。如果dict非常大?那会很浪费。

有了它,views您可以迭代实际的数据结构,而无需中间列表。

让我们使用示例。我有一个带有1000个随机字符串和数字键的字典,这k是我要查找的键

large_d = { .. 'NBBDC': '0RMLH', 'E01AS': 'UAZIQ', 'G0SSL': '6117Y', 'LYBZ7': 'VC8JQ' .. }

>>> len(large_d)
1000

# this is one option; It creates the keys() list every time, it's here just for the example
timeit.timeit('k in large_d.keys()', setup='from __main__ import large_d, k', number=1000000)
13.748743600954867


# now let's create the list first; only then check for containment
>>> list_keys = large_d.keys()
>>> timeit.timeit('k in list_keys', setup='from __main__ import large_d, k, list_keys', number=1000000)
8.874809793833492


# this saves us ~5 seconds. Great!
# let's try the views now
>>> timeit.timeit('k in large_d.viewkeys()', setup='from __main__ import large_d, k', number=1000000)
0.08828549011070663

# How about saving another 8.5 seconds?

如您所见,迭代view对象极大地提高了性能,同时减少了内存开销。需要执行Set类似操作时,应使用它们。

注意:我在Python 2.7上运行


在python> = 3中,我相信.keys()默认情况下会返回一个视图。可能要寿仔细检查
YOLO VOE

1
你是对的。Python 3+大量使用视图对象而不是列表,因此内存使用效率更高
Chen

1
这些计时结果非常有说服力,但是在Python中检查k字典的键之一large_d是否应该使用来完成k in large_d,这在本质上可能与使用视图一样快(换句话说,k in large_d.keys()它不是Python的,应避免使用-照原样k in large_d.viewkeys())。
Eric O Lebigot

感谢您提供可靠的示例。k in large_d实际上比快得多k in large_d.viewkeys(),因此应该避免,但这对于是有道理的k in large_d.viewvalues()
naught101 '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.