控制流#
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...do
和 while...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()
的对象均是迭代器类型
可迭代类型 |
不可迭代类型 |
|
---|---|---|
作⽤于 |
可 |
不可 |
变量类型 |
四大容器 + 字符串 |
整型 |
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=[])
可用于并行迭代多个迭代器,将 x
, y
中对应索引元素配对生成一个列表。它创建了一个产生元组的懒惰生成器,因此它可用于无限长的输入。
当存在不同长度的迭代器,
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:构建函数,在
for
或while
循环中使用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)