Effective Python 学习笔记 4

共1.6k字 阅读时长约5分 访问量

尽量使用异常来表示特殊情况,而不要返回None

    _表示用不到的变量
要点
  1. 用None这个返回值来表示特殊意义的函数,很容易使调用者犯错,以为None和0及空字符串之类的值,在表达式里面都会评估为False
  2. 函数遇到特特殊情况时应该抛出异常,而不是返回None

了解如何在闭包里使用外围作用域中的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'''
eg. 有一份列表,其中元素都是数字,现在对其排序,要把出现在某个群组内的数字,放在群组外的那些数字之前。
'''
def sort_pri(values, group):
def helper(x):
if x in group:
return (0, x)
return (1, x)
values.sort(key = helper)
'''
上述函数成立的原因:
1. 函数是一等对象(first-class object)
2. python支持闭包
3. python中使用特殊的规则来比较两个元组。它首先比较各元组中下标为0的对应元素,如果相等,再比较下标为1的元素,如果还是想等,就继续依次比较。
'''

numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = {2, 3, 5, 7}
sort_pri(numbers, group)
print(numbers)
>>> [2, 3, 5, 7, 1, 4, 6, 8]
要点
  1. 对于定义在某作用域内的闭包来说,它可以引用这些作用域中的变量
  2. 使用默认方式对闭包内的变量赋值,不会影响外围作用域的同名变量
  3. 在python 3中,程序可以在闭包内用nonlocal语句来修饰某个名称,使该闭包能够修改外围作用域中的同名变量
  4. 除了简单的函数,尽量不要使用nonlocal语句

考虑使用生成器来改写直接返回列表的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# eg.返回字符串中英文单词的首字母和其下标
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text):
if letter == ' ':
result.append(index + 1)
return result

words = 'I am python'
re = index_words(words)
print(re)
>>> [0, 2, 5]

以上程序的问题:

  1. 代码拥挤,每次找到新的结果,都要调用append方法。而我们真正强调的不是对append的调用,而是该方法给列表中添加的那个值且函数首位都要对resut进行创建和返回
1
2
3
4
5
6
7
8
9
10
11
# 生成器改写
def index)words_iter(text):
if text:
yield 0
for index, letter in enumerate(text):
if letter == ' ':
yield index + 1

### ···

re = list(index_words_iter(address))
  1. index_words函数在它返回前,要把所有结果都放在列表中。如果数据量非常大,那么程序可能会耗尽内存。用生成器改写后,可以应对任意长度的输入数据
要点
  1. 使用时生成器比用list返回结果更加清晰
  2. 由生成器函数所返回的那个迭代器,可以把生成器函数体中,传给yield表达式的那些值,逐次生产出来
  3. 无论数据量多大,生成器都能产生一系列输出,不会对内存造成压力

在参数上面迭代时要多加小心

    细节见书本第17条
要点
  1. 如果参数是迭代器,那么可能会导致奇怪的行为并错失某些值
  2. python的迭代器协议,描述了容器和迭代器应该如何与iter和next内置函数、for循环及相关表达式相互配合
  3. 把__iter__方法时限为生成器,即可定义自己的容器类型
  4. 想判断某个值是迭代器还是容器,可以拿该值为参数,两侧调用iter函数,若结果相同,则是迭代器,调用内置的next函数,即可令该迭代器前进一步

用数量可变的位置参数减少视觉杂讯

    令函数接受可选位置参数(由于这种参数习惯上写为*args,所以又称为star args,星号参数),能够使代码更加清晰,并减少视觉杂讯(visual noise)。

    visual noise:一种比喻,意思是使代码看起来不要太过杂乱,以强调其中的重要内容。
出现的问题
  1. 变长参数在传给函数时,总是先转化为元组。这就意味着,如果用带有*操作符的生成器为参数,来调用这种参数,python必须把该生成器完整迭代一轮,并把所生成的每个值,都放入元组之中。这可能会消耗大量内存。所以只有当我们确定参数个数较少时,才采用这种写法
  2. 如果以后要给函数添加新的位置参数,那就必须修改原来调用该函数的那些旧代码
要点
  1. def语句中用*args,即可令函数接受数量可变的位置参数
  2. 调用函数时,可以采用*操作符,把序列中的元素当成位置参数,传给该函数
  3. 对生成器使用*操作符,可能导致内存耗尽
  4. 在已经接受*args参数的函数上继续添加位置参数,可能会产生难以排查的bug

用关键字参数来表达可选行为

1
2
3
4
5
6
7
def func(arg1, arg2):
return arg1 + arg2
# 以下写法等效
func(1, 1)
func(arg1 = 1, 1)
func(1, arg2 = 1)
func(arg1 = 1, arg2 = 1)
关键字参数的好处
  1. 易于理解,参数含义与参数值都呈现在面前
  2. 可以在函数中提供默认值
  3. 它可以提供一种扩充函数参数的有效方式,使得扩充之后的函数依然能与原有的那些调用代码兼容
要点
  1. 函数参数可以按照位置或关键字来指定
  2. 只是用位置参数来调用函数,可能会导致这些参数数值含义不够明确,而关键字参数则能够阐明每个参数的意图
  3. 给函数添加新行为时,可以使用带默认值的关键字参数,以便与原有的函数点用代码保持兼容
  4. 可选的关键字参数,总是应该以关键字形式来指定,而不是以位置参数的形式来指定

参考资料

  1. python 闭包和装饰器详解
  2. Python3 List sort()方法
  3. 深入理解 Python yield