Effective python

目录

第一章:用pythonic的方式思考

1:确认使用的python版本

python --version
python3 --version

2:遵循PEP8风格指南

《Python Enhancement Proposal #8》又叫PEP8,它是针对Python代码风格而编订的风格指南。链接

采用一致的代码风格来书写可以令代码更加易懂、更加易读;

3:了解bytes、str与Unicode的区别

python2有两种表示字符的类型:str、Unicode;str包含原始的8位值,Unicode包含Unicode字符。

python3有两种表示字符的类型:bytes、str;bytes包含原始的8位值,str包含Unicode字符。

在编写程序时,一定要把编码、解码的操作放在界面最外围来做,程序的核心应该使用Unicode字符类型。

注意:

  1. python2中,如果str只包含7位ASCII字符,此时Unicode和str似乎就成了同一种类型,可以使用+、等价、不等价来判断str和Unicode,也可以使用%s来格式化Unicode。
  2. python3中,内置open函数默认会以utf-8编码格式来操作文件,python2中默认是二进制格式。

4:用辅助函数来取代复杂的表达式

总结下来就是:

  • 开发者很容易过度运用python的语法特性,从而写出那种特别复杂又难以理解的单行表达式。
  • 请把复杂的表达式移到辅助函数中,如果要反复使用相同的逻辑,那更应该这么做。
  • 使用if/else表达式,要比用or、and这样的bool操作符写成的表达式更加清晰。

5:了解切割序列的办法

  • 不要写多余的代码:当start索引为0,或者end索引为序列长度时,应该将其省略。
  • 切片操作不会计较start与end索引是否越界,这使得我们很容易就能从序列的前端或者后端开始,对其进行范围固定的切片操作。
  • 对list赋值的时候,如果使用切片操作,就会把原列表中处在相关范围内的值替换成新值,即便它们的长度不同也依然可以替换。

6:在单次切片操作内,不要同时指定start、end和stride

  • 既有start和end,又有stride的切割操作,可能会令人费解。
  • 尽量使用stride为正数,且不带start或end索引的切割操作。尽量避免使用负数做stride。
  • 在同一个切片操作内,不要同时使用start、end和stride。

7:用列表推导来取代map和filter

  • 列表推导要比内置的map和filter函数清晰,因为它无需额外编写lambda表达式。
  • 列表推导可以跳过输入列表中的某些元素,如果改用map来做,那就必须辅以filter方能实现。
  • 字典和集合也支持推导表达式。

8:不要使用包含两个以上表达式的列表推导

  • 超过两个表达式的列表推导是很难理解的,应该尽量避免。

9:用生成器表达式来改写数据量较大的列表推导

  • 当输入的数据量较大时,列表推导可能会因为占用太多内存而出问题。
  • 由生成器表达式返回的的迭代器,可以逐次产生输出值,从而避免了内存占用高的问题。

10:尽量用enumerate取代range

  • enumerate函数提供了一种简洁的写法,可以在遍历迭代器时获知每个元素的索引。
  • 尽量用enumerate来改写那种将range与下标访问相结合的序列遍历代码。
  • 可以给enumerate提供第二个参数,以指定开始计数时所用的值(默认为0)。

11:用zip函数同时遍历两个迭代器

12:不要在for和while循环后面写else块

  • 只有当整个循环主体都没有遇到break语句时,循环后面的else块才会执行。
  • 不要在循环后面使用else块,因为这种写法既不直观,又容易引人误解。

13:合理利用try/except/else/finally结构中的每个代码块

  • 无论try块是否发生异常,都可利用try/finally复合语句中的finally块来执行清理工作。
  • else块可以用来缩减try块中的代码量,并把没有发生异常时所要执行的语句与try/except代码块隔开。
  • 顺利执行try块后,若想使某些操作能在finally块的清理代码之前执行,则可以将这些操作写到else块中。

第二章:函数

14:尽量用异常来表示特殊情况,而不要返回None

  • 用None这个返回值来表示特殊意义的函数,很容易使调用者犯错,因为None和0以及空字符串之类的值,在条件表达式中都会评估为False。
  • 函数在遇到特殊情况时应该抛出异常,而不是返回None。

15:了解如何在闭包中使用外围作用域中的变量

去了解global和nonlocal的用法(nonlocal是python3的语法)

  • 对于定义在某作用域内的闭包来说,它可以引用这些作用域中的变量,但是不能修改不可变变量。
  • 使用默认方式对闭包内的变量赋值不会影响外围作用域中的变量值。
  • python3可以使用nonlocal修饰某个名称,使该闭包能够修改外围作用域中的同名变量。
  • python2可以使用可变值来实现nonlocal相仿的机制(放入列表中)。

16:考虑用生成器来改写直接返回表聊得函数。

  • 当数据量大时,使用迭代器替换直接返回列表可以避免消耗太大的内存。

17:在参数上面迭代时,要多加小心

  • 函数在参数上多次迭代要小心,如果参数是迭代器,那么可能会导致奇怪的行为并错失某些值。
  • python的迭代器协议,描述了容器和迭代器应该如何与iter和next内置函数、for循环及相关表达式配合。
  • __iter__实现为生成器,即可定义自己的容器。
  • 判断某个值是不是迭代器,可以两次调用iter方法,如果两次值相同,就是迭代器,可以调用next方法使迭代器前进一步。

for x in foo会先调用iter(foo)返回一个迭代器对象,iter会调用foo.__iter__方法。

18:用数量可变的位置参数减少视觉杂讯

  • def定义函数时使用*arg,可令函数接受数量可变的位置参数。

19:用关键字参数表达可选的行为

  • 函数参数可以按位置或关键字来指定。
  • 只使用位置参数可能会使函数调用不明晰。
  • 给函数添加新行为时,可以使用带默认值的关键字参数,以保持代码兼容。
  • 可选参数应该总是以关键字参数形式出现,而不是可变位置参数。

20:用None和文档字符串来描述具有动态默认值的参数

  • 参数的默认值只会在加载模块时计算一次,对于{},[]等动态的值,可能会出现奇怪的问题。
  • 应该把默认值设置为None,在函数中判断为None的时候再设置为需要的值。

21:用只能以关键字形式指定的参数来确保代码明晰

  • 关键字参数能够使函数调用的意图更加明确。

第三章:类与继承

22:尽量用辅助类来维护程序的状态,不要用元组或者字典

  • 不要使用包含其他字典的字段,也不要使用过长的元组。
  • 如果容器包含简单不可变的数据,可以考虑使用nametuple,但是nametuple也有自己的局限。
  • 保存内部状态的字典如果变得比较复杂,就应该把这些代码拆解为多个辅助类。

23:简单的接口应该接收函数,而不是类的实例

  • python中的函数和对象一样,都是都可以当做参数传递给函数的。
  • 通过名为__call__的特殊方法,可以使类的实例能够像普通的python函数那样得到调用。
  • 如果要用函数保存状态,那就定义新的类,并实现__call__方法,而不要定义带状态的闭包。

24:以@classmethod形式的多态去通用的构建对象

  • 在python中,每个类只能有一个构造器,也就是__init__方法。
  • 通过@classmethod机制,可以用一种与构造器相仿的方式来构造类的对象。
  • 通过类方法多态机制,能够以更加通用的方式构建并拼接具体的子类。

25:用super初始化父类

  • python采用标准的方法解析顺序来解决超类初始化次序及钻石继承问题,可以用mro方法来查看这个顺序,调用顺序和这个顺序相反。
  • 总是应该使用内置的super函数来初始化父类。

26:只在使用Mix-in组件制作工具时进行多重继承

mix-in是一种小型的类,它只定义了其他类可能需要提供的一套附加方法,而不定义自己的实例属性,也不要求使用者调用自己的__init__方法。

  • 能用mix-in组件实现的效果,就不要用多重继承来做。
  • 将各个功能实现为可插拔的mix-in组件,使需要该功能的类继承mix-in组件即可具备该行为。

27:多用public属性,少用private属性

  • python编译器无法严格保证private字段的私密性。
  • 不要盲目的将属性设置为private。
  • 应该多用protected,即以一个下划线为前缀的属性名。

28:继承collections.abc以实现自定义的容器类型

collections.abc模块定义了一系列抽象基类,它们提供了每一种容器类型所应该具备的常用方法。从这样的基类中继承之类之后,如果忘记实现某个方法,那么collections.abc模块就会指出这个错误。

  • 编写自定义的容器类型时,可以从collections.abc模块的抽象基类中继承,那些基类能够确保我们的之类拥有适当的接口和行为。

第四章:元类及属性

29:用纯属性取代get和set方法

  • 编写新类时,应该用最简单的public属性来定义其接口。
  • 如果要控制某个属性的访问时,应该是用@property。
  • @property修饰的方法应该尽量简单。

30:考虑用@property来代替属性重构

  • @property可以为现有的实例属性添加新的功能
  • 可以用@property来逐步完善数据模型

31:用描述符来改写需要复用的@property方法

  • 如果想复用@property方法及其验证机制,可以自己定义描述符类。
  • WeakKeyDictionary可以保证描述符类不会内存泄露。

32:用__getattr____get_attribute____setattr__实现按需生成的属性。

如果某个类定义了__getattr__,同时系统在该类对象的实例字典中又找不到待查询的属性,系统就会调用该方法。

  • 通过__setattr____getattr__,我们可以用惰性的方式来加载并保存对象的属性。
  • 要理解__getattr____getattribute__的区别:前者只会在属性不存在时才会被调用,而后者则会在每次访问属性时触发。
  • 如果要在__getattribute____setattr__方法中访问实例属性,应该直接通过super()来做,避免无限递归。

33:用元类来验证子类

元类最简单的一种用途,就是验证某个类定义的是否正确。构建复杂的类体系时,我们可能需要确保类的风格协调一致、确保某些方法得到了覆写,或是确保类属性之间具备某些严格的关系。

  • 通过元类,我们可以在生成子类对象之前,先验证子类的定义是否合乎规范。

34:用元类来注册子类

  • 在构建模块化的python程序时,类的注册是一种很有用的模式。
  • 通过元类来实现类的注册,可以确保所有的子类都不会被遗漏。

35:用元类来注解类的属性

  • 借助元类,我们可以在某个类完全定义好之前,率先修改该类的属性。

第五章:并发及并行

36:用subprocess模块管理子进程

  • 可以用subprocess模块运行子进程,并管理其输入流与输出流。

37:可以用线程来执行阻塞式io,但不要用它做平行计算

  • python的多线程适合io密集型应用。

38:在线程中使用lock来防止数据竞争

  • python虽然存在GIL,但是多线程依然需要加锁保护竞争资源。

39:用Queue来协调各线程之间的工作

40:考虑用协程来并发地运行多个函数。

虽然可以用开发多个线程来实现并发,但是线程有以下缺点:

  1. 为了确保数据安全,必须要使用同步工具。
  2. 线程需要占用大量内存,大约8M。
  3. 线程启动时的开销比较大。
  • 协程提供了一种有效的方式,令程序看上去好像能够同时运行大量函数。

41:考虑用concurrent.futures来实现真正的平行计算

  • 利用ProcessPoolExecutor可以突破GIL的限制,利用多核的优势,但是这种方案的限制是仅适用于需要在进程间传递少量数据的场景。

第六章:内置模块

42:用functools.wraps定义函数装饰器

  • python为装饰器提供了专门的语法,它使得程序在运行的时候,能够用一个函数来修改另一个函数。
  • 内置的functools模块提供了名为wraps的装饰器,开发者在定义自己的装饰器时应该用wraps对其做一些处理,避免一些问题。

43:考虑以contextlib和with语句来改写可复用的try/finally代码

  • 内置模块contextlib提供了contextmanager的装饰器,方便开发者用来实现自己的上下文管理。

44:用copyreg实现可靠的pickle操作

  • 内置的pickle模块,只适合用来在彼此信任的程序之间,对相关对象执行序列化和反序列化。
  • 把内置的copyreg和pickle结合起来使用,以便给旧数据添加缺失的属性值、进行类的版本管理。

45:应该用datetime模块来处理本地时间,而不是用time

time模块的实现依赖操作系统而运行,应该仅仅用来在UTC(时间戳)和当地时区时间之间转换。

  • 不要用time模块在不同时区之间进行转换。
  • 如果要在不同时区之间,可靠地执行转换操作,那就应该使用datetime和pytz。
  • 开发者应该总是把时间转换为UTC格式,然后做各种操作,最终转换为当地时间。

46:使用内置算法与数据结构

双向队列:deque

有序字典:OrderedDict()

带有默认值的字典:defaultdict()

堆队列:heap

与迭代有关的工具:itertools

47:在重视精确度的场合,应该使用decimal

48:学会安装有python开发者社区所构建的模块

第七章:协作开发

49:为每个函数、类和模块编写文档字符串

50:用包来安排模块,并提供稳固的API

51:为自编的模块定义根异常,以便调用者与API相隔离

52:用适当的方式打破循环依赖关系

53:用虚拟环境隔离项目,并重建其依赖关系

第八章:部署

54:考虑用模块级别的代码来配置不同的部署环境

55:通过repr字符串来输出调试信息

  • repr可以展示要输出的对象的类型。

56:用unittest来测试全部代码

57:考虑用pdb实现交互调试

import pdb
pdb.settrace()

58:先分析性能再优化

  • Profile和CProfile工具可以用来分析性能。

59:用tracemalloc来掌握内存的使用及泄露情况

  • python内置的gc
  • python3.4支持的tracemalloc
  • python2可以使用开源包:heapy
原文地址:https://www.cnblogs.com/lit10050528/p/13581952.html