介绍python字典中获取值的另一种方法,和设置键的默认值的方法。
get方法
在获取字典的值的时候,有一种方法是使用键中括号直接取值,如下:
d1 = {
"a": "A",
"b": "B",
"c": "C",
}
print(d1["a"])
但是这种方法有个缺点是,一旦键不存在,方法会报错,除非提前判断键是否存在,如下:
print(d1["e"])
# KeyError: 'e'
使用get方法一样可以取到键对应的值,但是好处是如果键不存在,则不会报错,而是给一个默认值。没有指定的情况下就是None。如下:
d1 = {
"a": "A",
"b": "B",
"c": "C",
}
# 不指定默认值
print(d1.get("e")) # 结果为None
# 指定默认值
print(d1.get("e", "")) # 结果为""
setdefault方法
setdefault方法会查询字典里有没有这个键,如果有,就返回对应的值,没有则把用户提供的默认值跟这个键关联并插入字典里。如下:
d1 = {
"a": "A",
"b": "B",
"c": "C",
}
d1.setdefault("d", "")
print(d1)
# {'a': 'A', 'b': 'B', 'c': 'C', 'd': ''}
但是使用这个方法时要注意,如果默认值设置为空列表[],最好不要用变量引用,否则,容易出bug。如下:
d1 = {
"a": [1],
"b": [1, 2],
"c": [1, 2, 3],
}
value = []
d1.setdefault("d", value)
d1.get("d", []).append(4)
print(d1) # {'a': [1], 'b': [1, 2], 'c': [1, 2, 3], 'd': [4]}
print(value) # [4]
value.append(5)
print(value) # [4, 5]
print(d1) # {'a': [1], 'b': [1, 2], 'c': [1, 2, 3], 'd': [4, 5]}
可以看到,当d1的"d"键的值变化,value变化跟着变化;value变化,d1的"d"键的值也跟着变化。这是因为d1的"d"键的列表和value都是引用同一个列表。
defaultdict 处理缺失值
假如字典是自己创建的,且需要字典实例来维护类对象的内部状态。如创建一个Visits类,记录去过哪些国家和这个国家的哪些城市,国家里的城市使用集合来存储,但是第一次记录国家时,还不一定有城市,就需要记个空集合。使用上一节学的setdefault,如下:
class Visits:
def __init__(self):
self.data = {}
def add(self, country, city):
city_set = self.data.setdefault(country, set())
city_set.add(city)
这种做法不够高效,每次调用add方法,无论所指定的国家是否在字典里,都必须构造新的city_set 实例。
collections
模块提供的defaultdict
,可以在键缺失的情况下,自动添加这个键所对应的默认值。只需要在构造这种字典时提供一个函数,每次键不在,就调用这个函数返回一个新的默认值。
下面使用defaultdict改造Visits:
from collections import defaultdict
class Visits:
def __init__(self):
self.data = defaultdict(set)
def add(self, country, city):
self.data[country].add(city)
visit = Visits()
visit.add('England', 'Bath')
visit.add('England', 'London')
print(visit.data)
# defaultdict(<class 'set'>, {'England': {'London', 'Bath'}})
这样add方法相当简短,也不会每次调用add方法就分配一些set实例。
建议:
1.如果自己管理的字典可能需要添加任意的键,那么优先考虑collections
模块提供的defaultdict
。
2.如果字典是别人传的,你不知道键存不存在,应该优先使用get方法获取键值。个别情况下可以使用setdefault减少判断。
__missing__构造依赖键的默认值
如果接受一个文件路径,要读取文件,并把文件内容存起来。如下:
pictures = {}
path = 'p_1234.png'
if (handel := pictures.get(path)) is None: # python3.8的赋值表达式
try:
handel = open(path, 'a+b')
except OSError:
print(f"Failed to open path {path}")
raise
else: # try执行成功会执行else
pictures[path] = handel
handel.seek(0)
image_data = handel.read()
python还内置了一些解决方案,可以通过继承dict类型并实现__missing__
特殊方法解决这个问题。我们可以把字典里不存在这个键时所执行的逻辑写在这个方法。
下面定义一个新的类:
class Pictures(dict):
def __missing__(self, key):
value = self.open(key)
self[key] = value
return value
def open_picture(self, path):
try:
return open(path, 'a+b')
except OSError:
print(f"Failed to open path {path}")
raise
path = 'p_1234.png'
pictures = Pictures()
handel = pictures[path]
handel.seek(0)
image_data = handel.read()
访问pictures[path]时,如果pictures字典里没有path这个键,就会调用__missing_
方法。这个方法根据key参数创建一份新的默认值,系统就会把这个默认值插入字典并返回给调用方。
建议:
1.传给defaultdict的函数必须是不需要参数的函数。
如果要构造的默认值必须根据键名来确定,那么可以定义自己的dict子类并实现__missing__
方法。