在这篇文章中,我们将探讨Python中集合模块中的ChainMap容器。
目录:
- 什么是链图
- 创建一个链图
- 对链图的操作
- 迭代一个链图
- 链图的复杂度分析
- 链图的应用
- 练习题
什么是链图
Python 的 ChainMap 容器提供了一种将多个字典组合成一个的方法,使它们作为一个字典出现。我们可以在 ChainMap 上执行所有我们通常在字典上执行的操作。
在内部,ChainMap 创建了一个指向原始字典的引用列表:

创建一个 ChainMap
为了开始使用链图,我们必须首先从集合模块中导入它:
from collections import ChainMap
考虑到两个字典 A 和 B,我们可以创建一个链图,如下所示:
A = { "pen" : 15, "eraser" : 22 }
B = { "marker" : 10 }
chain = ChainMap(A, B)
print(chain)
输出:
ChainMap({'pen': 15, 'eraser': 22}, {'marker': 10})
对链图的操作
使用键来访问元素
一旦创建,你就可以像在字典中一样访问 ChainMap 的元素,或者使用 ChainMap 对象的*.get()*方法。
print( chain["pen"] )
print( chain.get("marker") )
输出:
15
10
需要注意的是,如果同一个键出现在组成ChainMap的多个字典中,你将只得到该键的第一次出现。这取决于创建ChainMap对象时传递字典的顺序:
A = { "pen" : 15, "eraser" : 22 }
B = { "marker" : 10, "eraser" : 40 }
chain = ChainMap(A, B)
print( chain["eraser"] )
输出 :
22
向 Chain Map 添加一个新的字典
由于 ChainMap 内部使用一个列表,你可以使用*.maps.append()*方法将一个字典添加到 Chain 的结尾。
A = { "pen" : 15, "eraser" : 22 }
B = { "marker" : 10, "eraser" : 40 }
chain = ChainMap(A, B)
print(chain)
C = { "chart" : 50 }
chain.maps.append(C)
print(chain)
输出
ChainMap({'pen': 15, 'eraser': 22}, {'marker': 10, 'eraser': 40})
ChainMap({'pen': 15, 'eraser': 22}, {'marker': 10, 'eraser': 40}, {'chart': 50})
使用*.new_child(*) 方法将一个新的字典添加到链的开头:
A = { "pen" : 15, "eraser" : 22 }
B = { "marker" : 10, "eraser" : 40 }
chain = ChainMap(A, B)
print(chain)
C = { "chart" : 50 }
chain = chain.new_child(C)
print(chain)
输出
ChainMap({'pen': 15, 'eraser': 22}, {'marker': 10, 'eraser': 40})
ChainMap({'chart': 50}, {'pen': 15, 'eraser': 22}, {'marker': 10, 'eraser': 40})
变异操作
ChainMap 也支持添加、更新、删除键值对这样的操作。然而,这些操作只影响 ChainMap 中的第一个字典,即使该键存在于多个字典中。
添加一个新的配对
# Adding a new pair
chain["crayon"] = 5
print(chain)
输出 :
ChainMap({'pen': 15, 'eraser': 22, 'crayon': 5}, {'marker': 10, 'eraser': 40})
更新现有的配对
#Updating a key-value pair
chain["eraser"]=100
print( chain )
输出
ChainMap({'eraser': 22}, {'marker': 10, 'eraser': 40})
删除一个键值对
#Deleting a key-value pair
del chain["pen"]
print( chain )
输出
ChainMap({'eraser': 22}, {'marker': 10, 'eraser': 40})
重要的是!
使用突变操作修改 ChainMap 也会修改原始字典,反之亦然。这种行为是因为 ChainMap 存储的只是对原始字典的引用。
遍历ChainMap
我们可以使用*.items(*) 方法对 ChainMap 进行迭代,就像普通的 dictionary 一样:
A = { "pen" : 15, "eraser" : 22 }
B = { "marker" : 10, "eraser" : 40 }
chain = ChainMap(A, B)
for k,v in chain.items():
print(k,":",v)
输出
marker : 10
eraser : 22
pen : 15
如果一个键出现不止一次,那么只打印该键的第一次出现。在我们的例子中,橡皮擦出现了两次,但只打印了第一次出现的。
我们也可以分别使用*.key()和.values()*方法获得键和值的列表。
keys=list(chain.keys())
values=list(chain.values())
print("Keys : ",keys)
print("Values : ",values)
输出
Keys : ['marker', 'pen', 'eraser']
Values : [10, 15, 22]
链式图的复杂性分析
时间复杂度
假设有N个有M个键的字典。那么在最坏的情况下,建立一个链图的时间复杂度为O(N),检索一个值的时间复杂度为O(N)。
如果你使用*.update()*方法来组合所有的字典,那么建立最终字典的时间复杂度将是O(N*M),检索的时间复杂度将是O(1)
这使得链式映射成为一个完美的选择,如果你经常合并字典而很少检索的话。
空间复杂度
链图的空间复杂度是O(N),因为我们只存储了对N个字典的引用。
链图的应用
- 链式映射的一个非常常见的应用案例是当你需要把多个字典作为一个单一的字典来访问。
例如,考虑两个字典shirts和pants,它们存储不同类型的衬衫和裤子的价格:
shirts = { "short sleeve" : 500, "overshirt" : 1000, "t-shirt" : 100 }
pants = { "trousers" : 300, "chinos" : 150 }
现在想象一下,如果我们有一个购物车字典,顾客可以向其中添加衬衫和裤子。我们需要计算总价格:
cart = { "t-shirt" : 3, "chinos" : 4 }
chain = ChainMap(shirts, pants)
total = 0
for k,v in cart.items():
total += chain[k] * v
# 3x100 + 4x150
print(total)
输出
900
正如我们所看到的,我们可以使用链图非常容易地计算出总数,而不必担心购物车中的物品是衬衫还是裤子。
- 链图的另一个非常有趣的应用是当你需要将一些价值优先于另一个价值时。这是基于这样一个事实:当一个键在链图中出现不止一次时,只有第一次出现的时候会受到影响。
想象一下,如果你正在建立一个DNS(Domain Name System)解析器应用程序。用户可以输入他们自己的DNS服务器IP地址。如果没有输入IP地址,那么应该使用8.8.8.8的默认地址。
from collections import ChainMap
custom_dns={} # The user does not provide a address
public_dns={"DNS":"208.67.222.222"}
global_dns={"DNS": "8.8.8.8"}
chain = ChainMap(custom_dns,public_dns,global_dns)
print(chain["DNS"])
输出
208.67.222.222
正如我们在上面的例子中看到的,如果用户输入了一个DNS服务器,那么这个地址必须被使用,否则就应该使用下一个配置。
练习题
- 从collection模块导入ChainMap并创建一个ChainMap对象
- 反转ChainMap(提示:使用reversed()函数)
- 使用ChainMap模拟变量范围(本地、全局、外部)。
通过OpenGenus的这篇文章,你一定对Python中的ChainMap有了完整的了解。