第四条: 使用辅助函数代替复杂表达式
要点
- 开发者很容易过度运用Python的语法特性,从而写出那种特别复杂并且难以理解的单行表达式
- 请把复杂的表达式移入辅助函数之中,如果要反复使用相同的逻辑,那就更应该这么做
- 使用if/else表达式,要比用or或and这样的Boolean操作符写成的表达式更加清晰
第七条: 用列表推导式取代 map 和 filter
要点
- 列表推导要比内置的map和filter函数清晰,因为它无需额外编写lambda表达式。
- 列表推导可以跳过输入列表中的某些元素,如果改用map来做,那就必须辅以filter 方能实现
- 字典与集也支持推导表达式
Yes
>>> a = [1,2,3,4,5,6,7,8] >>> [x**2 for x in a] [1, 4, 9, 16, 25, 36, 49, 64]
No
>>> list(map(lambda x:x**2, a)) [1, 4, 9, 16, 25, 36, 49, 64]
Yes
>>> [x**2 for x in a if x % 2 == 0] [4, 16, 36, 64]
No
>>> list(map(lambda x:x**2, filter(lambda x:x % 2 == 0, a))) [4, 16, 36, 64]
Yes
>>> info = {'Dylan':1, 'Amy':2, 'Jack':3, 'Marry':4} >>> {rank:name for name,rank in info.items() if rank <= 3} {1: 'Dylan', 2: 'Amy', 3: 'Jack'}
第八条: 用不要使用含有2个以上表达式的列表推导式
要点
- 列表推导支持多级循环,每一级循环也支持多项条件
- 超过两个表达式的列表推导是很难理解的,应该尽量避免
No
flat= [x for sublistl in my_lists for sublist2 in sublistl for x in sublist2]
Yes
flat =[] for sublistl in my_lists: for sublist2 in sublistl: flat.extend(sublist2)
Yes
>>> matrix = [[1,2,3],[4,5,6],[7,8,9]] >>> [x for row in matrix for x in row] [1, 2, 3, 4, 5, 6, 7, 8, 9] >>> [[x**2 for x in row] for row in matrix] [[1, 4, 9], [16, 25, 36], [49, 64, 81]]
第九条: 用生成器表达式来改写数据量较大的列表推导式
要点
- 当输入的数据量较大时,列表推导可能会因为占用太多内存而出问题
- 由生成器表达式所返回的迭代器,可以逐次产生输出值,从而避免了内存用量问题
- 把某个生成器表达式所返回的迭代器,放在另一个生成器表达式的for子表达式中,即可将二者组合起来
- 串在一起的生成器表达式执行速度很快
No (万一 test.txt非常大)
value = [len(x) for x in open(test.txt)]
Yes
it = (len(x) for x in open(test.txt)) next(it)
第十条: 尽量用 enumerate 代替 range
要点
- enumerate函数提供了一种精简的写法,可以在遍历迭代器时获知每个元素的索引
- 尽量用enumerate来改写那种将range与下标访问相结合的序列遍历代码
- 可以给enumerate提供第二个参数,以指定开始计数时所用的值(默认为0)
No
>>> for i in range(len(favorite_language)): ... print(i, favorite_language[i]) ... 0 Python 1 Java 2 C++ 3 C 4 PHP
Yes
>>> favorite_language = ['Python', 'Java', 'C++', 'C','PHP'] >>> for i, favor in enumerate(favorite_language): ... print(i, favor) ... 0 Python 1 Java 2 C++ 3 C 4 PHP
第十一条: 用 zip 函数同时遍历两个迭代器
要点
- 内置的zip函数可以平行地遍历多个迭代器
- Python3中的zip相当于生成器,会在遍历过程中逐次产生元组,而Python2中的zip则是直接把这些元组完全生成好,并一次性地返回整份列表。
- 如果提供的迭代器长度不等,那么zip就会自动提前终止
- itertools内置模块中的zip_longest函数可以平行地遍历多个迭代器,而不用在乎它们的长度是否相等
No
# 需求: 在names列表中找到最长的单词 names =['Cecilia',Lise','Marie'] letters = [len(n) for n in names] longest _name = None max_letters =0 for i in range(len(names)): count = letters[i] if count > max_letters: longest_name = names[i] max letters = count print(longest_name) >>> Cecilia
Yes
>>> names ['Cecilia', 'Lise', 'Marie'] >>> letters = [len(n) for n in names] >>> for name, count in zip(names, letters): ... if count > max_letters: ... longest_name = name ... max_letters = count ... >>>prilongest_name Cecilia
第十四条: 尽量用异常表示特殊情况, 而不是返回None
要点
- 用None这个返回值来表示特殊意义的函数,很容易使调用者犯错,因为None和0及空字符串之类的值,在条件表达式里都会评估为False
- 函数在遇到特殊情况时,应该抛出异常,而不要返回None。调用者看到该函数的文档中所描述的异常之后,应该就会编写相应的代码来处理它们了
No
def divide(a,b): try: return a /b except ZeroDivisionError: return None result= divide(x,y) if result is None: print('Invalid inputs')
Yes
def divide(a,b): try: return a/b except ZeroDivisionError as e: raise ValueError('Invalid inputs') from e x,y=5,2 try: result = divide(x,y) except ValueError: print('Invalid inputs') else: print('Result is %.1f'% result) >>> Result is 2.5
第十六条: 考虑用生成器来改写直接返回列表的函数
要点
- 使用生成器比把收集到的结果放入列表里返回给调用者更加清晰
- 由生成器函数所返回的那个迭代器,可以把生成器函数体中,传给yield表达式的那些值,逐次产生出来
- 无论输入量有多大,生成器都能产生一系列输出,因为这些输入量和输出量,都不会影响它在执行时所耗的内存
No
# 需求: 计算一个字符串中每个首字母的下标 def index_words(text): result = [] if text: result.append(0) for index, letter in enumerate(text): if letter == ' ': result.append(index + 1) return result s = "Hello World!" print(index_words(s)) # [0, 6]
Yes
# 需求: 计算一个字符串中每个首字母的下标 def index_words(text): if text: yield 0 for index, letter in enumerate(text): if letter == ' ': yield index+1 s = "Hello World!" print(index_words(s)) # <generator object index_words at 0x000001C5FA889480> print(list(index_words(s))) # [0, 6]
第十八条: 使用数量可变的位置参数减少视觉杂讯
要点
- 在def语句中使用*args,即可令函数接受数量可变的位置参数
- 调用函数时,可以采用*操作符,把序列中的元素当成位置参数,传给该函数
- 对生成器使用*操作符,可能导致程序耗尽内存并崩溃
- 在已经接受*args参数的函数上面继续添加位置参数,可能会产生难以排查的bug
def fucn(a,b,*args, **kwargs): print(a,b) print(args) print(kwargs) fucn(name1,name2,3,4,5,x=1,y=2) """ name1 name2 (3, 4, 5) {'x': 1, 'y': 2} """
第二十条: 用纯属性代替 get 和 set 方法
要点
- 编写新类时,应该用简单的public属性来定义其接口,而不要手工实现set和get方法
- 如果访问对象的某个属性时,需要表现出特殊的行为,那就用@property来定义这种行为
- @property 方法应该遵循最小惊讶原则,而不应产生奇怪的副作用
- 在已经接受*args参数的函数上面继续添加位置参数,可能会产生难以排查的bug
- @property方法需要执行得迅速一些,缓慢或复杂的工作,应该放在普通的方法里面
No
class Base(object): def __init__(self, name): self.__name = name def setName(self): pass def getName(self): pass
Yes
class Base(object): def __init__(self, name): self.__name = name @property def name(self): return self.__name @name.setter def name(self, name): if isinstance(name, str): self.__name = name return self.__name b = Base("Dylan") print(b.name) b.name = "Jack" print(b.name)