改善python程序的91个建议读书笔记2

第二章 编程惯用法


建议8:利用assert语句来发现问题

断言在很多语言中都存在,它主要为调试程序服务,能够快速方便的检查程序的异常或者发现不恰当的输入等。

assert语法如下

assert expression1 [“,” expression]

其中计算expression1的值会返回True或者False,当值为False的时候会引发AssertionError,而expression2是可选的,常用来传递具体的异常信息。实际在执行过程中调用了if __debug__ and not x== y: raise AssertionError(“notequals”)__debug__的值默认设置为True,且是只读的。断言是有代价的,它会对性能有一定影响,对于编译型语言如C/C++这也许不重要,因为断言只在调试模式下启用,但python没有严格定义调试和发布之间的区别,但是运行.py文件时加上-O的参数,python–O test.py便可以禁用断言,忽略与断言相关的语句。

使用禁言需要注意以下几点:

(1).不要滥用,这是使用禁言最基本的原则。若由断言引发了异常,通常代表程序中村子bug。因此断言应该使用在正常逻辑不可达的地方和正常情况下总为真的场合。

(2).如果python本身能够处理的就不要使用断言。

(3).不要使用断言来检查用户的输入。

(4).在函数调用后,当需要确认返回值是否合理的时候可以使用断言。

(5).当条件是业务逻辑继续下去的先决条件时可以使用断言。


建议9:数据交换值的时候不推荐使用中间变量


我们可以直接使用x,y = y,x来实现交换值的功能,一般情况下python表达式计算的顺序是从左到右,但遇到表达式赋值的时候表达式右边的操作数先于左边的操作数计算,因此expr3expr4 = expr1expr2的计算顺序是expr1expr2-> expr3,expr4。因此对于表达式x,y=y,x,先计算右边的表达式y,x,因此现在内存中创建元组(y,x),其标示符和值分别为yx及其对应的值,其中yx是在初始化时已经存在于内存中的对象。之后计算表达式左边的值进行赋值。

我们还可以更深入的通过python生成的字节码来分析,调用dis模块来实现。


建议10:充分利用Lazy evaluation的特性

Lazy evaluation指的是仅仅在真正需要执行的时候才计算表达式的值。

(1).避免不必要的计算,带来性能上的提升。

对于python中的条件表达式ifx and y,在xfalse的情况下y的表达式的值将不再计算。对于ifx ory,当x的值为true的时候直接返回,不再计算y的值。如果对于or条件表达式应该敬爱那个值为真可能性较高的变量写在or前面,而and则应该推后。

(2).节省空间,使得无限循环的数据结构成为可能

pythonLazy evaluation最典型的例子就是生成器表达式,它仅在每次需要的时候才通过yield产生所需要的元素。


建议11:理解枚举替代实现的缺陷

对于枚举最经典的例子大概就是季节和星期莫属了,它能够以更接近自然语言的方式来表达数据。python3.4以前是不提供枚举的,但我们有各种替代实现方式。

(1).使用类属性

	
class Seasons:

    spring= 0

    summer= 1

    autumn= 2

    winter= 3

print Season.spring


(2).使用collections.namedtuple

Season= nametuple(“Season”,”Spring Summer AutumnWinter”)._make(range(4))
<span style="font-family:Calibri, serif;"><span lang="en-US">
print Season.Spring</span></span>

其实这些替代方式中,同样存在不合理的地方,如允许枚举值重复、可以进行无意义的操作等等。实际上python2.7中有一个第三方模块flufl.enum,它包含两种枚举类:一种是Enum,只要保证枚举值唯一即可,对值的类型没限制。另一种是IntEnum,其枚举性为int型。但其不支持枚举元素进行比较。

	
from flufl.enum import Enum

class Season(Enum):

spring= ‘spring’

summer= 2

autumn=3

winter= 4


Season= Enum(‘seasons’,’spring summer autumn winter’)


for member in Season.__members__:#通过__member__属性可以对枚举名称进行迭代

print member


print Season.summer.value#直接使用value属性获取枚举元素的值


建议12:不推荐使用type来进行类型检查


作为动态性的强类型脚本语言,python中的变量在定义的时候并不会指明具体类型,python解释器会在运行时自动进行类型检查并根据需要进行隐式类型转换。按照python的理念,为了利用其动态性的特征是不推荐进行类型检查的。不刻意进行类型检查,而是在出错的情况下通过抛出异常进行处理,这是较为常见的方式。

如果一定需要类型检查的时候,也同样不推荐使用type,因为其基于内建类型扩展的用户自定义类型,type函数并不能准确返回结果。那么我们如何来约束用户的输入类型使之其与我们期望的类型相同呢,如果类型有对应的工厂函数,可以使用工厂函数对齐进行相应的转换如list(listing)str(name)。否则可以使用isinstance()函数来检测。

isinstance(object,classinfo) #isinstance(2,float)


建议13:尽量转换为浮点类型后再做除法


涉及除法运算的时候尽量先将操作数转换为浮点类型再做运算。python3之前的版本可以通过from__future__ import division机制使整数除法不在截断,这样即使不进行浮点类型转换,输出结果也是正确的。但有时浮点数也不太靠谱,如0.1的二进制形式为0.000110011001…后面的1001无限循环,所以使用whilei!=5这种可能就会一直循环下去,对于whilei!=1.5这种表达式更是要避免,浮点数的比较同样最好能够指明精确度。


建议14:警惕eval()的安全漏洞


python中的eval(str)函数将字符串str当成有效的表达式来求值并返回结果。其函数声明如下:eval(expression[,globals[,local]]),其中参数globals为字典型,locals为任何映像对象,他们分别表示全局和局部命名空间。

用户可能会通过eval执行一些对计算机产生破坏的语句,对于有经验的侵入者来说,他可能还有一系列强大的手段,如果使用对象不是信任源,应该尽量避免使用eval(),在需要使用eval的地方可用安全性更好的ast.literal_eval替代。


建议15:使用enumerate()获取序列迭代的索引和值


对序列进行迭代并获取徐来中的元素进行处理的方法有很多种,可以使用循环对索引变量进行自增,也可以使用lenrange方法的结合,还可以使用while循环,用len获取循环次数,可以使用zip方法,可以使用enumerate()获取序列迭代的索引和值。

这里最推荐的就是使用enumerate()方法,因为其代码清晰简洁,可读性好。函数enumerate主要是为了解决在循环中获取索引以及对应值的问题。它具有一定的惰性(lazy),每次仅在需要的时候才会产生一个(index,item)对。其语法为enumerate(sequence,start=0),其中sequence可以为序列,也可以为一个iterato或者任何可以迭代的对象,默认start=0,函数返回本质上为一个迭代器,可以使用next()方法获取下一个迭代元素。由于其内部实现简单,我们可以实现自己需要的enumerate函数。对于字典的迭代循环,enumnerate函数并不适合,要获取字典的keyvalue,应该使用iteritems()方法。

#iteritems()方法

for k,v in personinfo.iteritems():

print k,”:”,v


#内部实现

def enumerate(sequence,start):

    n= start

    for elem in sequence:

    yield n,elem

    n+= 1


#使用范例

li= [‘a’,’b’,’c’,’d’,’e’]

for I,e in enumerate(li):

print “index:” , I ,“element:”,e


建议16:分清==is的使用场景


在判断两个字符串是否相等的时候,混用is==是很多初学者经常犯的错误,造成的结果是程序在不同情况下变现不一。官方文档上显示is的意义是objectidentity,而==的意思是equal。也就是说is表示的是对象标示符,而==表示的意思是相等显然两者不是一个东西。实际上造成结果不一致的原因在于:is的作用是用来检查对象的标示符是否一致,也就是比较两个对象在内存中是否拥有同一块内存空间,它并不适合判断两个字符串是否相等。xis y仅当检验xy是同一个对象时才返回Truexis b相当于id(x)== id(b)。而==才用来检验两个对象的值是否相等,它实际是调用内部__eq__()方法,因此a== b相当于a.__eq__(b),所以==操作符是可以被重载的,而is是不可以被重载的。判断两个对象相等应该使用==而不是使用is

python中的stringinterning(字符串驻留)机制:对于较小的字符串,为了提高系统性能会保留其值的一个副本,当创建新的字符串的时候直接指向该副本即可。


建议17:考虑兼容性,尽可能使用unicode

python的内建字符串有两种类型:strunicode,他们拥有共同的祖先basestringunicode为每一种语言设置了唯一的二进制编码表示方式,提供从数字代码到不同字符集之间的映射,从而可以满足跨平台、跨语言之间的文本处理要求。unicode编码系统可以分为编码方式和实现方式两个层次。在编码方式上,分为ucs-2ucs-4两种方式,ucs-2用两个字节编码,ucs-44个字节编码,目前基本都对应ucs-2unicode的实现方式称为unicode转换格式简称UTF,较常见的为UTF-8,其特点就是不同范围的字符使用不同长度的编码。

decodeencode方法,这两个方法的作用分别是对字符串进行解码和编码。其中decode方法将其他编码对应的字符串解码成unicode,而encode方法将unicode编码转换为另一种编码,unicode作为转换过程中的中间编码。

Unicode提供了不同编码系统之间字符转换的桥梁,要避免令人头疼的乱码问题,就需要弄清楚字符所采用的编码方式以及正确的解码方法。对于中文字符,为了做到不同系统之间的兼容,建议直接使用unicode表示方式。python2.6之后可以通过importunicode_literals自动将定义的普通字符识别为unicode字符串。


建议18:构建合理的包层次来管理module


我们知道,本质上每一个python文件都是一个模块,使用模块可以增强代码的可维护性和可重用性。但显然在大的项目中将所有的python文件放在一个目录下并不是一个值得推荐的方法,我们需要合理的组织项目的层次来管理模块,这就是包package发挥功效的地方了。什么是包呢,简单来说包就是目录,但与普通目录不同,它除了包含常规的python文件以外,还包含一个__init__.py文件,同时它允许嵌套。__init__.py文件的作用就是使包和普通目录区分,其次可以在该文件中申明模块级别的import语句,从而是其变成包级别可见。__init__.py还可以通过该文件定义__all__变量,控制需要导入的子包或者模块。 

你可能感兴趣的:(读书笔记,python,迭代器,文档,安全漏洞)