Python 入门之悦目的 Pythonic(二)数据约定

# 免责声明:
    本文内容主要是肥清大神的视频以及自己收集学习内容的整理笔记,目是主要是为了让博主这样的老白能更好的学习编程,如有侵权,请联系博主进行删除。

6. 控制结构

6.1. 复杂的列表推导

# 两个循环的条件以内可使用列表推导式

6.2. lambda 使用

# 一次性的结果可用
# 尽可能少用

6.3. 循环中使用else

# for/while 中只有循环体正确执行没有中断时才会执行 else
# 尽可能少用    

6.4. 生成器 or 列表推导

# 列表推导会全部 load 内存里, 相对省时间, 但费空间
# 生成器则不会, 相对节省内存, 但费时间(每次都要生成)
def read_lines(path: str) -> Iterator[str]:
    with open(paht, 'r') as file:
        for line in file:
            if line in file:
                if line.startswith('<<'):
                    yield line
                    
lines = read_lines('lines.txt')
print(lines)										# generator
for ln in lines:
    print(ln)

7. Python3 中增强的 range 对象

# 节省内存
	* 使用([start=0,] stop[, step=1]) 三个元素
    * 无论多大的范围内存大小几乎都一样
# 可进行逻辑比较
range(100) == range(100)							# True
range(100) > range(200)								# False
# 可使用切片
range(1: 10)[5:]									# range(6, 10)
# 速度比列表快

8. 数据结构优化

8.1. 列表/数组

# 列表是像数组一样的可变数据结构
# 列表在 Python 中排序并具有确定的大小
# 列表像数组一样是 0 索引的,并且可以容纳重复的元素
# 列表可以有效地保留数据序列以供将来迭代

8.1.1. 列表推导的特别之处

# 可以相对 for 循环加速

8.1.2. 使用列表推导而不是 map() 和 filter()

# 列表推导更简单

8.1.3. 负索引

# 快速反向访问

8.1.4. 确定可迭代性的 all 和 any

# all(): 全真为真, 一假为假
# any(): 一真为真, 全假为假

8.1.5. 使用 * 运算符操作剩余序列

# * 用于除前后变量的所有值
mylist = ['a', 'b', 'c', 'd', 'e']

(el1, el2, *remaining) = mylist
print(remaining)						  					# ['c', 'd', 'e']

(el1, *middle, len) = mylist
print(middle)												# ['b', 'c', 'd']

(*el1, leminus1, eln) = mylist
print(el1)													# ['a', 'b', 'c']

8.1.6. 使用 array.array 获取基本类型数组

# 只能是一维数组
# 不常用

8.1.7. str ——不可变 unicode 字符数组

# -*- coding: UTF-8 -*-
# version: python3.12


arr = 'characters'
arr[1] = 'm'                                                # TypeError: 'str' object does not support item assignment

8.1.8. bytearray —— 单字节可变序列

# 处理数据量不大的数组
# 可优化写入时间

8.1.9. 使用 bytes 作为不可变的单字节序列

# 0 ~ 255 
# 多次引用但数值不变
# 可优化读取时间

8.2. 创建高效字典

# 字典是高度优化的数据结构

8.2.1. 获取 value 时提供默认值

# 方法: dict.get()
# 特点: 
    * 键不存在时
        * 有默认值: 返回默认值
        * 无默认值: 返回None
        * 不会抛出异常

8.2.2. 使用 defaultdict 缺失键的默认值

# 方法: defaultdict()
# 特点: 
    * 优化速度

8.2.3. 通过字典推导优化 dict 构造

# 可以优化速度
    * 不明显

8.2.4. ChainMap

# ChainMap 类用于快速链接多个映射, 以便将它们视为一个单元
    * 通常比创建一个新的额外字典和运行多个 update() 调用要快得多
    * 查找时, 从左到右依次搜索底层字典, 直到找到键
        * 采用<分层>结构    
        * 只返回第一次出现的查找结果 
    * 插入 | 更新 | 删除仅影响添加到链中的第一个映射
# ChainMap 的惰性构造方法对写入更有效率
    * 若读操作高于写操作,原来的dict的哈希查找就比较高效率    
# 假设在有 X 层, 每层最多有Y个键的情况下: 
    * 最坏的情况下, 构造一个 ChainMap 需要 O(X), 每次查找 O(X), 而使用更新循环构造一个 dict 需要 O(XY), 每次查找需要 O(1)

8.2.4.1. 应用场景

 # 用在写多读少,对写的敏感的仅应用原生Python的语言特点的应用场合
 # 特别是数据量大,key多且复杂的情况下,会有优势。如果对于这些不敏感,从维护角度来看,就没有必要去用
# -*- coding: UTF-8 -*-
# version: python3.12


 from collections import ChainMap
 
 m1 = {'soccer': 100, 'basketball': 200, 'vollyball': 300}
 m2 = {'tennis': 400, 'golf': 500, 'soccer': 600}
 
 cmap = ChainMap(m1, m2)
 print(cmap, type(cmap))                 # 

8.2.5. MappingProxyType 创建只读字典

# 在大多数实际的生产应用程序中,配置管理可以有两种类型: 
	* 静态的类型: 很少随时间改变的配置
	* 动态的类型: 非常频繁地改变的配置
# 在大多数 Python 应用程序中,配置以字典的形式方便地存储
# 静态配置如何防止配置被修改
	* 强加只读性质
# MappingProxyType 类本质上是标准 Python 字典的包装器
	* 支持字典的所有读操作
	* Python 中的 types 模块
    * 一旦创建,就可以用作只读的字典
    	* 这个只读字典也可用于存储返回值并将其传递给类或模块的内部状态而无需任何外部更改或访问对象
			* 在这种情况下,可以通过使用 MappingProxyType 来限制编辑,而不是创建类的副本
			* 此功能仅在 Python 3 及更高版本中可用
# 高并发时代替锁的作用 —— 读写分离
	* 保持共享变量/资源只读
    * 修改共享变量/资源交给别一个任务
    	* 按顺序进行修改
    * 通知只读的代理做变更

8.2.6. 排序字典的方法

# Python 3 的字典默认实现保持插入字典的键的顺序
# 实现根据value进行排序
	* 将 callable 的函数或方法传递给排序方法的 key 参数
    	* lambda
        * sorted()
    * 这个可调用对象将被定义为以自定义方式比较序列的两个元素
from operator import (
    itemgetter,
)
d = {'a': 300, 'b': 200, 'c': 100}

re = sorted(d)
print(re)													# ['a', 'b', 'c']

re = sorted(d.values())
print(re)													# [100, 200, 300]

re = sorted(d.items())
print(re)													# [('a', 300), ('b', 200), ('c', 100)]

# d.items() 是一个元组, 1代表第1个元素 value
re = sorted(d.items(), key=lambda x: x[1]) 
print(re)													# [('c', 100), ('b', 200), ('a', 300)]

# itemgetter(1) 返回一个获取第1个元素的函数
re = sorted(d.items(), key=itemgetter(1))
print(re)													# [('c', 100), ('b', 200), ('a', 300)]

8.2.7. 合并字典的方法

# 从多个的字典中的键值对创建一个新的合并字典,必须制定冲突解决策略
	* 字典之间可以有公共的Key
# 在 Python 中合并 n 个字典的最简单方法: update() 方法
	* update() 的实现很简单
    	* 遍历字典的元素,将每个条目添加到结果字典中
        	* 如果key存在,它将覆盖key的先前值
            	* 任何重复key的最后一个值都将被保留,而其他的则被覆盖
	* update() 调用可以链接起来,并且可以扩展到任意数量的字典
# 最优化的方法: 使用 ** 运算符来解包对象
	* Python 3.5 以前仅支持一次合并两个字典
    	* 内置 dict() 方法
	* Python 3.5 及更高版本中,** 运算符支持将多个字典合并为一个
    	* 直接 {} 法
    	* 用这种方法写的代码看起来干净且可读
        * 使用 ** 运算符还可以在大型字典的情况下加快速度
        	* 在语言结构本身中进行了优化
fruit_price_map_gz = {'apple': 10.5, 'orange': 8.8}
fruit_price_map_sh = {'banana': 2.8, 'orange': 9.5}
fruit_price_map_bj = {'watermelon': 5.5, 'orange': 10.8}

merged_price_map = {}
merged_price_map.update(fruit_price_map_gz)
merged_price_map.update(fruit_price_map_sh)
merged_price_map.update(fruit_price_map_bj)
# 'orange' 被覆盖
print(merged_price_map)										# {'apple': 10.5, 'orange': 10.8, 'banana': 2.8, 'watermelon': 5.5}

merged_price_map = dict(fruit_price_map_gz, **fruit_price_map_sh)
merged_price_map = dict(merged_price_map, **fruit_price_map_bj)
print(merged_price_map)										# {'apple': 10.5, 'orange': 10.8, 'banana': 2.8, 'watermelon': 5.5}

merged_price_map = {
    **fruit_price_map_sh,
    **fruit_price_map_gz,
    **fruit_price_map_bj,
}
print(merged_price_map)										# {'apple': 10.5, 'orange': 10.8, 'banana': 2.8, 'watermelon': 5.5}

8.2.8. 漂亮打印字典

# 默认情况下,Python 将字典打印在一行中,并且不保留缩进
	* 当字典很大并且存储复杂的数据时,通常打印它们的易读性会降低
# 借助 Python 中的内置 json 模块,简单地使用 json.dumps() 可以漂亮地打印具有更结构化格式的字典,使输出更清晰
	* json 包中的方法仅适用于 dict 仅包含原始数据类型的情况
    	* 如果您在字典中存储诸如函数之类的实体,就不能处理了
    * 使用 json.dumps() 的另一个缺点是它不能字符串化复杂的数据类型
    	* 比如集合
# Python 的另一个经典解决方案是内置的 pprint 模块
	* pprint 能够打印集合之类的数据类型,并且它以可重现的顺序打印字典key
    	* 与 json.dumps() 相比,它在视觉上并不能很好地表示嵌套结构
# PyYaml模块也可以打印出漂亮的字典结构
	* 参见 <<精彩模块-第三方>>
# 我们应该首选使用 json.dumps() 打印字典
	* 因为它提高了可读性和格式
    	* 前提是确定它们没有非原始数据类型

8.2.9. 奇怪的表达式 !?

# Python 将 bool 视为 int 数据类型的子类型
	* 大部分情况:
        * True == 1 == 1.0
        * False == 0 
    * 例外: str(bool) -> str
        * str(True) -> 'True'
        * str(False) -> 'False'
# 若使用 bool 值 True/False 作为字典的 key,会产生一些奇怪的现象: 出现覆盖/合并值的现象
	* key: 保留第 1 个
    * value: 保留最后一个
	* 这是一个大坑,以免在实际生产中产生问题
dict_true = {
    True: 'apple',
    1: 'orange',
    1.0: 'banana',
}

print(dict_true)											# {True: 'banana'}

dict_false = {
    False: 'apple',
    0: 'orange',
    0.0: 'banana',
}

print(dict_false)											# {False: 'banana'}

8.2. 创建高效字典

# 字典是高度优化的数据结构

8.2.1. 获取 value 时提供默认值

# 方法: dict.get()
# 特点: 
    * 键不存在时
        * 有默认值: 返回默认值
        * 无默认值: 返回None
        * 不会抛出异常

8.2.2. 使用 defaultdict 缺失键的默认值

# 方法: defaultdict()
# 特点: 
    * 优化速度

8.2.3. 通过字典推导优化 dict 构造

# 可以优化速度
    * 不明显

8.2.4. ChainMap

# ChainMap 类用于快速链接多个映射, 以便将它们视为一个单元
    * 通常比创建一个新的额外字典和运行多个 update() 调用要快得多
    * 查找时, 从左到右依次搜索底层字典, 直到找到键
        * 采用<分层>结构    
        * 只返回第一次出现的查找结果 
    * 插入 | 更新 | 删除仅影响添加到链中的第一个映射
# ChainMap 的惰性构造方法对写入更有效率
    * 若读操作高于写操作,原来的dict的哈希查找就比较高效率    
# 假设在有 X 层, 每层最多有Y个键的情况下: 
    * 最坏的情况下, 构造一个 ChainMap 需要 O(X), 每次查找 O(X), 而使用更新循环构造一个 dict 需要 O(XY), 每次查找需要 O(1)

8.2.4.1. 应用场景

 # 用在写多读少,对写的敏感的仅应用原生Python的语言特点的应用场合
 # 特别是数据量大,key多且复杂的情况下,会有优势。如果对于这些不敏感,从维护角度来看,就没有必要去用
# -*- coding: UTF-8 -*-
# version: python3.12


 from collections import ChainMap
 
 m1 = {'soccer': 100, 'basketball': 200, 'vollyball': 300}
 m2 = {'tennis': 400, 'golf': 500, 'soccer': 600}
 
 cmap = ChainMap(m1, m2)
 print(cmap, type(cmap))                 # 

8.2.5. MappingProxyType 创建只读字典

# 在大多数实际的生产应用程序中,配置管理可以有两种类型: 
	* 静态的类型: 很少随时间改变的配置
	* 动态的类型: 非常频繁地改变的配置
# 在大多数 Python 应用程序中,配置以字典的形式方便地存储
# 静态配置如何防止配置被修改
	* 强加只读性质
# MappingProxyType 类本质上是标准 Python 字典的包装器
	* 支持字典的所有读操作
	* Python 中的 types 模块
    * 一旦创建,就可以用作只读的字典
    	* 这个只读字典也可用于存储返回值并将其传递给类或模块的内部状态而无需任何外部更改或访问对象
			* 在这种情况下,可以通过使用 MappingProxyType 来限制编辑,而不是创建类的副本
			* 此功能仅在 Python 3 及更高版本中可用
# 高并发时代替锁的作用 —— 读写分离
	* 保持共享变量/资源只读
    * 修改共享变量/资源交给别一个任务
    	* 按顺序进行修改
    * 通知只读的代理做变更

8.2.6. 排序字典的方法

# Python 3 的字典默认实现保持插入字典的键的顺序
# 实现根据value进行排序
	* 将 callable 的函数或方法传递给排序方法的 key 参数
    	* lambda
        * sorted()
    * 这个可调用对象将被定义为以自定义方式比较序列的两个元素
from operator import (
    itemgetter,
)
d = {'a': 300, 'b': 200, 'c': 100}

re = sorted(d)
print(re)													# ['a', 'b', 'c']

re = sorted(d.values())
print(re)													# [100, 200, 300]

re = sorted(d.items())
print(re)													# [('a', 300), ('b', 200), ('c', 100)]

# d.items() 是一个元组, 1代表第1个元素 value
re = sorted(d.items(), key=lambda x: x[1]) 
print(re)													# [('c', 100), ('b', 200), ('a', 300)]

# itemgetter(1) 返回一个获取第1个元素的函数
re = sorted(d.items(), key=itemgetter(1))
print(re)													# [('c', 100), ('b', 200), ('a', 300)]

8.2.7. 合并字典的方法

# 从多个的字典中的键值对创建一个新的合并字典,必须制定冲突解决策略
	* 字典之间可以有公共的Key
# 在 Python 中合并 n 个字典的最简单方法: update() 方法
	* update() 的实现很简单
    	* 遍历字典的元素,将每个条目添加到结果字典中
        	* 如果key存在,它将覆盖key的先前值
            	* 任何重复key的最后一个值都将被保留,而其他的则被覆盖
	* update() 调用可以链接起来,并且可以扩展到任意数量的字典
# 最优化的方法: 使用 ** 运算符来解包对象
	* Python 3.5 以前仅支持一次合并两个字典
    	* 内置 dict() 方法
	* Python 3.5 及更高版本中,** 运算符支持将多个字典合并为一个
    	* 直接 {} 法
    	* 用这种方法写的代码看起来干净且可读
        * 使用 ** 运算符还可以在大型字典的情况下加快速度
        	* 在语言结构本身中进行了优化
fruit_price_map_gz = {'apple': 10.5, 'orange': 8.8}
fruit_price_map_sh = {'banana': 2.8, 'orange': 9.5}
fruit_price_map_bj = {'watermelon': 5.5, 'orange': 10.8}

merged_price_map = {}
merged_price_map.update(fruit_price_map_gz)
merged_price_map.update(fruit_price_map_sh)
merged_price_map.update(fruit_price_map_bj)
# 'orange' 被覆盖
print(merged_price_map)										# {'apple': 10.5, 'orange': 10.8, 'banana': 2.8, 'watermelon': 5.5}

merged_price_map = dict(fruit_price_map_gz, **fruit_price_map_sh)
merged_price_map = dict(merged_price_map, **fruit_price_map_bj)
print(merged_price_map)										# {'apple': 10.5, 'orange': 10.8, 'banana': 2.8, 'watermelon': 5.5}

merged_price_map = {
    **fruit_price_map_sh,
    **fruit_price_map_gz,
    **fruit_price_map_bj,
}
print(merged_price_map)										# {'apple': 10.5, 'orange': 10.8, 'banana': 2.8, 'watermelon': 5.5}

8.2.8. 漂亮打印字典

# 默认情况下,Python 将字典打印在一行中,并且不保留缩进
	* 当字典很大并且存储复杂的数据时,通常打印它们的易读性会降低
# 借助 Python 中的内置 json 模块,简单地使用 json.dumps() 可以漂亮地打印具有更结构化格式的字典,使输出更清晰
	* json 包中的方法仅适用于 dict 仅包含原始数据类型的情况
    	* 如果您在字典中存储诸如函数之类的实体,就不能处理了
    * 使用 json.dumps() 的另一个缺点是它不能字符串化复杂的数据类型
    	* 比如集合
# Python 的另一个经典解决方案是内置的 pprint 模块
	* pprint 能够打印集合之类的数据类型,并且它以可重现的顺序打印字典key
    	* 与 json.dumps() 相比,它在视觉上并不能很好地表示嵌套结构
# PyYaml模块也可以打印出漂亮的字典结构
	* 参见 <<精彩模块-第三方>>
# 我们应该首选使用 json.dumps() 打印字典
	* 因为它提高了可读性和格式
    	* 前提是确定它们没有非原始数据类型

8.2.9. 奇怪的表达式 !?

# Python 将 bool 视为 int 数据类型的子类型
	* 大部分情况:
        * True == 1 == 1.0
        * False == 0 
    * 例外: str(bool) -> str
        * str(True) -> 'True'
        * str(False) -> 'False'
# 若使用 bool 值 True/False 作为字典的 key,会产生一些奇怪的现象: 出现覆盖/合并值的现象
	* key: 保留第 1 个
    * value: 保留最后一个
	* 这是一个大坑,以免在实际生产中产生问题
dict_true = {
    True: 'apple',
    1: 'orange',
    1.0: 'banana',
}

print(dict_true)											# {True: 'banana'}

dict_false = {
    False: 'apple',
    0: 'orange',
    0.0: 'banana',
}

print(dict_false)											# {False: 'banana'}

8.3. 集合处理

# set 是不允许重复元素的对象的无序集合
# 在正确的集合实现中: 
	* 检测成员是否存在将在 O(1) 时间内运行
    * 并集/交集/差集/子集操作平均需要 O(n) 时间
# Python 标准库中包含的集合实现遵循这些性能特征
# 集合在 Python 中得到特殊处理, 并具有一些语法糖, 使它们易于创建
	* <大括号集合表达式语法>和<集合推导>允许您方便地定义新的集合实例
    * 创建一个空集需要调用 set() 构造函数
    	* 使用空花括号 {} 是会创建一个空字典
		* 集合可以称为有键但没有值的字典
# set 类还实现了 Iterable 接口
	* set 可以在 for 循环中使用或作为 in 语句的主题

8.3.1. 理解和使用数学集合运算

# 了解基本的数学集合运算将有助于理解 Python 中集合的正确用法
	* 集合 A 和 B 相关的一些操作如下: 
		* 并集: A 和 B 中的元素
            * 语法: A | B
		* 交集: A 和 B 共有的元素
            * 语法: A & B
		* 差集: A 中有但 B 中没有的元素
            * 语法: A – B
                * 不可交换
            * 相对补集??
		* 对称差异: A 或 B 中的元素排除掉共同元素
            * 语法: A^B
# 技巧:             
	* 处理数据列表时, 一项常见任务是查找出现在所有列表中的元素
    	* 需要根据序列成员的属性从两个或多个序列中选择元素时, 应该选择使用集合
        * 使用<集合运算>

8.3.1.1. 集合运算

programming_languages = {'C#', 'Go', 'Java', 'Python', 'Ruby'}
dynamic_languages = {'Ruby', 'Python', 'JavaScript', 'Lua'}

# 并集
re = programming_languages | dynamic_languages
print(re, )													# {'Python', 'Ruby', 'JavaScript', 'C#', 'Go', 'Lua', 'Java'}

# 交集
re = programming_languages & dynamic_languages
print(re, )													# {'Python', 'Ruby'}
# 差集
re = programming_languages - dynamic_languages
print(re, )													# {'Java', 'Go', 'C#'}

re = dynamic_languages - programming_languages
print(re, )													# {'JavaScript', 'Lua'}
# 对称差异
re = programming_languages ^ dynamic_languages
print(re, )													# {'JavaScript', 'C#', 'Go', 'Lua', 'Java'}

8.3.1.2. 多列表取值

backend_developers = ['John', 'Rose', 'Jane', 'Steven']
frontend_developers = ['May', 'Rose', 'Jane', 'Jonny']

full_stack_developers = list(set(backend_developers) & set(frontend_developers))
print(full_stack_developers)

8.3.2. 集合推导

# 集合推导在语法上与 dict() 理解的语法相同
	* 是一个被低估的功能
    * 无值字典
# 使用推导式创建 Pythonic 数据结构的重要性再怎么强调也不为过
	* 尤其是当数据的大小相当大时
    	* 不仅由于底层优化而提供了性能加速, 而且使得代码更具可读性

8.3.3. 集合高效检测列表

# 这里讨论的集合的 & 运算符的扩展
	* 有助于找到两组之间的共同元素
# list() 和 set() 方法也分别接受集合和列表作为参数
# 当只关心出现在两个列表中的元素是否有共同的元素存在时
	* 可以使用<非空列表的真值>来创建清晰简洁的条件语句
    * 效率出奇的高
import dis
from timeit import timeit

# 判断两个列表中是否有公共元素
favorite_names = [f'name - {i}' for i in range(1000)]
other_names = [f'name - {i}' for i in range(500, 1500)]

* 方法 1:
def are_common_names(one_names: list, other_names: list) -> bool:
    for name in one_names:
        if name in other_names:
            return True
    return False

re1 = timeit(stmt='are_common_names(one_names, other_names)',
            setup='from __main__ import are_common_names; '   # 从主模块<当前运行模块>中导入 要运行的函数
                  'from __main__ import favorite_names; '     # 从主模块<当前运行模块>中导入 函数用到的实参,  具体数值也可直接写上
                  'from __main__ import other_names; '
                  'one_names = favorite_names;'               # 指定形参和实参的对应关系
                  'other_names = other_names',                # 形参和实参同名时可省略
            number=100,                                       # 适当调整程序执行的循环数量
            )
print(re1,)

* 方法 2:
def are_common_name_set_operation(
        one_names: list, other_names: list
) -> bool:
    return len(set(one_names) & set(other_names)) > 0

re2 = timeit(stmt='are_common_name_set_operation(one_names, other_names)',
            setup='from __main__ import are_common_name_set_operation; '   # 从主模块<当前运行模块>中导入 要运行的函数
                  'from __main__ import favorite_names; '                  # 从主模块<当前运行模块>中导入 函数用到的实参,  具体数值也可直接写上
                  'from __main__ import other_names; '
                  'one_names = favorite_names;'                            # 指定形参和实参的对应关系
                  'other_names = other_names',                             # 形参和实参同名时可省略
            number=100,                                                    # 适当调整程序执行的循环数量
            )
print(re2,)


print(f'<方法1>消耗的时间是<方法2>的{re1/re2}倍')     		 # 43.178728193062405倍

* 分析一下原因
print('-- dis(are_common_names)--')
dis.dis(are_common_names)

print('-- dis(are_common_name_set_operation)--')
dis.dis(are_common_name_set_operation)
-- dis(are_common_names)-- -- dis(are_common_name_set_operation)--
10 0 RESUME 0 28 0 RESUME 0
11 2 LOAD_FAST 0 (one_names) 31 2 LOAD_GLOBAL 1 (NULL + len)
4 GET_ITER 14 LOAD_GLOBAL 3 (NULL + set)
>> 6 FOR_ITER 9 (to 26) 26 LOAD_FAST 0 (one_names)
8 STORE_FAST 2 (name) 28 PRECALL 1
32 CALL 1
12 10 LOAD_FAST 2 (name) 42 LOAD_GLOBAL 3 (NULL + set)
12 LOAD_FAST 1 (other_names) 54 LOAD_FAST 1 (other_names)
14 CONTAINS_OP 0 56 PRECALL 1
16 POP_JUMP_FORWARD_IF_FALSE 3 (to 24) 60 CALL 1
70 BINARY_OP 1 (&)
13 18 POP_TOP 74 PRECALL 1
20 LOAD_CONST 1 (True) 78 CALL 1
22 RETURN_VALUE 88 LOAD_CONST 1 (0)
90 COMPARE_OP 4 (>)
12 >> 24 JUMP_BACKWARD 10 (to 6) 96 RETURN_VALUE
14 >> 26 LOAD_CONST 2 (False)
28 RETURN_VALUE

8.3.4. Iterables 高效去重

# 列表和字典等广泛使用的数据结构通常具有重复值
# 集合 set 已成为正确的去重候选者: 
	* 一个集合 set 只能容纳不重复的元素
	* 将已有的元素添加到集合 set 中将会被忽略
	* 可以从任何实现了 Hashable 的元素构建集合 set
# 在 Python 中, 集合 set 与 list 都是是 Iterable
	* 在循环 | 列表推导中可以互换使用
# 集合 set 去重既高效又简单
from timeit import timeit

list_of_games = [
    'PES2021',
    'FIFA22',
    '1943',
    '1943 Kai',
    'Super Street FighterII',
    '1943',
    'PES2021',
    'FIFA22',
]

# 列表去重
* 方法1:
def get_unique_of_games(games):
    unique_of_games = []
    for game in games:
        if game not in unique_of_games:
            unique_of_games.append(game)

re1 = timeit(stmt='get_unique_of_games(games*10000)',
             setup='from __main__ import get_unique_of_games;'
                   'from __main__ import list_of_games;'
                   'games = list_of_games;',
             number=1,
             )

print(re1)

* 方法2:
def get_unique_of_games_with_set(games):
    result = list(set(games))


re2 = timeit(stmt='get_unique_of_games_with_set(games*10000)',
             setup='from __main__ import get_unique_of_games_with_set;'
                   'from __main__ import list_of_games;'
                   'games = list_of_games;',
             number=1,
                   )

print(re2, )

print(f'{re1/re2}倍')							  		 # 4.272562027579992倍

8.3.5. frozenset 创建不可变集合

# frozenset 类可以在需要时创建一组在整个程序执行过程中唯一且不变的只读集合 set
	* 可以防止对集进行任何修改
    * 本质上是静态的
    	* 只能查询它们的元素
    * 内置集合类型
# frozenset 的优势: 
	* 可以将它们用作字典中的键/另一个集合的元素
    	* 因为它们的不变性, 自然就可以hashable, 作为字典的key是没有问题的
# 对于修改集合的操作, fronzenset 都会失败
# fozenset 支持所有集合操作而且也是无序的

8.3.6. Counter

# collections.Counter 类可以在保持集合实体的唯一性的同时又能处理集合中任何元素的多个实例
	* 也被称为多 multiset 或 bags 的数据结构
# 若想要跟踪集合中元素出现少次以及它在集合中的存在性, Counter 就很有用

8.4. 元组处理

# 元组是 Python 中非常灵活且非常有用的数据结构
	* 与列表类似: 同是序列
    * 与列表不同: 元组是不可变的
# 尽管元组不如列表流行, 但仍是一种基本数据类型, 甚至在核心 Python 语言内部使用
# 可以在下列情况下使用元组: 
    * 在函数中作为参数使用
    * 函数返回值包含多个项的时候使用
    * 遍历字典的键值对的时候使用
    * 作为字符串格式占位符使用

8.4.1. 使用元组解包数据

# Python 是支持语句中多个赋值的语言之一
# 允许使用元组对数据进行<解包>
    * 类似于那些熟悉 LISP 的人的解构绑定
# 将从2方面来讲解元组解包数据: 
	* 赋值解包
	* 函数带 * 的参数解包数据

8.4.1.1. 赋值解包

# -*- coding: UTF-8 -*-
# version: python3.12


csv_file_row = ['Jacky', 'jacky@local.dev', 48]

(name, email, age) = csv_file_row
output = f'{name}, {email}, {age}'
print(output,)                                                  # Jacky, jacky@local.dev, 48

group1 = ('Cat', 'Dog')
group2 = ('Eagle', 'Geese')

new_group = (*group1, *group2, 'snake')
print(new_group)											# ('Cat', 'Dog', 'Eagle', 'Geese', 'snake')

8.4.1.2. 带 * 的参数解包

# 函数带 * 参数意义: 参数的数目不定
	* 在函数内部首先会把这些参数解包为一个元组
    	* 若参数以数组<列表>形式传入, 就会被整体当成元组的一项
def display_users(*users):
    print(type(users))
    
display_users('Dany', 'Bobby', 'Jacky')						# <class 'tuple'>
def display_users(*users):
    print(type(users))
    print([user for user in users])
    
display_users('Dany', 'Bobby', 'Jacky')						# <class 'tuple'>
															# ['Dany', 'Bobby', 'Jacky']

display_users(['Dany', 'Bobby', 'Jacky']) 					# <class 'tuple'>
															# [['Dany', 'Bobby', 'Jacky']]   

8.4.2. 占位符<_> 与多值返回

# 使用<_>占位符忽略元组数据
	* 当将返回的函数值作为元组处理/分配一个等于某些数据的元组时, 不需要的字段可使用<_>用作丢弃数据的占位符
    * 占位符只使用同一内存地址
    * 占位符只保留最后一次赋值
    * 可与<*>配合使用
user = ('Steven', 28, 'Manager', 'DEV')

(name, age, _, _) = user
print(name, age,)											# Steven 28

(*_, department) = user
print(department)											# DEV

(_, *other_data) = user
print(other_data)											# [28, 'Manager', 'DEV']

8.4.3. 多值返回

# 使用元组接收从函数返回的多个值
    * 在标准库或外部包的大多数地方可以看到这种返回元组值的模式
def get_data_info(data):
    sum_value = sum(data)
    max_value = max(data)
    min_value = min(data)
    average = float(sum_value / len(data))
    return (average, sum_value, max_value, min_value)

list_of_data = [1, 2, 3, 4, 5, 6]
re = get_data_info(list_of_data)
print(re, type(re))											# (3.5, 21, 6, 1) <class 'tuple'>

8.4.4. 使用 namedtuple 使元组更清晰

# 在处理行列形式的表格数据时, 大多数返回值<尤其是多行>都是列表或元组的形式
# 在处理较大的数据集时, namedtuple 对于记住哪个索引对应于哪个逻辑字段是这一乏味且相当混乱的问题相当有效
# namedtuple 就像一个普通的元组, 提供了通过名称而不是索引访问字段的额外优势
# namedtuple 增强了代码的可读性和可维护性
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')

import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
    print(emp.name, emp.title)

import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
    print(emp.name, emp.title)

8.4.5. 具有高级功能的 NamedTuple

# typing.NamedTuple 实例化了一个与 collections.namedtuple 等效的子类
	* 将 __annotation__ | __field_defaults__ 和 __field_types__ 属性添加到类中
    * 生成的对象将以相同的方式运行, 并且可能会在您的 IDE 中添加一些额外的功能支持
# typing模块中的类提供了更自然的接口
	* 可以通过添加文档字符串或一些其他方法直接自定义类型
from typing import NamedTuple

class Car(NamedTuple):
    color: str
    mileage: float
    automatic: bool = False

car1 = Car('red', 3888.2,)
print(car1)

8.5. 字符串

# 字符串是 Python 中最流行的类型之一
# 可以简单地通过将字符括在引号中来创建 
    * Python 对待单引号与双引号的处理方式相同
# 创建字符串就像为变量赋值一样简单
# Python 不支持<字符类型>
	* 只有长度为 1 的字符串
    	* 被视为子字符串

8.5.1. 使用 <.join> 从列表元素创建单个字符串

# 使用连接操作从元素列表中创建字符串更快, 使用更少的内存, 并且是一种非常常见的做法
    * 在表示为字符串的分隔符上调用 join 方法
    * 零长度分隔符将简单地连接字符串
list_of_str = ['Syntax Error', 'Network Error', 'File not found']

concat_string = ' '.join(list_of_str)
print(concat_string, type(concat_string))						# Syntax Error Network Error File not found

8.5.2. 通过字符串函数的链式操作来提高代码的可读性

# 使用字符串函数执行一些转换, 最好将它们转为链式操作, 而不是在每个阶段都使用临时变量
	* 代码更具可读性和效率
	* 过度链式操作可能导致代码可读性降低
    	* 建议不超过 3 层
# 将下列字符串去除两端空格后, 使用<by>替代<:>, 最终转换成大写字母输出

book_title = ' The Go Design Patterns : Some Publications '

formatted_title = book_title.strip(' ')\
    .replace(':', 'by')\
    .upper()
print(formatted_title)

8.5.3. 格式化字符串的秘密

# 在 Python 中混合/构造字符串可以通过以下三种方式完成: 
	* 使用 + 运算符连接字符串和变量
	* 使用传统的字符串格式化程序: 使用 % 运算符替换
        * 类似于其他语言中的 printf 的值
	* 对字符串使用格式化函数或 Python3+ 的格式化字符串
# 前两种可读性较差, 且性能不太好

8.5.3.1. + 运算符

# -*- coding: UTF-8 -*-
# version: python3.12


s1 = 'this is'
s2 = ' a '
s3 = 'string'

s = s1 + s2 + s3
print(s)

8.5.3.2. C 语言风格

# %s
# -*- coding: UTF-8 -*-
# version: python3.12


s = 'this %s %s'%('is a', 'string')
print(s)

8.5.3.3. format 样式

# -*- coding: UTF-8 -*-
# version: python3.12


from collections import namedtuple

Person = namedtuple('Person', 'name, age')
person = Person('Laura', 38)

s = 'Name: {name}, Age: {age}'.format(name=person.name, age=person.age)
print(s)															# Name: Laura, Age: 38

8.5.3.4. fstring样式

from collections import namedtuple

Person = namedtuple('Person', 'name, age')
person = Person('Laura', 38)

s = f'Name: {person.name}, Age: {person.age}'
print(s)															# Name: Laura, Age: 38

8.5.4. 字符串模板

from string import Template

template = Template('Hello, $val')
template.substitute(val='world')

8.5.5. 使用 ord 和 chr 处理 ASCII 代码

# Python 有两个经常被忽视的内置函数 ord() 和 chr()
	* 用于执行字符与其 ASCII 字符代码表示之间的转换
def get_hash(string: str) -> int:
    hash_value = 0
    for c in string:
        hash_value += ord(c)
    return hash_value

re = get_hash('this is a string')
print(re)															# 1516

8.6. 其他数据结构

8.6.1. 使用 Struct 创建序列化的 C 结构体

# 数据结构是一种存储和组织数据的方式, 定义了数据之间的关系以及可以对数据执行的操作
# Python 标准库附带 struct.Struct 类
	* 在 Python 值和序列化为 Python 字节对象的 C 结构之间进行转换
	* 用来处理网络流量的数据流封包或处理存储在文件中的二进制数据
from struct import Struct
from collections import namedtuple

Person = namedtuple('Person', 'name, desc, age, upgraded, score,')
person = Person(
    bytes('Bobby', encoding='utf-8'),
    bytes('hello, Boddy!测试哦!', encoding='utf-8'),
    28,
    False,
    99.5
)

struct_ = Struct(f'{len(person.name)}s{len(person.desc)}si?f')
# 打包
data = struct_.pack(*person)
print(data)

# 解包
unpack_data = struct_.unpack(data)

# 重建数据
person = Person._make(unpack_data)
print(str(person.name, encoding='utf-8',))
print(str(person.desc, encoding='utf-8',))
print(person.age, person.upgraded, person.score)

8.6.2. SimpleNamespace 的属性默认值

# types.SimpleNamespace 是在 Python 中创建数据对象的快捷方式
    * 提供了访问其命名空间的默认值
        * 将其所有 key 公开为类属性
    * 默认情况下所有实例还包含一个有意义的 __repr__ 的魔术方法
# 与空类相比, 提供了以下优点: 
    * 允许在构造对象时初始化属性: sn = SimpleNamespace(a=1, b=2)。
    * 提供了一个可读的 repr()
    * 会覆盖默认比较
    	* 不是通过 id() 进行比较, 而是比较属性值
# 简而言之, types.SimpleNamespace 只是一个超简单的类
	* 允许自由设置 | 更改 | 删除属性
    * 提供了一个不错的 repr 输出字符串
from types import SimpleNamespace

print('--创建第 1 个 SimpleNamespace--')
data = SimpleNamespace(a=1, b='b', c=[1, 2, 3])
print(data, type(data))
# 添加属性
data.d = {'name': 'Steven'}
print(data)
# repr 魔术方法
print(repr(data))
print(data.__repr__())

print('--创建第 2 个 SimpleNamespace--')
sn2 = SimpleNamespace(d={'name': 'Steven'}, b='b', c=[1, 2, 3], a=1)
print(sn2)

print('--比较 data 和 sn2--')
re = data == sn2
print(re)

# 删除属性
del data.d
print(data)

# 修改属性
data.b = 2
print(data)
# 自定义
from types import SimpleNamespace

class CustomSimpleNamespace(SimpleNamespace):
    def __repr__(self):
        keys = sorted(self.__dict__)
        items = (f'{k}: {self.__dict__[k]}' for k in keys)
        return f'{type(self).__name__}({", ".join(items)})'

custom_data = CustomSimpleNamespace(a=1, b=2, c=3)
print(custom_data)											# CustomSimpleNamespace(a: 1, b: 2, c: 3)
print(repr(custom_data))									# CustomSimpleNamespace(a: 1, b: 2, c: 3)

8.6.3. 双端队列 deque 实现快速且健壮的堆栈与队列

# Python 标准库集合框架中的 deque 类实现了一个线程安全的双端队列
    * 支持在 O(1) 时间内从两端插入和删除
# 考虑到双端操作, 可以将这些数据结构用作队列和堆栈
# 在内部, Python 中的双端队列被实现为双向链表
	* 在插入和删除操作中保持一致的性能
    * 在随机访问堆栈中的中间元素的 O(n) 性能方面表现不佳
# 支持线程安全

8.6.3.1. 模拟 tail() 功能

from collections import deque

def tail(filename, n=10) -> deque:
    """
    Return the last n lines of a file
    :param filename: 
    :param n: 
    :return: 
    """
    with open(filename,encoding='utf-8') as f:
        return deque(f, n)


re = tail('test-beauty.txt', 2)
print(re)

8.6.3.2. 轮询

from collections import deque

def roundrobin(*iterables) -> list:
    """ roundrobin('ABC', 'DE', 'F') --> A D F B E C"""
    # 返回内容是三个迭代器的 deque
    iterators = deque(map(iter, iterables))
    
    while iterators:
        try:
            while True:
                yield next(iterators[0])
                iterators.rotate(-1)
        except StopIteration:
            iterators.popleft()


re = roundrobin('ABC', 'DE', 'F')
print(type(re))
for i in re:
    print(i)

8.6.4. Queue 并行计算锁

# Queue 模块提供 FIFO 实现, 适合多线程编程
# 可用于在生产者和消费者线程之间安全地传递消息或其他数据
# 锁定机制为调用者使用同一个 Queue 实例在多个的线程中使用
# 队列的大小(元素数量)是可控的, 以限制内存使用
from queue import Queue
from datetime import datetime
from concurrent.futures import (
    ThreadPoolExecutor,
    as_completed,
)


def worker(q, idx):
    print('---- Executing Job ----')
    data = q.get()
    print('Done', idx, end='\n')
    return f'{data}[H]'

q = Queue()
start_time = datetime.now()
with ThreadPoolExecutor(max_workers=4) as executor:
    futures = {
        executor.submit(worker, q, idx): idx for idx in range(12)
    }
    # 生产者制程数据
    for i in range(12):
        q.put(i)

    # 归并结果
    for f in as_completed(futures):
        print('RESULT: ', f.result())

end_time = datetime.now()
elapsed = end_time - start_time
print(f'elapsed: {elapsed}')

8.6.5. Lifo Queue 并行计算锁

# LifoQueue 使用后进先出的顺序
	* 通常与堆栈数据结构相关联
# 提供锁定语义来支持多个并发的生产者和消费者

8.6.6. Multiporcessing.Queue 作为共享队列

# multiprocessing 是一个支持生成进程的包
    * 使用类似于 threading 模块的 API
    * multiprocessing 包提供本地和远程并发, 通过使用子进程而不是线程来有效地回避全局解释器锁
	* 允许开发人员充分利用给定机器上的多个处理器
# 可以在 Unix/Linux 和 Windows 上运行
# multiprocessing 模块引入了典型的例子是 Pool 对象
	* 线程模块中没有此API
    * 提供了一种可以跨多个输入值并行执行函数的方便方法
    * 跨进程分布输入数据
    	* 数据并行性
    * 这是一个共享作业队列实现
    	* 允许多个并发工作进程并行处理排队的项目
# 基于进程的并行化在 CPython 中很流行
	* 可以避开全局解释器锁<GIL>阻止在单个解释器进程上进行的并行执行的问题
# multiprocessing.Queue作为一种特殊的队列实现
	* 用于在进程之间共享数据
    * 更轻松地跨多个进程分配工作
    	* 解决 GIL 限制
        * 可以跨进程边界存储和传输可二进制序列化的对象
import time
import random
from multiprocessing import (
    Process,
    Queue,
    current_process,
)


def worker(input: Queue, output: Queue) -> None:
    for func, args in iter(input.get, 'QUIT'):
        result = calculate(func, args)
        output.put(result)

def calculate(func, args):
    result = func(*args)
    return f'{current_process().name}: {func.__name__}{args} = {result}'

def mul(a, b) -> int:
    time.sleep(0.5*random.random())
    return a * b

def plus(a, b) -> int:
    time.sleep(0.5*random.random())
    return a + b

def main():
    NUMBER_OF_PROCESS = 4
    COUNT_OF_TASK1 = 20
    COUNT_OF_TASK2 = 10

    TASK1 = [(mul, (i, 10)) for i in range(COUNT_OF_TASK1)]
    TASK2 = [(plus, (i, 20)) for i in range(COUNT_OF_TASK2)]

    task_queue = Queue()
    done_queue = Queue()

    for task in TASK1:
        task_queue.put(task)

    for i in range(NUMBER_OF_PROCESS):
        Process(target=worker, args=(task_queue, done_queue)).start()

    print('results: ')
    for i in range(len(TASK1)):
        print('\t', done_queue.get())

    for task in TASK2:
        task_queue.put(task)

    for i in range(len(TASK2)):
        print('\t', done_queue.get())

    # Send QUIT message to stop the child processes
    for i in range(NUMBER_OF_PROCESS):
        task_queue.put('QUIT')

if __name__ == '__main__':
    main()

8.6.7. PriorityQueue 实现优先级队列

# heapq 模块提供了堆队列算法的实现
	* 也称为<优先队列算法>
    * 堆是二叉树, 每个父节点的值都小于或等于其任何子节点
    * 此实现对所有 k 使用 heap[k] <= heap[2*k+1] 和 heap[k] <= heap[2*k+2] 的数组, 从零开始计算元素
    * 为了比较, 不存在的元素被认为是无限的
    * 堆(实际上是 minheap)的一个有趣属性是它的最小元素始终是根, heap[0]
# PriorityQueue优先级队列在内部实现为 heapq, 并且具有相似的时间和空间复杂性
	* 不同之处在于 PriorityQueue 是同步的, 并提供锁定语义以支持多个并发的生产者和消费者。

8.6.7.1. heapq

import heapq

items = []

heapq.heappush(items, (2, 'dog'))
heapq.heappush(items, (1, 'cat'))
heapq.heappush(items, (3, 'tiger'))

while items:
    next_thing = heapq.heappop(items)
    print(next_thing)

8.6.7.2. PriorityQueue

import queue
import threading

def worker(q):
    while True:
        item = q.get()
        print(f'Working on {item}')
        print(f'Finished {item}')
        q.task_done()

q = queue.PriorityQueue()
threading.Thread(target=worker, args=(q, ), daemon=True).start()
for item in range(10):
    q.put(10 - item)

q.join()
print('All work done.')
import time
import random
from datetime import datetime
from queue import PriorityQueue
from concurrent.futures import (
    ThreadPoolExecutor,
    wait,
    ALL_COMPLETED,
)


def worker(input_queue: PriorityQueue, output_queue: PriorityQueue, idx):
    print('---- Executing Job ----')
    data = input_queue.get()
    time.sleep(1)
    print('Done', idx, end='\n')
    output_queue.put(data)

input_queue = PriorityQueue()
output_queue = PriorityQueue()
start_time = datetime.now()
with ThreadPoolExecutor(max_workers=4) as executor:
    futures = {
        executor.submit(
            worker, input_queue, output_queue, idx): idx for idx in range(12)
    }
    # 生产者制程数据
    raw_data = list(range(12))
    random.shuffle(raw_data)
    for data in raw_data:
        input_queue.put(data)

    # 归并结果
    wait(futures, return_when=ALL_COMPLETED)
    result = [output_queue.get() for _ in range(len(raw_data))]
    print('RESULT: ', result)

end_time = datetime.now()
elapsed = end_time - start_time
print(f'elapsed: {elapsed}')
posted @ 2024-05-31 15:53  怎么也学不明白的老白  阅读(30)  评论(0)    收藏  举报