本系列文章是对《编写高质量代码——改善Python程序的91个建议》的精炼总结。使用assert语句查找问题assert语句的基本语法如下:assertexpression1[","expression2]其中expression1为判断语句,返回True或False,返回False时会抛出AssertionError。[]中的内容是可选的,用于传递具体的异常信息。>>>a=1>>>b=2>>>asserta==b,"aequalsb"Traceback(最近调用last):File"",line1,inAssertionError:aequalsb使用assert语句来查找程序中的问题。断言(assert)存在于多种语言中,主要用于调试程序,可以快速方便地检查程序异常或输入不当。需要注意的是,使用assert是有代价的,对性能会有一定的影响,尽量使用。当两个变量交换数据时,不建议使用中间变量。#交换x,y#使用中间变量temp=xx=yy=temp#不使用中间变量x,y=y,x第二种方法在内存中执行顺序如下:先计算表达式y,x上对,在内存中创建一个元组(y,x),其标注值为y,x及其对应的值,其中y和x是初始化时已经存在于内存中的对象。通过拆包操作(unpacking),将元组的第一个标识符(fory)赋值给左边的第一个元素(此时为x),将元组的第二个标识符(forx)赋值给左边第二个元素(fory),从而达到交换x和y值的目的。充分利用Lazyevaluation的特性Lazyevaluation常译为“延迟计算”或“惰性计算”,是指只有在真正需要执行时才计算表达式的值。避免不必要的计算并带来性能提升。对于Python中的条件表达式ifxandy,如果x为假,则不会计算y表达式的值。对于ifxory,当x的值为true时,直接返回,不计算y的值。节省空间并使无限循环数据结构成为可能。Python中惰性求值最典型的例子是生成器表达式。例如Fibonacci:deffib():a,b=0,1whileTrue:yieldaa,b=b,a+bfromitertoolsimportisliceprint(list(islice(fib(),5)))不是推荐的类型用于类型检查。内置函数type(object)用于返回当前对象的类型。可以与Python内置模块类型中定义的名称进行比较,根据其返回值判断变量类型是否符合要求。所有基本类型对应的名称都可以在types模块中找到,但是使用type()函数不适合做变量类型检查。这是因为:基于内置类型扩展的用户自定义类型,类型函数无法准确返回结果。在经典类中,所有类实例的类型值都是相等的。解决方法是,如果类型有对应的工厂函数,可以使用工厂函数对类型进行相应的转换,否则可以使用isinstance()函数检测。isinstance(object,classinfo)其中,classinfo可以是直接或间接的类名,基本类型名,或者由它们组成的元组。如果classinfo参数错误,此函数将抛出TypeError异常。#isinstance的基本用法如下:>>>isinstance(2,float)False>>>isinstance("a",(str,unicode))True>>>isinstance((2,3),(str,list,tuple))#支持多种类型的列表True当心eval()中的安全漏洞Python中的eval()函数将一个字符串作为有效表达式求值并返回计算结果。其函数声明如下:eval(expression[,globals[,locals]])其中,参数globals为字典形式,locals为任意映射对象,分别代表全局和局部命名空间。如果传递给globals参数的字典中缺少内置函数,则当前全局命名空间将作为globals参数输入并在计算表达式之前解析。locals参数默认与globals相同,如果两者都省略,表达式将在eval()调用的环境中执行。eval存在安全漏洞,举个简单的例子:importsysfrommathimport*defExpCalcBot(string):try:print"Youransweris",eval(user_func)#calculatetheinputvalueexceptNameError:print"Theexpressionyouenterisnotvalid"print'嗨,我是ExpCalcBot。pleaseinputyourexpressionorenteretoend'inputstr=''whileTrue:print'请输入数字或运算。输入c完成。:'inputstr=raw_input()ifinputstr==str('e'):#当输入为e时退出sys.exit()elifrepr(inputstr)!=repr(''):ExpCalcBot(inputstr)inputstr=''因为它是在网络环境下运行的,并不是所有的用户都是值得信任的。例如,输入__import__("os").system("dir")将显示当前目录下所有文件的列表;如果恶意输入__import__("os").system("del*/Q"),则在没有任何提示的情况下,将删除当前目录下的所有文件。在globals参数中禁用对全局命名空间的访问:defExpCalcBot(string):try:math_fun_list=["acos","asin","atan","cos","e","log","log10","pi","pow","sin","sqrt","tan"]math_fun_dict=dict([(k,globals().get(k))forkinmath_fun_list])#形成可访问函数Dictionaryprint"Yournameis",eval(string,{"__builtins__":None},math_fun_dict)exceptNameError:print"Theexpressionyouenterisnotvalid"再次恶意输入:[cforcin().__class__.__bases__[0].__subclasses__()ifc.__name__=="Quitter"][0](0)(),#().__class__.__bases__[0].__subclasses__()用于显示对象的所有子类班级。Quitter类绑定了“quit”函数,所以上面的输入导致程序退出。对于一个有经验的入侵者来说,他可能拥有一系列强大的方法,让eval解释调用这些方法,从而造成更大的危害。另外,eval()函数也给程序的调试带来了一定的困难。很难查看eval()中表达式的具体执行过程。因此,在实际应用过程中,如果使用的对象不是可信源,则应避免使用eval,可以使用更安全的ast.literal_eval代替eval。使用enumerate()获取序列迭代的索引和值使用函数enumerate(),主要解决循环中获取索引和对应值的问题。它具有一定的惰性(lazy),每次需要的时候只生成一个(index,item)对。函数签名如下:enumerate(sequence,start=0)示例:#使用enumerate()获取序列迭代的索引和值li=['a','b','c','d','e']fori,einenumerate(li):print("index:",i,"element:",e)区分==的适用场景是==:用来检查值是否存在两个对象相等。它实际上调用了内部的__eq__()方法,所以a==b等同于a.__eq__(b)。is:用来比较两个对象在内存中是否有相同的内存空间。仅当x和y是同一个对象时才返回True,xisb基本上等同于id(x)==id(y)。==运算符也可以重载,但不能重载。一般来说,如果xisy为True,则x==y的值也为True(特殊情况除外,如NaN,a=float('NaN'),aisa为True,a==ais错误的)。构建合理的包层次结构来管理模块。每个Python文件都可以看作是一个模块。使用模块可以增强代码的可维护性和可重用性。包是一个目录,但与普通目录不同的是,它除了包含常规Python文件(即模块)之外,还包含一个__init__.py文件,并且它允许嵌套。Package/__init__.pyModule1.pyModule2.pySubpackage/__init__.pyModule1.pyModule2.py包中的模块可以通过“.”访问。访问符号,即“包名.模块名”。导入方式有以下几种:直接导入一个包:importPackageimportsubmoduleorsubpackage,嵌套包的情况下可以进行嵌套导入:fromPackageimportModule1importPackage.Module1fromPackageimportSubpackageimportPackage.SubpackagefromPackage.Subpackageimport作用Module1importPackage.Subpackage.Module1__init__.py:区别于普通目录的包。您可以在此文件中的模块级别声明import语句,以便它在包级别可见。如果__init__.py文件为空,当打算使用fromPackageimport*将包中的所有模块导入当前命名空间时,导入的模块无法生效。这是因为不同平台之间的文件命名规则不同,Python解释器无法正确判断模块是否应该在对应的平台中。如何导入,所以只要执行__init__.py文件即可,如果要控制模块的导入,需要修改__init__.py文件。__init__.py文件的另一个作用是通过在文件中定义__all__变量来控制需要导入的子包或模块。然后运行from...import*,可以看到__all__变量中定义的模块和包被导入到当前命名空间中。使用包可以带来以下便利:代码组织合理,易于维护和使用,有效避免命名空间冲突。如果模块中包含的属性和方法存在同名冲突,使用importmodule可以有效避免名称冲突。在嵌套的包结构中,每个模块都以其所在的完整路径为前缀,所以即使名称相同,也不会因为模块对应不同的前缀而冲突。本文由多发平台ArtiPub自动发布