编写高质量Python代码的59个有效方法
第17条:在参数上面迭代是,要多加小心
def read_visits(data_path): with open(data_path) as f: for line in f: yield int(line) visits = read_visits("./my_numbers.txt") print(list(visits)) print(list(visits))print(list(visits))
产生这个结果的原因是:迭代器只能产生一轮结果,在抛出StopIteration异常的迭代器或生成器 上面继续迭代第二轮,是不会有结果的
解决办法:实现迭代器协议的容器类:
class ReadVisits(object): def __init__(self, data_path): self.data_path = data_path def __iter__(self): with open(self.data_path) as f: for line in f: yield int(line) def normalize_defensive(numbers): if iter(numbers) is iter(numbers): raise TypeError("Must supply a container") total = sum(numbers) result = [] for value in numbers: percent = 100 * value / total result.append(percent) return result numbers = ReadVisits("./my_numbers.txt") result = normalize_defensive(numbers) print(result)
迭代器协议约定:
如果把迭代器对象传给内置的iter函数,那么此函数会把该迭代器返回,反之,如果传给iter函数的是个容器类型的对象,那么iter函数则每次都会返回新的迭代器对象。
要点:
函数在输入的参数上面多次迭代时要当心:如果参数是迭代器,那么可能会导致奇怪的行为并错失某些值。
Python的迭代器协议,描述了容器和迭代器应该如何与iter和next内置函数、for循环及相关表达式相互配合。
把__iter__方法实现为生成器,即可定义自己的额容器类型。
想判断某个值是迭代器还是容器,可以拿该值为参数,两次调用iter函数,若结果相同,则是迭代器,调用内置的next函数,即可令该迭代器前进一步。
第18条:用数量可变的位置参数减少视觉杂讯
要点:
在def语句中使用*args,即可令函数接受数量可变的位置参数。
调用函数时,可以采用*操作符,把序列中的元素当成尾椎参数,传给函数。
对生成器使用*操作符,可能导致程序耗尽内存并崩溃。
在已经接受*args参数的函数上面继续添加位置参数,可能会产生难以排查的bug。
第20条:用None和文档字符串来描述具有动态默认值的参数
要点:
参数的默认值,只会在程序加载模块并读到本函数的定义时评估一次。对于{}或[]等动态的值,这可能会导致奇怪的行为。
对于以动态值作为实际默认值的关键字参数来说,应该把形式上的默认值写为None,并在函数的文档字符串里面描述该默认值所对应的时间行为。