Python 入门:ChainMap 有效管理多个上下文
摘要: Python 的 ChainMap 从 collections 模块提供用于管理多个词典作为单个的有效工具。
本文分享自华为云社区《从零开始学python| ChainMap 有效管理多个上下文》,作者: Yuchuan 。
有时,当您使用多个不同的词典时,您需要将它们作为一个进行分组和管理。在其他情况下,您可以拥有多个代表不同范围或上下文的字典,并且需要将它们作为单个字典来处理,以便您可以按照给定的顺序或优先级访问底层数据。在这种情况下,你可以利用 Python 的的 ChainMap 从 collections 模块。
ChainMap 将多个字典和映射组合在一个具有字典式行为的可更新视图中。此外,ChainMap 还提供了允许您有效管理各种词典、定义关键查找优先级等的功能。
在本教程中,您将学习如何:
在 Python 程序中创建 ChainMap 实例
探索差异之间 ChainMap 和 dict
用于 ChainMap 将多个字典合二为一
管理键查找优先使用 ChainMap
为了充分利用本教程,您应该了解在 Python 中使用字典和列表的基础知识。
在旅程结束时,您将找到一些实际示例,这些示例将帮助您更好地了解 ChainMap.
Python 入门 ChainMap
Python 的 ChainMap 加入 collections 中的 Python 3.3 作为管理多一个方便的工具范围和环境。此类允许您将多个字典和其他映射组合在一起,使它们在逻辑上显示并表现为一个整体。它创建一个单一的可更新视图,其工作方式类似于常规字典,但有一些内部差异。
ChainMap 不会将其映射合并在一起。相反,它将它们保存在一个内部映射列表中。然后 ChainMap 在该列表的顶部重新实现常见的字典操作。由于内部列表保存对原始输入映射的引用,因此这些映射中的任何更改都会影响整个 ChainMap 对象。
将输入映射存储在列表中允许您在给定的链映射中拥有重复的键。如果您执行键查找,则 ChainMap 搜索映射列表,直到找到第一次出现的目标键。如果密钥丢失,那么您会 KeyError 像往常一样得到一个。
当您需要管理嵌套作用域时,将映射存储在列表中会真正发挥作用,其中每个映射代表一个特定的作用域或上下文。
为了更好地理解作用域和上下文的含义,请考虑 Python 如何解析名称。当 Python 查找名称时,它会在 locals()、globals()、 中搜索,最后 builtins 直到找到第一次出现的目标名称。如果名称不存在,那么您会得到一个 NameError. 处理范围和上下文是您可以解决的最常见的问题 ChainMap。
当您使用 ChainMap 时,您可以使用不相交或相交的键链接多个字典。
在第一种情况下,ChainMap 允许您将所有字典视为一个。因此,您可以像使用单个字典一样访问键值对。在第二种情况下,除了将字典作为一个来管理之外,您还可以利用内部映射列表为字典中的重复键定义某种访问优先级。这就是为什么 ChainMap 对象非常适合处理多个上下文。
一个奇怪的行为 ChainMap 是突变,例如更新、添加、删除、清除和弹出键,仅作用于内部映射列表中的第一个映射。以下是主要功能的摘要 ChainMap:
从多个输入映射构建可更新视图
提供与字典几乎相同的界面,但具有一些额外的功能
不合并输入映射,而是将它们保存在内部公共列表中
查看输入映射的外部变化
可以包含具有不同值的重复键
通过内部映射列表按顺序搜索键
在搜索整个映射列表后 KeyError 缺少键时抛出 a
仅对内部列表中的第一个映射执行更改
在本教程中,您将详细了解 ChainMap. 以下部分将指导您了解如何 ChainMap 在您的代码中创建新实例。
实例化 ChainMap
要 ChainMap 在您的 Python 代码中创建,您首先需要从该类导入 collections,然后像往常一样调用它。类初始值设定项可以将零个或多个映射作为参数。没有参数,它初始化一个链映射,里面有一个空字典:
在这里,您 ChainMap 使用不同的映射组合创建多个对象。在每种情况下,ChainMap 返回所有输入映射的单个类似字典的视图。请注意,您可以使用任何类型的映射,例如 OrderedDict 和 defaultdict。
您还可以 ChainMap 使用类方法 创建对象.fromkeys()。此方法可以采用可迭代的键和所有键的可选默认值:
如果你调用.fromkeys()上 ChainMap 与迭代键作为参数,那么你得到的链条地图一个字典。键来自输入可迭代对象,值默认为 None。或者,您可以传递第二个参数 来.fromkeys()为每个键提供合理的默认值。
运行类似字典的操作
ChainMap 支持与常规字典相同的 API,用于访问现有密钥。拥有 ChainMap 对象后,您可以使用字典样式的键查找来检索现有键,或者您可以使用.get():
键查找搜索目标链映射中的所有映射,直到找到所需的键。如果密钥不存在,那么您将获得通常的 KeyError. 现在,当您有重复的键时,查找操作的行为如何?在这种情况下,您将获得第一次出现的目标键:
当您访问重复键(例如"dogs"and )时"cats",链映射仅返回该键的第一次出现。在内部,查找操作按照它们在内部映射列表中出现的相同顺序搜索输入映射,这也是您将它们传递到类的初始值设定项的确切顺序。
这种一般行为也适用于迭代:
该 for 循环遍历在字典 pets 和打印每个键-值对的第一次出现。您还可以直接或使用 and 遍历字典.keys(),.values()就像使用任何字典一样:
同样,行为是相同的。每次迭代都经过底层链映射中每个键、项和值的第一次出现。
ChainMap 还支持突变。换句话说,它允许您更新、添加、删除和弹出键值对。这种情况下的不同处在于这些操作仅作用于第一个映射:
改变给定链映射内容的操作只会影响第一个映射,即使您尝试改变的键存在于列表中的其他映射中。例如,当您尝试"b"在第二个映射中进行更新时,真正发生的是您向第一个字典添加了一个新键。
您可以利用此行为来创建不修改原始输入字典的可更新链映射。在这种情况下,您可以使用空字典作为第一个参数 ChainMap:
在这里,您使用一个空字典 ( {}) 创建 alpha_num. 这确保您执行的更改 alpha_num 永远不会影响您的两个原始输入字典 numbers 和 letters,并且只会影响列表开头的空字典。
合并与链接字典
作为使用 链接多个字典的替代方法 ChainMap,您可以考虑使用 dict.update()以下方法将它们合并在一起:
在此特定示例中,当您从两个具有唯一键的现有字典构建链映射和等效字典时,您会得到类似的结果。
与将字典.update()与 ChainMap. 第一个也是最重要的缺点是,您无法使用多个范围或上下文来管理和确定对重复键的访问的优先级。使用.update(),您为给定键提供的最后一个值将始终占上风:
常规词典不能存储重复的键。每次调用.update()现有键的值时,该键都会更新为新值。在这种情况下,您将无法使用不同的范围对重复密钥的访问进行优先级排序。
注意:从 Python 3.5 开始,您还可以使用字典解包运算符 ( **)将字典合并在一起。此外,如果您使用的是 Python 3.9,那么您可以使用字典联合运算符 ( |) 将两个字典合并为一个新字典。
现在假设您有 n 个不同的映射,每个映射最多有 m 个键。ChainMap 从它们创建对象将花费 O ( n )执行时间,而在最坏的情况下检索键将花费 O ( n ),其中目标键位于内部映射列表的最后一个字典中。
或者,.update()在循环中创建一个常规字典需要 O ( nm ),而从最终字典中检索一个键需要 O (1)。
结论是,如果您经常创建字典链并且每次只执行几个键查找,那么您应该使用 ChainMap. 如果是相反的方式,则使用常规词典,除非您需要重复的键或多个范围。
合并字典和链接字典之间的另一个区别是,当您使用 时 ChainMap,输入字典中的外部更改会影响基础链,而合并字典的情况并非如此。
探索附加功能 ChainMap
ChainMap 提供与常规 Python 字典大致相同的 API 和功能,但有一些您已经知道的细微差别。
ChainMap 还支持一些特定于其设计和目标的附加功能。
在本节中,您将了解所有这些附加功能。当您访问字典中的键值对时,您将了解它们如何帮助您管理不同的范围和上下文。
管理映射列表 .maps
ChainMap 将所有输入映射存储在一个内部列表中。此列表可通过名为的公共实例属性访问.maps,并且可由用户更新。中的映射.maps 顺序与您将它们传递到 中的顺序相匹配 ChainMap。此顺序定义执行键查找操作时的搜索顺序。
以下是您如何访问的示例.maps:
在这里,您用于.maps 访问 pets 保存的映射的内部列表。此列表是常规 Python 列表,因此您可以手动添加和删除映射、遍历列表、更改映射的顺序等:
在这些示例中,您首先将一个新字典添加到.mapsusing 中.append()。然后使用 del 关键字删除位置 处的字典 1。您可以.maps 像管理任何常规 Python 列表一样进行管理。
注意:映射的内部列表.maps 将始终包含至少一个映射。例如,如果您使用 ChainMap()不带参数创建一个空链映射,那么该列表将存储一个空字典。
您可以.maps 在对所有映射执行操作时对其进行迭代。遍历映射列表的可能性允许您对每个映射执行不同的操作。使用此选项,您可以解决仅更改列表中的第一个映射的默认行为。
一个有趣的例子是,您可以使用以下命令颠倒当前映射列表的顺序.reverse():
反转内部映射列表允许您在查找链映射中的给定键时反转搜索顺序。现在,当您查找"cats"时,您会得到接受兽医治疗的猫的数量,而不是准备收养的猫的数量。
添加新的子上下文 .new_child()
ChainMap 也实现.new_child(). 此方法可选择将映射作为参数并返回一个新 ChainMap 实例,该实例包含输入映射,后跟底层链映射中的所有当前映射:
在这里,.new_child()返回一个 ChainMap 包含新映射的新对象 son,后跟旧映射,mom 和 dad。请注意,新映射现在占据内部映射列表中的第一个位置,.maps。
使用.new_child(),您可以创建一个子上下文,您可以在不更改任何现有映射的情况下更新该子上下文。例如,如果您.new_child()不带参数调用,则它使用一个空字典并将其放在.maps. 在此之后,您可以对新的空映射执行任何更改,使映射的其余部分保持只读。
跳过子上下文.parents
ChainMap 的另一个有趣的功能是.parents。此属性返回一个新 ChainMap 实例,其中包含底层链映射中除第一个之外的所有映射。当您在给定链映射中搜索键时,此功能对于跳过第一个映射很有用:
在此示例中,您使用.parents 跳过包含儿子数据的第一个字典。在某种程度上,.parents 是.new_child()的逆。前者删除一个字典,而后者在列表的开头添加一个新字典。在这两种情况下,您都会获得一个新的链图。
管理范围和上下文 ChainMap
可以说,主要用例 ChainMap 是提供一种有效的方法来管理多个范围或上下文并处理重复键的访问优先级。当您有多个存储重复键的字典并且您想定义代码访问它们的顺序时,此功能很有用。
在 ChainMap 文档 中,您将找到一个经典示例,该示例模拟 Python 如何解析不同命名空间中的变量名称。
当 Python 查找名称时,它会按照相同的顺序依次搜索本地、全局和内置作用域,直到找到目标名称。Python 作用域是将名称映射到对象的字典。
要模拟 Python 的内部查找链,您可以使用链映射:
在此示例中,您首先创建一个名为的全局变量 input,该变量隐藏 input()作用 builtins 域中的内置函数。然后创建 pylookup 一个链映射,其中包含保存每个 Python 作用域的三个字典。
当您 input 从检索时 pylookup,您会 42 从全局范围中获得值。如果您 input 从 globals()字典中删除键并再次访问它,那么您 input()将从 builtins 作用域中获得内置函数,它在 Python 的查找链中具有最低优先级。
同样,您可以使用 ChainMap 来定义和管理重复键的键查找顺序。这允许您优先访问所需的重复密钥实例。
ChainMap 在标准库中跟踪
ChainMap 的起源密切相关的性能问题中 ConfigParser,其生活中 configparser 的标准库模块。有了 ChainMap,核心 Python 开发人员通过优化 ConfigParser.get().
您还可以在模块中找到 ChainMap 作为一部分。此类将字符串模板作为参数,并允许您执行 PEP 292 中所述的字符串替换。输入字符串模板包含您以后可以用实际值替换的嵌入标识符:Templatestring
当您为字典提供值 name 并 place 通过字典提供值时,.substitute()在模板字符串中替换它们。此外,.substitute()可以将值作为关键字参数 ( **kwargs),这在某些情况下可能会导致名称冲突:
在此示例中,.substitute()替换 place 为您作为关键字参数提供的值,而不是输入字典中的值。如果您深入研究此方法的代码,就会发现它用于 ChainMap 在发生名称冲突时有效地管理输入值的优先级。
这是来自 的源代码片段.substitute():
在这里,突出显示的行发挥了作用。它使用一个链映射,该映射将两个字典 kws 和 mapping, 作为参数。通过放置 kws 作为第一个参数,该方法设置输入数据中重复标识符的优先级。
将 PythonChainMap 付诸行动
到目前为止,您已经学会了如何将 ChainMap 多个字典合二为一。您还了解了 ChainMap 本课程与常规词典的特点和不同之处。ChainMap 的用例是相当具体的。它们包括:
在单个视图中有效地对多个字典进行分组
搜索具有特定优先级的多个词典
提供一系列默认值并管理它们的优先级
提高经常计算字典子集的代码的性能
在本节中,您将编写一些实际示例,这些示例将帮助您更好地了解如何使用它 ChainMap 来解决实际问题。
将多个库存作为一个访问
您将编写代码的第一个示例用于 ChainMap 在单个视图中高效搜索多个字典。在这种情况下,您会假设您有一堆具有唯一键的独立字典。
假设您正在经营一家销售水果和蔬菜的商店。您已经编写了一个 Python 应用程序来管理您的库存。该应用程序从数据库中读取数据并返回两个字典,分别包含有关水果和蔬菜价格的数据。您需要一种有效的方法在单个字典中对这些数据进行分组和管理。
经过一些研究,您最终使用 ChainMap:
在此示例中,您使用 aChainMap 创建一个类似字典的对象,该对象将来自 fruits_prices 和 veggies_prices 的数据分组。这允许您访问底层数据,就像您有效地拥有单个字典一样。该 for 循环迭代的产品在一个给定的 order。然后它计算每种产品类型的小计,并将其打印在您的屏幕上。
您可能会考虑将数据分组到一个新字典中,.update()在循环中使用。当您的产品种类有限且库存较少时,这可能会很好地工作。但是,如果您管理许多不同类型的产品,那么.update()与 ChainMap.使用 ChainMap 来解决这类问题也可以帮助你定义不同批次产品的优先级,让你管理一个先入/先出(你的库存 FIFO)的方式。
优先考虑命令行应用程序设置
ChainMap 在管理应用程序中的默认配置值方面特别有用。如您所知,它的主要功能之一 ChainMap 是允许您设置关键查找操作的优先级。这听起来像是解决管理应用程序配置问题的正确工具。
例如,假设您正在开发一个命令行界面 (CLI)应用程序。该应用程序允许用户指定用于连接到 Internet 的代理服务。设置优先级是:
1. 命令行选项 ( --proxy, -p)
2. 用户主目录中的本地配置文件
3. 系统范围的代理配置
如果用户在命令行提供代理,则应用程序必须使用该代理。否则,应用程序应使用下一个配置对象中提供的代理,依此类推。这是最常见的用例之一 ChainMap。在这种情况下,您可以执行以下操作:
ChainMap 允许您为应用程序的代理配置定义适当的优先级。键查找搜索 cmd_proxy,然后 local_proxy,最后 system_proxy,返回手头键的第一个实例。在示例中,用户没有在命令行提供代理,因此应用程序从 中获取代理 local_proxy,这是列表中的下一个设置提供程序。
管理默认参数值
ChainMap 的另一个用例是管理方法和函数中的默认参数值。假设您正在编写一个应用程序来管理有关贵公司员工的数据。您有以下类,它代表一个通用用户:
在某些时候,您需要添加一项功能,允许员工访问 CRM 系统的不同组件。您的第一个想法是修改 User 以添加新功能。但是,这可能会使类过于复杂,因此您决定创建一个子类 CRMUser,以提供所需的功能。
该类将用户 name 和 CRMcomponent 作为参数。它也需要一些**kwargs。您希望以 CRMUser 一种允许您为基类的初始值设定项提供合理的默认值的方式实现,而不会失去**kwargs.以下是您使用以下方法解决问题的方法 ChainMap:
在此代码示例中,您将创建 User. 在类初始化,你拿 name,component 以及**kwargs 作为参数。然后,您创建一个本地字典,其中 user_id 和具有默认值 role。然后.__init__()使用 super(). 在此调用中,您 name 直接传递给父级的初始值设定项,并使用链映射为其余参数提供默认值。
请注意,该 ChainMap 对象将 kwargs 然后 defaults 作为参数。此顺序可确保在实例化类时手动提供的参数 ( kwargs) 优先于 defaults 值。
结论
Python 的 ChainMap 从 collections 模块提供用于管理多个词典作为单个的有效工具。当您有多个字典表示不同的范围或上下文并且需要设置对底层数据的访问优先级时,这个类很方便。
ChainMap 将多个字典和映射组合在一个可更新的视图中,该视图的工作方式与字典非常相似。您可以使用 ChainMap 对象在 Python 中高效地处理多个字典、定义关键查找优先级以及管理多个上下文。
在本教程中,您学习了如何:
在 Python 程序中创建 ChainMap 实例
探索差异之间 ChainMap 和 dict
使用多个字典作为一个管理 ChainMap
设置关键查找操作的优先级 ChainMap
在本教程中,您还编写了一些实际示例,帮助您更好地理解何时以及如何 ChainMap 在 Python 代码中使用。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/9f4e0adc79343c378b5190845】。文章转载请联系作者。
评论