EffectivePython之编写⾼质量Python代码的59个有效⽅法
  这个周末断断续续的阅读完了《Effective Python之编写⾼质量Python代码的59个有效⽅法》,感觉还不错,具有很⼤的指导价
值。下⾯将以最简单的⽅式记录这59条建议,并在⼤部分建议后⾯加上了说明和⽰例,⽂章篇幅⼤,请您提前备好⽠⼦和啤酒!
1. ⽤Pythonic⽅式思考
第⼀条:确认⾃⼰使⽤的Python版本
(1)有两个版本的python处于活跃状态,python2和python3
(2)有很多流⾏的Python运⾏时环境,CPython、Jython、IronPython以及PyPy等
(3)在开发项⽬时,应该优先考虑Python3
第⼆条:遵循PEP风格指南
(1)当编写Python代码时,总是应该遵循PEP8风格指南
(2)当⼴⼤Python开发者采⽤同⼀套代码风格,可以使项⽬更利于多⼈协作
(3)采⽤⼀致的风格来编写代码,可以令后续的修改⼯作变得更为容易
第三条:了解bytes、str、与unicode的区别
(1)python2提供str和unicode,python3中修改为bytes和str,bytes为原始的8位值,str包含unicode字符,在进⾏编码转换时使⽤decode和encode⽅法
(2)从⽂件中读取⼆进制数据,或向其中写⼊⼆进制数据时,总应该以‘rb’或‘wb’等⼆进制模式来开启⽂件
第四条:⽤辅助函数来取代复杂的表达式
(1)开发者很容易过度运⽤Python的语法特性,从⽽写出那种特别复杂并且难以理解的单⾏表达式
(2)请把复杂的表达式移⼊辅助函数中,如果要反复使⽤相同的逻辑,那更应该这么做
第五条:了解切割序列的⽅法
(1)不要写多余的代码:当start索引为0,或end索引为序列长度时,应将其省略a[:]
(2)切⽚操作不会计较start与end索引是否越界,者使得我们很容易就能从序列的前端或后端开始,对其进⾏范围固定的切⽚操
作,a[:20]或a[-20:]
(3)对list赋值的时候,如果使⽤切⽚操作,就会把原列表中处在相关范围内的值替换成新值,即便它们的长度不同也依然可以替换
第六条:在单词切⽚操作内,不要同时指定start、end和step
(1)这条的⽬的主要是怕代码难以阅读,作者建议将其拆解为两条赋值语句,⼀条做范围切割,另⼀条做步进切割
(2)注意:使⽤[::-1]时会出现不符合预期的错误,看下⾯的例⼦
msg = '谢谢'
print('msg:',msg)
x = de('utf-8')
y = x.decode('utf-8')
print('y:',y)
z=x[::-1].decode('utf-8')
print('z:', z)
  输出:
第七条:⽤列表推导式来取代map和filter
(1)列表推导要⽐内置的map和filter函数清晰,因为它⽆需额外编写lambda表达式
(2)字典与集合也⽀持推导表达式
第⼋条:不要使⽤含有两个以上表达式的列表推导式
第九条:⽤⽣成器表达式来改写数据量较⼤的列表推导式
(1)列表推导式的缺点
  在推导过程中,对于输⼊序列中的每个值来说,可能都要创建仅含⼀项元素的全新列表,当输⼊的数据⽐较少时,不会出现问题,但如果输⼊数据⾮常多,那么可能会消耗⼤量内存,并导致程序崩溃,⾯对这种情况,python提供了⽣成器表达式,它是列表推导和⽣成器的⼀种泛化,⽣成器表达式在运⾏的时候,并不会把整个输出序列呈现出来,⽽是会估值为迭代器。
  把实现列表推导式所⽤的那种写法放在⼀对园括号中,就构成了⽣成器表达式
numbers = [1,2,3,4,5,6,7,8]
li = (i for i in numbers)
print(li)
>>>> <generator object <genexpr> at 0x0000022E7E372228>
(2)串在⼀起的⽣成器表达式执⾏速度很快
第⼗条:尽量⽤enumerate取代range
(1)尽量使⽤enumerate来改写那种将range与下表访问结合的序列遍历代码
(2)可以给enumerate提供第⼆个参数,以指定开始计数器时所⽤的值,默认为0
color = ['red','black','write','green']
#range⽅法
for i in range(len(color)):
print(i,color[i])
#enumrate⽅法
for i,value in enumerate(color):
print(i,value)
第11条:⽤zip函数同时遍历两个迭代器
(1)内置的zip函数可以平⾏地遍历多个迭代器
(2)Python3中的zip相当于⽣成器,会在遍历过程中逐次产⽣元组,⽽python2中的zip则是直接把这些元组完全⽣成好,并⼀次性地返回整份列表、
(3)如果提供的迭代器长度不等,那么zip就会⾃动提前终⽌
attr = ['name','age','sex']
values = ['zhangsan',18,'man']
people = zip(attr,values)
for p in people:
print(p)
第12条:不要在for和while循环后⾯写else块
(1)python提供了⼀种很多编程语⾔都不⽀持的功能,那就是在循环内部的语句块后⾯直接编写else块
for i in range(3):
print('loop %d' %(i))
else:
print('else block!')
  上⾯的写法很容易让⼈产⽣误解:如果循环没有正常执⾏完,那就执⾏else,实际上刚好相反
(2)不要再循环后⾯使⽤else,因为这种写法既不直观,⼜容易让⼈误解
第13条:合理利⽤try/except/else/finally结构中的每个代码块
try:
#执⾏代码
except:
#出现异常
else:
#可以缩减try中代码,再没有发⽣异常时执⾏
finally:
#处理释放操作
2. 函数
第14条:尽量⽤异常来表⽰特殊情况,⽽不要返回None
(1)⽤None这个返回值来表⽰特殊意义的函数,很容易使调⽤者犯错,因为None和0及空字符串之类的值,在表达式⾥都会贝评估为False
(2)函数在遇到特殊情况时应该抛出异常,⽽不是返回None,调⽤者看到该函数的⽂档中所描述的异常之后,应该会编写相应的代码来处理它们
第15条:了解如何在闭包⾥使⽤外围作⽤域中的变量
(1)理解什么是闭包
  闭包是⼀种定义在某个作⽤域中的函数,这种函数引⽤了那个作⽤域中的变量
(2)表达式在引⽤变量时,python解释器遍历各作⽤域的顺序:
  a. 当前函数的作⽤域
  b. 任何外围作⽤域(例如:包含当前函数的其他函数)
  c. 包含当前代码的那个模块的作⽤域(也叫全局作⽤域)
  d. 内置作⽤域(也即是包含len及str等函数的那个作⽤域)
  e. 如果上卖弄这些地⽅都没有定义过名称相符的变量,那么就抛出NameError异常
(3)赋值操作时,python解释器规则
  给变量赋值时,如果当前作⽤域内已经定义了这个变量,那么该变量就会具备新值,若当前作⽤域内没有这个变量,python则会把这次赋值视为对该变量的定义
(4)nonlocal
  nonlocal的意思:给相关变量赋值的时候,应该在上层作⽤域中查该变量,nomlocal的唯⼀限制在于,它不能延申到模块级别,这是为了防⽌它污染全局作⽤域
(5)global
  global⽤来表⽰对该变量的赋值操作,将会直接修改模块作⽤域的那个变量
第16条:考虑⽤⽣成器来改写直接返回列表的函数
  参考第九条
第17条:在参数上⾯迭代时,要多加⼩⼼
(1)函数在输⼊的参数上⾯多次迭代时要当⼼,如果参数是迭代对象,那么可能会导致奇怪的⾏为并错失某些值
  看下⾯两个例⼦:
  例1:
def normalize(numbers):
total = sum(numbers)
print('total:',total)
print('numbers:',numbers)
result = []
for value in numbers:
percent = 100 * value / total
result.append(percent)
return result
numbers = [15,35,80]
print(normalize(numbers))
  输出:
  例2:将numbers换成⽣成器
def fun():
li = [15,35,80]
for i in li:
yield i
print(normalize(fun()))
  输出:
  原因:迭代器只产⽣⼀轮结果,在抛出过StopIteration异常的迭代器或⽣成器上⾯继续迭代第⼆轮,是不会有结果的。
(2)python的迭代器协议,描述了容器和迭代器应该如何于iter和next内置函数、for循环及相关表达式互相配合
python新手代码及作用
(3)想判断某个值是迭代器还是容器,可以拿该值为参数,两次调⽤iter函数,若结果相同,则是迭代器,调⽤内置的next函数,即可令该迭代器前进⼀步
if iter(numbers) is iter(numbers):
raise TypeError('Must supply a container')
第18条:⽤数量可变的位置参数减少视觉杂讯
(1)在def语句中使⽤*args,即可令函数接收数量可变的位置参数
(2)调⽤函数时,可以采⽤*操作符,把序列中的元素当成位置参数,传给该函数
(3)对⽣成器使⽤*操作符,可能导致程序耗尽内存并崩溃,所以只有当我们能够确定输⼊的参数个数⽐较少时,才应该令函数接受*arg式的变长参数
(4)在已经接收*args参数的函数上⾯继续添加位置参数,可能会产⽣难以排查的错误
第19条:⽤关键字参数来表达可选的⾏为
(1)函数参数可以按位置或关键字来指定
(2)只使⽤位置参数来调⽤函数,可能会导致这些参数值的含义不够明确,⽽关键字参数则能够阐明每个参数的意图
(3)该函数添加新的⾏为时,可以使⽤带默认值的关键字参数,以便与原有的函数调⽤代码保持兼容
(4)可选的关键字参数总是应该以关键字形式来指定,⽽不应该以位置参数来指定
第20条:⽤None和⽂档字符串来描述具有动态默认值的参数
import datetime
import time
def log(msg,when=w()):
print('%s:%s' %(when,msg))
log('hi,first')
time.sleep(1)
log('hi,second')
输出:
  两次显⽰的时间⼀样,这是因为w()只执⾏了⼀次,也就是它只在函数定义的时候执⾏了⼀次,参数的默认值,会在每个模块加载进来的时候求出,⽽很多模块都在程序启动时加载。我们可以将上⾯的函数改成:
import datetime
import time
def log(msg,when=None):
"""
arg when:datetime of when the message occurred
"""
if when is None:
when=w()
print('%s:%s' %(when,msg))
log('hi,first')
time.sleep(1)
log('hi,second')
输出:
(1)参数的默认值,只会在程序加载模块并读到本函数定义时评估⼀次,对于{}或[]等动态的值,这可能导致奇怪的⾏为
(2)对于以动态值作为实际默认值的关键字参数来说,应该把形式上的默认值写为None,并在函数的⽂档字符串⾥⾯描述该默认值所对应的实际⾏为

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。