内容简介:本系列文章为《编写高质量代码——改善Python程序的91个建议》的精华汇总。Python的3种引入外部模块的方式:在使用
本系列文章为《编写高质量代码——改善 Python 程序的91个建议》的精华汇总。
关于导入模块
Python的3种引入外部模块的方式: import
语句、 from ... import ...
和 __import__
函数。其中前两种比较常见。
在使用 import
时,应注意:
-
优先使用
import A
或import A as a
-
有节制的使用
from A import B
-
尽量避免使用
from A import *
对于 from a import ...
,如果无节制的使用,会带来的问题:
- 命名空间的冲突
- 循环嵌套导入的问题(两个文件相互导入对方的变量或函数或类)
i += 1
不等于 ++i
Python 解释器会将 ++i
解释为 +(+i)
,其中 +
表示正数符号。对于 --i
也是类似。
因此,要明白 ++i
在 Python 的语法层面上是合法的,但并不是通常意义上的自增操作。
使用 with
自动关闭资源
对文件操作完成后,应该立即关闭它们,因为打开的文件不仅会占用系统资源,而且可能影响其他程序或者进程的操作,甚至会导致用户期望与实际操作结果不一致。
Python 提供了 with 语句,语法为:
with 表达式 [as 目标]: 代码块
with 语句支持嵌套,支持多个 with 子句,它们两者可以相互转换。 with expr1 as e1, expr2 as e2
与下面的嵌套形式等价:
with expr1 as e1: with expr2 as e2:
使用 else
子句简化循环(异常处理)
在循环中, else
子句提供了隐含的对循环是否由 break
语句引发循环结束的判断。例子:
# 以下两段代码等价 # 借助了一个标志量 found 来判断循环结束是不是由 break 语句引起的。 def print_prime(n): for i in range(2, n): found = True for j in range(2, i): if i % j == 0: found = False break if found: print("{} is a prime number".format(i)) def print_prime2(n): for i in range(2, n): for j in range(2, i): if i % j == 0: break else: print("{} is a prime number".format(i))
当循环“自然”终结(循环条件为假)时 else
从句会被执行一次,而当循环是由 break
语句中断时, else
子句就不被执行。
与 for
语句相似, while
语句中的 else
子句的语意是一样的: else
块在循环正常结束和循环条件不成立时被执行。
遵循异常处理的几点基本原则
Python中常用的异常处理语法是 try
、 except
、 else
、 finally
,它们可以有多种组合。语法形式如下:
# Run this main action first try: <statements> # 当 try 中发生 name1 的异常时,进行处理 except <name1>: <statements> # 当 try 中发生 name2 或 name3 中的某一个异常时 except (name2, name3): <statements> # 当 try 中发生 name4 的异常时处理,并获取对应实例 except <name4> as <data>: <statements> # 其他异常时,进行处理 except: <statements> # 没有异常时,执行 else: <statements> # 无论有没有异常,都执行 finally: <statements>
异常处理,通常需要遵循以下几点基本原则:
-
不推荐在
try
中放入过多的代码 。在 try 中放入过多的代码带来的问题是如果程序中抛出异常,将会较难定位,给 debug 和修复带来不便,因此应尽量只在可能抛出异常的语句块前面放入 try 语句。 -
谨慎使用单独的
except
语句处理所有异常, 最好能定位具体的异常 。同样也不推荐使用except Exception
或者except StandardError
来捕获异常。如果必须使用,最好能够使用raise
语句将异常抛出向上层传递。 -
注意异常捕获的顺序,在合适的层次处理异常。
-
用户也可以继承自内建异常构建自己的异常类,从而在内建类的继承结构上进一步延伸。在这种情况下捕获异常的顺序显得非常重要。为了更精确地定位错误发生的原因,推荐的方法是将继承结构中子类异常在前面的
except
语句中抛出,而父类异常在后面的except
语句抛出。这样做的原因是当try
块中有异常发生的时候,解释器根据except
声明的顺序进行匹配,在第一个匹配的地方便立即处理该异常。 - 异常捕获的顺序非常重要,同时异常应该在适当的位置被处理,一个原则就是 如果异常能够在被捕获的位置被处理,那么应该及时处理,不能处理也应该以合适的方式向上层抛出。 向上层传递的时候需要警惕异常被丢失的情况,可以使用不带参数的 raise 来传递。
-
用户也可以继承自内建异常构建自己的异常类,从而在内建类的继承结构上进一步延伸。在这种情况下捕获异常的顺序显得非常重要。为了更精确地定位错误发生的原因,推荐的方法是将继承结构中子类异常在前面的
- 使用更为友好的异常信息,遵守异常参数的规范。通常来说有两类异常阅读者:使用软件的人和开发软件的人。
避免 finally 中可能发生的陷阱
无论 try
语句中是否有异常抛出, finally
语句总会被执行。由于这个特性, finally
语句经常被用来做一些清理工作。
但使用 finally
时,也要特别小心一些陷阱。
-
当
try
块中发生异常的时候,如果在except
语句中找不到对应的异常处理,异常将会被临时保存起来,当finally
执行完毕的时候,临时保存的异常将会再次被抛出,但如果finally
语句中产生了新的异常或者执行了return
或者break
语句,那么临时保存的异常将会被丢失,从而导致异常屏蔽。 -
在实际应用程序开发过程中,并不推荐在
finally
中使用return
语句进行返回,这种处理方式不仅会带来误解而且可能会引起非常严重的错误。
深入理解 None,正确判断对象是否为空
Python 中以下数据会当作空来处理:
-
常量
None
-
常量
False
-
任何形式的数值类型零,如
0
、0L
、0.0
、0j
-
空的序列,如
''
、()
、[]
-
空的字典,如
{}
-
当用户定义的类中定义了
__nonzero__()
和__len__()
方法,并且该方法返回整数0
或False
的时候。
if list1 # value is not empty Do something else: # value is empty Do some other thing
-
执行过程中会调用内部方法
__nonzero__()
来判断变量list1
是否为空并返回其结果。
注: __nonzero__()
方法 —— 该内部方法用于对自身对象进行空值测试,返回 0/1 或 True/False。
-
如果一个对象没有定义该方法,Python 将获取
__len__()
方法调用的结果来进行判断。__len__()
返回值为 0 则表示为空。如果一个类中既没有定义__len__()
方法也没有定义__nonzero__()
方法,该类的实例用 if 判断的结果都为 True。
格式化字符串时尽量使用 .format
方式而不是 %
推荐尽量使用 format
方式而不是 %
操作符来格式化字符串,理由:
-
format
方式在使用上较%
操作符更为灵活。使用format
方式时,参数的顺序与格式化的顺序不必完全相同 -
format
方式可以方便的作为参数传递weather = [("Monday", "rain"), ("Tuesday", "sunny"), ("Wednesday", "sunny"), ("Thursday", "rain"), ("Friday", "cloudy")] formatter = "Weather of '{0[0]}' is '{0[1]}'".format for item in map(formatter, weather): print(item)
-
%
最终会被 .format 方式所代替。根据 Python 的官方文档,之所以仍然保留%
操作符是为了保持向后兼容 -
%
方法在某些特殊情况下使用时需要特别小心,对于%
直接格式化字符的这种形式,如果字符本身为元组,则需要使用在%
使用(itemname,)
这种形式才能避免错误,注意逗号。
区别对待可变对象和不可变对象
Python 中一切皆对象,对象根据其值能否修改分为 可变对象 和 不可变对象 。
-
不可变对象
- 数字
- 字符串
- 元组
-
可变对象
- 字典
- 列表
- 字节数组
在将可变对象 作为函数默认参数 的时候要特别紧惕, 对可变对象的更改会直接影响原对象。
最好的方法是传入 None
作为默认参数,在创建对象的时候动态生成可变对象。
-
对于一个可变对象, 切片操作相当于浅拷贝。
-
对于不可变对象,当我们对其进行相关操作的时候,Python 实际上 仍然保持原来的值而且重新创建一个新的对象 ,所以字符串对象不允许以索引的方式进行赋值,当有两个对象同时指向一个字符串对象的时候,对其中一个对象的操作并不会影响另一个对象。
函数传参既不是传值也不是传引用
对于Python中函数的传参方法, 既不是传值,也不是传引用 。
正确的叫法应该是 传对象 (call by object)或者说 传对象的引用 (call-by-object-reference)。
函数参数在传递的过程中将整个对象传入,
- 对于 可变对象 :它的修改在函数外部以及内部都可见,调用者和被调用者之间共享这个对象
- 对于 不可变对象 :由于并不能真正被修改,因此,修改往往是通过生成一个新对象然后赋值来实现的
慎用变长参数
慎用可变长度参数 *args, **kwargs
,原因如下:
- 使用过于灵活。变长参数意味着这个函数的签名不够清晰,存在多种调用方式。另外变长参数可能会破坏程序的健壮性。
-
如果一个函数的参数列表很长,虽然可以通过使用
*args
和**kwargs
来简化函数的定义,但通常这个函数可以有更好的实现方式,应该被重构。例如可以直接传入元组和字典。
可变长参数适合在下列情况下使用:
- 为函数添加一个 装饰器
- 如果参数的数目不确定,可以考虑使用变长参数
- 用来实现函数的多态,或者在继承情况下子类需要调用父类的某些方法的时候
深入理解 str()
和 repr()
的区别
函数 str()
和 repr()
都可以将 Python 中的对象转换为字符串,两者的使用以及输出都非常相似。有以下几点区别:
-
两者的目标不同:
str() repr()
-
在解释器中直接输入时默认调用
repr()
函数,而print
则调用str()
函数 -
repr()
的返回值一般可以用eval()
函数来还原对象。通常有如下等式:obj == eval(repr(obj))
-
一般,类中都应该定义
__repr__()
方法,而__str__()
方法则为可选,当可读性比准确性更为重要的时候应该考虑定义__str__()
方法。如果类中没有定义__str__()
方法,则默认会使用__repr__()
方法的结果来返回对象的字符串表示形式。用户实现__repr__()
方法的时,最好保证其返回值可以用eval()
方法使对象重新还原。
分清静态方法和类方法的适用场景
静态方法:
class C(object): @staticmethod def f(arg1, arg2, ...):
类方法:
class C(object): @classmethod def f(cls, arg1, arg2, ...):
都可以通过 类名.方法名
或者 实例.方法名
的形式来访问。
其中,静态方法没有常规方法的特殊行为,如绑定、非绑定、隐式参数等规则,而类方法的调用使用类本身作为其隐含参数,但调用本身并不需要显示提供该参数。
类方法
- 在调用的时候没有显式声明 cls,但实际上类本身是作为隐藏参数传入的
- 类方法可以判断出自己是通过基类被调用,还是通过某个子类被调用
- 类方法通过子类调用时,可以返回子类的属性而非基类的属性
- 类方法通过子类调用时,可以调用子类的其他类方法
静态方法
- 既不跟特定的实例相关也不跟特定的类相关
- 静态方法定义在类中的原因是,能够更加有效地将代码组织起来,从而使相关代码的垂直距离更近,提高代码的可维护性
文章首发于公众号【Python与算法之路】
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 对高质量数据的追求
- 如何写出高质量的技术文章?
- 机器学习高质量数据集大合辑
- 做到这些细节,就是高质量C代码!
- 高质量前端快照方案:来自页面的 “自拍”
- 如何创建高质量的TypeScript声明文件(二)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。