流畅的python

第1章 python数据模型

---1.1 一摞Python风格的纸牌

  • 特殊方法,即__method__,又被称为魔术方法(magic method)或者双下方法(dunder-method).

  • 特殊方法的存在是为了被python解释器调用的

  • collections.namedtuple用于构建一个只有少数属性但是没有方法的对象

  • 通过实现__getitem__,可以使对象有[]操作,支持切片操作,可迭代

  • for i in x:实际上是用了iter(x),而这个函数背后则是x.__iter__()方法

  • random.choice可以从一个序列中随机选出一个元素

  • 通过实现一个针对对象的key函数,就可以对该对象进行排序

  • 实现特殊方法来利用python模型的好处是:

    1. 类有统一的标准操作名称

    2. 可以更加方便地利用python的标准库

---1.2 如何使用特殊方法

  • __repr__使对象有一个字符串表示形式.它与__str__的区别是,后者在str()函数或是print()函数被使用.如果一个对象没有__str__函数,python会用__repr__代替.

  • __add__, __sub__, __mul__, __truediv__对应+-*\四种算数运算符

  • 默认情况下,自定义的类的实例总被认为是True,除非对__bool__或__len__有实现.bool(x)会先调用前者,再调用后者,0为False,否则为True.

---1.3 特殊方法一览

第2章 序列构成的数组

---2.1 内置序列类型概览

  • 容器序列可以存放不同类型的数据,存放的是所包含的对象的引用:list,tuple,collections.deque

  • 扁平序列只能容纳一种类型,存放的是值,是一段连续的内存空间:str,bytes,bytearray,memoryview,array.array

  • 可变序列:list,bytearray,array.array,collections.deque,memoryviw

  • 不可变序列:tuple,str,bytes

---2.2 列表推导和生成器表达式

  • 最好只用列表推导来生成列表,而用生成器表达式来初始化元组,数组或其他序列类型.因为生成器表达式可以逐个产出元素,更节省内存.

---2.3 元组不仅仅是不可变的列表

  • 元组除了用作不可变列表,还可以用于没有字段名的记录,其中的每个元素都存放了记录中一个字段的数据和位置

  • 元组拆包(例如平行赋值,函数返回多个值)可应用到任何可迭代对象上,只要被可迭代对象中的元素数量和接受这些元素的元组的空挡数一致.

  • *前缀的变量名可以处理元组拆包中的剩余元素

  • collections.namedtuple可以用来构建一个带字段名的元组和一个有名字的类.

  • 元组支持除了增减元素相关的方法之外的其他所有列表的方法.

---2.4 切片

  • 如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象,即使是单个值也要转换成可迭代序列.

---2.5 对序列使用+和*

  • +和*操作不会修改序列,而会新建一个包含同样类型数据的序列作为拼接的结果.

  • 注意:如果a*n中的a是其他可变对象的引用的话,它们会指向同一个可变对象.正确的方式是[[] * m for i in range(n)]

---2.6 序列的增量赋值

  • +=(*=)对应的特殊方法是__iadd__(__imul__),如果没有被实现,解释器会退一步调用__add__.可变序列一般都实现了__iadd__

  • 可变序列的增量赋值会直接追加到原序列上,不可变序列则会创建一个新对象,把原来的元素复制进去后再追加新的元素,因此效率很低.但是str是不可变序列中的一个例外.

  • 下列语句会成功改变t的值,同时抛出异常.因此最好不要把可变对象放在元组里.

t= (1, 2, [3, 4])
t[2] += [5,6]

---2.7 list.sort方法和内置函数sorted

  • list.sort方法会就地排序.因此它返回None,这也是python中函数或者方法对对象进行就地改动时的惯例.

  • sorted可以接受任何可迭代对象作为参数,并新建一个列表作为返回值.

---2.8 用bisect来管理已排序的序列

  • bisect模块提供了二分查找算法.包含两个主要函数:bisect用于搜索,insort用于插入.

---2.9 当列表不是首选时

  • 如果需要一个只包含数字的列表,那么数组(array.array)比list更高效.创建数组需要一个类型码,表示在底层的C语言应该存放怎样的数据类型.以二进制文件读写数据会显著提高效率.

  • 内存视图(memoryview)能在不复制内容的情况下操作同一个数组的不同切片.

  • 双向队列(collections.deque)是一个线程安全,可以快速从两端添加或者删除元素的数据类型.可以在多线程程序中当作先进先出的栈使用.

第3章 字典和集合

---3.1 泛映射类型

  • 如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现__hash__和__eq__方法.如果两个可散列对象是相等的,那么它们的散列值一定一样.str,bytes,数值类型,frozenset以及只包含可散列类型的元组都是科三咧类型.

---3.2 字典推导

  • 字典推导类似于列表推导,可以从任何以键值对作为元素的可迭代对象中构建出字典.

---3.3 常见的映射方法

  • 当需要通过查找来插入新值的时候,dict.setdefault比if语句要高效很多.

---3.4 映射到弹性键查询

  • collections.defaultdict为不存在的键提供了一个默认值.

  • 还可以通过继承dict并实现__missing__方法来处理键不存在的情况.

---3.6 子类化UserDict

  • 如果需要创造自定义映射类型,继承UserDict是一个很好的选择.

---3.7 不可变映射类型

  • 如果不能让用户错误地修改某个映射,可以使用types.MappinrgProxyType.将一个字典传给它,它会动态返回原字典的改动,但是不能通过它做任何修改.

---3.8 集合论

  • 集合的本质是许多唯一对象的聚集,可以用于去重.

  • 集合中的元素必须是可散列的,set本身是不可散列的,但是frozenset是可以散列的.

  • 集合包含很多中缀运算符,|取合集,&取交集,-取差集.

  • 同样可以用集合推导式来创建集合

---3.9 dict和set的背后

  • 散列表是一个稀疏数组,python会设法保证大概有1/3的表元是空的.在快要达到阈值的时候,原有的散列表会被复制到一个更大的空间里面.

  • 散列值应该在索引空间中尽量分散,因此越是相似但不相等的对象,它们散列值的差别应该越大.实际运用中,多数搜索过程中并不会有冲突发生.

  • 字典在内存上的开销巨大,因此最好使用元组来存放大量数据.

  • 字典键的次序取决于添加顺序,往里面添加新键可能会改变已有的次序,因此不要对字典同时进行迭代和修改.

  • 上述特点,对集合也几乎都适用.

第4章 文本和字节序列

---4.1 字符问题

  • Unicode中字符的标识(码位)是0~1114111的数字,以4~6个16进制数字表示,而且加前缀"U+".字符的具体表述取决于所用的编码.

---4.2 字节概要

  • python3的bytes或bytearray对象的各个元素是介于0-255之间的整数,但是它们的切片仍是同一类型的二进制序列.

  • 二进制序列可能以ASCII字符本身,转义序列和十六进制转义序列三种形式显示.

---4.3 基本的编解码器

  • python自带超过100种编解码器.

  • UTF编码设计的目的就是处理每一个Unicode码位.

---4.4 了解编码问题

  • 多数非UTF编解码器只能处理Unicode字符的一小部分,此时在编码时会抛出UnicodeEncodeError.

  • 不是每一个字符序列都是有效的UTF编码,如果无法转换时会抛出UnicodeDecodeError.

---4.5 处理文本文件

  • 处理文本的最佳方法是:尽早把输入解码成字符串,尽量晚地把字符串编码成字节序列.python3的open,read和write方法已经帮忙实现了这个原则.

  • 需要在多台设备中或多种场合下运行的代码,打开文件时一定要明确传入encoding,因为不同设备的默认编码可能不同.

---4.6 为了正确比较而规范化Unicode字符串

  • Unicode有组合字符(变音符号和附加到前一个字符上的记号),所以字符串比较起来很复杂.问题点解决方案是使用unicodedata.normaliza进行规范化.一般选取NFC

第5章 一等函数

---5.1 把函数视作对象

  • 在python中,函数是一等对象,函数对象本身是function类的实例.

---5.2 高阶函数

  • 接受函数为参数,或者把函数作为结果返回的函数是高阶函数,例如map,filter.最好使用列表推导或生成器表达式替代它们.

---5.3 匿名函数

  • lambda可以创建匿名函数,一般用于作为参数传给高阶函数.它只是语法糖,实际上也会创建函数对象.

---5.5 用户定义的可调用类型

  • 任何python对象都可以被调用,只要实现了__call__

---5.7 从定位参数到仅限关键字参数

  • 在函数参数中使用*和**可以展开参数并捕获.如果把参数放在前面有*的参数后面,就成为了仅限关键词参数.

---5.10 支持函数式编程的包

  • operator模块为多个算数运算符提供了对应的函数,还有itemgetter和attrgetter可以从序列中取出元素.

第6章 使用一等函数实现设计模式

---6.1 案例分析:重构"策略"模式

  • 策略模式是指:定义一系列算法,把它们一一封装起来,并且使它们可以相互替换.

  • 可以把策略函数作为参数传入对象,简化策略模式.

  • 可以使用globals来获取所有的策略函数,但要求策略函数有一个统一命名规则.

第7章 函数装饰器和闭包

---7.1 装饰器基础知识

  • 装饰器是可调用的对象,其参数是被装饰的函数.装饰器通常把函数替换成另一个函数.

---7.2 Python何时执行装饰器

  • 函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行.

---7.3 使用装饰器改进"策略"模式

  • 在上一章中,如果用promos列表存储策略函数,可能会存在忘记添加的问题.如果使用一个装饰器函数来将策略函数添加到列表中,并将每一个策略函数都用这个装饰器函数来装饰,就可以解决上述问题.

---7.4 变量作用域规则

  • python编译函数的定义体时,会将在函数中赋值的变量判断为局部变量.如果需要让解释器把变量当成全局变量,需要在函数内用global声明.

---7.5 闭包

  • 闭包指延伸了作用域的函数,其中包含函数定义体中引用,但是不在定义体中定义的非全局变量.

  • 未在本地作用域中绑定的变量叫自由变量.

  • 闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定.《流畅的python》读书笔记-LMLPHP

---7.6 nonlocal声明

  • 在7.5的例子中,实际上利用了列表是可变的对象.如果想用两个数值count和total来实现,那么在+=时相当于赋值,于是它们会变成局部变量,而不是自由变量.

  • 为了解决这个问题,需要使用nonlocal声明.它的作用是把变量标记为自由变量.

  • 另一种实现方式是,把内部函数需要修改的变量存储伟可变对象的元素或属性,并且把那个对象绑定给一个自由变量.

---7.8 标准库中的装饰器

  • functools.lru_cache可以把耗时的函数的结果保存起来,避免传入相同的参数时重复计算.可以用于优化递归算法.注意lru_cache使用字典存储结果,所以被它装饰的函数的所有参数都必须是可散列的.

  • functools.singledispatch可以把整体方案拆分成多个模块,也就是将普通函数变成泛函数:根据第一个参数的类型,以不同方式执行相同操作.

---7.9 叠放装饰器

  • 把@d1和@d2两个装饰器按顺序应用到f函数上,相当于f=d1(d2(f))

---7.10 参数化装饰器

  • 通过装饰器工厂函数,将真正的装饰器放在内部,就可以接受除了函数以外的参数.

第8章 对象引用,可变性和垃圾回收

---8.1 变量不是盒子

  • 对于面向对象语言中的引用式变量,它们是附加在对象上的标注,而不是盒子.

  • 创建对象之后才会把变量分配给对象.对象在赋值语句右边创建或获取,然后左边的变量才会绑定到对象上.

05-06 11:09