【问题标题】:What are dictionary view objects?什么是字典视图对象?
【发布时间】:2012-02-15 23:35:33
【问题描述】:

在 python 2.7 中,dictionary view methods 可用。

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

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

dict.viewitems()(等等)有什么用?他们有什么好处?它是如何工作的?究竟什么是视图?

我读到视图总是反映字典中的更改。但从性能和内存的角度来看,它的表现如何?有什么好处和坏处?

【问题讨论】:

    标签: python view dictionary


    【解决方案1】:

    字典视图本质上就像它们的名字所说的那样:视图就像一个窗口,位于字典的键和值(或项)上。以下是 Python 3 的 official documentation 的摘录:

    >>> 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()。)

    这个例子展示了视图的动态特性:键视图不是键在给定时间点的副本,而是一个简单的窗口,显示你的钥匙;如果它们发生了变化,那么您通过窗口看到的内容也会发生变化。此功能在某些情况下可能很有用(例如,可以使用程序的多个部分中的键视图,而不是每次需要时重新计算当前的键列表)——请注意,如果字典键被修改在对视图进行迭代时,迭代器的行为方式并没有很好地定义,可以lead to errors

    一个优点是查看键仅使用固定数量的小内存并且需要固定数量的少量处理器时间,因为没有创建键列表(另一方面,Python 2 经常不必要地创建一个新列表,正如 Rajendran T 所引用的那样,它占用的内存和时间与名单)。继续窗户的类比,如果你想看到墙后的风景,你只需在里面开一个洞(你建造一扇窗户);将键复制到列表中相当于在墙上绘制景观的副本 - 副本需要时间、空间,并且不会自行更新。

    总而言之,视图只是……字典上的视图(窗口),即使字典发生更改,它也会显示字典的内容。它们提供了不同于列表的功能:键列表包含给定时间点字典键的副本,而视图是动态的,并且获取速度更快,因为它确实如此无需复制任何数据(键或值)即可创建。

    【讨论】:

    • +1。好的,这与直接访问内部密钥列表有何不同?是不是更快,更慢?内存效率更高?受限制的 ?如果您可以阅读和编辑它,那感觉与引用此列表完全一样。
    • 谢谢。问题是视图您对“内部键列表”的访问(请注意,这个“键列表”不是 Python 列表,而是一个视图)。视图比 Python 2 的键(或值或项)列表更节省内存,因为它们不复制任何内容;它们确实就像“对键列表的引用”(还要注意,“对列表的引用”实际上在 Python 中简称为列表,因为列表是可变对象)。另请注意,您不能直接编辑视图:相反,您仍然可以编辑字典,并且视图会立即反映您的更改。
    • 好的,我还不清楚实现,但这是迄今为止最好的答案。
    • 谢谢。事实上,这个答案主要是关于视图的语义。我没有关于它们在 CPython 中实现的信息,但我猜想视图基本上是指向正确结构(键和/或值)的指针,并且这些结构是字典对象本身的一部分。
    • 我认为值得指出的是,这篇文章中的示例代码来自 python3,而不是我在 python2.7 中得到的。
    【解决方案2】:

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

    现在通过下面的例子来看看dict的interator和dict的view的区别

    >>> 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()
    • 问题是关于 Python 2.7,所以viewitems() 实际上是正确的(items() 正确给出了 Python 3 中的视图)。
    • 但是,视图不能在修改字典时用于迭代字典。
    【解决方案3】:

    仅仅通过阅读文档我得到了这样的印象:

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

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

    【讨论】:

    • +1 从我们获得的少量信息中分析利弊。
    • 如果我在视图上创建一个迭代器,它仍然会在字典更改时失效。这与字典本身的迭代器相同(例如iteritems())。那么这些观点的意义何在?我什么时候很高兴拥有它们?
    • @Alfe 你说得对,这是字典迭代的问题,视图根本没有帮助。假设您需要将字典的值传递给函数。您可以使用.values(),但这涉及将整个副本制作为列表,这可能会很昂贵。有.itervalues(),但您不能多次使用它们,因此它不适用于每个功能。视图不需要昂贵的副本,但它们作为独立值仍然比迭代器更有用。但它们仍然不打算帮助同时进行迭代和修改(你真的想要一份副本)。
    【解决方案4】:

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

    来自Python 3.0 - dict methods return views - why?

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

    如果您只是想遍历键然后创建一个新列表 没有必要。如果您确实需要它作为单独的列表(作为 复制)然后您可以轻松地从视图中创建该列表。

    【讨论】:

    • 视图方法返回视图对象,不符合列表接口。
    【解决方案5】:

    视图让您无需复制即可访问底层数据结构。除了动态而不是创建列表之外,它们最有用的用法之一是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 上运行

    【讨论】:

    • 在 python >=3 中,我相信.keys() 默认返回一个视图。可能要仔细检查一下
    • 你是对的。 Python 3+ 大量使用视图对象而不是列表,它的内存效率更高
    • 这些计时结果非常有说服力,但是检查k是否是字典large_d的键之一是用k in large_d完成的,在Python中,这可能基本上一样快就像使用视图一样(换句话说,k in large_d.keys() 不是 Pythonic,应该避免——就像 k in large_d.viewkeys() 一样)。
    • 感谢您提供了一个可靠、有用的示例。 k in large_d 实际上比 k in large_d.viewkeys() 快得多,所以应该避免这种情况,但这对于 k in large_d.viewvalues() 是有意义的。
    猜你喜欢
    • 1970-01-01
    • 2018-04-26
    • 1970-01-01
    • 1970-01-01
    • 2015-10-23
    • 1970-01-01
    • 2018-02-07
    • 1970-01-01
    相关资源
    最近更新 更多