控制流#

1. 条件控制#

1.1. if...elif...else...#

name = "Bob"
age = 30

if name == "Alice":
    print("Hi, Alice.")
elif age < 12:
    print("You are not Alice, kiddo.")
else:
    print("You are neither Alice nor a little kid.")

1.2. 赋值表达式#

赋值表达式,也称为海象运算符,是 Python 3.8 中引入的一种新的语法,以解决语言中一个长期存在的问题,即可能导致代码重复。一般的赋值语句写成 a = b ,读作 “a 等于 b”,而这些赋值写成 a := b ,读作 “a walrus b”。

赋值表达式主要用于简化带有条件判断的循环,如 while...dowhile...if...break

age = 20

if age > 18:
    print("已经成年了")

if (age:= 20) > 18:
    print("已经成年了")

while True:
    line = fp.readline()
    if not line:
        break
    do_something(line)

while line := fp.readline():
    do_something(line)

1.3. while#

while (spam := 0) < 5:
    print("Hello, world.")
    spam = spam + 1

1.4. while...break...#

while True:
    print("Please type your name.")
    name = input()
    if name == "your name":
        break
print("Thank you!")

1.5. while...if...continue...#

while True:
    print("Who are you?")
    name = input()
    if name != "Joe":
        continue
    print("Hello, Joe. What is the password? (It is a fish.)")
    password = input()
    if password == "swordfish":
        break

print("Access granted.")

2. 迭代#

2.1. 迭代器#

迭代器(Iterator):⼀个可记住遍历的位置的对象,迭代器从容器的第 1 个元素开始访问,直到所有元素被访问完结束,只能往前,不会后退。

  • 凡是可作⽤于 for 的对象均是可迭代类型类型(Iterable)

  • 可迭代类型不一定是迭代器,需使用 iter() 进行转化

  • 凡是可作⽤于 next() 的对象均是迭代器类型

可迭代类型

不可迭代类型

作⽤于 for

不可

变量类型

四大容器 + 字符串

整型

from collections import Iterator

flash = ["jay garrick", "barry allen", "wally west", "bart allen"]

# for 循环打印
for person in flash:
    print(person)
# 迭代器打印
superspeed = iter(flash)
print(next(superspeed))
print(next(superspeed))
print(next(superspeed))
print(next(superspeed))

2.2. 反向迭代#

a = [1, 2, 3, 4]
for x in reversed(a):
    print(x)

# 4
# 3
# 2
# 1

2.3. 索引范围迭代#

import random

for i in range(5):
    print(random.randint(1, 10))

2.4. 索引 - 值迭代#

将索引作为 key ,元素作为 value ,创建一个生成器,本身不能被打印,需要变换为列表。

# Create a list of strings: mutants
mutants = ["charles xavier", "bobby drake", "kurt wagner", "max eisenhardt"]
mutant_list = list(enumerate(mutants))
print(mutant_list)

# Unpack and print the tuple pairs
for index1, value1 in enumerate(mutants):
    print(index1, value1)
# Change the start index
for index2, value2 in enumerate(mutants, start=1):
    print(index2, value2)

循环取值时,优先使用 enumerate() 代替 range()

snacks = [("bacon", 350), ("donut", 240), ("muffin", 190)]

for i in range(len(snacks)):
    item = snacks[i]
    name = item[0]
    calories = item[1]
    print(f"#{i+1}: {name} has {calories} calories")

for rank, (name, calories) in enumerate(snacks, 1):
    print(f"#{rank}: {name} has {calories} calories")

2.5. 并行迭代#

zip(x, y=[]) 可用于并行迭代多个迭代器,将 xy 中对应索引元素配对生成一个列表。它创建了一个产生元组的懒惰生成器,因此它可用于无限长的输入。

  • 当存在不同长度的迭代器, zip() 将其输出默默截断为最短迭代器。

  • 在不同长度的迭代器上使用 zip() ,请使用 itertools 内置的 zip_longest()

# 嵌套列表->字典,字符串->字典
lst = [["key1", "value1"], ["key2", "value2"], ["key3", "value3"]]
print(dict(lst))

# 双列表->字典
list1 = ["key1", "key2", "key3"]
list2 = ["1", "2", "3"]
print(dict(zip(list1, list2)))

3. 生成器#

一边循环一边计算的机制,称为生成器(generator),其保存的是算法,生成器是迭代器的一种特殊类型

  • 生成器可记住上一次返回时在函数体中的位置,迭代的参数均是第一次调用时保留的,节约内存。

  • 生成器的运算结果多使用 for 循环打印。

  • for 循环调用生成器时,无法获取返回值,若要获取返回值,必须捕获 StopIteration 错误,返回值包含在 StopIteration 的值中。

  • 生成器的运算可通过使用 next() 获取,其等价于调用方法 generator.__next__()

3.1. 生成式 → 生成器#

生成式(comprehensions): 循环生成四大容器,其本质是一种生成器

# 多重循环
[m + n for m in "ABC" for n in "XYZ"]
# 条件生成
[i * i for i in range(1, 11) if i % 2 == 0]
# 词典生成
{i: i % 2 == 0 for i in range(3)}
# 集合生成
{s.upper() for s in "AbcdE"}

大型生成式会消耗大量资源。这时候就应该考虑构建生成器。

  • 方法 1:将生成式外部的 [],改成 ()。

  • 方法 2:构建函数,在 forwhile 循环中使用 yield ,生成返回值。

# 方法 1
G = (x*2 for x in range(5))
print("method 1: ")
print(next(G))
print(next(G))

# 方法 2
def Fibonacci(times):
    n = 0
    a, b = 0, 1
    while n < times:
        yield b
        # 递推表达式
        a, b = b, a + b
        n += 1
    return "done"


# 此时的函数为生成器函数
fib = Fibonacci(5)
# 遍历
print("method 2: ")
for i in fib:
    print(i)

3.2. 多生成器#

通过 yield from 表达式,可将多个嵌套的生成器组合成一个组合生成器。它提供了比手动迭代嵌套生成器更好的性能。

from timeit import timeit

def child():
    for i in range(1_000_000):
        yield i

def slow():
    for i in child():
        yield i

def fast():
    yield from child()


baseline = timeit(stmt="for _ in slow(): pass", globals=globals(), number=50)
print(f"Manual nesting {baseline:.2f}s")

comparison = timeit(stmt="for _ in fast(): pass", globals=globals(), number=50)
print(f"Composed nesting {comparison:.2f}s")

reduction = -(comparison - baseline) / baseline
print(f"{reduction:.1%} less time")

3.3. 自定义生成器#

当读取的文件过大时,直接使用生成器会返回空值,但不会返回异常。此时,可使用 __iter__() 方法。下例中, normalize 中的 sum() 调用了 ReadVisits.__iter__ 来分配一个新的迭代器对象。对数字进行标准化的 for 循环也调用 __iter__ 来分配第二个迭代器对象。这些迭代器中的每个都将独立地被推进和耗尽,以确保每个独特的迭代都能看到所有的输入数据值。这种方法唯一的缺点是它会多次读取输入数据。

def normalize(numbers):
    total = sum(numbers)
    result = []
    for value in numbers:
        percent = 100 * value / total
        result.append(percent)
    return result


def read_visits(data_path):
    with open(data_path) as f:
        for line in f:
            yield int(line)


it = read_visits("my_numbers.txt")
percentages = normalize(it)
print(percentages)
# []


class ReadVisits:
    self.data_path = data_path

    def __iter__(self):
        with open(self.data_path) as f:
            for line in f:
                yield int(line)


visits = ReadVisits(path)
percentages = normalize(visits)
print(percentages)
# [11.538461538461538, 26.923076923076923, 61.53846153846154]

3.4. 注入数据#

yield 表达式为生成器函数提供了一种简单的方式来产生一系列可迭代的输出值。但,这个通道似乎是单向的。在生成器运行的过程中,没有直接明显的方法可同时将数据流入和流出生成器。Python 生成器支持 send() 方法,它将 yield 表达式升级为双向通道。 send() 方法可用于在生成器输出的同时向它提供流式输入。

def gen():
    i = 0
    while i < 5:
        # temp 不保存 i,保存 send() 传入的结果
        temp = yield i
        print(temp)
        i += 1


g = gen()
# send() 不能给第一个值赋值,对第一个值,只能赋值 None
g.send(None)
g.send("ABC")

这个机制会导致一系列的问题,这时需要先构建迭代器,然后在其中使用生成器输出。

from math import pi


def wave_cascading(amplitude_it, steps):
    step_size = 2 * pi / steps

    for step in range(steps):
        radians = step * step_size
        fraction = sin(radians)
        amplitude = next(amplitude_it)  # Get next input
        output = amplitude * fraction
        yield output


def complex_wave_cascading(amplitude_it):
    yield from wave_cascading(amplitude_it, 3)
    yield from wave_cascading(amplitude_it, 4)
    yield from wave_cascading(amplitude_it, 5)


def transmit(output):
    if output is None:
        print(f"Output is None")
    else:
        print(f"Output: {output:>5.1f}")


def run_cascading():
    amplitudes = [7, 7, 7, 2, 2, 2, 2, 10, 10, 10, 10, 10]
    it = complex_wave_cascading(iter(amplitudes))
    for amplitude in amplitudes:
        output = next(it)
        transmit(output)


run_cascading()

4. 技巧#

4.1. 无穷#

# A. 根据年龄升序排序,没有提供年龄放在最后边
users = {"tom": 19, "jenny": 13, "jack": None, "andrew": 43}
sorted(users.keys(), key=lambda user: users.get(user) or float('inf'))
# ['jenny', 'tom', 'andrew', 'jack']

# B. 作为循环初始值,简化第一次判断逻辑
max_num = float('-inf')
# 找到列表中最大的数字
for i in [23, 71, 3, 21, 8]:
    if i > max_num:
        max_num = i

print(max_num)