前言

本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理。


编写干净的,Python式的代码就是要尽可能地使其易于理解,但又要简明扼要。以下是有关Python重构的系列文章的第一部分,其它部分会继续推出。本系列的重点是为什么这样做是好的做法,而不仅仅是教你如何做。

1. 合并嵌套的if条件

太多的嵌套使代码难以理解,尤其在python中,因为Python中没有使用括号来描述不同的嵌套级别。阅读多层嵌套的代码令人困惑,因为必须跟踪哪些条件与哪些级别相关。因此,我们应尽量减少嵌套。使用and可以很容易地将两个if条件结合在一起。
Before:

if a:
if b:
return c

After:

if a and b:
return c

2. 合并条件语句的重复代码

我们应当一直寻找去除重复代码的方法。代码提升则是一种很好的方式。

有时,部分代码在条件语句的两个分支上完全相同。这意味着代码将始终执行。应将重复的代码提升到条件语句外。

if sold > DISCOUNT_AMOUNT:
total = sold * DISCOUNT_PRICE
label = f'Total: {total}'
else:
total = sold * PRICE
label = f'Total: {total}'

通过在条件语句之外给label赋值,我们删除了重复的一行代码,并使条件语句中total的内容更加清晰。

if sold > DISCOUNT_AMOUNT:
total = sold * DISCOUNT_PRICE
else:
total = sold * PRICE
label = f'Total: {total}'

3. 用yield from替换for循环中的yield

一个经常被忽略的小技巧是,Python的yield关键字对于集合有一个对应的yield from,因此不需要使用for循环遍历集合。这使得代码稍微短一些,并删除了for循环额外的变量和mental overhead。消除for循环还可以使yield from版本的代码快15%左右。。

Before:

def get_content(entry):
for block in entry.get_blocks():
yield block

After:

def get_content(entry):
yield from entry.get_blocks()

4. 使用any()代替for循环

一个常见的模式是,我们需要找出某个条件是否适用于集合中的一个或所有项。可以通过如下的for循环实现:

found = False
for thing in things:
if thing == other_thing:
        found = True
break

一种更简洁的方式是使用Python的内置函数any()和all()来更清楚地实现。

found = any(thing == other_thing for thing in things)

any()将在至少一个元素求值为True时返回True, all()仅在所有元素求值为True时返回True。

如果可能,这些也会短路执行。如果对 any() 的调用找到一个值值为 True 的元素,它可以立即返回。如果代码还没有短路,这可能会导致性能改进。

5. 将list()替换为[]

创建列表最简洁也最符合python风格的方法是使用[]符号。

l = []

这符合我们用元素创建列表的方式,节省了一些精力。节省的精力可用于思考创建list的两种不同的方法。

l = ['first', 'second']

这样做还有一个额外的好处就是性能的提升,以下就是更改前后的时间对比:

$ python3 -m timeit "l = list()"
5000000 loops, best of 5: 63.3 nsec per loop

$ python3 -m timeit "l = []"
20000000 loops, best of 5: 15.8 nsec per loop

用{}替换dict()也会产生类似的推理和性能结果。

6. 从for/while循环中提升语句

另一种提升代码的方式是将不变式语句从循环中取出。如果一个语句只是为循环中使用设置了一些变量,那么它不需要在循环中。循环本身就很复杂,所以在编写循环时,应该考虑让它们更短、更容易理解。

在本例中,city变量在循环中被赋值,但只读取它而不修改它。

for building in buildings:
    city = 'Beijing'
    addresses.append(building.street_address, city)

因此,把它提出来是安全的,这就更清楚地表明,同样的value将适用于所有building。

city = 'Beijing'
for building in buildings:
    addresses.append(building.street_address, city)

这样还可以提高性能——循环中的任何语句都将在每次循环运行时执行。在这些多次执行上花费的时间就被浪费了,因为它只需要执行一次。如果语句涉及到对数据库的调用或其他耗时的任务,那么这种节省可能是非常重要的。