【流畅的Python】第三章:字典和集合——《Fluent Python》学习笔记
字典和集合
- 字典和集合
1 字典的现代用法
1.1 字典推导式
字典推导式可以从可迭代对象中获取键值对构建字典。
dial_codes = [
(880, 'Bangladesh'),
(55, 'Brazil'),
(86, 'China'),
(91, 'India'),
(62, 'Indonesia'),
(81, 'Japan'),
(234, 'Nigeria'),
(92, 'Pakistan'),
(7, 'Russia'),
(1, 'United States'),
]
country_dial = {country: code for code, country in dial_codes}
print(country_dial)
1.2 映射拆包
从 Python 3.5 开始,调用函数时,多个参数可使用**,且**可在 dict 字面量中使用。
def dump(**kwargs):
"""
定义一个函数,接受关键字参数并返回一个字典
"""
return kwargs
# 使用**拆包多个字典作为参数传递给函数
print(dump(**{'x': 1}, y=2, **{'z': 3}))
# 输出: {'x': 1, 'y': 2, 'z': 3}
# 在字典字面量中使用**合并多个字典
print({'a': 0, **{'x': 1}, 'y': 2, **{'z': 3, 'x': 4}})
# 输出: {'a': 0, 'x': 4, 'y': 2, 'z': 3}
1.3 使用 | 合并映射
Python 3.9 支持使用|
和|=
合并映射,|
创建新映射,|=
就地更新现有映射。
d1 = {'a': 1, 'b': 3}
d2 = {'a': 2, 'b': 4, 'c': 6}
# 使用|合并两个字典,返回一个新字典
print(d1 | d2) # 右边字典(后出现的)中的键值对会覆盖左边字典(先出现的)中的键值对。
# 输出: {'a': 2, 'b': 4, 'c': 6}
# 使用|=就地更新字典d1
d1 |= d2
print(d1)
# 输出: {'a': 2, 'b': 4, 'c': 6}
2 使用模式匹配处理映射
match/case语句的匹配对象可以是映射,模式看似dict字面量,能匹配collections.abc.Mapping的具体子类或虚拟子类,可通过模式匹配处理半结构化数据,注意模式中键的顺序、部分匹配、捕获多余键值对等问题。
def get_creators(record: dict) -> list:
"""
根据记录类型和内容返回创作者名字列表
"""
match record:
case {'type': 'book', 'api': 2, 'authors': [*names]}:
return names
case {'type': 'book', 'api': 1, 'author': name}:
return [name]
case {'type': 'book'}:
raise ValueError(f"Invalid 'book' record: {record!r}")
case {'type': 'movie', 'director': name}:
return [name]
case _:
raise ValueError(f'Invalid record: {record!r}')
b1 = dict(api=1, author='Douglas Hofstadter', type='book', title='Gödel, Escher, Bach')
print(get_creators(b1))
# 输出: ['Douglas Hofstadter']
from collections import OrderedDict
b2 = OrderedDict(api=2, type='book', title='Python in a Nutshell', authors='Martelli Ravenscroft Holden'.split())
print(get_creators(b2))
# 输出: ['Martelli', 'Ravenscroft', 'Holden']
print(get_creators({'type': 'book', 'pages': 770}))
# 输出: ValueError: Invalid 'book' record: {'type': 'book', 'pages': 770}
print(get_creators('Spam, spam, spam'))
# 输出: ValueError: Invalid record: 'Spam, spam, spam'
3 映射类型的标准API
3.1 “可哈希”指什么
“可哈希”对象的哈希码在生命周期内不变且课比较,数值类型、不可变扁平类型及满足条件的容器是可哈希的,用户定义类型默认可哈希,自定义__eq__
方法时需注意__hash__
方法的返回值。
tt = (1, 2, (30, 40))
print(hash(tt))
# 输出: 8027212646858338501
tl = (1, 2, [30, 40])
try:
hash(tl)
except TypeError as e:
print(e)
# 输出: unhashable type: 'list'
tf = (1, 2, frozenset([30, 40]))
print(hash(tf))
# 输出: -4118419923444501110
3.2 常用映射方法概述
介绍了dict、defaultdict和OrderedDict实现的常用方法,如clear、copy、get、items、keys、pop、popitem、setdefault、update、values等,以及它们的功能和用法。
3.3 插入或更新可变的值
当键不存在时,d[k]
会抛错,可使用d.get(k, default)
获取默认值,若要更新可变值,dict.setdefault
是更好的方法,它可避免重复搜索键。
# 使用dict.get获取并更新词出现的位置列表(不完美示例)
import re
import sys
WORD_RE = re.compile(r'\w+')
index = {}
with open(sys.argv[1], encoding='utf-8') as fp:
for line_no, line in enumerate(fp, 1):
for match in WORD_RE.finditer(line):
word = match.group()
column_no = match.start() + 1
location = (line_no, column_no)
# 使用get方法获取单词位置列表,若不存在则返回空列表
occurrences = index.get(word, [])
occurrences.append(location)
index[word] = occurrences
for word in sorted(index, key=str.upper):
print(word, index[word])
# 使用dict.setdefault获取并更新词出现的位置列表
index = {}
with open(sys.argv[1], encoding='utf-8') as fp:
for line_no, line in enumerate(fp, 1):
for match in WORD_RE.finditer(line):
word = match.group()
column_no = match.start() + 1
location = (line_no, column_no)
# 使用setdefault方法获取单词位置列表,若不存在则设置为空列表并返回
index.setdefault(word, []).append(location)
for word in sorted(index, key=str.upper):
print(word, index[word])
4 自动处理缺失的键
4.1 defaultdict:处理缺失键的另一种选择
collections.defaultdict
在查找键不存在时,使用默认值创建对应项,默认值由default_factory
指定。
import collections
import re
import sys
WORD_RE = re.compile(r'\w+')
# 创建一个defaultdict,默认值为列表
index = collections.defaultdict(list)
with open(sys.argv[1], encoding='utf-8') as fp:
for line_no, line in enumerate(fp, 1):
for match in WORD_RE.finditer(line):
word = match.group()
column_no = match.start() + 1
location = (line_no, column_no)
index[word].append(location)
for word in sorted(index, key=str.upper):
print(word, index[word])
4.2 __miss__
方法
dict子类可定义__missing__
方法,在__getitem__
找不到键时调用,可自定义键查找逻辑,但需注意避免无限递归,__contains__
方法的实现也需特殊处理。
class StrKeyDict0(dict):
def __missing__(self, key):
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __contains__(self, key):
return key in self.keys() or str(key) in self.keys()
d = StrKeyDict0([('2', 'two'), ('4', 'four')])
print(d['2'])
# 输出: two
print(d[4])
# 输出: four
try:
print(d[1])
except KeyError as e:
print(e)
# 输出: KeyError: '1'
print(d.get('2'))
# 输出: two
print(d.get(4))
# 输出: four
print(d.get(1, 'N/A'))
# 输出: N/A
print(2 in d)
# 输出: True
print(1 in d)
# 输出: False
4.3 标准库对__missing__方法的使用不一致
继承标准库中的映射时,不同基类对__missing__
方法的使用方式不同,需谨慎处理,dict
子类、collections.UserDict
子类、abc.Mapping
子类在实现相关方法时对__missing__
方法的触发情况不同。
5 dict的变体
5.1 collections.OrderedDict
自 Python 3.6 起,dict保留键顺序,OrderedDict
在键顺序相关操作上有优势,如等值检查考虑顺序、popitem方法签名不同、多了move_to_end
方法,适用于跟踪近期存取情况。
5.2 collections.ChainMap
ChainMap
存放一组映射,可作为整体搜索,查找顺序按输入映射顺序,不复制映射,更新或插入操作影响第一个输入映射,可用于实现支持嵌套作用域的语言解释器。
d1 = dict(a=1, b=3)
d2 = dict(a=2, b=4, c=6)
from collections import ChainMap
chain = ChainMap(d1, d2)
print(chain['a'])
# 输出: 1
print(chain['c'])
# 输出: 6
chain['c'] = -1
print(d1)
# 输出: {'a': 1, 'b': 3, 'c': -1}
print(d2)
# 输出: {'a': 2, 'b': 4, 'c': 6}
5.3 coolections.Counter
Counter
是对键计数的映射,可用于统计可哈希对象数量或当作多重集,实现了组合计数运算符和most_common
等方法,用于获取计数最多的项。
import collections
ct = collections.Counter('abracadabra')
print(ct)
# 输出: Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
ct.update('aaaaazzz')
print(ct)
# 输出: Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
print(ct.most_common(3))
# 输出: [('a', 10), ('z', 3), ('b', 2)]
5.4 shelve.Shelf
shelve.Shelf
是持久存储字符串键与 Python 对象映射的简单键值 DBM 数据库,是abc.MutableMapping
的子类,具有I/O
管理方法,键必须是字符串,值必须可被pickle
序列化,使用时需注意相关事项。
5.5 子类应该继承UserDict而不是dict
创建新映射类型最好扩展collections.UserDict
,它避免了继承dict
的一些问题,内部使用组合模式,简化了__contains__
等方法的实现,还介绍了MutableMapping.update
和Mapping.get
方法的作用。
import collections
class StrKeyDict(collections.UserDict):
def __missing__(self, key):
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
def __contains__(self, key):
return str(key) in self.data
def __setitem__(self, key, item):
self.data[str(key)] = item
6 不可变映射
types
模块的MappingProxyType
可将映射包装成只读的mappingproxy
实例,原映射更新会反映在代理实例上,但不能通过代理实例更改映射,可用于防止映射被意外更改。
from types import MappingProxyType
d = {1: 'A'}
d_proxy = MappingProxyType(d)
print(d_proxy[1])
# 输出: A
try:
d_proxy[2] = 'x'
except TypeError as e:
print(e)
# 输出: 'mappingproxy' object does not support item assignment
d[2] = 'B'
print(d_proxy[2])
# 输出: B
7 字典视图
dict
的.keys()
、.values()
和.items()
方法返回字典视图,是dict
内部数据结构的只读投影,支持一些基本操作,视图对象是动态代理,更新原dict
后视图立即能看到变化,dict_keys
和dict_items
类支持类似frozenset
的运算符。
d = dict(a=10, b=20, c=30)
values = d.values()
print(values)
# 输出: dict_values([10, 20, 30])
print(len(values))
# 输出: 3
print(list(values))
# 输出: [10, 20, 30]
print(reversed(values))
# 输出: <dict_reversevalueiterator object at 0x10e9e7310>
try:
print(values[0])
except TypeError as e:
print(e)
# 输出: 'dict_values' object is not subscriptable
d['z'] = 99
print(d)
# 输出: {'a': 10, 'b': 20, 'c': 30, 'z': 99}
print(values)
# 输出: dict_values([10, 20, 30, 99])
8 dict的实现方式对实践的影响
Python 使用哈希表实现dict
,键必须可哈希,访问项速度快,内存布局紧凑但仍占用大量内存,为节省内存不要在__init__
方法外创建实例属性,还介绍了相关原因及优化措施。
9 集合论
9.1 set字面量
set
字面量句法与数学表示法类似,空set
必须写作set()
,使用字面量句法比调用构造函数速度快且更具可读性,frozenset
没有字面量句法,必须调用构造函数创建。
s = {1}
print(type(s))
# 输出: <class 'set'>
print(s)
# 输出: {1}
print(s.pop())
# 输出: 1
print(s)
# 输出: set()
print(frozenset(range(10)))
# 输出: frozenset({0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
9.2 集合推导式
集合推导式与列表推导式类似,用于从可迭代对象中筛选元素构建集合,不同 Python 进程得到的输出顺序可能不同。
from unicodedata import name
# 构建一个集合,包含码点在32到255之间且名称中带有'SIGN'的字符
print({chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i), '')})
# 输出: {'§', '=', '¢', '#', '¤', '<', '¥', 'μ', '×', '$', '¶', '£', '©', '°', '+', '÷', '±', '>', '¬', '®', '%'}
10 集合的实现方式对实践的影响
set和frozenset使用哈希表实现,元素必须可哈希,成员测试效率高,但占用内存大,元素顺序取决于插入顺序且不稳定,向集合中添加元素可能导致顺序变化,还介绍了集合元素数量对搜索速度的影响。
11 字典视图的集合运算
.keys()和.items()方法返回的视图对象与frozenset相似,支持集合运算符,可用于获取两个字典的共同键等操作,dict_keys视图始终可当作集合使用,dict_items视图仅当字典值可哈希时可当作集合使用,使用集合运算符处理视图可提高效率。
d1 = dict(a=1, b=2, c=3, d=4)
d2 = dict(b=20, d=40, e=50)
# 获取两个字典的共同键
print(d1.keys() & d2.keys())
# 输出: {'b', 'd'}
s = {'a', 'e', 'i'}
print(d1.keys() & s)
# 输出: {'a'}
print(d1.keys() | s)
# 输出: {'a', 'c', 'b', 'd', 'i', 'e'}
12 本章小结
字典在 Python 中很重要,其字面量句法增强,标准库提供多种映射类型,如defaultdict、ChainMap、Counter等,OrderedDict在特定场景仍有用。映射的setdefault和update方法强大,__missing__方法可自定义键查找行为,MappingProxyType可防止映射被意外更改,字典视图消除了 Python 2 中的内存开销且支持集合运算符。
13 延伸阅读
推荐阅读 Python 标准库文档中 “collections—Container datatypes” 获取映射类型示例和实践技巧,查看collections模块源码了解映射逻辑,《Python Cookbook 中文版(第 3 版)》第 1 章有相关经典实例。还介绍了其他相关文章和演讲,如继续使用OrderedDict的理由、字典视图功能的提出、不同 Python 解释器中字典的实现、集合相关的 PEP 和演讲等,以及一些与字典和集合相关的其他知识,如语法糖、哈希表的发明等。
原文地址:https://blog.csdn.net/HearMeRoar_/article/details/144411116
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!