什么是字典视图对象?

在 python 2.7中,我们可以使用 字典查看方法字典查看方法

现在,我知道以下的利弊:

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

什么是 dict.viewitems()(和类似的) ?他们有什么好处?它是怎么工作的?风景到底是什么?

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

94299 次浏览

视图方法返回一个列表(与 .keys().items().values()相比,不是列表的副本) ,所以它更轻量级,但是反映了 dictionary 的当前内容。

来自 Python 3.0-dict 方法返回视图-为什么?

主要原因是,对于许多用例来说,返回一个完全 独立列表是不必要的,也是浪费的。它需要复制 整个内容(可能很多,也可能不多)。

如果只是想在键上迭代,那么创建一个新列表 如果你确实需要它作为一个单独的列表(作为一个 复制) ,然后您可以很容易地从视图创建该列表。

仅从阅读这些文件我就得到了这样的印象:

  1. 视图是“伪集合样式”的,因为它们不支持索引,所以你可以对它们进行成员资格测试并对它们进行迭代(因为键是散列的并且是唯一的,所以键和条目视图更“集合样式”,因为它们不包含重复的内容)。
  2. 您可以存储它们并多次使用它们,就像列表版本一样。
  3. 因为它们反映了底层字典,所以字典中的任何更改都将更改视图,并且将 几乎肯定会改变迭代的顺序。因此,与列表版本不同,它们不是“稳定的”。
  4. 因为它们反映了底层字典,所以几乎可以肯定它们是小的代理对象; 复制键/值/条目需要它们以某种方式观察原始字典,并在发生更改时多次复制它,这将是一个荒谬的实现。所以我希望内存开销非常小,但是访问速度要比直接访问字典慢一些。

所以我猜测关键用例是如果您保留一个字典,并在其键/条目/值之间进行修改,从而重复迭代这些键/条目/值。您可以只使用视图,将 for k, v in mydict.iteritems():转换为 for k, v in myview:。但是如果您只是在字典上迭代一次,我认为 ITER 版本仍然是可取的。

字典视图实质上就是它们的名称所说的: 字典的键和值(或项)上的 风景就像一扇窗户。下面是 正式文件 for 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()。)

这个例子展示了 视图的动态特性视图的动态特性: key 视图是 没有,是给定时间点上键的一个副本,而是一个简单的窗口,向您显示键; 如果它们发生了变化,那么您通过窗口看到的内容也会发生变化。这个特性在某些情况下很有用(例如,一个人可以在一个程序的多个部分处理一个关键字的视图,而不是每次需要时重新计算当前的关键字列表)ーー注意,如果在视图上迭代时字典关键字被修改,那么迭代器应该如何工作是没有定义好的,这可以 导致错误

一个优势是,比如说,寻找只使用 一小部分固定的内存,并且需要 小而固定的处理器时间,因为没有创建键列表(另一方面,Python 2经常不必要地创建一个新列表,正如 Rajendran T 引用的那样,它以与列表长度成比例的方式占用内存和时间)。继续窗口的类比,如果你想看到墙后的风景,你只需要在它上面打开一个口子(你建一个窗口) ; 把钥匙复制到一个列表中相当于在你的墙上画一个风景的复制品ー复制品需要时间和空间,而且不会自动更新。

总而言之,视图仅仅是字典上的... 视图(窗口) ,它显示字典的内容,即使它发生了变化。它们提供了与列表不同的特性: 一个键列表在给定的时间点包含字典键的 收到,而一个视图是动态的,获取速度快得多,因为它不需要复制任何数据(键或值)来创建。

正如您提到的,dict.items()返回字典(key,value)对列表的一个副本,这是浪费的,而 dict.iteritems()返回字典(key,value)对上的一个迭代器。

现在,以下面的例子为例,来看看 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()将抛出一个错误。

视图允许您访问底层数据结构,而无需复制它。除了是动态的而不是创建一个列表之外,它们最有用的用法之一是 in测试。假设您想检查一个值是否在 dict 中(要么是键,要么是值)。

选项一是使用 dict.keys()创建一个键列表,这种方法可行,但显然会消耗更多的内存。如果字典很大?那就太浪费了。

使用 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上运行