使用生成式改进循环语句

@dataclass
class Employee:
    name: str
    age: int
    salary: int
    address: str = field(default="")
    role: str = field(default="")

def find_developers(employees: Sequence[Employee]) -> List[Employee]:
    developers = []
    for employee in employees:
        if employee.role == "developer":
            developers.append(employee)
    return developers

列表生成式

使用列表生成式进行重构:

def find_developers(employees: Sequence[Employee]) -> List[Employee]:
    return [e for e in employees if e.role == "developer"]

生成式的优势不仅在于语法简单,将多行代码合并成一行,它的性能更是比使用 for 循环语句要好。具体好多少,可以自行搜索,也可以自己测试。

请记住一点,当对列表或者序列进行简单变形或者过滤时,使用列表生成式由于使用for循环。当循环条件较为复杂时,请继续使用 for 循环,保持代码的可读性。

生成器推导式

使用生成器推导式,只需要将[]替换成()即可,比如

def find_developers(employees: Sequence[Employee]) -> List[Employee]:
    return (e for e in employees if e.role == "developer")

注意!生成器的使用特点是“迭代时计算”,也就是说,当生成器被创建时,内存中是没有新数据的,只存有转换方式而已。只有当其被迭代时,才会从内存中逐个读取数据,根据转换方式进行计算。所以,生成器的优势是根据需要占用内存,而不是提前在内存中准备好数据。

当需要提前在内存中准备好数据时,不应该使用生成器推导式,而应该使用列表推导式。

当多次对生成器进行迭代时,可能会出现生成器枯竭问题。看下面代码

developers = find_developers(employees=employees)

for developer in developers:
    print(developer.name)

for developer in developers:
    print(developer.salary)

developers变量是一个生成器,我们对其进行了两次迭代,第一次迭代会预期的打印出名字,但是第二次迭代时什么都不会执行。所以,请记住一点,不要对一个生成器进行多次迭代。

生成器枯竭的问题在实际工作中,还算容易发现,但是另一种问题,就更难发现,比如下面的:(假设employees是按照 id 从小到大排序后的序列)

developers = find_developers(employees=employees)

for developer in developers:
    if developer.salary > 10000:
        print(developer.name)
        break

for developer in developers:
    if developer.age > 30:
        print(developer.name)
        break

这里有两次循环,我们期望在第一次循环中找到第一个工资大于 10000 的 developer,第二次循环中,我们期望找到所有员工中第一个年龄大于 30 的developer。但是实际情况是,第一次循环按照预期找到了第一个工资大于 10000 的developer,但是第二个循环,找到的是在这个工资大于 10000 的 developer 之后,第一个年龄大于 30 的 developer,而这跟预期是不符合的。

使用高阶函数

上面的例子中,生成器推导式可以使用高阶函数 filter 来替代

def find_developers(employees: Sequence[Employee]) -> Generator[Employee]:
    return filter(lambda e: e.role == "developer", employees)

我们可以对其返回值进行迭代。我们也可以定义自己的高阶函数来实现更加灵活的过滤规则

def find_developers(employees: Sequence[Employee], predicate: Callable) -> Generator[Employee]:
    return (e for e in employees if predicate(e))

find_developers(employees, lambda e: e.role == "developer")
find_developers(employees, lambda e: e.role == "developer" and e.age > 30)

同样的,我们可以使用map函数实现对可迭代对象的变形操作,在这里不提供示例了。

尽量避免使用while True

while True是无尽循环,在使用时需要格外注意退出条件,否则会一直执行下去,除非这是预期的行为。大多数情况下,除非是编写常驻进程的程序,否则不应该期望程序不会退出。所以在这个大前提下,应该尽量避免使用while True来进行循环。

如果是需要进行轮询操作,推荐通过 retry 机制(我们项目的框架中提供retry装饰器,想自己实现一个也很简单),并且设置明确的执行次数。

记住一点,除非明确的知道程序应该无休止的运行下去,否则不要使用while True,你永远有更好的选择。

避免使用递归

除非递归的实现方式比循环的方式更加简洁搞笑,并且易读,否则不要使用递归。不可否认的是,递归是高效的解决某些问题的方法,但是我的建议是,除非对自己的代码和算法能力有充分的自信,并且计划进行充分的测试(单元测试或者其他测试),否则不要使用递归,它可能会带来维护上的困难。


Drinkey
334 声望7 粉丝