本文接[上篇]()。python
第29条:用纯属性取代get和set方法算法
(1)编写新类时,应该用简单的public属性来定义其接口,而不要手工实现set和get方法json
(2)若是访问对象的某个属性,须要表现出特殊的行为,那就用@property来定义这种行为网络
好比下面的示例:成绩必须在0-100范围内数据结构
class Homework:
def __init__(self):
self.__grade = 0多线程
@property
def grade(self):
return self.__grade并发
@grade.setter
def grade(self,value):
if not (0<=value<=100):
raise ValueError('Grade must be between 0 and 100')
self.__grade = value函数
(3)@property方法应该遵循最小惊讶原则,而不该该产生奇怪的反作用工具
(4)@property方法须要执行得迅速一些,缓慢或复杂的工做,应该放在普通的方法里面性能
(5)@property的最大缺点在于和属性相关的方法,只能在子类里面共享,而与之无关的其余类都没法复用同一份实现代码
第30条:考虑用@property来代替属性重构
做者的意思是:当咱们须要迁移属性时(也就是对属性的需求发生变化的时候),咱们只须要给本类添加新的功能,原来的那些调用代码都不须要改变,它在持续完善接口的过程当中是一种重要的缓冲方案
(1)@property能够为现有的实例属性添加新的功能
(2)能够用@properpy来逐步完善数据模型
(3)若是@property用的太过频繁,那就应该考虑完全重构该类并修改相关的调用代码
第31条:用描述符来改写须要复用的@property方法
首先对描述符进行说明,先看下面的例子:
class Grade:
def __init(self):
self.__value = 0
def __get__(self, instance, instance_type):
return self.__value
def __set__(self, instance, value):
if not (0 <= value <= 100):
raise ValueError('Grade must be between 0 and 100')
self.__value = value
class Exam:
math_grade = Grade()
chinese_grade = Grade()
science_grade = Grade()
if name == "__main__":
exam = Exam()
exam.math_grade = 99
exam1 = Exam()
exam1.math_grade = 75
print('exam.math_grade:',exam.math_grade, 'is wrong')
print('exam1.math_grade:',exam1.math_grade, 'is right')
输出:
会发如今两个Exam实例上面分别操做math_grade时,致使了错误的结果,出现这种状况的缘由是由于 该math_grade属性为Exam类的实例 ,为了解决这个问题,看下面的代码
class Grade:
def __init__(self):
self.__value = {}
def __get__(self, instance, instance_type):
if instance is None:
return self
return self.__value.get(instance,0)
def __set__(self, instance, value):
if not (0 <= value <= 100):
raise ValueError('Grade must be between 0 and 100')
self.__value[instance] = value
class Exam:
math_grade = Grade()
chinese_grade = Grade()
science_grade = Grade()
if name == "__main__":
exam = Exam()
exam.math_grade = 99
exam1 = Exam()
exam1.math_grade = 75
print('exam.math_grade:',exam.math_grade, 'is wrong')
print('exam1.math_grade:',exam1.math_grade, 'is right')
输出:
上面这种实现方式很简单,并且可以正常运做,但它仍然有个问题,那就是会泄露内存,在程序的生命期内,对于传给__set__方法的每一个Exam实例来讲,__values字典都会保存指向该实例的一份引用,者就致使实例的引用计数没法降为0,从而使垃圾收集器没法将其收回。使用python的内置weakref模块,可解决上述问题。
class Grade:
def __init(self):
self.__value = weakref.WeakKeyDictionary()
(1)若是想复用@property方法及其验证机制,那么能够本身定义描述符
(2)WeakKeyDictionary能够保证描述符类不会泄露内存
(3)经过描述符协议来实现属性的获取和设置操做时,不要纠结于__getattribute__的方法具体运做细节
第32条:用__getattr__、__getattribute__和__setattr__实现按需生成的属性
若是某个类定义了__getattr__,同时系统在该类对象的实例字典中又找不到待查询的属性,那么就会调用这个方法
惰性访问的概念:初次执行__getattr__的时候进行一些操做,把相关的属性加载进来,之后再访问该属性时,只需从现有的结果中获取便可
程序每次访问对象的属性时,Python系统都会调用__getattribute__,即便属性字典里面已经有了该属性,也以让会触发__getattribute__方法
(1)经过__getattr__和__setattr__,咱们能够用惰性的方式来加载并保存对象的属性
(2)要理解__getattr__和__getattribute__的区别:前者只会在待访问的属性缺失时触发,,然后者则会在每次访问属性时触发
(3)若是要在__getattribute__和__setattr__方法中访问实例属性,那么应该直接经过super()来作,以免无限递归
第33条:用元类来验证子类
元类最简单的一种用途,就是验证某个类定义的是否正确,构建复杂的类体系时,咱们可能须要确保类的风格协调一致,确保某些方法获得了覆写,或是确保类属性之间具有某些严格的关系。
下例判断类属性中是否含有name属性:
class Meta(type):
def __new__(meta,name,bases,class_dict):
print('class_dict:',class_dict)
if not class_dict.get('name',None): #判断类属性中是否含有name属性
raise AttributeError('must has name attribute')
return type.__new__(meta,name,bases,class_dict)
class A(metaclass=Meta):
def __init__(self):
self.chinese_grade = 90
self.math_grade = 99
if name == '__main__':
a = A()
输出:
(1)经过元类,咱们能够在生成子类对象以前,先验证子类的定义是否合乎规范
(2)python系统把子类的整个class语句体处理完毕以后,就会调用其元类的__new__方法
第34条:用元类来注册子类
元类还有一个用途就是在程序中自动注册类型,对于须要反向查找(reverse lookup)的场合,这种注册操做颇有用
看下面的例子:对对象进行序列化和反序列化
import json
register = {}
class Meta(type):
def __new__(meta,name,bases,attr_dic):
cls = type.__new__(meta,name,bases,attr_dic)
print('create class in Meta:', cls)
register[cls.__name__] = cls
return cls
class Serializable(metaclass=Meta):
def __init__(self,*args):
self.args = args
def serialize(self):
return json.dumps({'class':self.__class__.__name__, 'args':self.args})
def deserilize(self,json_data):
json_dict = json.loads(json_data)
classname = json_dict['class']
args = json_dict['args']
return registerclassname
class Point2D(Serializable):
def __init__(self,x,y):
super().__init__(x,y)
self.x = x
self.y = y
def add(self):
return self.x + self.y
if name == "__main__":
p = Point2D(2,5)
data = p.serialize()
print('serialize_data:',data)
new_point2d = p.deserilize(data)
print('new_point2d:',new_point2d)
print(new_point2d.add())
输出:
(1)经过元类来实现类的注册,能够确保全部子类就都不会泄露,从而避免后续的错误
第35条:用元类来注解类的属性
(1)借助元类,咱们能够在某个类彻底定义好以前,率先修改该类的属性
(2)描述符与元类可以有效的组合起来,以便对某种行为作出修饰,或在程序运行时探查相关信息
(3)若是把元类与描述符相结合,那就能够在不使用weakref模块的前提下避免内存泄漏
并发和并行的关键区别在于能不能提速,如果并行,则总任务的执行时间会减半,如果并发,那么即便能够看似平行的方式分别执行多条路径,依然不会使总任务的执行速度获得提高,用Python语言编写并发程序,是比较容易的,经过系统调用、子进程和C语言扩展等机制,也能够用Python平行地处理一些事务,可是,要想使并发式的python代码以真正平行的方式来运行,却至关困难。
第36条:用subprocess模块来管理子进程
在多年的发展过程当中,Python演化出了多种运行子进程的方式,其中包括popen、popen2和os.exec*等,然而,对于至今的Python来讲,最好且最简单的子进程管理模块,应该是内置的subprocess模块
第37条:能够用线程来执行阻塞式I/O,但不要用它作平行计算
(1)由于受全局解释锁(GIL)的限制,因此多条Python线程不能在多个CPU核心上面平行地执行字节码
(2)尽管受制于GIL,可是python的多线程功能依然颇有用,它能够轻松地模拟出同一时刻执行多项任务的效果
(3)经过python线程,咱们能够平行地执行多个系统调用,这使得程序可以在执行阻塞式I/O操做的同时,执行一些运算操做
第38条:在线程中使用Lock来防止数据竞争
class LockingCounter:
def __init__(self):
self.lock = threading.Lock()
self.count = 0
def increment(self, offset):
with self.lock:
self.count += offset
第39条:用Queue来协调各线程之间的工做
做者举了一个照片处理系统的例子:
需求:该系统从数码相机里面持续获取照片、调整其尺寸,并将其添加到网络相册中。
实现:使用三阶段的管线实现,须要4个自定义的deque消息队列,第一阶段获取新照片,第二阶段把下载好的照片传给缩放函数,第三阶段把缩放后的照片交给上传函数
问题:该程序虽然能够正常运行,可是每一个阶段的工做函数都会有差异,这使得前一阶段可能会拖慢后一阶段的进度,从而令整条管线迟滞,后一阶段会在其循环语句中,反复查询输入队列,以求获取新的任务,而任务却迟迟未到达,这将令后一阶段陷入饥饿,会白白浪费CPU时间,效率特低
内置的queue模块的Queue类能够解决上述问题,由于其get方法会持续阻塞,直到有新的数据加入
import threading
from queue import Queue
class ClosableQueue(Queue):
SENTINEL = object()
def close(self):
self.put(SENTINEL)
def __iter__(self):
while True:
item = self.get()
try:
if item is self.SENTINEL:
return
yield item
finally:
self.task_done()
class StoppabelWoker(threading.Thread):
def __init__(self,func,in_queue,out_queue):
self.func = func
self.in_queue = in_queue
self.out_queue = out_queue
def run(self):
for item in self.in_queue:
result = self.func(item)
self.out_queue.put(result)
(1)管线是一种优秀的任务处理方式,它能够把处理流程划分未若干个阶段,并使用多条python线程来同时执行这些任务
(2)构建并发式的管线时,要注意许多问题,其中包括:如何防止某个阶段陷入持续等待的状态之中,如何中止工做线程,以及如何防止内存膨胀等
(3)Queue类所提供的机制,能够cedilla解决上述问题,它具有阻塞式的队列操做,可以指定缓冲区的尺寸,并且还支持join方法,这使得开发者能够构建出健壮的管线
第40条:考虑用协程来并发地运行多个函数
(1)协程提供了一种有效的方式,令程序看上去好像可以同时运行大量函数
(2)对于生成器内的yield表达式来讲,外部代码经过send方法传给生成器的那个值就是该表达式所要具有的值
(3)协程是一种强大的工具,它能够把程序的核心逻辑,与程序同外部环境交互时所使用的代码相隔离
第41条:考虑用concurrent.futures来实现真正的平行计算
第42条:用functools.wrap定义函数修饰器
为了维护函数的接口,修饰以后的函数,必须保留原函数的某些标准Python属性,例如__name__和__module__,这个时候咱们须要使用functools.wraps来确保修饰后函数具有正确的行为
第43条:考虑以contextlib和with语句来改写可复用的try/finally代码
(1)能够用with语句来改写try/finally块中的逻辑,以提高复用程度,并使代码更加整洁
import threading
lock = threading.Lock()
lock.acquier()
try:
print("lock is held")
finally:
lock.release()
能够直接使用下面的语法:
import threading
lock = threading.Lock()
with lock:
print("lock is held")
(2)内置的contextlib模块提供了名叫为contextmanager的修饰器,开发者只须要用它来修饰本身的函数,便可令该函数支持with语句
from contextlib import contextmanager
@contextmanager
def file_open(path):
''' file open test'''
try:
fp = open(path,"wb")
yield fp
except OSError:
print("We had an error!")
finally:
print("Closing file")
fp.close()
if name == "__main__":
with file_open("contextlibtest.txt") as fp:
fp.write("Testing context managers".encode("utf-8"))
(3)情景管理器能够经过yield语句向with语句返回一个值,此值会赋给由as关键字所指定的变量
第44条:用copyreg实现可靠pickle操做
(1)内置的pickle模块,只适合用来彼此信任的程序之间,对相关对象执行序列化和反序列化操做
(2)若是用法比较复杂,那么pickle模块的功能可能就会出现问题,咱们能够用内置的copyreg模块和pickle结合起来使用,以便为旧数据添加缺失的属性值、进行类的版本管理、并给序列化以后的数据提供固定的引入路径
第45条:应该用datetime模块来处理本地时间,而不是time模块
(1)不要用time模块在不一样时区之间进行转换
(2)若是要在不一样时区之间,可靠地执行转换操做,那就应该把内置的datetime模块与开发者社区提供的pytz模块打起来使用
(3)开发者老是应该先把时间表示为UTC格式,而后对其执行各类转换操做,最后再把它转回本地时间
第46条:使用内置算法和数据结构
(1)双向队列 collections.deque
(2)有序字典 dollections.OrderDict
(3)带有默认值的有序字典 collections.defaultdict
(4)堆队列(优先级队列)heapq.heap
(5)二分查找 bisect模块中的bisect_left函数等提供了高效的二分折半搜索算法
(6)与迭代器有关的工具 itertools模块
第47条:在重视精度的场合,应该使用decimal
(1)decimal模块中的Decimal类默认提供28个小数位,以进行定点数字运算,还能够按照开发射所要求的精度及四舍五入
第48条:学会安装由Python开发者社区所构建的模块
第49条:为每一个函数、类和模块编写文档字符串
第50条:用包来安排模块,并提供稳固的API
(1)只要把__init__.py文件放入含有其余源文件的目录里,就能够将该目录定义为包,目录中的文件,都将成为包的子模块,该包的目录下面,也能够含有其余的包
(2)把外界可见的名称,列在名为__all__的特殊属性里,便可为包提供一套明确的API
第51条:为自编的模块定义根异常,以便调用者与API相隔离
意思就是单独用个模块提供各类异常API
第52条:用适当的方式打破循环依赖关系
(1)调整引入顺序
(2)先引入、再配置、最后运行
只在模块中给出函数、类和常量的定义,而不要在引入的时候真正去运行那些函数
(3)动态引入:在函数或方法内部使用import语句
第53条:用虚拟环境隔离项目,并重建其依赖关系
第54条:考虑用模块级别的代码来配置不一样的部署环境
(1)能够根据外部条件来决定模块的内容,例如,经过sys和os模块来查询宿主操做系统的特性,并以此来定义本模块中的相关结构
第55条:经过repr字符串来输出调试信息
第56条:经过unittest来测试所有代码
这个在后面会单独写篇博客对unittest单元测试模块进行详细说明
第57条:考虑用pdb实现交互调试
第58条:先分析性能,而后再优化
(1)优化python程序以前,必定要先分析其性能,由于python程序的性能瓶颈一般很难直接观察出来
(2)作性能分析时,应该使用cProfile模块,而不要使用profile模块,由于前者可以给出更为精确的性能分析数据
第59条:用tracemalloc来掌握内存的使用及泄露状况
在Python的默认实现中,也就是Cpython中,内存管理是经过引用计数来处理的,另外,Cpython还内置了循环检测器,使得垃圾回收机制可以把那些自我引用的对象清除掉
(1)使用内置的gc模块进行查询,列出垃圾收集器当前所知道的每一个对象,该方法至关笨拙
(2)python3.4提供了内置模块tracemalloc能够打印出Python系统在执行每个分配内存操做时所具有的完整堆栈信息
文章到这里就所有结束了,感谢您这么有耐心的阅读!
本文由博客群发一文多发等运营工具平台 OpenWrite 发布