今天讨论Python编程风格,如何写出更Python化的代码是本文的主题。基本目录结构:1基本编程习惯1.1额外空格1.2是否为None判断1.3lamda表达式1.4尽量减少保护代码1.5保持逻辑完整性1.6使用语义更清晰的方法2EAFP防御性编程风格3LBYL防御性编程风格3.1程序需要每次运行都要检查3.2很难一次性考虑到所有可能的异常3.3代码的可读性降低1基本编程习惯Python代码的编程习惯主要参考PEP8:https://www.python.org/dev/peps/pep-0008/主要包括比如每行代码的长度不超过80,函数之间一行等。同时我们可以使用一些有用的小工具来帮助我们编写更习惯的Python代码,比如flake8等小插件。结合以上参考资料和工具,我们在本专题总结中不再过多谈论语法相关的格式化。而是更多的着眼于一些典型的和常用的对比分析,告诉大家常用的代码编写习惯,哪些写法不符合习惯等等。1.1额外空格以下函数的赋值是惯用的:foo(a,b=0,{'a':1,'b':2},(10,))但是,下面出现的额外空格不是惯用的:#这些空格是多余的foo(a,b=0,{'a':1,'b':2},(10,))下面的代码,带空格,比较习惯:i+=1num=num**2+1deffoo(nums:List)是特别容易忽略的空格。添加函数元信息时,必须有一个空格:defffoo(nums:list):#按照官方的建议,nums:list之间应该有一个空格pass1.2是否为None判断一个对象是否为None,下面是习惯的:ifarrisNone:passifarrisnotNone:pass下面的写法不是习惯的,一般很少见:ifarr==None:pass特别的,对于list,tuple,set,dict,str等对象,比较习惯的是使用判断是否为None的方法如下:ifnotarr:#为None时满足条件passifarr:#不为None时满足条件pass1.3lambda表达式lambda表达式适用于一些关键的参数赋值,etc一般不习惯这样写:f=lambdai:i&1下面是比较习惯的:defis_odd(i):returni&11.4最小化protectedcode为了让代码更健壮,我们一般做防御工作,最小化protected的代码比较习惯。为了防止key出问题,在上面加一个try:try:val=d['c']exceptKeyError:print('c'notexistence)是合理的,但是当下面的代码捕获到KeyError时,就不是了习惯嵌套另一个函数:try:val=foo(d['c'])#这也会捕获foo函数中的KeyError异常exceptKeyError:print('c'notexistence)这样也会捕获foo函数中的KeyError异常功能不习惯。1.5维护逻辑完整性按照官方的指导方针,只有if逻辑return,忽略x为负时可能的else逻辑。不可取:defffoo(x):ifx>=0:returnmath.sqrt(x)建议这样写:defffoo(x):ifx>=0:returnmath.sqrt(x)else:returnNone或者这样写:defffoo(x):ifx<0:returnNonereturnmath.sqrt(x)所以,不要忽视Habit的使用。1.6在使用更语义化的方法判断字符串是否以ize结尾时,不建议这样写:ifs[-3:]=='ize':print('endsize')使用字符串的endswith方法来判断是否以什么字符结尾字符串结尾显然更易读:ifs.endswith('ize'):print('endsize')只要多注意上面的,理解起来不是问题.事实上,除了PEP8规定的编码习惯外,还有一种编程风格与代码的健壮性密切相关。今天我们将重点介绍这方面的编程习惯。2EAFP防御性编程风格为了提高代码的健壮性,我们需要进行防御性编程。Python中的try和except主要用于此:d={'a':1,'b':[1,2,3]}try:val=d['c']exceptKeyError:print('keynotexistence')try块中的代码受到保护。如果key不存在,except捕获KeyError异常,处理异常信息。而下面的代码,一旦从字典中获取到了不存在的key,如果没有try保护,程序会直接中断到这里,显示的现象是app直接挂掉或者崩溃,显然是很不友好的.d={'a':1,'b':[1,2,3]}val=d['c']再举一个使用try和except的例子,如果目录已经存在,则触发OSError异常,并传递except被捕获,然后在块中做一些异常处理逻辑。importostry:os.makedirs(path)exceptOSErrorsexception:ifexception.errno!=errno.EEXIST:raise#PermissionErrorandotherexceptionselse:#pathdirectoryalreadyexists上面使用try和except的防御性编程风格在Python抽象名称中有比较:EAFP全称:EasiertoAskforForgivenessthanPermission。上面这句话的哲学意义就不用纠结了。知道它在编程方面的含义就足够了:首先相信程序会正确执行,然后如果出现问题,我们将处理错误。使用tryandexcept的防御方式有明显的优势。只有我们的业务逻辑是用try写的,异常处理逻辑是用except写的,几乎没有多余的代码。Python指南中也提倡这种风格。但凡事都有两面,本文也不例外。那么,EAFP防守风格有什么问题呢?它主要有我们不想要的副作用。举个例子,try块中的逻辑是这样的:出现某种情况修改磁盘上csv文件中的某个值。这些逻辑顺利完成,但是当下面这行代码出现时,程序出现了异常,然后被except捕获,然后做一些异常处理:try:ifcondition:revise_csv#Thecsvfilehasbeenpolluteddo_something#TriggerexceptionexceptException:handle_exception由于try块中的逻辑是分两步执行的,它们不是原子操作,所以先修改csv文件,但是do_something确实发生了污染csv文件的异常。其实,除了上面的EAFP防御性编程风格之外,还有另一种编程风格是和它完全不同的。虽然可以很好的解决EAFP的副作用,但是它的缺点比较明显,所以不建议在Python中大量使用这种风格。3LBYLdefensiveprogrammingstyle介绍另一种编程风格:LBYL的特点:是指在执行正常的业务逻辑之前检查各种可能的错误,需要一个接一个地写if和else逻辑。比如EAFP风格的代码:d={'a':1,'b':[1,2,3]}try:val=d['c']exceptKeyError:print('keynotexistence')是用LBYL写的如下:if'c'ind:val=d['c']else:print('keynotexistence')EAFP风格代码如下:importostry:os.makedirs(path)exceptOSErrorasexception:ifexception.errno!=errno.EEXIST:raise#PermissionErrorandotherexceptionselse:#pathdirectoryalreadyexists使用LBYL写如下:importosifnotos.path.isdir(path):print('notalegalpath')else:ifnotos.path.exists(path):os.makedirs(path)else:print('Thepathalreadyexists')通过上面两个例子可以看出LBYL风格和EAFP风格有很大的不同。LBYLif和else代码较多,这种风格有以下缺点。3.1程序每次运行都会检查程序每次运行都会检查,不管程序是否真的触发了这些异常。if'c'ind:#Everycheckmustbedoneval=d['c']ifnotos.path.isdir(path):#Everycheckmustbedoneprint('notalegalpath')else:ifnotos.path.exists(path):#每次都要检查os.makedirs(path)else:print('路径已经存在')3.2很难一次考虑所有可能的异常很难一次考虑所有可能的异常,更麻烦的是,一旦漏掉了某个异常,错误往往不在发生的地方,而是在很外层的调用处。这会导致我们要花费大量的时间调试才能找到最终的错误所在。deff1ifcon1:#do1ifcon2:#do2#但是省略了case3,f1函数中没有报异常3.3代码可读性降低。需要写很多和主要逻辑无关的if-else,程序真正的逻辑变得难以阅读。最后,我们很难看出这只是一个判断,还是程序逻辑/业务的判断。但是,如果使用try-catch,则只能将程序的逻辑写在try代码块中,所有的异常都在except中处理。结论:就Python语言而言,推荐使用EAFP风格,对于个别受保护的块,在无法实现原子操作的地方可以使用LBYL风格。
