流程控制#

视频

Python 不仅仅需要可以求值的 表达式函数,还需要一些结构用于表达循环和控制等。

Python 语句 就是告诉你的程序应该做什么的句子。

  • 程序由模块构成。

  • 模块包含语句。

  • 语句包含表达式。

  • 表达式建立并处理对象。

真值测试#

  • 所有的对象都有一个固有的布尔值:真或假。

  • 任何非零的数字或非空的对象都是真。

  • 0、空对象和特殊对象 None 被视为假。

  • 比较和相等测试是递归地应用于数据结构。

  • 比较和相等测试返回 TrueFalse

  • 布尔运算符 andor 返回一个真或假的操作对象。

  • 一旦知道结果,布尔运算符就会停止评估(“短路”)。

真值判定

结果

X and Y

如果 XY 都为真,则为真。

X or Y

如果 XY 为真,则为真。

not X

如果 X 是假的,则为真。

比较、相等和真值#

  • == 操作符测试值的相等性。

  • is 表达式测试对象的一致性。

真值判断:

S1 = 'spam'
S2 = 'spam'

S1 == S2, S1 is S2
(True, True)

比较:

L1 = [1, ('a', 3)] 
L2 = [1, ('a', 3)]

L1 == L2, L1 is L2, L1 < L2, L1 > L2
(True, False, False, False)
bool('')
False

短路计算#

  • or: 从左到右求算操作对象,然后返回第一个为真的操作对象。

  • and: 从左到右求算操作对象,然后返回第一个为假的操作对象。

2 or 3, 3 or 2
(2, 3)
[] or 3
3
[] or {}
{}
2 and 3, 3 and 2
(3, 2)
[] and {}
[]
3 and []
[]

断言#

用于测试推断:

num = -1
assert num > 0, 'num 应该为正数!'
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[10], line 2
      1 num = -1
----> 2 assert num > 0, 'num 应该为正数!'

AssertionError: num 应该为正数!

if 条件#

year = 1990
if year % 4 == 0:
    if year % 400 == 0:
        print('闰年')
    elif year % 100 == 0:
        print('平年')
    else:
        print('闰年')
else:
    print('平年')
平年

使用 andor 的短路逻辑简化表达式:

year = 1990
if (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:
    print('闰年')
else:
    print('平年')
平年

if 的短路(short-ciecuit)计算:A = Y if X else Z

year = 1990
print('闰年') if (year % 4 == 0 and year % 100 != 0) or year % 400 == 0 else print('平年')
平年
't' if 'spam' else 'f'
't'

for 循环#

遍历序列对象:

for target in object:                 # 将对象项目分配给目标   
    statements                        # 循环体
  • pass / ...:空占位语句

for i in range(5):
    ...  # 等价于 pass
list(range(1, 10, 6))
[1, 7]
# 阶乘
x = 1
for i in range(1, 11):
    x *= i
print(f'10!={x}')
10!=3628800

Python 的 for 语句迭代列表或字符串等任意序列,元素的迭代顺序与在序列中出现的顺序一致。例如:

# 可以是 Python 的可迭代容器
seq = [1, 2, 3, 4, 5] 
for i in seq:
    print(i)
1
2
3
4
5

循环的技巧#

在序列中循环时,用 enumerate() 函数可以同时取出位置索引和对应的值:

for i, v in enumerate(['苹果', '相机', '飞机']):
    print(i, v)
0 苹果
1 相机
2 飞机

同时循环两个或多个序列时,用 zip() 函数可以将其内的元素一一匹配:

questions = ['名字', '缺点', '最喜爱的颜色']
answers = ['Judy', '比较懒', '天空蓝']
for q, a in zip(questions, answers):
    print(f'你的 {q} 是什么? 答案是 {a}。')
你的 名字 是什么? 答案是 Judy。
你的 缺点 是什么? 答案是 比较懒。
你的 最喜爱的颜色 是什么? 答案是 天空蓝。

逆向循环序列时,先正向定位序列,然后调用 reversed() 函数:

for i in reversed(range(1, 10, 2)):
    print(i)
9
7
5
3
1

按指定顺序循环序列,可以用 sorted() 函数,在不改动原序列的基础上,重新返回一个序列:

basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
for i in sorted(basket, key=len):
    print(i)
pear
apple
apple
orange
orange
banana

使用 set() 去除序列中的重复元素。使用 sorted()set() 则按排序后的顺序,循环遍历序列中的唯一元素:

basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
for f in sorted(set(basket)):
    print(f)
apple
banana
orange
pear

序列和其他类型的比较#

序列对象可以与相同序列类型的其他对象比较。这种比较使用 字典式 顺序:

  • 首先,比较首个元素,如果不相等,则可确定比较结果;如果相等,则比较之后的元素,以此类推,直到其中一个序列结束。

  • 如果要比较的两个元素本身是相同类型的序列,则递归地执行字典式顺序比较。

  • 如果两个序列中所有的对应元素都相等,则两个序列相等。

  • 如果一个序列是另一个的初始子序列,则较短的序列可被视为较小(较少)的序列。

  • 对于字符串来说,字典式顺序使用 Unicode 码位序号排序单个字符。

下面列出了一些比较相同类型序列的例子:

(1, 2, 3) < (1, 2, 4)
True
[1, 2, 3] < [1, 2, 4]
True
'ABC' < 'C' < 'Pascal' < 'Python' # 支持链式比较
True
(1, 2, 3, 4) < (1, 2, 4)
True
(1, 2) < (1, 2, -1)
True
(1, 2, 3) == (1.0, 2.0, 3.0)
True
(1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)
True

while 循环#

while 循环结构:

初值条件
while test:  # 循环测试
    statements  # 循环体
x = 'spam'
while x: # 直至耗尽 x
    print(x, end=' ')
    x = x[1:]
spam pam am m 
x = 1  # 初值条件
while x <= 100:  # 终止条件
    print(x)
    x += 27
1
28
55
82

Callataz 猜想#

备注

任意取一个正整数 \(n\),如果 \(n\) 是一个偶数,则除以 \(2\) 得到 \(n/2\); 如果 \(n\) 是一个奇数,则乘以 \(3\)\(1\) 得到 \(3n+1\),重复以上操作,我们将得到一串数字。

Collatz 猜想:任何正整数 \(n\) 参照以上规则,都将回归 \(1\)

def collatz_guess(num):
    assert num > 0, 'num 必须为正数'
    while num != 1:
        if num % 2 == 0:
            # 保证 num 在接下来的运算为整数
            num //= 2
        else:
            num *= 3 
            num += 1
    return num

collatz_guess(75)
1

斐波那契数列#

备注

斐波那契数列:

\[ \begin{cases} f_0 = f_1 = 1\\ f_{n+2} = f_{n} + f_{n+1}, & n \in \mathbb{N} \end{cases} \]
def fib(n): # 写出斐波那契数列,直到n
    """打印直到 n 的斐波那契数列"""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 

备注

  1. 第一行中的 多重赋值:变量 ab 同时获得新值 01

  2. 最后一行又用了一次多重赋值,这体现在右表达式在赋值前就已经求值了。右表达式求值顺序为从左到右。

continue#

continue:跳到最近所在循环的开头处(来到循环的首行)

x = 10
while x:
    x -= 1
    if x % 2 != 0:
        continue  # 跳过打印
    print(x, end=' ')
8 6 4 2 0 
for num in range(2, 8):
    if num % 2 == 0:
        print(f"{num} 是偶数")
        continue
    print(f"{num} 是奇数")
2 是偶数
3 是奇数
4 是偶数
5 是奇数
6 是偶数
7 是奇数

else 子句#

  • break:跳出所在的最内层循环(跳过整个循环语句)

  • else:只有当循环正常离开时才会执行(也就是没有碰到 break 语句)

和循环 else 子句结合,break 语句通常可以忽略所需要的搜索状态标志位。

def fator(y):
    '''仅仅打印 y 的首个因子'''
    x = y // 2
    while x > 1:
        if y % x == 0:
            print(y, '有因子', x)
            break
        x -= 1
    else:  # 没有碰到 break 才会执行
        print(y, '是质数!')
        
fator(7), fator(88), fator(45);
7 是质数!
88 有因子 44
45 有因子 15

看一个更复杂的例子:

for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            print(n, '=', x, 'x', n//x)
            break
    else:
        # 循环失败,没有找到一个因子
        print(n, '是质数!')
2 是质数!
3 是质数!
4 = 2 x 2
5 是质数!
6 = 2 x 3
7 是质数!
8 = 2 x 4
9 = 3 x 3