摘要: 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,然后像往常一样调用它。类初始值设定项可以将零个或多个映射作为参数。没有参数,它初始化一个链映射,里面有一个空字典:

>>> from collections import ChainMap
>>> from collections import OrderedDict, defaultdict

>>> # Use no arguments
>>> ChainMap()
ChainMap({})

>>> # Use regular dictionaries
>>> numbers = {"one": 1, "two": 2}
>>> letters = {"a": "A", "b": "B"}

>>> ChainMap(numbers, letters)
ChainMap({'one': 1, 'two': 2}, {'a': 'A', 'b': 'B'})

>>> ChainMap(numbers, {"a": "A", "b": "B"})
ChainMap({'one': 1, 'two': 2}, {'a': 'A', 'b': 'B'})

>>> # Use other mappings
>>> numbers = OrderedDict(one=1, two=2)
>>> letters = defaultdict(str, {"a": "A", "b": "B"})
>>> ChainMap(numbers, letters)
ChainMap(
    OrderedDict([('one', 1), ('two', 2)]),
    defaultdict(<class 'str'>, {'a': 'A', 'b': 'B'})
)

在这里,您ChainMap使用不同的映射组合创建多个对象。在每种情况下,ChainMap返回所有输入映射的单个类似字典的视图。请注意,您可以使用任何类型的映射,例如OrderedDict和defaultdict。

您还可以ChainMap使用类方法 创建对象.fromkeys()。此方法可以采用可迭代的键和所有键的可选默认值:

>>> from collections import ChainMap

>>> ChainMap.fromkeys(["one", "two","three"])
ChainMap({'one': None, 'two': None, 'three': None})

>>> ChainMap.fromkeys(["one", "two","three"], 0)
ChainMap({'one': 0, 'two': 0, 'three': 0})

如果你调用.fromkeys()上ChainMap与迭代键作为参数,那么你得到的链条地图一个字典。键来自输入可迭代对象,值默认为None。或者,您可以传递第二个参数 来.fromkeys()为每个键提供合理的默认值。

运行类似字典的操作

ChainMap支持与常规字典相同的 API,用于访问现有密钥。拥有ChainMap对象后,您可以使用字典样式的键查找来检索现有键,或者您可以使用.get():

>>> from collections import ChainMap

>>> numbers = {"one": 1, "two": 2}
>>> letters = {"a": "A", "b": "B"}

>>> alpha_num = ChainMap(numbers, letters)
>>> alpha_num["two"]
2

>>> alpha_num.get("a")
'A'

>>> alpha_num["three"]
Traceback (most recent call last):
    ...
KeyError: 'three'

键查找搜索目标链映射中的所有映射,直到找到所需的键。如果密钥不存在,那么您将获得通常的KeyError. 现在,当您有重复的键时,查找操作的行为如何?在这种情况下,您将获得第一次出现的目标键:

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"dogs": 4, "cats": 3, "turtles": 1}
>>> pets = ChainMap(for_adoption, vet_treatment)

>>> pets["dogs"]
10
>>> pets.get("cats")
7
>>> pets["turtles"]
1

当您访问重复键(例如"dogs"and )时"cats",链映射仅返回该键的第一次出现。在内部,查找操作按照它们在内部映射列表中出现的相同顺序搜索输入映射,这也是您将它们传递到类的初始值设定项的确切顺序。

这种一般行为也适用于迭代:

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"dogs": 4, "cats": 3, "turtles": 1}
>>> pets = ChainMap(for_adoption, vet_treatment)

>>> for key, value in pets.items():
...     print(key, "->", value)
...
dogs -> 10
cats -> 7
turtles -> 1
pythons -> 3

该for循环遍历在字典pets和打印每个键-值对的第一次出现。您还可以直接或使用and遍历字典.keys(),.values()就像使用任何字典一样:

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"dogs": 4, "cats": 3, "turtles": 1}
>>> pets = ChainMap(for_adoption, vet_treatment)

>>> for key in pets:
...     print(key, "->", pets[key])
...
dogs -> 10
cats -> 7
turtles -> 1
pythons -> 3

>>> for key in pets.keys():
...     print(key, "->", pets[key])
...
dogs -> 10
cats -> 7
turtles -> 1
pythons -> 3

>>> for value in pets.values():
...     print(value)
...
10
7
1
3

同样,行为是相同的。每次迭代都经过底层链映射中每个键、项和值的第一次出现。

ChainMap还支持突变。换句话说,它允许您更新、添加、删除和弹出键值对。这种情况下的不同之处在于这些操作仅作用于第一个映射:

>>> from collections import ChainMap

>>> numbers = {"one": 1, "two": 2}
>>> letters = {"a": "A", "b": "B"}

>>> alpha_num = ChainMap(numbers, letters)
>>> alpha_num
ChainMap({'one': 1, 'two': 2}, {'a': 'A', 'b': 'B'})

>>> # Add a new key-value pair
>>> alpha_num["c"] = "C"
>>> alpha_num
ChainMap({'one': 1, 'two': 2, 'c': 'C'}, {'a': 'A', 'b': 'B'})

>>> # Update an existing key
>>> alpha_num["b"] = "b"
>>> alpha_num
ChainMap({'one': 1, 'two': 2, 'c': 'C', 'b': 'b'}, {'a': 'A', 'b': 'B'})

>>> # Pop keys
>>> alpha_num.pop("two")
2
>>> alpha_num.pop("a")
Traceback (most recent call last):
    ...
KeyError: "Key not found in the first mapping: 'a'"

>>> # Delete keys
>>> del alpha_num["c"]
>>> alpha_num
ChainMap({'one': 1, 'b': 'b'}, {'a': 'A', 'b': 'B'})
>>> del alpha_num["a"]
Traceback (most recent call last):
    ...
KeyError: "Key not found in the first mapping: 'a'"

>>> # Clear the dictionary
>>> alpha_num.clear()
>>> alpha_num
ChainMap({}, {'a': 'A', 'b': 'B'})

改变给定链映射内容的操作只会影响第一个映射,即使您尝试改变的键存在于列表中的其他映射中。例如,当您尝试"b"在第二个映射中进行更新时,真正发生的是您向第一个字典添加了一个新键。

您可以利用此行为来创建不修改原始输入字典的可更新链映射。在这种情况下,您可以使用空字典作为第一个参数ChainMap:

>>> from collections import ChainMap

>>> numbers = {"one": 1, "two": 2}
>>> letters = {"a": "A", "b": "B"}

>>> alpha_num = ChainMap({}, numbers, letters)
>>> alpha_num
ChainMap({}, {'one': 1, 'two': 2}, {'a': 'A', 'b': 'B'})

>>> alpha_num["comma"] = ","
>>> alpha_num["period"] = "."

>>> alpha_num
ChainMap(
    {'comma': ',', 'period': '.'},
    {'one': 1, 'two': 2},
    {'a': 'A', 'b': 'B'}
)

在这里,您使用一个空字典 ( {}) 创建alpha_num. 这确保您执行的更改alpha_num永远不会影响您的两个原始输入字典numbers和letters,并且只会影响列表开头的空字典。

合并与链接字典

作为使用 链接多个字典的替代方法ChainMap,您可以考虑使用dict.update()以下方法将它们合并在一起:

>>> from collections import ChainMap

>>> # Chain dictionaries with ChainMap
>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"hamsters": 2, "turtles": 1}

>>> ChainMap(for_adoption, vet_treatment)
ChainMap(
    {'dogs': 10, 'cats': 7, 'pythons': 3},
    {'hamsters': 2, 'turtles': 1}
)

>>> # Merge dictionaries with .update()
>>> pets = {}
>>> pets.update(for_adoption)
>>> pets.update(vet_treatment)
>>> pets

{'dogs': 10, 'cats': 7, 'pythons': 3, 'hamsters': 2, 'turtles': 1}
在此特定示例中,当您从两个具有唯一键的现有字典构建链映射和等效字典时,您会得到类似的结果。

与将字典.update()与ChainMap. 第一个也是最重要的缺点是,您无法使用多个范围或上下文来管理和确定对重复键的访问的优先级。使用.update(),您为给定键提供的最后一个值将始终占上风:

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"cats": 2, "dogs": 1}

>>> # Merge dictionaries with .update()
>>> pets = {}
>>> pets.update(for_adoption)
>>> pets.update(vet_treatment)
>>> pets
{'dogs': 1, 'cats': 2, 'pythons': 3}

常规词典不能存储重复的键。每次调用.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:

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"dogs": 4, "turtles": 1}

>>> pets = ChainMap(for_adoption, vet_treatment)
>>> pets.maps
[{'dogs': 10, 'cats': 7, 'pythons': 3}, {'dogs': 4, 'turtles': 1}]

在这里,您用于.maps访问pets保存的映射的内部列表。此列表是常规 Python 列表,因此您可以手动添加和删除映射、遍历列表、更改映射的顺序等:

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"cats": 1}
>>> pets = ChainMap(for_adoption, vet_treatment)

>>> pets.maps.append({"hamsters": 2})
>>> pets.maps
[{'dogs': 10, 'cats': 7, 'pythons': 3}, {"cats": 1}, {'hamsters': 2}]

>>> del pets.maps[1]
>>> pets.maps
[{'dogs': 10, 'cats': 7, 'pythons': 3}, {'hamsters': 2}]

>>> for mapping in pets.maps:
...     print(mapping)
...
{'dogs': 10, 'cats': 7, 'pythons': 3}
{'hamsters': 2}

在这些示例中,您首先将一个新字典添加到.mapsusing 中.append()。然后使用del 关键字删除位置 处的字典1。您可以.maps像管理任何常规 Python 列表一样进行管理。

注意:映射的内部列表.maps将始终包含至少一个映射。例如,如果您使用ChainMap()不带参数创建一个空链映射,那么该列表将存储一个空字典。

您可以.maps在对所有映射执行操作时对其进行迭代。遍历映射列表的可能性允许您对每个映射执行不同的操作。使用此选项,您可以解决仅更改列表中的第一个映射的默认行为。

一个有趣的例子是,您可以使用以下命令颠倒当前映射列表的顺序.reverse():

>>> from collections import ChainMap

>>> for_adoption = {"dogs": 10, "cats": 7, "pythons": 3}
>>> vet_treatment = {"cats": 1}
>>> pets = ChainMap(for_adoption, vet_treatment)
>>> pets
ChainMap({'dogs': 10, 'cats': 7, 'pythons': 3}, {'cats': 1})

>>> pets.maps.reverse()
>>> pets
ChainMap({'cats': 1}, {'dogs': 10, 'cats': 7, 'pythons': 3})

反转内部映射列表允许您在查找链映射中的给定键时反转搜索顺序。现在,当您查找 时"cats",您会得到接受兽医治疗的猫的数量,而不是准备收养的猫的数量。

添加新的子上下文 .new_child()

ChainMap也实现.new_child(). 此方法可选择将映射作为参数并返回一个新ChainMap实例,该实例包含输入映射,后跟底层链映射中的所有当前映射:

>>> from collections import ChainMap

>>> mom = {"name": "Jane", "age": 31}
>>> dad = {"name": "John", "age": 35}

>>> family = ChainMap(mom, dad)
>>> family
ChainMap({'name': 'Jane', 'age': 31}, {'name': 'John', 'age': 35})

>>> son = {"name": "Mike", "age": 0}
>>> family = family.new_child(son)

>>> for person in family.maps:
...     print(person)
...
{'name': 'Mike', 'age': 0}
{'name': 'Jane', 'age': 31}
{'name': 'John', 'age': 35}

在这里,.new_child()返回一个ChainMap包含新映射的新对象son,后跟旧映射,mom和dad。请注意,新映射现在占据内部映射列表中的第一个位置,.maps。

使用.new_child(),您可以创建一个子上下文,您可以在不更改任何现有映射的情况下更新该子上下文。例如,如果您.new_child()不带参数调用,则它使用一个空字典并将其放在.maps. 在此之后,您可以对新的空映射执行任何更改,使映射的其余部分保持只读。

跳过子上下文 .parents

的另一个有趣的功能ChainMap是.parents。此属性返回一个新ChainMap实例,其中包含底层链映射中除第一个之外的所有映射。当您在给定链映射中搜索键时,此功能对于跳过第一个映射很有用:

>>> from collections import ChainMap

>>> mom = {"name": "Jane", "age": 31}
>>> dad = {"name": "John", "age": 35}
>>> son = {"name": "Mike", "age":  0}

>>> family = ChainMap(son, mom, dad)
>>> family
ChainMap(
    {'name': 'Mike', 'age': 0},
    {'name': 'Jane', 'age': 31},
    {'name': 'John', 'age': 35}
)

>>> family.parents
ChainMap({'name': 'Jane', 'age': 31}, {'name': 'John', 'age': 35})

在此示例中,您使用.parents跳过包含儿子数据的第一个字典。在某种程度上,.parents是 的逆.new_child()。前者删除一个字典,而后者在列表的开头添加一个新字典。在这两种情况下,您都会获得一个新的链图。

管理范围和上下文 ChainMap

可以说,主要用例ChainMap是提供一种有效的方法来管理多个范围或上下文并处理重复键的访问优先级。当您有多个存储重复键的字典并且您想定义代码访问它们的顺序时,此功能很有用。

在ChainMap文档 中,您将找到一个经典示例,该示例模拟 Python 如何解析不同命名空间中的变量名称。

当 Python 查找名称时,它会按照相同的顺序依次搜索本地、全局和内置作用域,直到找到目标名称。Python 作用域是将名称映射到对象的字典。

要模拟 Python 的内部查找链,您可以使用链映射:

>>> import builtins

>>> # Shadow input with a global name
>>> input = 42

>>> pylookup = ChainMap(locals(), globals(), vars(builtins))

>>> # Retrieve input from the global namespace
>>> pylookup["input"]
42

>>> # Remove input from the global namespace
>>> del globals()["input"]

>>> # Retrieve input from the builtins namespace
>>> pylookup["input"]
<built-in function input>

在此示例中,您首先创建一个名为的全局变量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

>>> import string

>>> greeting = "Hey $name, welcome to $place!"
>>> template = string.Template(greeting)

>>> template.substitute({"name": "Jane", "place": "the World"})
'Hey Jane, welcome to the World!'

当您为字典提供值name并place通过字典提供值时,.substitute()在模板字符串中替换它们。此外,.substitute()可以将值作为关键字参数 ( **kwargs),这在某些情况下可能会导致名称冲突:

>>> import string

>>> greeting = "Hey $name, welcome to $place!"
>>> template = string.Template(greeting)

>>> template.substitute(
...     {"name": "Jane", "place": "the World"},
...     place="Real Python"
... )
'Hey Jane, welcome to Real Python!'

在此示例中,.substitute()替换place为您作为关键字参数提供的值,而不是输入字典中的值。如果您深入研究此方法的代码,就会发现它用于ChainMap在发生名称冲突时有效地管理输入值的优先级。

这是来自 的源代码片段.substitute():

# string.py
# Snip...
from collections import ChainMap as _ChainMap

_sentinel_dict = {}

class Template:
    """A string class for supporting $-substitutions."""
    # Snip...

    def substitute(self, mapping=_sentinel_dict, /, **kws):
        if mapping is _sentinel_dict:
            mapping = kws
        elif kws:
            mapping = _ChainMap(kws, mapping)
        # Snip...

在这里,突出显示的行发挥了作用。它使用一个链映射,该映射将两个字典kws和mapping, 作为参数。通过放置kws作为第一个参数,该方法设置输入数据中重复标识符的优先级。

将 PythonChainMap付诸行动

到目前为止,您已经学会了如何将ChainMap多个字典合二为一。您还了解了ChainMap本课程与常规词典的特点和不同之处。的用例ChainMap是相当具体的。它们包括:

  • 单个视图中有效地对多个字典进行分组
  • 搜索具有特定优先级的多个词典
  • 提供一系列默认值并管理它们的优先级
  • 提高经常计算字典子集的代码的性能

在本节中,您将编写一些实际示例,这些示例将帮助您更好地了解如何使用它ChainMap来解决实际问题。

将多个库存作为一个访问

您将编写代码的第一个示例用于ChainMap在单个视图中高效搜索多个字典。在这种情况下,您会假设您有一堆具有唯一键的独立字典。

假设您正在经营一家销售水果和蔬菜的商店。您已经编写了一个 Python 应用程序来管理您的库存。该应用程序从数据库中读取数据并返回两个字典,分别包含有关水果和蔬菜价格的数据。您需要一种有效的方法在单个字典中对这些数据进行分组和管理。

经过一些研究,您最终使用ChainMap:

>>> from collections import ChainMap

>>> fruits_prices = {"apple": 0.80, "grape": 0.40, "orange": 0.50}
>>> veggies_prices = {"tomato": 1.20, "pepper": 1.30, "onion": 1.25}
>>> prices = ChainMap(fruits_prices, veggies_prices)

>>> order = {"apple": 4, "tomato": 8, "orange": 4}

>>> for product, units in order.items():
...     price = prices[product]
...     subtotal = units * price
...     print(f"{product:6}: ${price:.2f} × {units} = ${subtotal:.2f}")
...
apple : $0.80 × 4 = $3.20
tomato: $1.20 × 8 = $9.60
orange: $0.50 × 4 = $2.00

在此示例中,您使用 aChainMap创建一个类似字典的对象,该对象将来自fruits_prices和 的数据分组veggies_prices。这允许您访问底层数据,就像您有效地拥有单个字典一样。该for循环迭代的产品在一个给定的order。然后它计算每种产品类型的小计,并将其打印在您的屏幕上。

您可能会考虑将数据分组到一个新字典中,.update()在循环中使用。当您的产品种类有限且库存较少时,这可能会很好地工作。但是,如果您管理许多不同类型的产品,那么.update()与ChainMap.

使用ChainMap来解决这类问题也可以帮助你定义不同批次产品的优先级,让你管理一个先入/先出(你的库存FIFO)的方式。

优先考虑命令行应用程序设置

ChainMap在管理应用程序中的默认配置值方面特别有用。如您所知,它的主要功能之一ChainMap是允许您设置关键查找操作的优先级。这听起来像是解决管理应用程序配置问题的正确工具。

例如,假设您正在开发一个命令行界面 (CLI)应用程序。该应用程序允许用户指定用于连接到 Internet 的代理服务。设置优先级是:

  1. 命令行选项 ( --proxy, -p)
  2. 用户主目录中的本地配置文件
  3. 系统范围的代理配置

如果用户在命令行提供代理,则应用程序必须使用该代理。否则,应用程序应使用下一个配置对象中提供的代理,依此类推。这是最常见的用例之一ChainMap。在这种情况下,您可以执行以下操作:

>>> from collections import ChainMap

>>> cmd_proxy = {}  # The user doesn't provide a proxy
>>> local_proxy = {"proxy": "proxy.local.com"}
>>> system_proxy = {"proxy": "proxy.global.com"}

>>> config = ChainMap(cmd_proxy, local_proxy, system_proxy)
>>> config["proxy"]
'proxy.local.com'

ChainMap允许您为应用程序的代理配置定义适当的优先级。键查找搜索cmd_proxy,然后local_proxy,最后system_proxy,返回手头键的第一个实例。在示例中,用户没有在命令行提供代理,因此应用程序从 中获取代理local_proxy,这是列表中的下一个设置提供程序。

管理默认参数值

的另一个用例ChainMap是管理方法和函数中的默认参数值。假设您正在编写一个应用程序来管理有关贵公司员工的数据。您有以下类,它代表一个通用用户:

class User:
    def __init__(self, name, user_id, role):
        self.name = name
        self.user_id = user_id
        self.role = role
# Snip...

在某些时候,您需要添加一项功能,允许员工访问CRM系统的不同组件。您的第一个想法是修改User以添加新功能。但是,这可能会使类过于复杂,因此您决定创建一个子类CRMUser,以提供所需的功能。

该类将用户name和 CRMcomponent作为参数。它也需要一些kwargs。您希望以CRMUser一种允许您为基类的初始值设定项提供合理的默认值的方式实现,而不会失去kwargs.

以下是您使用以下方法解决问题的方法ChainMap:

from collections import ChainMap

class CRMUser(User):
    def __init__(self, name, component, **kwargs):
        defaults = {"user_id": next(component.user_id), "role": "read"}
        super().__init__(name, **ChainMap(kwargs, defaults))

在此代码示例中,您将创建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 代码中使用。

点击关注,第一时间了解华为云新鲜技术~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量