第四条: 使用辅助函数代替复杂表达式

要点

  • 开发者很容易过度运用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)