第一章:Python数据类型
为什么需要特殊方法
在类中定义特殊方法的例子:
1 | class MyObj: |
特殊方法存在是为了被Python解释器调用的,自己并不需要调用,比如说不应使用myobj.__len__(),而应该使用len(myobj)
随机返回某个元素
1 | from random import choice |
使用特殊方法实现一个Vector类
1 | from math import hypot |
- Python有一个内置的函数叫repr,它能把一个对象用字符串形式表达式以便辨认
- 通过add和mul函数,支持+和*运算
- 默认情况,自定义类实例总为真,除非这个类对bool有自己的实现
特殊方法一览
3. Data model — Python 3.11.4 documentation
为什么len不是普通方法
为了让Python自带的数据结构可以走后门,abs也是同理,但多亏了它是特殊方法,可以把len用于自定义数据类型
本章小结
- 通过特殊方法,自定义数据类型可以表现得跟内置类型一样,从而写出更具表达力的代码
- Python对象的基本要求就是它得有合理的字符串表示形式,可以通过repr和str满足这个要求,前者方便调试日志,后者给终端用户看(print)
第二章:序列构成的数组
学会使用列表推导,增强代码可读性
例:把一个字符串变成Unicode代码
1 | symbols = 'abcdefg' |
例:笛卡尔积
1 | colors = ['black', 'white'] |
生成器表达式
生成器表达式可以逐个产生元素,而不是先建立一个完整列表,这种方式更加节省内存
1 | symbols = 'abcdefg' |
元组不仅是不可变列表,还可用于没有字段名的记录
元组拆包
使用collections.namedtuple构建简单类
1 | Card = collections.namedtuple('Card', \['rank', 'list']) beer\_card = Card('7', 'diamonds') |
对象切片
可以用s[a: b: c]形式对a和b之间以c为间隔取值,c的值可以为负,负值意味着反向取值。下面3个例子更直观些
c为负值意味着可以反向取值,举例
1 | 'bicycle' s = |
切片操作对序列做修改
1 | l = list(range(10)) |
对序列使用+和*
1 | board = [['_'] * 3] * 3 |
序列的增量赋值
+= 背后特殊方法是iadd,如果一个类没实现这种方法就回退一步调用add, 比如
1 | a += b |
- 如果a实现了iadd,就会调用iadd,a就会就地改动; 如果a没实现iadd,a += b这个表达式效果和a = a + b一样
对不可变序列做拼接操作
1 | 1,2,3) t = ( |
- ID发生了变化,对不可变序列进行重复拼接操作效率低,因为每次都有一个新对象,解释器需把原来对象中元素先复制到新对象,再追加新元素
- str是一个例外,str做+=,id不一定会变,因为CPython做了优化,str初始化时会留额外的可扩展空间,做增量操作时不一定会复制原有字符串到新位置到这类操作
关于+=的谜题
1 | 1,2,[30,40]) t = ( |
教训:不要把可变对象放在元组里,增量赋值并非完整操作
list.sort方法和内置函数sorted
- list.sort方法就地排序列表, sorted总是返回一个列表
- 两种方法都有两个可选的关键字参数
- reverse 如果被设定为True,降序输出
- key,默认用元素自己的值做排序
- 是稳定排序,每次排序结果相对位置固定的
1
2
3
4
5
6
7
8
9
10
11
12'grape', 'raspberry', 'apple', 'banana'] fruits = [
sorted(fruits)
['apple', 'banana', 'grape', 'raspberry']
sorted(fruits, reverse=True)
['raspberry', 'grape', 'banana', 'apple']
sorted(fruits, key=len)
['grape', 'apple', 'banana', 'raspberry']
sorted(fruits, key=len, reverse=True)
['raspberry', 'banana', 'grape', 'apple']
fruits.sort()
fruits
['apple', 'banana', 'grape', 'raspberry']
bisect操作(排序,插入已排序的序列)
插入已排序的序列
1 | 1, 3, 4, 6] a = [ |
array数组
如需要一个只包含数字的列表, array.array比list更高效
数组支持所有可变序列相关操作,包括pop, insert, extend
数组提供从文件读取和存入文件的快速方法,如frombytes, tofile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from array import array
from random import random
'd', (random() for i in range(10**7))) floats = array(
1] floats[-
0.5225102971704024
open('floats.bin', 'wb') fp =
floats.tofile(fp)
fp.close()
'd') floats2 = array(
open('floats.bin', 'rb') fp =
10**7) floats2.fromfile(fp,
fp.close()
1] floats2[-
0.5225102971704024
floats2 == floats
Truetofile和fromfile速度比从文本文件读快60多倍,原因是后者使用内置的float把每一行文字转成浮点数
另一个快速序列化数字类型方法是使用pickle,速度和array相当,但pickle可以处理几乎所有的内置数字类型,包含复数、嵌套集合,用户自定义类
内存视图
memoryview
是一个内置类,能让用户在不复制内容情况下操作同一个数组的不同切片
1 | import array |
NumPy 和 SciPy
- NumPy提供高阶数组和矩阵操作, SciPy是基于NumPy的库,提供科学计算有关算法
双向队列和其他形式的队列
collections.deque类是一个线程安全,可以快速从两端添加或删除元素的数据类型
1 | from collections import deque |
- maxlen是一个可选参数,代表队列可以容纳的元素数量,一旦设定不可修改
- rotate操作接受一个参数n,当n > 0时,最右边n个元素移动到最左,n < 0, 最左边n个元素移动到最右
除了deque,其他Python标准库也有对队列的实现
- queue
- multiprocessing 实现了自己的Queue
- asyncio
- heapq
sorted, list.sort背后的排序算法 Timsort
Timsort是一种自适应排序算法,会根据原始数据的顺序特点交替用插入排序和归并排序,以达到最佳效率
第3章 字典和集合
可散列的数据类型
- 如一个对象时可散列的,在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现hash方法,另外可散列对象要有qe方法,才能跟其他键做比较
- 原子不可变数据类型(str, bytes, 数值类型)都是可散列类型, frozenset也是可散列的
- 对于元组,只有当一个元组包含所有元素都是可散列类型情况下,它才是可散列的
1 | 1, 2, (30, 40)) ttt = ( |
字典的构造方法
1 | dict(one=1, two=2) a = |
字典推导
字典推导可以从任何以键值对作为元素可迭代对象中构建出字典
1 | 'one', 1), ('two', 2), ('three', 3)] array = [( |
用setdefault处理找不到的键
以下两种写法是等价的
1 | if key not in my_dict: |
也可以用get方法获得key对应的value,如果不存在返回默认值
>>> dic = { 1: 'one'}
>>> dic.get(2, 'N/A')
'N/A'
字典的变种
collections.OrderedDict
添加key时会保持顺序
collections.ChainMap
容纳数个不同映射对象,进行key查找时,这些对象会被当做一个整体被逐个查找
不可变映射类型
MappingProxyType
集合论
set和它的不可变姊妹类型frozenset,集合中元素必须是可散列的
创建空集合用 set(), frozenset({0,1,2,3})
往字典里添加新键可能会改变已有键顺序
不要对字典同时进行迭代和修改,最好分两步:首先对字典迭代,得出要添加内容,把这些内容放在一个新字典里,迭代结束后再对原有字典进行更新
第4章 文本和字节序列
- 字符、码位和字节表述
- bytes、bytearray 和 memoryview 等二进制序列的独特特性
- 全部 Unicode 和陈旧字符集的编解码器
- 避免和处理编码错误
- 处理文本文件的最佳实践
- 默认编码的陷阱和标准 I/O 的问题
- 规范化 Unicode 文本,进行安全的比较
- 规范化、大小写折叠和暴力移除音调符号的实用函数
- 使用 locale 模块和 PyUCA 库正确地排序 Unicode 文本
- Unicode 数据库中的字符元数据 能处理字符串和字节序列的双模式 API
字符串是一个字符序列,从Python3的str对下井获取元素是Unicode字符,Unicode标准把字符的标识和具体字节表述进行如下明确区分
- 字符标识,即码位,是0-1114111的数字,在Unicode标准中以4-6个十六进制数字表示,加前缀U+, 例如字母A的码位是U+0041,欧元符号码位阿是U+20AC
- 字符具体表述取决于所用编码,UTF-8中, A(U+0041)的码位编码成单个字节\x41, 而在UTF-16LE编码中编成两个字节\x41\x00
1 | 'café' s = |
字节概要
Python内置两种基本二进制序列类型: bytes, bytearray
BOM是什么
字节序标记 byte-order mark
第7章 函数装饰器和闭包
装饰器特性,能把被装饰的函数替换成其他函数, 第二个特性:装饰器在加载模块时立即执行
1 | def deco(func): |
打印函数运行时间
1 | import time |
为什么用闭包
举例:求平均数,每次调用avg,新增一个数并重新计算平均值
实现方法1:缺点是需定义全局变量
1 | import time |
实现方法2:使用类,缺点是麻烦
1 | class Aver(): |
实现方法3:用函数,引入nonocal声明
1 | def make_aver(): |
闭包和匿名函数区别
第8章 对象引用、可变性和垃圾回收
默认做浅复制
1 | l1 = [3, [66,55,44], (7,8,9)] |
- 对可变对象来说,l2[1]引用的列表, +=运算符就地修改列表,修改在l1[1]中也有体现
- 对元组来说,+=创建一个新元组,然后重新绑定给变量l2[2]
不要使用可变类型作为参数默认值
防御可变参数
如果定义函数接收可变参数,应该谨慎考虑调用方是否期望修改传入参数
例:从TwilightBus下车后,乘客消失了
1 | class Bus: |
del和垃圾回收
对象不会自行销毁,然而无法得到对象时,可能被当做垃圾回收
del语句删除名称,而不是对象,举例:
1 | import weakref |
弱引用
当对象的饮用数量归零后,垃圾回收程序会把对象销毁,但是,有时需要引用对象,而不让对象存在时间超过所需时间,这经常用在缓存中
弱引用不会增加对象引用数量,引用的目标对象称为所指对象,因此我们说,弱引用不会妨碍所指对象被当做垃圾回收
1 | import weakref |
Python对不可变类型施加的把戏
例:使用另一个元组构建元组,得到的是同一个元组
1 | 1, 2, 3) t1 = ( |
例:字符串字面量可能会创建共享的对象
1 | 1, 2, 3) t1 = ( |
第9章 符合Python风格的对象
讨论两个概念:
- 如何以及何时使用@classmethod和@staticmethod装饰器
- Python的私有属性和受保护属性的用法,约定和局限
classmethod和staticmethod
- classmethod定义了操作类,而不是操作实例的方法, (参数没有this)
- staticmethod就是普通函数,不过它碰巧在类的定义中体现 (不太有用)
1 | class Demo: |
https://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methods
Python的私有属性和受保护的属性
- _private_attrs:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。 在类内部的方法中使用时 self.__private_attrs
- __private_method:两个下划线开头,声明该方法为私有方法,不能在类地外部调用