《流畅的Python》读书部分笔记
内容全部都是手动整理,里边避免不了有错误,欢迎指出,欢迎讨论!
-
__repr__
方法是将对象用字符串的形式表达出来,repr()函数实际调用的就是这个模仿方法, 在控制台中打印实例对象的信息就是用的这个方法.__str__
是在str()函数中被调用,或者用print()时被调用. 如果类中没有实现__str__
时,解释器会用__repr__
作为替代. -
python2中列表推导式中的变量会覆盖推导式以外的变量,python3中不会.(for循环中还是会污染for循环以外的变量。)
-
元组中的每个元素都存放了记录中一个字段的数据,因为其不可变性,其字段的位置信息给数据有了其他意义.(如果在任何的表达式里我们在元组内对元素排序,这些元素的位置信息就会丢失)
-
不要把可变类型放到元组里边.不然改动可变类型的时候会有奇迹出现
-
list.sort 方法是列表就地排序,不会创建新的list. sorted()方法不管接受的是什么参数,排序结束都会返回一个list.
-
原子不可变数据类型都是可散列类型, frozenset也是可散列的.
-
不可变类型里包含的数据必须都是不可变类型才可以被散列(这里说的还是元组里边包含可变类型).
-
字典的变种: collection.OrderedDict 保序词典 collections.ChainMap 可以包含多个dict, 在查找的时候可以挨个字典查找,直到找到为止. collections.Counter 统计键的次数,返回的也是字典.
collections.UserDict 用户继承的字典类型,用于自己构建自己的字典类型. -
set是不可散列的数据类型. frozenset是可以散列的类型.
-
高阶函数:接受函数为参数,或者把函数作为结果返回的函数.
-
python种的可调用对象: 函数、内置函数、类、方法、内置方法、类的实例、生成器函数. 判断对象是否可以调用,最安全的方式是使用内置的callable()函数.
-
用户定义可调用类型 需要实现实例方法
__call__
实现__call__
方法的类是创建函数类对象的简便方法. -
形参放到*后边,必须以关键字参数传参. 如 def a(b, *, c) , 形参c必须以关键字参数传参,不然会报错.
-
函数对象有个
__defaults__
属性,值为一个元组,里边保存着定位参数和关键字参数的默认值,关键字参数的默认值在__kwdefaults__
属性中.参数的名称在__code__.co_varname
的属性中,不过里面还有函数定义体中创建的局部变量.因此,参数名称只是__code__.co_varname
中的前N个字符串,N的值由__code__.co_argcount
确定,然而之这里边不包括或者*的不定长参数.参数的默认值只能通过他们的__defaults__
元组中的位置确定, 因此要从后向前扫描才能把参数和默认值对应起来(指定了默认值的形参一般放在普通形参的后边,所以从后往前扫描才能对应起来). -
inspect模块 模块提供了一些有用的函数帮助获取对象的信息,例如模块、类、方法、函数、回溯、帧对象以及代码对象.
-
注解: python对注解不做检查、不做强制、不做验证. 唯一做的事情是把它们存储在函数的
__annotations__
属性里. -
函数式编程重要的两个模块:operator 和 functools. operator主要提供了计算的一些函数.attrgetter 可以提取具名元组属性的值. itemgetter 根据下标提取值,还支持映射和任何实现
__getitem__
方法的类.多用于排序时key参数的实参. -
methodcaller 函数可以自行创建函数.将方法当作methodcaller函数的参数.
-
functools.partial 固定函数某些参数的值 这个可以需要花时间了解.
-
装饰器是可调用对象.
-
装饰器的一大特性是能把被装饰的函数替换成其他函数.第二个特性是 装饰器在加载模块时立即执行(即加载模块时被装饰的函数即被替换成装饰器返回的函数对象),被装饰的函数旨在明确调用的时候运行.
-
闭包指延伸了作用域的函数,关键是能访问定义体之外定义的非全局变量.(只有嵌套在其他函数中的函数才有可能需要处理不在全局作用与中的外部变量)
-
自由变量: 指未在本地作用域中绑定的变量.(闭包中函数体之外的变量)
-
当闭包中的自由变量为不可变类型时,闭包中的函数无法对其修改.如果需要修改需要使用nonlocal进行声明.
-
装饰器的行为: 把被装饰的函数替换成新函数,二者接受相同的参数,而且返回被装饰的函数本该返回的值.同时还会做些额外的操作.
-
@functools.wraps(func) 可以保证装饰器不会对被装饰函数造成影响.能把被装饰的函数的属性复制到替换函数上.
-
函数的注解只存在于函数的
__annotations__
属性中. -
函数的默认参数的值在函数仅仅在函数定义的时候赋值一次. 其次函数的默认参数的默认值应该是不可变对象,否则在函数多次调用的时候结果不可控. 再次 默认值为None的时候,判断时好用is,而不是直接判断,因为 空字符串、空列表等都会被当作None处理.
-
匿名函数lambda表达式中的x是一个自由比变量,在运行时绑定.而不是在定义时绑定,这是跟普通函数的区别.(即lambda表达式中的值只是运行时当下的值.)
-
匿名函数如何在定义时捕获到定义的值的原理?(如:a = lambda y, x=x: x + y)
-
== 运算符比较两个对象的值(对象中保存的数据),而is比比较对象的标识.
-
元组的不可性其实是指tuple数据结构的物理内容(即保存的引用)不可变,与引用的对象是否可变无关(而str,bytes和array.array等单一类型序列是扁平的,它们保存的不是引用,而是连续内存中保存数据本身.
-
原子不可变数据类型(str、bytes 和数值类型)都是可散列类 型,frozenset 也是可散列的,因为根据其定义,frozenset 里只能容纳可散列类型.元组的话,只有当一个元组包含的所有元素都是可散列类型的情况下,它才是可散列的.
-
python唯一支持的参数传递模式是共享传参.(共享传参指的是函数的各个形式参数获得实参中各个引用的副本,参数中可以修改变量的值,但是不会修改变量的标识(ID))
-
del 不删除对象,而是删除对象的引用.
-
弱引用不会增加对象的引用数量.引用的目标对象称为所指对象.因此,弱引用不会妨碍所指对象被当作垃圾回收.
-
每个python对象都有标识、类型和值.只有对象的值会不时的变化.(对象的类型也可以变,方法只有一种:为
__class__
属性指定其他类.但这是在作 恶,我后悔加上这个脚注了.) -
弱引用 和 深浅复制仔细研究一下.
-
__repr__()
方法返回一个实例的代码表示形式.内置的repr()和!r或%r都是调用的这个方法.这个跟交互是加湿器显示的值一样. str()、print()、%s调用的是__str__()
这个方法. -
__format__(self, format_spec)
方法是format()函数、str.format()以及f"{}"语法糖调用的方法.format_spec默认是空字符串.其类型根据value的值进行判断(format(value)),它是1.format(value, format_spec)的第二个参数;2.str.format()方法的格式字符串,{}里代换字段中冒号后面的部分. -
python3中,
__repr__、__str__和__format__
都必须返回unicode字符串,只有__bytes__
方法应该返回字节序列.
-
实现上下文管理协议(with),要实现两个方法:
__enter__()
和__exit__()
.当出现with语句的时候,__enter__()
方法被触发,它的返回值会被赋值给as后的变量.然后with语句块的代码开始执行,结束后,__exit__()
方法被处罚,进行资源回收,垃圾清理.值得注意的是不管代码块中是否产生异常,控制流都会执行完,__exit__()
中的三个参数exc_type, exc_value, traceback在没有异常的情况下都是None, 如有异常产生,中的三个参数exc_type表示错误类型,exc_value表示信息,traceback表示追溯信息.产生异常时,该方法希望抑制异常(即,防止它被传播),则它应该返回一个True.否则,在退出此方法时将正常处理异常. -
__slots__
类属性的主要作用是通过替换实例中dict(a._class.dict)中属性默认的字典替换成__slots__
执行的数据结构.有一个副作用就是:实例不能再有__slots__
之外的其他属性.也就是说__slots__
作用是用来优化,而不是用来限制属性的个数.在实现实例的弱引用,需要实现__weakref__
方法,并且需要将'__weakref__'
加入到__slots__
中,否则实例就不能作为弱引用的目标. -
双下划线的实例属性以及方法都会被重命名,导致实例属性以及方法无法被继承以及无法被子类覆盖,python这个语言特性叫做==名称改写==.名称改写只是一种保护措施,目的是避免意外访问.
-
@classmethod
装饰器修饰符对应的函数不需要实例化,不需要self参数,第一个参数需要表式类自身的cls参数,可以用来调用类属性,类方法,实例化对象等.classmethod
最常见的用途式定义备选构造方法.staticmethod
装饰器也会改变方法的调用方式,但是第一个参数不是特殊的值,简单的讲,就是一个普通的函数封装到类里边了. -
一个类要实现
__hash__
方法,需要考虑的地方:1.属性不可变 2.要是现__eq__
方法,因为相等的对象应该具有相同的散列值.. -
@property
装饰器,一般是将一个获取值的方法变成属性.访问property装饰器的时候会自动触发getter、setter、deleter方法.只需要将setter、deleter所装饰的方法跟property所装饰的实例方法的名称相同就可以. -
类属性可用于为实例属性提供默认值(实例可以访问类属性).类属性可以继承,子类的同名类属性会覆盖父类中的同名类属性.
-
reprlib.repr 会将超过6个分量的数据,repr()生成的字符串就会使用...省略一部分.
-
在面向对象编程中,协议是非正式的接口,只在文档中定义,在代码中不定义.
-
indices 方法开放了内置序列实现的棘手逻辑,用于优雅地处理缺失索引和负 数索引,以及长度超过目标序列的切片.这个方法会“整顿”元组,把 start、stop 和 stride 都变成非负数,而且都落在指定长度序列的边界内.
-
__getitem__
是实现序列下标取值、切片的必要实现的方法.其参数item可以是数字或者切片对象slice. -
__getattr__
是实例获取实例属性所调用的方法.__setattr__
是对实例属性设置值所调用的方法.多数情况下,在类中实现了__getattr__
方法的同时,那么也要实现__setattr__
方法,以防读取以及赋值的行为不一致. -
itertools.zip_longes 函数,使用可选的fillvalue(默认值为None)填充缺失值.因此可以继续产出,直到最长的可迭代对象耗尽.
-
super()
函数,可以在子类中调用父类(超类)中某个已经被覆盖的方法.super()
函数的一个常见的用法是在__init__()
方法中取保父类被正确的初始化.另一个常见的用法出现在覆盖python特殊方法(魔术方法)的代码中. -
关于类的
__mro__
MRO表(方法解析顺序表)表.这个列表就是一个简单的所有基类顺序列表.为了实现继承, Python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止.MRO列表遵循三条准则:1. 子类会先于父类被检查 2. 多个父类会根据它们在列表中的顺序被检查. 3.如果对下一个类存在的两个合法的选择,选择第一个父类. 简单的讲,super() 方法会顺序线性的从MRO表中查找制定的基类.如果找到的基类中同样调用了super()方法,那么会接着顺序的往下查找,直到所选择的基类中不再调用super() 为止.
-
迭代器和可迭代对象的区别:1.迭代对象中需要实现
__iter__()
或者__getitem__()
方法,当使用item()内置函数调用时,默认使用__iter__()
,如果不存在会调用__getitem__()
,返回一个迭代器.2. 迭代器中需要实现__next__()
和__iter__()
方法,也就是说迭代器需要可以用next()函数进行取值. -
可变类型必须实现
__setitem__()
方法. -
猴子补丁:在运行时修改类或模块,而不改动源码.
-
抽象基类提供了一种要求子类实现指定协议的方式,如果一个抽象基类要求实现指定的方法,而子类没有实现的话,当试图创建子类或者执行子类代码时会抛出异常.
-
抽象类的作用:抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现的一组抽象方法.抽象类不能实例化,从抽象类派生出一个子类,如果没有重写所有抽象方法,则这个类也是抽象的,不能实例化. 抽象类提供了逻辑和实现解耦的能力,即抽象类定义模块提供的功能,在具体实现类来提供实现,这样在不同的模块中通过抽象类来调用,可以用最精简的方式展示出代码之间的逻辑关系,让模块之间的依赖清晰简单.同时,一个抽象类可以有多个实现,让系统的运转更加灵活.而针对抽象类的编程,让每个人可以关注当前抽象类,只关注其方法和描述,而不需要考虑过多的其他逻辑,这对协同开发有很大意义.极简版的抽象类实现,也让代码可读性更高.
-
抽象类的实现:Python为了实现抽象类的支持,支持定义抽象基类(Abstract Base Class),Python使用模块abc提供了抽象基类的支撑能力.抽象基类用于指定子类必须提供哪些功能,却不实现这些功能.抽象基类提供基本类和最基本的抽象方法,可以为子类定义共有的方法,但不需要具体实现,但子类中必须实现抽象方法,否则会报错.
-
抽像类的真实子类和虚拟子类:==真实子类==就是直接从抽象基类中派生,抽象基类中可以定义”抽象方法“和“抽象属性”, 抽象基类可以不实现具体的方法,也可以实现部分,子类继承抽象基类的抽象内容并实现,只有完全重写了抽象基类中的“抽象”内容后,才能被实例化,如果有个抽象内容没有重写则子类本身也是抽象类,不能实例化.==虚拟子类==是将其他的不是从抽象基类派生的类”注册“到抽象基类,让Python解释器将该类作为抽象基类的子类使用,因此称为虚拟子类,这样第三方类不需要直接继承自抽象基类.注册的虚拟子类不论是否实现抽象基类中的抽象内容,Python都认为它是抽象基类的子类,调用 issubclass(子类,抽象基类),isinstance (子类对象,抽象基类)都会返回True.
-
这种通过注册增加虚拟子类是抽象基类动态性的体现,也是符合Python风格的方式.它允许我们动态地,清晰地改变类的属别关系.当一个类继承自抽象基类时,该类必须完成抽象基类定义的语义;当一个类注册为虚拟子类时,这种限制则不再有约束力,可以由程序开发人员自己约束自己,因此提供了更好的灵活性与扩展性(当然也带来了一些意外的问题).这种能力在框架程序使用第三方插件时,采用虚拟子类即可以明晰接口,只要第三方插件能够提供框架程序要求的接口,不管其类型是什么,都可以使用抽象基类去调用相关能力,又不会影响框架程序去兼容外部接口的内部实现.老猿认为,从某种程度上讲,虚拟子类这种模式,是在继承这种模式下的一种多态实现.参考真实子类和虚拟子类的文章
-
迭代器模式:一种惰性获取数据项的方式,即按需一次获取一个数据项.
-
所有的生成器都是迭代器,因为生成器完全实现了迭代器接口.
-
在python中,所有的集合都可以迭代. 迭代器用于支持:1. for循环.2.构建和拓展结合类型.3:逐行遍历文本文件.4:列表推导、字典推导和集合推导.5.元组拆包.6.使用*进行拆包.
-
内置
iter()
函数的作用:1.检查对象是否实现了__iter__
方法,如果实现了就调用,并返回一个迭代器.2.如果没有实现__iter__
方法,但是实现了__getitem__
,python会创建一个迭代器,尝试使用下标(从0开始)获取元素.3. 如果尝试失败,Python 抛出 TypeError 异常. -
任何 Python 序列都可迭代的原因是,它们都实现了
__getitem__
方法.其实,标准的序 列也都实现了__iter__
方法,因此你也应该这么做.之所以对__getitem__
方法做特殊处 理,是为了向后兼容. -
可迭代对象不一定能通过issubclass测试,只有实现了
__iter__
方法的对象才能通过测试.只实现了__getitem__
方法的可迭代对象无法通过测试.检查==对象x能否迭代(这里说的不是迭代器,而是能否迭代)==,最准确的方法是iter()函数,如果不可迭代,再处理TypeError异常.这比使用isinstance(x, abc.Iterable)更准确,因为iter(x)会考虑遗留的__getitem__
方法. -
标准的迭代器接口有两个方法:
__next__()
(返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常.)和__iter__()
(返回实例self本身). -
迭代器是这样的对象:实现了无参数的
__next__
方法,返回下序列中的下一个元素;如果没有元素了,那么抛出StopIteration异常.迭代器中还实现了__iter__
方法,因此迭代器也可以迭代. -
迭代器可以迭代,但是可迭代对象不是迭代器.
-
可迭代对象有个
__iter__
方法,每次实例化得到一个一个新得迭代器;而实现迭代器需要实现__next__
方法,返回序列中得下一个元素,此外还要实现__iter__
,返回迭代器本身. -
可迭代得对象一能是自身得迭代器.也就是说,可迭代对象必须实现
__iter__
方法,但不能实现__next__
方法.另一方面,迭代器应该一直可以迭代.迭代器的__iter__
应该返回自身.
- iter()函数,传入两个参数时,使用常规的函数或任何可调 用的对象创建迭代器.这样使用时,第一个参数必须是可调用的对象,用于不断调用(没 有参数),产出各个值;第二个值是哨符,这是个标记值,当可调用的对象返回这个值时, 触发迭代器抛出 StopIteration 异常,而不产出哨符.
-
.throw(...) 作用是让调用方法抛出异常,在生成器中处理..close(...)的作用是终止生成器.
-
协程的四个状态:
GEN_CREATED
:等待开始执行(未激活状态)GEN_RUNNING
:正在执行GEN_SUSPENDED
在yield处暂停状态GEN_CLOSED
执行结束. -
协程需要用next()或者用.send(None)进行预激活,如果用send()激活,传入None之外的值,会报错.
-
使用yield from时会自动预激子协程,而不会预激委派生成器,需要手动预激委派生成器. 在python3.4+版本中的 asyncio.coroutine 不会预激携程,所以能与yield from 兼容.
-
值得注意的是:1.子生成器产出的值都直接传给委派生成器的调用方. 2.使用send()方法发给委派生成器的值都直接传给子生成器.如果发送的值为None,那么调用子生成器的
__next__()
方法.如果发送的值不是None,那么会调用子生成器的send()
方法.如果调用的方法抛出StopIteration异常,那么委派生成器恢复运行,其他的错误会向上冒泡,传给委派生成.3.生成器退出时,生成器(或子生成器)中的 return expr 表达式会触发 StopIteration(expr)
异常抛出.4.yield from 表达式的值是子生成器终止时传给 StopIteration 异常的第一个参数.
-
可以在生成器对象上调用两个方法,显示地把异常发给协程:
generator.throw(exc_type[, exc_value[, traceback]])
致使生成器在暂停的 yield 表达式处抛出指定的异常.如果生成器处理了抛出的异常, 代码会向前执行到下一个 yield 表达式,而产出的值会成为调用 generator.throw 方法 得到的返回值.如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下 文中.
generator.close()
致使生成器在暂停的 yield 表达式处抛出 GeneratorExit 异常.如果生成器没有处 理这个异常,或者抛出了 StopIteration 异常(通常是指运行到结尾),调用方不会 报错.如果收到 GeneratorExit 异常,生成器一定不能产出值,否则解释器会抛出 RuntimeError 异常.生成器抛出的其他异常会向上冒泡,传给调用方. -
如果子生成器不终止,委派生成器会一直在yield from表达式处永远暂停.委派生成器的程序不会向前执行,因为yield from把控制权交给委派成生成器的调用方了,这样直接导致了委派生成器中有任务无法完成.
-
传入委派生成器的异常,除了 GeneratorExit 之外都传给子生成器的 throw() 方法.如 果调用 throw() 方法时抛出 StopIteration 异常,委派生成器恢复运行.StopIteration 之外的异常会向上冒泡,传给委派生成器.
-
如果把 GeneratorExit 异常传入委派生成器,或者在委派生成器上调用 close() 方法, 那么在子生成器上调用 close() 方法,如果它有的话.如果调用 close() 方法导致异常 抛出,那么异常会向上冒泡,传给委派生成器;否则,委派生成器抛出 GeneratorExit 异常.
-
数据的属性和处理数据的方法统称属性.
-
不管服务是由存储还是计算实现的,一个模块提供所有服务都应该通过统一的方式使用.
-
用于构建实例的特殊方法市
__new__
:这是个特殊的类方法,不需要用@classmethod处理,必须返回一个实例.
+- -
__new__
方法返回的实例回作为__init__
方法的第一个参数,即self. -
调用
__init__
方法时要传入实例,而且禁止返回任何值,__init__
方法时实例的初始化方法.构造实例的方法实际上是__new__
.
-
对象的
__dict__
属性中存储着对象的属性(前提是类中没有声明__slots__
属性),更新实例的__dict__
属性,把值设为一个映射,能够快速地再那个实例中创建一堆属性. -
特性会覆盖实例属性, 特性都是类属性,特性管理的其实就是实例属性的读取.
-
描述符是实现了特定协议的类,这个协议包括
__get__
、__set__
和__delete__
方法. -
property类实现了完整的描述符协议.
-
实现
__set__
方法的描述符,称之为"覆盖型描述符":实现了__set__
方法的话,会覆盖对实例属性的赋值操作. -
没有实现
__set__
方法的描述符,都成为"非覆盖型描述符":对实例属性的操作都会在实例上完成,不会通过描述符处理. -
依附在类上的描述符无法控制为类属性赋值的操作(除非自己创建元类):读类属性的操作可以由依附在托管类上定义的
__get__
方法的描述符处理;但是写类属性的操作不由依附在托管类上定义有__set__
方法的描述符处理.
-
在类中定义的函数属于绑定方法,用的定义的函数都有
__get__
方法,依附到类上是,相当于非覆盖型描述符.(绑定方法:1.类中的方法和函数都是绑定给对象使用的(除了@classmethod和@staticmethod)2.绑定方法都是有自动传值的功能, 通过实例访问时,函数的__get__
方法返回的时绑定方法对象:一种可调用对象,里边包装着函数,并把托管实例绑定给函数的第一个参数.3.如果类想调用绑定方法,就必须遵循函数的参数规则,有几个参数传几个参数,包括self在内. ) -
type是一个类. type()当函数用时返回获取对象所属的类.当类使用时,传入三个参数name、bases、和dict可以创建一个类,也就是说,type() 的实例是一个类.
-
类装饰器只对直接依附的类有效(如果用super()方法调用父类的方法也会调用父类的装饰器).
-
顶层代码: 在python中用缩进来表示代码层次的,所谓顶层代码,表示没有缩进的代码:即在导入时会执行.但是类的定义体在导入时也会执行,所以类的定义体也属于顶层代码.
-
在导入时,解释器会从上到下一次性解析完.py模块的源码,然后生成用于执行的字节码.句法错误就地报告.本地的
__pycache__
文件夹中有最新的.pyc文件,解释器会跳过上述步骤. -
在进程中首次导入模块时,还会运行所导入模块中的全部顶层代码(再次倒入相同的模块则使用缓存,只做名称绑定.)
-
导入模块时,解释器会执行顶层的def语句,首次导入时解释器会编译函数的定义体,把函数对象绑定导对应的全局名称上,解释器不会执行函数的定义体.对于类来讲,解释器会执行每个类的定一体,甚至会执行嵌套类(类中类)的定义体.执行类定义体的结果时定义了类的属性和方法,并构建了类对象.在有类装饰器的情况下,先执行被装饰的类的定义体,然后再运行装饰器函数.(必须先构建类对象,装饰器才有类对象可处理.)
-
object类和type类之际嗯的关系很独特:object是type的实例(type的实例是类),而type是object的子类.type是自身的实例.
-
所有类都是type的实例,元类还是type的子类,可以作为制造类的工厂.
-
类都是type的实例,所以元类可以通过实现
__init__
方法定制实例.元类的__init__
方法可以做到类装饰器能做的任何事情,作用更大.通常,编写元类时,__init__
的方法中,self的参数名替换为cls,能清楚的表明构建的实例是类. -
如果父类中的metaclass指定了元类,那么子类中也会受到元类的影响,在导入或者执行的时候,会先执行元类中的
__init__
.