effictive-python笔记
第一章 用Pythonic方式来思考
1、确认自己所用的python版本(python3)
两个主流的python版本:python2(2020年就不维护) python3(推荐)
多种流行的python运行时环境 CPython(默认), Jython(java)
IronPython,PyPy(JIT,python2效果显著,python3基本无效果)
2、遵循PEP8风格指南
PEP8列出很多细节,以描述如何撰写清晰的python代码。
空白
使用空格表示缩进,不适用tab制表符
和语法相关的每一层缩进用4个空格表示
每行字符数不应超过79
占据多行的长表达式,除了首行之外的,其余行需要再之前的级别上再加4个空格
文件中函数与类之间应该用两个空行隔开
同一个类中,个方法之间应该用一个空行隔开
使用下表获取列表元素,调用函数或给关键字参数赋值的时候,不要再两边加空格。
a[0]
未变量赋值的时候,左右两侧应该加上空格,而且只写一个就好
a = b
命名
函数、变量及属性应该用小写字母来拼写,单词之间用下划线连接
def test_a(), my_name, self.my_name
受保护的实例属性,应该以单个下划线开头
self._leading_underscore
私有的实例属性,应该以两个下划线开头
self.__double_leading_underscore
类与异常,每个单词首字母均大写的格式
CapitalizedWord
模块级别的常量,全部大写
ALL_CAPS
类中的实例方法,应该把首个参数命名为self,表示对象自己
类方法的首个参数应该命名为cls,表示类自己
表达式与语句
采用内联形式的否定词,if a is not b而不是if not a is b
不要通过检测长度的方法来判断列表是否为空,if not somelist而不是if len(somelist) == 0
检测somelist是否为[1]或“hi”等非空值时,也应如此.
不要编写单行的if语句、for循环、while循环及except复合语句,而应该把这些语句分成多行来书写
import语句应该总是放在文件开头
引入模块的时候,使用绝对路径而不是相对路径
如果一定要以相对路径来写import语句,应采用 from . import foo
文件中那些import语句应该按顺序分成三个部分。分别表示标准库模块,第三方模块,自用模块
import sys
import requests
import myself
3、了解bytes、str与unicode的区别
python3 有两种字符序列类型 bytes和str,前者包含原始的8位值
后者包含unicode
python3 str和unicode,str包含原始的8位值,unicode包含unicode
把unicode转为二进制文件有很多种方法,常见的是设置编码方式为utf-8。
unicode转二进制 encode
二进制转unicode decode
编写python程序的时候,编码和解码放到界面最外围,核心部分使用unicode字符类型。
有两个问题要注意
一、python2中,如果str包含7个ascii那么unicode和str似乎成了同一个类型。
在只处理ascii情境下,如果某函数接受str,那么可以传入unicode,如果接受unicode,也可以传入str。而在python3中bytes和str不等价。
二、如果使用内置open函数获取文件句柄.python3默认使用utf-8
,python3默认是二进制。可以将模式改为wb适配
4、用辅助函数来取代复杂的表达式
如果表达式变得复杂,可以将其拆解成小块,移入辅助函数。
5、了解切割序列的办法
对原列表进行切割之后,会产生新的列表,修改新列表不会影响原列表
对list赋值的时候,如果使用切片操作,就会把原列表中处于相关范围内的值替换成新值。长度不同也可以替换。
6、单词切片操作内,不要同时指定start、end、和stride
如果stride为负数,可以考虑将其拆解成两条赋值语句。一条做范围切割,一个做步进切割。或者使用内置itertools模块的islice
7、使用列表推到来取代map和filter(字典和集合也支持)
代码更加清晰
8、不要使用超过两个以上的表达式的列表推导
9、用生成器表达式来改写数据量较大的里列表推导
将中括号改为小括号即可将列表推导改为生成器
数据量太大,列表推到会因为占用太多内存出问题。
10、尽量用enumerate取代range
enumerate可以将迭代器包装为生成器,返回索引和元素。
11、用zip函数同时遍历两个迭代器
python2中zip不是生成器,如果列表太大也会有内存问题
python3中zip相当于生成器
如果两个迭代器长度不通过,会表现出奇怪的行为。
遍历到长度最小的迭代器。
itertools内置模块中的zip_longest函数可以平行的遍历多个迭代器,不用在乎长度是否相等
12、不要在for和while循环后面写else块
13、合理利用try\except\else\finally结构中的每个代码块
else块可以用来缩减try中代码量,把没有发生异常时索要执行的语句与try/except代码块分隔开
第二章 函数
14、尽量用异常来表示特殊情况,而不要返回None
15、了解如何在闭包里使用外围作用域中的变量
在表达式中引用变量时,python解释器将按如下顺序遍历各作用域,以解析该引用
当前函数作用域
任何外围作用域 (闭包外围函数的作用域)
包含当前代码的那个模块的作用域(全局作用域)
内置作用域(包含len和str等函数的那个作用域)
如果都没有名称相符的变量,抛出NameError异常。
闭包内如果使用了外围的变量,相当于在闭包内定义了一个新的变量,闭包外该变量不改变。
如何获取闭包内的数据
使用nonlocal,python2不支持,可以使用列表替代
16、考虑用生成器来改写直接返回列表的函数
17、在参数上面迭代时,要多加小心
把__iter__方法实现为生成器,即可定义自己的容器类型
判断某个值是迭代器还是容器,可以拿该值作为参数,两次调用iter函数,结果相同则是迭代器。调用next函数,可令该迭代器前进一步。
18、用数量可变的位置参数减少视觉杂讯
只有当我们能够确定输入的参数个数较少时,才应该令函数接受*arg的变长参数
19、用关键字参数来表达可选的行为。
位置参数必须在关键字参数之前
20、用None和文档字符串来描述具有动态默认值的参数
参数默认值时可变类型时,如list或者dict,传入函数作为参数时默认值应该为None
21、用只能以关键字形式指定的参数类确保代码清晰
第三章 类与继承(需要多次复读深入理解)
22、尽量用辅助类来维护程序的状态,而不要用字典和元组
不要使用包含其他字典的字典,也不要使用过长的元组
如果容器中包含简单而又不可变的数据,那么可以使用namedtuple。如果有需要,再需改为完整的类
保存内部状态的字段如果变得比较复杂,应该把这些代码拆解为多个辅助类。
23、简单的接口应该接受函数,而不是类的实例
对于连接各种python组件的简单接口来说,通常应该给其直接传入函数,而不是先定义某个类,再传入该类的实例。
24、以@classmethod的形式的多态去通用地构建对象
在python程序中,每个类只能有一个构造器,__init__方法
通过@classmethod机制,可以用一种与构造器相仿的凡是来构造类的对象
通过类方法多态机制,我们能够以更加通用的方式来构建并拼接具体的子类
25、用super初始化父类
python2.2增加了内置的super函数,并且定义了方法解析顺序(method resolution order MRO)。
python2
super(ClassName, self).-_init__()
python3
super(__class, self).__init__()
26、只在使用Mix-in组件制作工具类时进行多重继承(需要多次复读深入理解)
27、多用public属性,少用private属性
python编译器无法严格保证private字段的私密性
不要盲目将属性设为private
应该多用protected属性
只有当子类不受自己控制时,才考虑用private避免名称冲突
28、继承collections.abc以实现自定义的容器类型
如果要定制的子类比较简单,可以直接从python容器类型(list和dict)继承
想正确实现自定义的容器类型,可能需要编写大量的特殊方法
编写自制的容器类型时,可以从collections.abc模块的抽象基类中继承,那些基类可以确保我们的子类具备适当的接口及行为。
第四章 元类及属性
29、用纯属性取代get和set方法
编写新类时,应该用简单的public属性来定义其接口,而不要手工实现set和get方法
如果访问对象的某个属性时,需要表现出特殊的行为,用@proerty来定义
@property方法需要执行的迅速一些,缓慢或复杂的工作,应该放在普通的方法里面。
30、考虑用@property来代替属性重构(多次复读深入理解)
@property可以为现有的实例属性添加新的功能
可以用@property逐步完善数据模型
如果@property使用太过频繁,应该考虑重构该类
31、用描述符来改写需要复用的@property方法
如果想复用@property方法及其验证机制,可以自己定义描述符类
WeakKeyDictionary可以保证描述符类不会泄露内存。
通过描述符协议来实现属性的获取和设置操作时,不要纠结与__getattribute__的方法具体运作细节。
32、用__getattr__、__getattribute__和__setattr__实现按需生成的属性(多次复读深入理解)
33、用元类来验证子类
通过元类,我们可以在生成子类对象之前,先验证子类的定义是否合乎规范
python2和python3指定元类的语法略有不同
python系统把子类的整个class语句体处理完毕之后,会调用其元类的__new__方法
34、用元类类注册子类
35、用元类来注解类的属性
第五章 并发与并行
36、用subprocess模块来管理子进程
subprocess.Popen()
可以给communicate方法传入timeout参数,避免子进程死锁或失去相应
37、可以用线程来执行阻塞式I/O,但不要用它做平行计算
标准的Python实现叫做CPython,分两步运行Python程序。首先把文本形式的源代码解析并编译成字节码。然后,用一种基于栈的解释器来运行这份字节码。执行Python程序时,字节码解释器必须保持协调一致的状态。Python采用GIL(全局解释器所)机制来确保这种协调性。
GIL实际上就是一把互斥锁,用以防止CPython受到占先式多进程切换操作的干扰。所谓占先式多线程切换,是指某个线程可以通过打断另外一个线程的方式,来获取程序控制权。如果执行时机不恰当,会破坏解释器的状态。
38、在线程中使用Lock来防止数据竞争(threading的Lock)
为了保证所有的线程都能够公平的执行,Python解释器会给每个线程分配大致相等的处理器时间。为了达成这样的分配策略,Python解释器可能当某个线程正在执行的时候,将其暂停,然后使另一个线程继续往下执行。问题在于,开发者无法准确获知Python系统何时暂停这些线程。所以可能导致数据竞争。
39、用Queue来协调各线程之间的工作(类似golang中的channel)
40、考虑用写成来并发地运行多个函数
为了确保数据安全,需要做额外的工作(Lock互斥锁)
线程占用大量内存,开销大
41、考虑用concurrent.futures来实现真正的平行计算(concurrent.futures中的multiprocessing模块)
该模块以子进程的形式,平行地运行多个解释器,从而令Python程序能够利用多核心CPU来提升执行速度。由于子进程与主解释器相分离,所以它们的全局解释器锁也是互相独立的。每个子进程都可以完整地利用一个CPU内核。并且都与主进程之间有着联系。
第六章 内置模块
42、用functools.wraps定义函数装饰器
普通的装饰器返回的不是原函数,可能导致问题。使用wraps装饰器会把内部函数相关的重要元数据全部复制到外围函数。
43、考虑用contextlib和with语句来改写可复用的try/finally代码
with语句会自动关闭,contextlib中的contextmanager装饰器,可以让开发者自己编写的对象和函数支持with语句。如果按标准方式来做,需要定义新的类,并提供名为__enter__,__exit__的特殊方法。
情境管理器可以用过yield语句向with语句返回一个值,此值会赋给由as关键字所指定的变量。该机制阐明了这个特殊情境的编写动机,并令with块中的语句能够直接访问这个目标变量。
44、用copyreg实现可靠的pickle操作
内置的pickle模块能够将Python对象序列化为字节流,也能将字节反序列化为Python对象。但是采用的是不安全的格式。json模块采用的是安全的格式。
45、应该用datetime模块来处理本地时间,而不是用time模块
46、使用内置算法和数据结构
双向队列 collections模块的deque类
有序字典 collections模块的OrderedDict类
带有默认值的字典 collections的defaultdict类
堆队列 heap
二分查找 bisect模块中的bisect_left函数
与迭代器有关的工具 itertools模块中提供大量的函数
47、在重视精确度的场合,应该使用decimal
48、学会安装由Python开发者社区所构建的模块
第七章 协作开发
49、为每个函数、类和模块编写文档字符串
50、用包来安排模块,并提供稳固的API
51、为自编的模块定义根异常,以便将调用者与API相隔离
52、用且当的方式打破循环依赖关系
调整引入顺序 将import移动到app底部
先引入,再配置,最后运行 将import移动到函数中
动态引入 上述两种的结合
如果两个模块必须相互调用对方,才能完成引入操作,就会出现循环依赖现象,可能导致程序启动崩溃
打破循环依赖关系的最佳方案,是把导致两个模块相互依赖的那部分代码,重构为单独的模块,放到依赖树的底部。
打破循环依赖关系的最简方案,是执行动态的模块引入操作,这样既可以缩减重构精力,也可以尽量降低代码复杂度。
53、用虚拟环境隔离项目,并重建其依赖关系(3.4及以上用pyenv,以下用virtualenv)
第八章 部署
54、考虑用模块级别的代码来配置不同的部署环境
55、通过repr字符串来输出调试信息
56、用unittest来测试全部代码
57、考虑用pdb实现交互调试
58、先分析性能,然后再优化(cProfile)
59、用tracemalloc来撞我内存的使用及泄露情况