collections.ChainMap 的目的是啥?
Posted
技术标签:
【中文标题】collections.ChainMap 的目的是啥?【英文标题】:What is the purpose of collections.ChainMap?collections.ChainMap 的目的是什么? 【发布时间】:2014-06-17 01:29:45 【问题描述】:在 Python 3.3 中,ChainMap
类被添加到 collections
模块中:
提供了一个 ChainMap 类用于快速链接多个映射 因此可以将它们视为一个单元。它通常比 创建一个新字典并运行多个 update() 调用。
例子:
>>> from collections import ChainMap
>>> x = 'a': 1, 'b': 2
>>> y = 'b': 10, 'c': 11
>>> z = ChainMap(y, x)
>>> for k, v in z.items():
print(k, v)
a 1
c 11
b 10
它的动机是 this issue,并由 this one 公开(没有创建 PEP
)。
据我了解,它是拥有额外字典并使用update()
s 维护它的替代方法。
问题是:
ChainMap
涵盖哪些用例?
有ChainMap
的真实例子吗?
是否在切换到python3的第三方库中使用?
额外问题:有没有办法在 Python2.x 上使用它?
我在 Raymond Hettinger 的 Transforming Code into Beautiful, Idiomatic Python
PyCon 演讲中听说过它,我想将它添加到我的工具包中,但我不知道什么时候应该使用它。
【问题讨论】:
实际用例:Web 框架中的 GET 和 POST 参数映射,提供两个不同且独立的字典的组合视图。 至于在 2.x 中使用它,source code 看起来可能只是工作,虽然我没有尝试过 FWIW,Python2.7 中已经有一个先驱:from ConfigParser import _ChainMap as ChainMap
。
@RaymondHettinger 感谢您提供的漂亮实用的 cmets 以及 ChainMap
本身。你本可以从 cmets 链中得到一个很好的答案:)
如果你想像Raymond建议的那样在2.7中导入,它实际上是_Chainmap
(注意m的大写)
【参考方案1】:
我将对此进行破解:
Chainmap 看起来像是一种非常简单的抽象。对于非常专业的问题,这是一个很好的解决方案。我提出这个用例。
如果你有:
-
多个映射(例如,dicts)
这些映射中的某些键重复(同一个键可以出现在多个映射中,但并非所有键都出现在所有映射中)
希望访问“最高优先级”映射中键的值的消费应用程序,其中对任何给定键的所有映射都有总排序(也就是说,映射可能具有相同的优先级,但前提是它已知这些映射中没有重复的键)(在 Python 应用程序中,包可以存在于同一目录中(相同优先级)但必须具有不同的名称,因此,根据定义,该目录中的符号名称不能重复.)
消费应用程序不需要更改键的值
同时映射必须保持其独立身份,并且可以通过外力异步更改
并且映射足够大、足够昂贵以访问或在应用程序访问之间经常更改,因此每次应用程序需要计算投影 (3) 的成本对您的应用程序来说是一个重要的性能问题...
那么, 您可能会考虑使用链图在映射集合上创建视图。
但这都是事后证明。 Python 人员遇到了一个问题,在他们的代码上下文中提出了一个很好的解决方案,然后做了一些额外的工作来抽象他们的解决方案,以便我们可以根据需要使用它。给他们更多的权力。但是否适合您的问题由您决定。
【讨论】:
Chainmap 在所有键都出现在所有映射中的情况下很有用,例如,如果您想推送和弹出映射的版本。 很公平,但如果你所有的字典都有相同的键,你可能可以更快地创建一个更新值的新字典。 是的,它会更快,但它不支持弹出/推送功能。【参考方案2】:我可以看到将ChainMap
用于配置对象,其中您具有多个配置范围,例如命令行选项、用户配置文件和系统配置文件。由于查找是按构造函数参数中的顺序排序的,因此您可以覆盖较低范围的设置。我个人没有使用过或见过ChainMap
使用过,但这并不奇怪,因为它是标准库中最近添加的。
如果您尝试自己实现词法范围,它也可能对模拟堆栈帧很有用,您可以在其中推送和弹出变量绑定。
standard library docs for ChainMap 提供了几个示例和第三方库中类似实现的链接。具体来说,它命名为 Django 的 Context class 和 Enthought 的 MultiContext class。
【讨论】:
+1 这是一个很好的答案,涵盖了替代实现以及文档中提到的多个链式命名空间示例的链接。【参考方案3】:我喜欢@b4hand 的示例,事实上我在过去使用过类似 ChainMap 的结构(但不是 ChainMap 本身)用于他提到的两个目的:多层配置覆盖和变量堆栈/范围仿真。
我想指出ChainMap
的另外两个动机/优势/差异,与使用 dict-update 循环相比,因此只存储“最终”版本:
更多信息: 由于 ChainMap 结构是“分层的”,因此它支持回答以下问题:我得到的是“默认”值还是被覆盖的值?原始(“默认”)值是多少?该值在什么级别被覆盖(借用@b4hand 的配置示例:user-config 或 command-line-overrides)?使用简单的 dict,回答这些问题所需的信息已经丢失。
速度权衡:假设你有N
层和每个层最多M
键,构造一个ChainMap需要O(N)
和每个查找O(N)
最坏情况[ *],而使用更新循环构造字典需要O(NM)
和每次查找O(1)
。这意味着,如果您经常构造并且每次只执行几次查找,或者如果 M
很大,ChainMap 的惰性构造方法对您有利。
[*] (2) 中的分析假设 dict-access 是O(1)
,而实际上它平均是O(1)
,最坏的情况是O(M)
。查看更多详情here。
【讨论】:
+1 这是一个合理的比较。与其他技术有一些类比会很好。例如,操作系统命令行有一个路径的概念,它本质上是一个目录查找链,直到找到匹配项。在 Python 中,这将使用 ChainMap 建模。【参考方案4】:不完美地回答你的问题:
额外问题:有没有办法在 Python2.x 上使用它?
from ConfigParser import _Chainmap as ChainMap
但请记住,这不是真正的 ChainMap
,它继承自 DictMixin
,并且仅定义:
__init__(self, *maps)
__getitem__(self, key)
keys(self)
# And from DictMixin:
__iter__(self)
has_key(self, key)
__contains__(self, key)
iteritems(self)
iterkeys(self)
itervalues(self)
values(self)
items(self)
clear(self)
setdefault(self, key, default=None)
pop(self, key, *args)
popitem(self)
update(self, other=None, **kwargs)
get(self, key, default=None)
__repr__(self)
__cmp__(self, other)
__len__(self)
它的实现似乎也不是特别有效。
【讨论】:
以上是关于collections.ChainMap 的目的是啥?的主要内容,如果未能解决你的问题,请参考以下文章