本系列文章为《编写高质量代码——改善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
声明的顺序进行匹配,在第一个匹配的地方便立即处理该异常。无论 try
语句中是否有异常抛出,finally
语句总会被执行。由于这个特性,finally
语句经常被用来做一些清理工作。
但使用 finally
时,也要特别小心一些陷阱。
try
块中发生异常的时候,如果在 except
语句中找不到对应的异常处理,异常将会被临时保存起来,当 finally
执行完毕的时候,临时保存的异常将会再次被抛出,但如果 finally
语句中产生了新的异常或者执行了 return
或者 break
语句,那么临时保存的异常将会被丢失,从而导致异常屏蔽。finally
中使用 return
语句进行返回,这种处理方式不仅会带来误解而且可能会引起非常严重的错误。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。
__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()
面向开发人员,其目的是准确性,其返回值表示 Python 解释器内部的含义,常用作 debug在解释器中直接输入时默认调用 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, ...):
都可以通过类名.方法名
或者实例.方法名
的形式来访问。
其中,静态方法没有常规方法的特殊行为,如绑定、非绑定、隐式参数等规则,而类方法的调用使用类本身作为其隐含参数,但调用本身并不需要显示提供该参数。
类方法
静态方法