1、确认python的版本
2、遵循PEP8风格指南
3、了解bytes,str与unicode的区别
4、用辅助函数取代复杂的表达式
- 容易过度使用python语法特性,写出特别复杂并且难以理解的复杂表达式
- 把复杂表达式移入辅助函数中
5、了解切割序列的办法
- 不要写多余的代码,如start索引为0或者end为序列长度时,应省略。
- 对list赋值的时候,如果使用切片操作则会把原列表中处于相关范围的值替换成新值。
6、单次切片操作时,不要同时指定start,end和stride
7、用列表推导代替 map 和 filter
8、不要使用含有两个以上表达式的列表推导
9、用生成器表达式来改写数据量比较大的列表推导
10、使用 enumerate 取代 range
11、用zip函数同时遍历两个函数
12、不要在for和while循环后面写else语句
13、合理利用try/except/else/finally结构中的每个代码块
14、尽量用异常表示特殊情况,而不要返回None
- 返回值拆成二元组里面,首个元素表示是否成功,接下来的元素是真正的运算结果。
- 不返回None,而是把异常抛给上一级,使得调用者必须应对它。
15、了解如何在闭包里使用外围作用域中的变量
- 可以在闭包内用nonlocal修饰某个名称,使该闭包能够修改外围作用域中的同名变量。
16、用生成器来改写直接返回列表的函数
17、在参数上面迭代,要多加小心
- 迭代器只能产生一轮结果
- 在 iter()方法中,应创建返回一个新的迭代器
18、用数量可变的位置参数减少视觉杂讯
- 变长参数在传给函数时,总要先转化成元组,如果用带有*操作符的生成器为参数,则会把生成器迭代一遍,可能会消耗大量内存。
- 如果要给函数添加新的位置参数,就必须修改原来调用该函数的旧代码。
19、用关键字参数表达可选的行为
- 位置参数必须出现在关键字参数之前
- 每个参数只能指定一次
- 好处
- 读代码的人更容易理解
- 可以在函数中提供默认值
- 提供可扩充函数参数的有效方式,使得扩充后的函数依然能与原有的调用代码兼容。
- 可选的关键字参数,总是应该以关键字指定,不应该以未知参数的形式来指定。
20、用None和文档字符串来描述具有动态默认值的参数
- 参数默认值在模块加载的时候计算的,代码的模块一旦加载进来,参数的默认值就固定不变了,典型错误使用如下
def log(message, when=datetime.now()):
print('%s: %s' % (when, message))
- 如果参数的实际默认值是可变的,一定要用None作为形式上的默认值。
21、用只能以关键字形式指定的参数来确保代码明晰
22、尽量用辅助类来维护程序的状态,而不要用字典或元组
- namedtuple 的局限
- 无法指定各参数的默认值
- 实例的各项属性,依然可以通过下标及迭代来访问,没办法完全控制。
23、简单的接口应该接受函数,而不是类的实例
- 如果要用函数来保存状态,那就应该定义新的类,并令其实现call方法,而不要定义带状态的闭包。
24、以@classmethod形式的多态去通用的构建对象
25、用 super 初始化父类
26、只有使用Mix-in组件制作工具类时进行多重继承
- 能用mix-in组件实现的效果,就不要用多继承来做
- 各功能实现为可插拔的mix-in组件,令相关的类继承自己需要的哪些组件,即可定制该类示例所应具备的行为
- 简单的行为封装到mix-in组件里,然后就可以用多个mix-in组合出复杂的行为了。
27、多用public属性,少用private属性
- python编译器无法保证private字段的私密性
- 不要盲目的将属性设置为private属性
- 应该更多的用protected属性
- 当子类不受自己控制时,才考虑用private属性避免名称冲突。
28、继承collections.abc以实现自定义容器类型
- 如果要定制的子类比较简单,那就可以直接从python的容器类型(如list, dict)中继承
- 想正确实现自定义的容器类型,可能需要编写大量的特殊方法
- 可以从collections.abc模块的抽象基类中继承,那些基类能确保我们的子类具备适当的接口及行为
29、用纯属性取代set和get方法
- 编写新类时,应该用简单的public属性定义其接口,而不要手工实现set和get方法
- 如果访问对象的某个属性时,需要表现出特殊的行为,就用@property来定义
- @property应该遵循最小惊讶原则,而不应该产生奇怪的副作用
- @property需要执行的迅速一些,缓慢或复杂的工作,应该放到普通的方法里面。
class A:
def __init__(self, name):
self.name = name
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
30、考虑使用@property来代替属性重构
31、用描述符来改写需要复用的@property方法
# 实现
__get__(self, instance, cls),
__set__(self, instance, value)
方法即实现了描述符协议。其中实现了__set__方法的描述器为覆盖型描述器,覆盖型描述符同样会覆盖对实例属性的覆盖操作
不管描述符是不是覆盖型描述符,对类属性赋值都能覆盖描述符
32、用getattribute, setattr实现按需生成属性
33、用元类来验证子类
class Meta(type):
def __new__(meta, name, bases, class_dict):
print(meta, name, bases, class_dict)
return type.__new__(meta, name, bases, class_dict)
# py3
class MyClass(object, metaclass=Meta):
pass
# py2
class MyClass(object):
__metaclass__ = Meta
"""
1、通过元类可以在生成子类对象之前,先验证子类的定义是否是合乎规范
2、Python系统把子类的整个class语句处理完毕后,就会调用其元类的__new__方法
"""
34、用元类来注册子类
1、构建模块化的Python程序时,类的注册是一种很有用的模式
2、开发者每次从基类中继承子类时,基类的元类都可以自动运行注册代码
3、通过元类实现类的注册,确保子类不会遗漏,避免后续的错误
35、用元类注解类的属性(结合修饰符,很牛逼)
class Field:
def __init__(self):
self.name = None
self.internal_name = None
def __get__(self, instance, instance_type):
return getattr(instance, self.internal_name, '')
def __set__(self, instance, value):
if value != 'py':
raise ValueError('must be py')
setattr(instance, self.internal_name, value)
class Meta(type):
def __new__(meta, name, bases, class_dict):
for key, value in class_dict.items():
if isinstance(value, Field):
value.name = key
value.internal_name = '_' + key
cls = type.__new__(meta, name, bases, class_dict)
return cls
class Person(metaclass=Meta):
name = Field()
gender = Field()
age = Field()
"""
1、借助元类,可以在某个元类完全定义好之前,率先修改类的属性
2、描述符与元类有效的组合起来,便于对某种行为作出修饰,或在程序运行时探查相关信息
3、把元类和描述符相结合,可以在不使用weakref模块的前提下,避免内存泄露
"""
36、用subprocess模块管理子进程
# 不建议使用该模块
37、使用线程来执行阻塞式I/O,但不要用它做平行计算
1、受到全局解释器的限制,多条Python线程不能再多个CPU核心上面平行的执行字节码
2、Python多线程可以轻松的模拟出同一时刻执行多任务的效果
3、平行的执行多个系统的调用,使得程序能够在执行阻塞式I/O操作的同时,执行一些运算操作
38、在线程中使用Lock来防止数据竞争
1、Python确实有全局解释器,但是在编写自己的程序时,依然要设法防止多个线程争用同一份数据。
2、在不加锁的前提下,允许多条线程修改同一个对象,程序的数据结构可能被破坏
3、Python内置的threading模块中,有个名叫Lock的类,他用标准的方式实现了互斥锁
39、使用Queue协调各线程之间的工作
40、考虑用协程来并发的运行多个函数
41、考虑使用concurrent.futures来实现真正的平行计算
1、引发cpu性能瓶颈的那部分代码,用c语言扩展来写,可在尽量发挥Python特性的前提下,有效的提升程序的执行速度,但是工作量比较大,而且很有可能会引入bug
2、multiprocessing模块提供了强大的工具。对于某些类型的任务来说,开发者只需要编写少量代码,即可实现平行计算
3、若想利用强大的multiprocess模块,最恰当的做法就是通过内置的concurrent.futures模块及其ProcessPoolExecutor类来使用它
42、用functions.wraps定义函数修饰器
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
# ...
return wrapper
43、考虑以contextlib和with语句来改写可复用的try/finally代码
1、可以通过 with 语句来改写 try/finally 块中的逻辑,以便提升复用程度,使代码更1加整洁
2、内置的 contextlib 模块提供了名为 contextmanager 的装饰器,开发者只需要用它来装饰自己的函数(函数中需要增加 yield 语句),即可令该函数支持 with 语句
3、情景管理器可以通过 yield 语句向 with 语句返回一个值,此值会赋给由 as 关键字所指定的变量。
44、用copyreg实现可靠地pickle操作
45、应该用datetime模块来处理本地时间,而不是time模块
1、不要用time模块在不同时区之间进行转换
46、使用内置算法和数据结构
# 1、双向队列 collections.deque FIFO
>>> d = deque()
>>> d.append(1)
>>> d.append(2)
>>> d.pop() # 2
# 2、有序字典 collections.OrderDict
# 按照键的插入顺序,来保留键值对在字典中的次序
# 3、带有默认值的字典 collections.defaultdict
>>> dct = defaultdict(int)
>>> dct['score'] += 1
# 4、堆队列 heapq
>>> a = []
>>> heap.heappush(a, 5)
>>> heap.heappush(a, 3)
>>> heap.heappush(a, 7)
>>> heappop(a) # 3
>>> heappop(a) # 5
>>> heappop(a) # 7
# 5、二分查找
>>> import bisect
>>> x = list(range(10**8))
>>> i = x.index(99123400)
>>> # 二分法
>>> i = bisect.bisect_left(x, 99123400)
# 6、与迭代器有关工具
>>> # itertools
>>> # chain: 将多个迭代器顺序连成一个迭代器
>>> # cycle
>>> # tee
>>> # zip_longest
>>> # islice
>>> # takewhile
>>> # dropwhile
>>> # filterfalse
>>> # product
>>> # permutations
>>> # combination
47、重视精度的场合应该使用 decimal
Decimal 类非常适合用在那种对精度要求很高,且对摄入行为要求很严的场合,例如,设计货币基金计算的场合
48、学会安装由Python开发者社区所构建的模块
49、为每个函数,类和模块编写文档和字符串
50、用包来安排模块,并提供稳固的API
1、python 包是一种含有其他模块的模块。我们可以把包代码划分成各自独立且互不冲突的名称空间,使得每块代码都能具备独立的绝对模块名称
2、把外接可见的名称列在名为 __init__ 的特殊属性里,即可为包提供一套明确的API
3、如果想隐藏某个包的内部实现,可以在 __init__ 文件中,只把外界可见的名称引入进来,或给仅限内部使用的那些名称添加下划线前缀
51、为自己的模块定义根异常以便调用者与 API 隔离
1、为模块定义根异常,可以把 API 的调用者与模块的API相隔离
2、调用者使用 API 时,通过捕获根异常,来发现调用代码中隐藏的bug
3、调用者可以通过捕获 Python 的 Exception 基类,来帮助模块的研发者找寻 API 实现代码中的 bug
52、适当的方式打破循环依赖关系
53、用虚拟环境隔离项目,并重建其依赖关系
54、考虑模块级别的代码来配置不同的部署环境
55、通过 repr 字符串来输出调试信息
56、用 unittest 测试全部代码
57、考虑用pdb实现交互调试
58、先分析性能,然后再优化
59、用 tracemalloc 来掌握内存的使用及泄露情况
网友评论