流畅的python笔记

时间:2022-01-31 05:04:25

鸭子类型协议不完全总结
序列:len,getitem
切片:getitem
v[0]分量的取值和写值:getitem和setitem
v.x属性的取值和写值:getattr和setattr
迭代:1)iter,2)getitem
上下文管理器:enter,exit
+=:1)iadd,2)add
可散列:hash,eq
in测试:1)contains,2)iter,3)getitem

第一章,python数据模型
__repr__得到对象的字符串表现形式
if obj,实际执行的是bool(obj),python解释器调用obj.__bool__,如果没有__bool__,会调用__len__看是否为0
len(obj),python解释器调用obj.__len__()
obj[key],python解释器调用obj.__getitem__(key)
'hello world %s' % x, 'hello world %r' % x ,对于x=123两者输出都一样,x='abc'前者输出hello world abc,后者输出hellow world 'abc'

第二章,序列构成的数组
容器序列:引用,list、tuple、collections.deque
扁平序列:值,str、bytes、array.array
可变序列:list、array.array
不可变序列:tuple、str
*args :序列中的值
args:序列
列表推导不再会有变量泄漏的问题
元组拆包:_,a, b, *args = [1, 2, 3, 4, 5, 6],不要的可以用_或者*args
切片实际操作步骤:首先生成切片对象slice(a, b, c),再使用__getitem__方法取值
序列使用 + 和 * 时,原序列都不会被修改,而是新建序列
创建3个空列表:应该使用[[] for i in range(3)],而不是使用[[]] * 3
array.array:浮点数效率高
set:in测试效率高

第三章,字典和集合
用户自定义类型的对象都是可散列的,散列值就是id()返回的值
dict.get(k, w):查找key时,设置默认
dict.setdefault(k, w):更新key时,设置默认
defaultdict(type):可以设置字典value的缺省类型,例如list
__missing__:找不到key时会调用__missing__特殊方法
集合没有getitem,但是有iter

第四章,文本和字节序列
待补充

第五章,一等函数
函数本质上就是定义了__call__的类,此外还有些函数特有的属性,例如__kwdefaults__,__defaults__,__get__等等
建议用生成器表达式取代map,filter
匿名函数lambda,主要适用于传参,可以省略函数名
operator:提供了一些算数运算符函数,例如相乘mul
functools.partial(func, arg):用于冻结参数,func是可调用对象,arg是冻结的参数

第六章,使用一等函数实现设计模式
用类实现策略模式:用类实现具体策略,相同部分作为基类,不同部分作为子类
用函数实现策略模式:用函数实现具体的策略,函数作为参数传递给类的实例

第七章,函数装饰器和闭包
*变量:未在本地作用域中绑定的变量,可以被内部嵌套函数访问,并能保留下来的
nonlocal:声明变量为*变量
基本装饰器(2层嵌套):必须return2次,如果只return wrapper,则被装饰函数有返回值时会永远返回None
参数化装饰器(3层嵌套):支持装饰器传参

基本装饰器代码实现:

def clock(fn):
@functools.wraps()
def wrapper(*arg, **kwargs):
ret = fn(*arg, **kwargs)
return ret
return wrapper

第八章,对象引用、可变性和垃圾回收
变量是标注,不是盒子:引用式变量(例如列表、字典)可以同时有多个不同的变量名
引用性变量的不同变量名,值/id都是相等的
python的'==':只判断值是否相等,id可以不等。有点类似js的'==='
可以用is判断id是否相同,不必使用id()函数
元组的不可变性:保存的引用不可变,但是引用的对象可以变
创建别名:b = lista
浅复制:b = lista[:],b = lista.copy(),b= copy.copy(lista)
深复制:b= copy.deepcopy(lista)
如果lista列表内部有“可变的引用对象”(例如字典、集合、列表,元组算不可变的引用对象),发生改变时,会影响到浅拷贝,但不会影响到深拷贝。
防御可变参数:函数传参时,可变参数的默认值必须使用None。如果使用可变参数作为默认值,则不传参时,多个函数调用方会共享同一个可变参数。
python是按引用传参的,对参数修改后:1)不可变参数会重新开辟内存,实质相当于传统的按值传参;2)可变参数会就地修改,实质相当于传统的按引用传参。

第九章,符合python风格的对象
尽早捕获错误:如果涉及到参数转换,在__init__中完成可以尽早捕获错误
classmethod:静态函数,第一个参数是类,可以调用类属性/类方法
staticmethod:静态函数,没有特殊参数,本质上就是放在类中的全局函数
格式化:format
私有属性:__x可以通过“object_classname__x”来访问,所以本质上单下划线和双下划线没什么区别
属性设置为只读:私有属性 + @property
__slots__属性:节省空间,使用后无法新增属性

第十章,序列的修改、散片和切片
获取类的引用方法:type(self),self.__class__,被@classmethod装饰的函数的第一个参数。要访问类变量,必须先获取类的引用。
实现了一个多维向量类(序列):
  v1:兼容2d,一些基本的序列方法
  v2:实现getitem,支持分量访问、切片访问。没有实现setitem,所以不支持分量设值、切片设值。
  v3:实现getattr和setattr,支持属性访问,setattr必须同时实现,否则对属性进行设值后(v.x)会与分量值(v[0])产生冲突
  v4:实现eq和hash,可散列
  v5:格式化

第十一章,从协议到抽象基类
鸭子类型:协议是非正式的接口
白鹅类型:使用抽象基类正式明确接口
abc:自定义抽象基类,抽象方法
collections.abc:提供一些内置的抽象基类
numbers:抽象基类数字塔。包括Number,Complex,Real,Rational,Intergral。
抽象基类(abc.ABC):抽象基类必须继承abc.ABC,才能约束子类必须实现抽象方法,抽象基类不能被实例化
抽象方法(@abc.abstractmethod):抽象基类的抽象方法,不用实现,子类继承抽象基类时必须实现抽象方法,才能被实例化
抽象基类主要作用:作为超类;isinstance检查;注册(xx.register);自动识别(不用注册或继承也能通过isinstance检查)
isinstance检查举例:collections.abc.Sequence抽象基类包含的方法有__contains__,__iter__,__len__,__getitem__,__reversed__,index,count,其中抽象方法是__len__,__getitem__。如果某对象想通过isinstance(obj, collections.abc.Sequence)检查,方法一是继承Sequence并实现2个抽象方法,方法二是自己实现所有的方法,并注册Sequence.register。
猴子补丁:直接给类(注意不是类的实例)添加新的方法。这样可以在运行时修改类或模块,而不改动源码。
python是动态强类型语言:动态指运行时检查类型,强类型指很少隐式转换类型。

第十二章,继承的优缺点
内置类型字典更新值的方法:
DoppeDict(one=1):调用__init__()
DoppeDict['two'] = 2:设置时调用__setitem__(),获取时调用__getitem__()
DoppeDict.update('three', 3):调用update(),
子类化内置类型时,不会调用用户定义的类覆盖的特殊方法
多重继承:广度优先,深度优先,python3是广度优先
委托给父类执行:super().func(),多重继承时可以指定由哪个父类执行xx.func()
继承UserDict, UserList, UseString:速度慢,以扩展
继承Dict, List, String:速度快,难扩展

第十三章,正确重载运算符
一元: -neg,+pos,~invert
算术: +add,
比较: ==eq,!=ne
增量赋值: +=iadd
运算时,先正向,后反向,比较运算还会比较id,通过返回NotImplemented切换
python3.5新引入@做点积运算

第十四章,可迭代的对象、迭代器和生成器
iterable具有可调用的iterator,可以通过iter(iterable)从iterable中获得iterator。
iterator的实现方法:1)iter + next,其中iter方法中return self,next方法中逐个返回值。2)iter,iter方法中使用yield生成器返回值。
generator属于iterator。
Sequence实现了getitem和len特殊方法就可以被迭代,因为解释器在尝试迭代对象x时,会执行如下步骤:1)调用iter(x);2)没有iter就调用getitem;
StopIteration表示迭代器到头。
典型的迭代器:2个类,iterable和iterator作为2个类分开实现,iterable中的iter返回iterator;iterator中的iter返回self,next逐个返回值。
糟糕的迭代器:1个类,同时在iterable中实现了iterator,即iterable中的iter返回self,同时具有next方法逐个返回值。
生成器:1个类,iterable的iter返回yield生成器。
惰性生成器:1个类,iterable的iter返回yield生成器,但是生成器的内容是惰性获取,例如获取文本时由finditer替代findall。
生成器表达式:1个类,iterable的iter返回一个生成器表达式。
多层for循环时,可以用yield from替代内层for循环

第十五章,上下文管理器和else块
用法:with xxx as yyy
支持同时打开两个对象:with xxx as yyy, aaa as bbb
上下文管理器协议:__enter__方法中返回yyy(没有提供内容时直接返回None),__exit__方法中做好退出释放资源操作

第十六章,协程
进程:有独立内存空间。
线程:CPU调度最小单位,共享内存。
协程:用户态的轻量级线程,上下文切换块,CPU感知不到协程,只使用一个线程。
在其他语言中,协程意义不大,可以通过多线程解决I/O,但是python有GIL多线程也是伪多线程,同一时间只能有一个线程,同步编程I/O阻塞称为瓶颈,可以用协程处理高I/O操作。
同步编程并发:单纯通过多进程和多进程进行同步编程,切换线程和切换进程有开销,还需要解决进程间/线程间资源交互问题。
异步编程并发:协程,回调。
协程: a = yield b,先执行b产出值;再执行a赋值;可以没有a,即只产出不赋值(生成器);可以没有b,即只赋值不产出值(最后一起返回)。
协程流程:
1)预激:手动next; 装饰器; yield from;
2)发送至:send;
3)关闭:发送哨符值; close(); throw()输入未捕捉的异常
4)异常处理:throw()输入except捕捉的异常
5) 获取协程返回值:PEP380定义; yield from;

第十七章,使用future处理并发
I/O密集型:多线程threading —> futures.ThreadPoolExecutor
CPU密集型:多进程multiprocessing —> futures.ProcessExecutor
python有GIL,同一时间只能允许一个线程执行python字节码,而线程是调度CPU的最小单位,因此多线程无法满足CPU密集型操作。其他语言开启多线程可以调度多个CPU的可以满足CPU密集型操作。
futures多进程/多线程处理方法:
map:全部完成后获取结果,再执行后续代码,只能处理相同的函数(函数参数可以不同)
submit + as_completed:有完成的就先获取结果,可处理不同函数

第十八章,使用asyncio包处理并发
asyncio用于异步IO
python3.5开始引入了新语法async和await:@asyncio.coroutine替换为async def …,yield from替换为await

第十九章,动态属性和特性
如果想把属性x变成只读:可以通过@property完成,特性名称需要与init中静态属性名称不同,因为如果相同没有提供setter方法无法完成赋值。
如果想限制静态属性:可以增加@x.setter装饰同名函数,静态属性名称self.x,@property特性名称x,@x.setter装饰的名称x三者相同,这时setter中的函数可以实现一些限制和验证操作,如果不做这些操作那就可以不用property和setter。

第二十章,属性描述符
限制静态属性的方法:
1)init函数中判断并raise error,缺点是只有实例化时有效,实例化后无法限制对属性进行更改
2)@property + @xx.setter,缺点是不能重复使用,有多个属性想限制时代码量会非常大
3)描述符

第二十一章,元类编程
待补充