第3章 字典和集合

本文深入探讨Python中的字典和集合数据结构,讲解字典的多种构造方法、字典推导、常见方法如setdefault和update,以及集合的基本操作和优势。同时,文章分析了字典和集合背后散列表的实现原理,包括效率实验和潜在影响。
#《流畅的Python》读书笔记
# 第3章 字典和集合
# 字典这个数据结构活跃在所有 Python 程序的背后,即便你的源码里并没有直接用到它。
# 正是因为字典至关重要,Python对它的实现做了高度优化,而散列表则是字典类型性能出众的根本原因。
# 本章内容的大纲如下:
    # 常见的字典方法
    # 如何处理查找不到的键
    # 标准库中 dict 类型的变种
    # set 和 frozenset 类型
    # 散列表的工作原理
    # 散列表带来的潜在影响(什么样的数据类型可作为键、不可预知的顺序,等等)

# 3.1 泛映射类型
# 如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现 __hash__() 方法。
# 另外可散列对象还要有 __qe__() 方法,这样才能跟其他键做比较。如果两个可散列对象是相等的,那么它们的散列值一定是一样的……
# 原子不可变数据类型(str、bytes 和数值类型)都是可散列类型,frozenset 也是可散列的,因为根据其定义,frozenset 里只能容纳可散列类型。
# 元组的话,只有当一个元组包含的所有元素都是可散列类型的情况下,它才是可散列的。
# 来看下面的元组tt、tl 和 tf:
>>> tt = (1, 2, (30, 40))
>>> hash(tt)
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    hash(tl)
TypeError: unhashable type: 'list'
>>> tf = (1, 2, frozenset([30, 40]))
>>> hash(tf)
# 根据这些定义,字典提供了很多种构造方法,“Built-in Types”
# >>> a = dict(one=1, two=2, three=3)
# >>> b = {'one': 1, 'two': 2, 'three': 3}
# >>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
# >>> d = dict([('two', 2), ('one', 1), ('three', 3)])
# >>> e = dict({'three': 3, 'one': 1, 'two': 2})
# >>> a == b == c == d == e
# True

# 3.2 字典推导
# 自Python2.7以来,列表推导和生成器表达式的概念就移植到了字典上,从而有了字典推导。

# 示例 3-1 字典推导的应用
DIAL_CODES = [(86,'China'),(91,'India'),(1,'United States'),(62,'Indonesia'),(55,'Brazil'),(92,'Pakistan'),(880,'Bangladesh'),(234,'Nigeria'),(7,'Russia'),(81,'Japan'),]
country_code={country:code for code,country in DIAL_CODES}
print(country_code)#{'Bangladesh': 880, 'Russia': 7, 'Nigeria': 234, 'Brazil': 55, 'United States': 1, 'Indonesia': 62, 'India': 91, 'Japan': 81, 'China': 86, 'Pakistan': 92}
print({code:country.upper() for country,code in country_code.items() if code < 66})#{1: 'UNITED STATES', 55: 'BRAZIL', 62: 'INDONESIA', 7: 'RUSSIA'}

# 3.3 常见的映射方法
# 映射类型的方法其实很丰富。表3-1为我们展示了dict/defaultdict和OrderDict的常见方法,后面两个数据类型是dict的变种,位于collections模块内。

# 用setdefault处理找不到的键
# 当字典 d[k] 不能找到正确的键的时候,Python 会抛出异常,这个行为符合 Python 所信奉的“快速失败”哲学。

# 示例 3-2 index0.py 这段程序从索引中获取单词出现的频率信息,并把它们写进对应的列表里(更好的解决方案在示例3-4中)
import sys
import re
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)
            # 这其实是一种很不好的实现,这样写只是为了证明论点
            # ❶ 提取 word 出现的情况,如果还没有它的记录,返回 []。
            occurrences=index.get(word,[])
            # ❷ 把单词新出现的位置添加到列表的后面。
            occurrences.append(location)
            # ❸ 把新的列表放回字典中,这又牵扯到一次查询操作。
            index[word]=occurrences
            # 以字母顺序打印出结果
# ❹ sorted函数的key = 参数没有调用str.uppper,而是把这个方法的引用传递给sorted函数,这样在排序的时候,单词会被规范成统一格式。
for word in sorted(index,key=str.upper):
    print(word,index[word])

# 示例 3-3 这里是示例3-2的不完全输出,每一行的列表都代表一个单词的出现情况,列表中的元素是一对值,第一个值表示出现的行,第二个表示出现的列

# 示例 3-4 index.py用一行就解决了获取和更新单词的出现情况列表,当然跟示例3-2不一样的是,这里用到了dict.setdefault
"""创建从一个单词到其出现情况的映射"""
import sys
import re
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)
            index.setdefault(word, []).append(location)
# 以字母顺序打印出结果
for word in sorted(index, key=str.upper):
    print(word, index[word])

# 3.4 映射的弹性键查询
# 有时候为了方便起见,就算某个键在映射里不存在,我们也希望在通过这个键读取值的时候能得到一个默认值。
# 有两个途径能帮我们达到这个目的,一个是通过defaultdict这个类型而不是普通的dict,另一个是给自己定义一个dict的子类,然后在子类中实现__missing__方法。

# 3.4.1 defaultdict:处理找不到的键的一个选择
# 具体而言,在实例化一个defaultdict的时候,需要给构造方法提供一个可调用对象,这个可调用对象会在__getitem__碰到找不到的键的时候被调用,让__getitem__返回默认值。

# 示例 3-5 index_default.py:利用 defaultdict 实例而不是setdefault 方法

# 3.4.2 特殊方法__missing__
# 所有的映射类型在处理找不到的键的时候,都会牵扯到 __missing__方法。
# 这也是这个方法称作“missing”的原因。
# 虽然基类 dict 并没有定义这个方法,但是 dict 是知道有这么个东西存在的。
# 也就是说,如果有一个类继承了 dict,然后这个继承类提供了 __missing__ 方法,那么在 __getitem__ 碰到找不到的键的时候,Python 就会自动调用它,而不是抛出一个 KeyError 异常。

# 示例 3-6 当有非字符串的键被查找的时候,StrKeyDict0 是如何在该键不存在的情况下,把它转换为字符串的

# 示例 3-7 StrKeyDict0 在查询的时候把非字符串的键转换为字符串

# 3.5 字典的变种
# 这一节总结了标准库里 collections 模块中,除了 defaultdict 之外的不同映射类型。

# collections.OrderedDict这个类型在添加键的时候会保持顺序,因此键的迭代次序总是一致的。
# collections.ChainMap该类型可以容纳数个不同的映射对象,然后在进行键查找操作的时候,这些对象会被当作一个整体被逐个查找,直到键被找到为止。
# colllections.UserDict这个类其实就是把标准 dict 用纯 Python 又实现了一遍。
# collections.Counter这个映射类型会给键准备一个整数计数器。

# 下面的小例子利用Counter来计算单词中各个字母出现的字数:
>>> import collections
>>> ct = collections.Counter('abracadabra')
>>> ct
Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
>>> ct.update('aaaaazzz')
>>> ct
Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
>>> ct.most_common(2)
[('a', 10), ('z', 3)]

# 3.6 子类化UserDict
# 就创造自定义映射类型来说,以UserDict为基类,总比以普通的dict为基类要来得方便。
# 而更倾向于从 UserDict 而不是从 dict 继承的主要原因是,后者有时会在某些方法的实现上走一些捷径,导致我们不得不在它的子类中重写这些方法,但是 UserDict 就不会带来这些问题。

# 示例 3-8 无论是添加、更新还是查询操作,StrKeyDict都会把非字符串的键转换为字符串
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

# 3.7不可变映射类型
# 标准库里所有的映射类型都是可变的,但有时候你会有这样的需求,比如不能让用户错误地修改某个映射。
# 从 Python 3.3 开始,types 模块中引入了一个封装类名叫MappingProxyType。如果给这个类一个映射,它会返回一个只读的映射视图。

# 示例 3-9 用MappingProxyType来获取字典的只读实例mappingproxy
>>> from types import MappingProxyType
>>> d = {1:'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({1: 'A'})
>>> d_proxy[1]
'A'
>>> d_proxy[2] = 'x'
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    d_proxy[2] = 'x'
TypeError: 'mappingproxy' object does not support item assignment
>>> d[2] = 'B'
>>> d_proxy
mappingproxy({1: 'A', 2: 'B'})
>>> d_proxy[2]
'B'

# 3.8 集合论
# 本书中“集”或者“集合”既指 set,也指 frozenset。当“集”仅指代 set 类时,我会用等宽字体表示 。
# 集合的本质是许多唯一对象的聚集。因此,集合可以用于去重:
>>> l = ['spam', 'spam', 'eggs', 'spam']
>>> set(l)
{'spam', 'eggs'}
>>> list(set(l))
['spam', 'eggs']

# 除了保证唯一性,集合还实现了很多基础的中缀运算符。给定两个集合a 和 b,a | b 返回的是它们的合集,a & b 得到的是交集,而 a - b得到的是差集。
# 例如,我们有一个电子邮件地址的集合(haystack),还要维护一个较小的电子邮件地址集合(needles),然后求出needles中有多少地址同时也出现在了heystack里。
# 借助集合操作,我们只需要一行代码就可以了(见示例 3-10)。

# 示例 3-10 needles 的元素在haystack里出现的次数,两个变量都是set类型
    # found = len(needles & haystack)

# 如果不使用交集操作的话,代码可能就变成了示例 3-11 里那样。
# 示例 3-11 needles 的元素在 haystack 里出现的次数(作用和示例 3-10 中的相同)
found = 0
for n in needles:
    if n in haystack:
        found += 1

# 示例 3-12needles的元素在haystack里出现的次数,这次的代码可以用在任何可迭代对象上
# found = len(set(needles) & set(haystack))
#  另一种写法:
# found = len(set(needles).intersection(haystack))

# 3.8.1 集合字面量
# 除空集之外,集合的字面量——{1}、{1, 2},等等——看起来跟它的数学形式一模一样。如果是空集,那么必须写成 set() 的形式。
# 句法的陷阱:不要忘了,如果要创建一个空集,你必须用不带任何参数的构造方法 set()。
# 如果只是写成 {} 的形式,跟以前一样,你创建的其实是个空字典。

# 用 dis.dis(反汇编函数)来看看两个方法的字节码的不同:
from dis import dis
print(dis('{1}'))
# 1      0 LOAD_CONST    0(1)
#        3 BUILD_SET     1
#        6 RETURN_VALUE
# None
print(dis('set[1]'))
# 1             0 LOAD_NAME                0 (set)
#               3 LOAD_CONST               0 (1)
#               6 BINARY_SUBSCR
#               7 RETURN_VALUE
# None

# 3.8.2 集合推导
# 示例 3-13 新建一个Latin-1字符集合,该集合里的每个字符的Unicode名字里都有“SIGN”这个单词
from unicodedata import name
print({chr(i) for i in range(32,256) if 'SIGN' in name(chr(i),'')})
#{'§', '<', '>', '¤', '=', '¢', '¥', '$', '©', '+', '#', '°', '%', '¬', '±', 'µ', '¶', '×', '÷', '£', '®'}

# 3.8.3 集合的操作
# 表3-2:集合的数学运算:
# 表3-3:集合的比较运算符,返回值是布尔类型
# 表3-4:集合类型的其他方法

# 3.9 dict和set的背后
# 想要理解 Python 里字典和集合类型的长处和弱点,它们背后的散列表是绕不开的一环。

# 3.9.1 一个关于效率的实验
# 所有的 Python 程序员都从经验中得出结论,认为字典和集合的速度是非常快的。接下来我们要通过可控的实验来证实这一点。

# 示例 3-14 在 haystack 里查找 needles 的元素,并计算找到的元素的个数
found = 0
for n in needles:
    if n in haystack:
        found += 1

# 示例 3-15 利用交集来计算 needles 中出现在 haystack 中的元素的个数
# found = len(needles & haystack)

# 3.9.2 字典中的散列表
# 因为 Python 会设法保证大概还有三分之一的表元是空的,所以在快要达到这个阈值的时候,原有的散列表会被复制到一个更大的空间里面。
# 示例 3-16 在32 位的 Python 中,1、1.0001、1.0002 和 1.0003这几个数的散列值的二进制表达对比

# 3.9.3 dict的实现及其导致的结果

# 3.9.4 set的实现以及导致的结果
# 集合里的元素必须是可散列的。
# 集合很消耗内存。
# 可以很高效地判断元素是否存在于某个集合。
# 元素的次序取决于被添加到集合里的次序。
# 往集合里添加元素,可能会改变集合里已有元素的次序。

# 3.10 本章小结
# 大多数映射类型都提供了两个很强大的方法:setdefault 和update。
# setdefault方法可以用来更新字典里存放的可变值(比如列表),从而避免了重复的键搜索。
# update方法则让批量更新成为可能,它可以用来插入新值或者更新已有键值对,它的参数可以是包含(key, value)这种键值对的可迭代对象,或者关键字参数。
# 在映射类型的API中,有个很好用的方法是__missing__,当对象找不到某个键的时候,可以通过这个方法自定义会发生什么。

# 3.11 延伸阅读

 

转载于:https://www.cnblogs.com/larken/p/9557306.html

标题基于Python的自主学习系统后端设计与实现AI更换标题第1引言介绍自主学习系统的研究背景、意义、现状以及本文的研究方法创新点。1.1研究背景与意义阐述自主学习系统在教育技术领域的重要性应用价值。1.2国内外研究现状分析国内外在自主学习系统后端技术方面的研究进展。1.3研究方法与创新点概述本文采用Python技术栈的设计方法系统创新点。第2相关理论与技术总结自主学习系统后端开发的相关理论技术基础。2.1自主学习系统理论阐述自主学习系统的定义、特征理论基础。2.2Python后端技术栈介绍DjangoFlask等Python后端框架及其适用场景。2.3数据库技术讨论关系型非关系型数据库在系统中的应用方案。第3系统设计与实现详细介绍自主学习系统后端的设计方案实现过程。3.1系统架构设计提出基于微服务的系统架构设计方案。3.2核心模块设计详细说明用户管理、学习资源管理、进度跟踪等核心模块设计。3.3关键技术实现阐述个性化推荐算法、学习行为分析等关键技术的实现。第4系统测试与评估对系统进行功能测试性能评估。4.1测试环境与方法介绍测试环境配置采用的测试方法。4.2功能测试结果展示各功能模块的测试结果问题修复情况。4.3性能评估分析分析系统在高发等场景下的性能表现。第5结论与展望总结研究成果提出未来改进方向。5.1研究结论概括系统设计的主要成果技术创新。5.2未来展望指出系统局限性提出后续优化方向。
### 关于第七集合字典的数据结构 #### 集合(Set) 集合是一种无序且不重复的容器数据类型,适用于存储唯一项。它支持数学运算如交集、差集等操作[^1]。 在 Python 中,可以通过 `{}` 或 `set()` 函数来创建集合。例如: ```python s1 = {1, 2, 3} s2 = set([4, 5, 6]) ``` 集合的主要特性包括: - **去重**:即使初始化时提供多个相同元素,最终只保留一个。 - **无序性**:集合中的元素没有固定的顺序。 常见的集合操作有: - 集 (`|`):`s1 | s2` 表示两个集合中所有不同元素组成的集合- 交集 (`&`):`s1 & s2` 表示两个集合中共有的元素组成的集合- 差集 (`-`):`s1 - s2` 表示属于 `s1` 而不属于 `s2` 的元素组成的集合- 对称差集 (`^`):`s1 ^ s2` 表示存在于其中一个集合但不在另一个集合中的元素组成的集合。 #### 字典(Dict) 字典是一种键值对形式的映射数据结构,在内部实现上依赖哈希表技术[^2]。其主要特点如下: - 键必须是不可变的对象(如字符串、整数或元组),而值可以是任何类型的对象。 - 访问时间复杂度接近 O(1),因为它是通过计算键的哈希值快速定位对应的值。 字典的操作方式多样,常用的有: - 添加或更新键值对:`d[key] = value` - 删除键值对:`del d[key]` 或者使用 `pop(key)` 方法。 - 获取所有键、值或键值对:分别使用 `.keys()`, `.values()`, `.items()` 方法[^3]。 对于较大的字典Python 动态调整底层存储空间大小以优化性能。当负载因子超过一定阈值(通常为 2/3)时会触发扩容机制[^2]。 以下是针对具体需求的一个综合例子演示如何操作字典: ```python # 初始化字典 my_dict = {&#39;Tom&#39;: &#39;apple&#39;, &#39;Bob&#39;: &#39;banana&#39;, &#39;Judy&#39;: &#39;orange&#39;} # 获取字典中最喜欢水果名称长度 print(len(my_dict[&#39;Tom&#39;])) # 增加新的键值对 my_dict.update({&#39;Sarah&#39;: &#39;watermelon&#39;}) # 删除特定条目 if &#39;Judy&#39; in my_dict: del my_dict[&#39;Judy&#39;] # 修改现有条目的值 my_dict[&#39;Sarah&#39;] = &#39;melon&#39; # 输出全部内容 for name, fruit in my_dict.items(): print(f"{name} loves {fruit}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值