yuque_diagram (11).jpg

第3章 流程控制 - 图7

1. 常用运算

Python支持比较(关系)运算、成员运算、逻辑运算、真值测试等运算形式。

比较运算

比较运算符用于比较两个值,并确定他们之间的关系,结果是一个逻辑值:True 或 False。
Python 中有 8 种比较运算
2种一致性比较(==、!=):比较值,不同对象值可能相同,如5==5.0,结果为True
4种次序比较 (<、>、<=、>=):比较值
2种标识号比较(is 和 is not):比较是否相同对象,如5 is 5.0结果为False,推荐id(5) == id(5.0)
他们的优先级相同,比布尔运算优先级高。
比较运算符可以连续使用,例如: x < y <= z 相当于同时满足条件 x < y 和 y <= z,即 x < y 且 y <= z。

  1. score = int(input())
  2. if 0 <= score <= 100: # if 0 <= score and score <= 100:
  3. print('这是合法成绩')
  4. if score >= 60:
  5. print('pass')

运算符 is 和 is not 用于检测对象的标识号是否相同,也就是比较两个对象的存储单元是否相同

运算符 描述 实例(设a = 5, b = 10)
== 等于:比较a、b两个对象是否相等 (a == b) 返回值 False
!= 不等于:比较a、b两个对象是否不相等 (a != b) 返回值 True
> 大于:返回a是否大于b (a > b) 返回值 False
< 小于: 返回a是否小于b。返回1表示真,返回0表示假。这分别与特殊的变量True和False等价 (a < b) 返回值 True
>= 大于等于:返回a是否大于等于b (a >= b) 返回值 False
<= 小于等于: 返回a是否小于等于b (a <= b) 返回值 True
is is 是判断两个标识符是不是引用自一个对象 c = 10
print(c is b) 返回值 True
is not is not 是判断两个标识符是不是引用自不同对象 print(a is not b) 返回值 True

成员运算

运算符 in 和 not in 用于成员检测。如果 x 是 s 的成员则 x in s 值为 True,否则为 False。
对于字符串和字节串类型来说,当且仅当 x 是 y 的子串时 x in y 为 True。空字符串总是被视为任何其他字符串的子串,因此 “” in “abc” 将返回 True。

  1. obj [not] in sequence
  2. # sequence 可以是字符串,列表、元组、range、字典的键、集合等
letter = input()                          # 输入一个字符
lowercase = 'abcdefghijklmnopqrstuvwxyz'  # 所有小写字母
if letter in lowercase:                   # 存在性测试,输入字符在字符串中存在时
    print('这是小写字母')                  # 输出'这是小写字母'
if letter not in lowercase:
    print('这不是小写字母')

布尔运算

Python 语言支持逻辑运算符,包括“and(与)”、“or(或)”、“not(非)”运算。
在执行布尔运算或当表达式被用于流程控制语句时,以下值会被解析为False:
False、None
所有类型的数字零
空字符串和空容器(包括字符串、元组、列表、字典、集合与冻结集合)
所有其他值都会被解析为True。
表 3.3 布尔运算符

运算符 表达式 功能描述
or x or y 首先对表达式 x 求值,如果值为True则返回 x的值,否则对表达式y求值并返回其结果值。
and x and y 首先对表达式 x 求值,如果值为False则返回 x的值,否则对表达式y求值并返回其结果值。
not not x 表达式 x 值为 False时返回 True,否则返回 False。
print(10 and 'hello')  # 输出 hello,非空字符串,布尔值为True
print(10 or 'hello')   # 输出整数 10,非0,布尔值为True
print(10 or '')   # 输出整数 10,非0,布尔值为True
print(10 and '')  # 输出'',空字符串,布尔值为False

if 10 and '':     # 10 and ''布尔运算的值是空字符串,条件运算结果为False
    print(True)   # 当10 and ''布尔运算值是True时,输出True
else:             # 否则
    print(False)  # 输出 False,执行此分支
birthdate = input('请输入出生日期: ') or '保密'
print(birthdate)                    # 无输入,直接回车时,输出'保密'

解释器先对逻辑运算符左侧的操作数进行运算这种特性称为短路特性。
当发生短路之后,该语句短路处之后的所有代码都不会被执行。
短路特性可以有效的提高效率。
把容易判断、计算量较小的表达式放在逻辑运算符的左侧,可以减少不必要的运算,提高算法效率。
例如判断一个数是否是回文素数时,将判断回文表达式str(i) == str(i)[::-1]放在运算符左侧,当判断不是回文时,不再执行右侧判定素数函数prime(i)。因判定素数的计算量较大,这样设计可以极大的降低运算量,提高效率。

    # 利用短路提高效率
    if str(i) == str(i)[::-1] and is_prime(i): # 字符串i是回文,数值i素数
        print(i)

判断回文素数的完整程序如下(第4-5章详细讲):

def is_prime(n):
    """接收正整数n,判断是否为素数,返回布尔值"""
    if n < 2:
        return False       # 0和1不是素数
    for i in range(2, n):  # 遍历(2, n-1)中的数
        if n % i == 0:     # 若在(2, n-1)中存在因子则不是素数
            return False   # 不是素数时返回False
    else:   # for语句遍历(2, n-1)中所有数,未发现因子存在时,才是素数
        return True        # 素数时返回True

for i in range(20000):
    # 利用短路提高效率
    if str(i) == str(i)[::-1] and is_prime(i): # 字符串i是回文,数值i素数
        print(i)
year = int(input())
if year % 4 == 0 and year % 100 != 0 or year % 400 == 0:
    print(f'{year}年是闰年')
else:
    print(f'{year}年不是闰年')

逻辑运算符 or、and 和 not 中,not 优先级最高,or 最低,按优先级升序排序为 or < and < not。

print(1 or 0 and 2 )     # 输出 1

由于 or 优先级最低,最后参与运算,先计算0 and 2的值,整个表达式等价于在0 and 2上加括号:

print(1 or (0 and 2))    # 输出 1

在同一个表达式中同时出现 and 和 or 时,建议用加小括号的方法明确顺序,这样可以更准确的表达逻辑顺序,同时提高程序的可读性和易维护性。

真值测试

利用if或while条件表达式或通过逻辑运算,任何对象都能被用于真值测试。
真值测试的结果只有True和False,True 和 Fasle 可以分别以整数“0”和“1”直接参与数学运算。

print(True == 1)             # 输出True
print(False == 0)            # 输出True
print(1 == True)             # 输出True
print(0 == False)            # 输出True
print(5 + True)              # 5 + 1,输出值为6
print(True + False)          # 1 + 0,输出值为1
print(True * 5 + False * 2)  # 1 * 5 + 0 * 2,输出值为5

Python中,除以下几种情形以外的值均为True:
1. 定义常量为 None 或者 False。
2. 任何数值类型的0,包括: 0、0.0、0j、Decimal(0)、 Fraction(0, 1))。
3. 空序列、空字典、空集合、空对象等,如: (), [], {}, set(), range(0)。
None是一个特殊的常量,起到空的占位作用,他有自己的数据类型NoneType。None不是False、不是0、不是空字符串。
Python中一切都是对象,一切空对象的值均为False,所有非空对象均为True。

from decimal import Decimal     # decimal模块中的小数函数Decimal
from fractions import Fraction  # fractions模块中的分数函数Fraction

print(bool(Fraction(0, 1)))     # Fraction(0,1)表示分子为0分数,0/1
print(bool(Decimal(0)))         # Decimal(0) 表示0
print(bool('hello'))            # 非空字符串,True
print(bool(100))                # 非0数字,True
print(bool(0.0))                # 浮点数0.0与数字0等值,False
print(bool(' '))                # 空格是非空字符串,True
print(bool(''))                 # 空字符串,False
print(bool([]))                 # 空列表,False
print(bool(None))               # None类型,False

运算优先级

运算符的优先级的描述如表 3.5 所示。
表 3.5 运算符的优先级(由高到低排列)

序号 运算符 描述
1 ()、[]、{} 括号表达式,元组、列表、字典、集合显示
2 x[i],x[m:n] 索引、切片
3 ** 幂运算
4 ~ 按位翻转
5 +x、 -x 正、负
6 *、 / 、% 乘法、除法与取模
7 + 、- 加法与减法
8 << 、>> 移位
9 & 按位与
10 ^ 按位异或
11 | 按位或
12 <、<=、 >、 >=、 !=、 ==、
is、 is not、in、not in
比较运算符、标识符测试、成员测试
13 not x 逻辑非
14 and 逻辑与运算符
15 or 逻辑或运算符
16 if…else 条件表达式
17 lambda lambda表达式
18 := 赋值表达式、海象运算符
print(4 * 2 ** 3)      # 先幂运算,再计算乘法,输出:32
print(3 + 4 * -2)      # 2先取反、与4相乘,再做加法,输出:-5
print(3 + 4 * 2 / 2)   # 先计算乘除法,除法运算结果为浮点数,输出:7.0
print(3 << 2 + 1)      # 加法优先级高,先2+1,3(11)左移3位变成24 (11000)


括号的优先级最高,可以强制表达式按照需要的顺序求值。可以通过加入小括号“()”的方法来提供弱优先级的优先执行。加了括号,无需比较哪个优先级更高,使程序和表达式更加易于阅读和维护。

实例3.1 判断是否直角三角形

输入三个数a,b,c, 判断能否以它们为三个边长构成直角三角形。若能,输出YES,否则输出NO。

a = eval(input())
b = eval(input())
c = eval(input())
# 三角形判定条件
# 任意一边都应为正数,任意两边和大于第三边
if a<=0 or b<=0 or c<=0 or (a+b)<=c or (a+c)<=b or (b+c)<=a :
    print('NO')
# 直角三角形条件,两边平方和等于第三边
elif a * a + b * b ==c * c or a * a + c * c ==b * b or c * c + b * b ==a * a:
    print('YES')
else:
    print('NO')

代码行数换逻辑简化

# 先找出最长边和最短边
shortest = min(a, b, c)
longest = max(a, b, c)
middle = sum([a, b, c]) - shortest - longest
# 三角形判定条件
if shortest <= 0 or shortest + middle <= longest:
    pass  # 代码略
# 直角三角形条件,两边平方和等于第三边
elif shortest ** 2 + middle ** 2 == longest ** 2:
    pass  # 代码略
else:
    pass  # 代码略

2. 循环结构

能力点1:确定次数循环

可迭代对象:range,文件对象,列表,元组,字典、集合,生成器
第3章 流程控制 - 图8

for i in range(n):  # 文件对象,列表,元组,字典、集合,生成器
    语句块          # 缩进四个空格或按一次tab键

实例3.2 输出乘法表

在一行内输出11到19的结果

for i in range(1, 10):
    print(f'{1}x{i}={1 * i}', end=' ')
# 1x1=1 1x2=2 1x3=3 1x4=4 1x5=5 1x6=6 1x7=7 1x8=8 1x9=9

在9行内输出11到19的结果,外面加一层循环,多一层缩进

for i in range(1,10):
    for j in range(1, 10):
        print(f'{j}x{i}={j * i}', end=' ')  # 在同一行内输出
    print()                                 # 内层循环结束后换行
1x1=1 2x1=2 3x1=3 4x1=4 5x1=5 6x1=6 7x1=7 8x1=8 9x1=9 
1x2=2 2x2=4 3x2=6 4x2=8 5x2=10 6x2=12 7x2=14 8x2=16 9x2=18 
1x3=3 2x3=6 3x3=9 4x3=12 5x3=15 6x3=18 7x3=21 8x3=24 9x3=27 
1x4=4 2x4=8 3x4=12 4x4=16 5x4=20 6x4=24 7x4=28 8x4=32 9x4=36 
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25 6x5=30 7x5=35 8x5=40 9x5=45 
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36 7x6=42 8x6=48 9x6=54 
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49 8x7=56 9x7=63 
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64 9x8=72 
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81

输出出下三角

for i in range(1,10):
    for j in range(1, i+1):           # 内层循环次数等于i
        print(f'{j}x{i}={j * i}', end=' ')
    print()
1x1=1 
1x2=2 2x2=4 
1x3=3 2x3=6 3x3=9 
1x4=4 2x4=8 3x4=12 4x4=16 
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25 
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36 
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49 
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64 
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81

考虑输出格式对齐,用{:2}控制输出宽度

for i in range(1,10):
    for j in range(1, i+1):
        print(f'{j}x{i}={j * i: 2}', end=' ')
    print()

能力点2:不确定次数循环

2.1 while循环

无法确定程序应该执行多少次时用while循环

while 判断条件:
    语句块

实例3.3 猜数游戏

输入一个不超过1000的整数,输出自己猜测的结果,猜中时输出“你猜中了”,然后结束程序

num = int(input())  # 不超过1000的整数
while True:
    guess = int(input())
    if guess == num:
        print('你猜中了')
        break

实例3.4 正整数a+b

# 产生10道正整数加法的题目,用户输入计算结果,判断输入正确与否
for i in range(10):   # 循环10次,每次产生一个新问题
    a = int(input())  # 输入字符串'5',转整数5
    b = int(input())  # 输入字符串'10',转整数10
    print(f'{a}+{b}=', end='')  # 5+10=15,格式化输出
    result = int(input())  # 用户输入计算结果
    if result == a + b:
        print('恭喜你,回答正确')
    else:
        print('回答错误,你要加油哦!')

两种循环语句:
第一种是for循环语句。一般用于循环次数可确定的情况下,一般也被称为遍历循环。
第二种是while循环语句。一般用于循环次数不确定的情况下,通过判断是否满足某个指定的条件来决定是否进行下一次循环,也称为条件循环语句。
while循环还有一个重要的应用就是构造无限次循环,在循环体中使用if语句判定是否达到结束循环的条件,当满足条件时,用break语句终止循环。
此时需要注意,在程序执行过程中,务必要有使if 语句中的表达式结果趋于False的语句,以避免程序陷入无限循环。

2.2 for循环

for 语句可以依据可遍历结构中的子项,按他们的顺序进行迭代。
这些可遍历结构包括:range()、字符串、列表和文件对象等可遍历(可迭代)数据类型和文件对象。
在遍历列表、集合和字典等可变数据类型时,一般不可在循环中对遍历对象进行增加、删除等改变对象长度的操作,避免出现遍历不完全或循环无法正常结束等问题。

for 循环变量  in 可遍历结构:
    语句块
else:
    语句块

程序执行时,从可遍历结构中逐一提取元素,赋值给循环变量,每提取一个元素执行一次语句块中的所有语句,总的执行次数由可遍历结构中元素的个数确定。
else 部分可以省略,这部分语句只在循环正常结束时被执行,如果在循环语句块中遇break语句跳出循环或遇到return语句结束程序,则不会执行else 部分。
四种可遍历结构中,应用最广泛的是第一种,应用range()函数控制循环次数,本章主要学习这种方法。

# i是普通变量名,仅用于控制循环次数
# range(n)可生成0到n-1的序列, i依次取值0到n-1的数字,循环n次
for i in range(n):
    语句块

能力点3:获得整数序列

2.3 Range

在Python 中,Range是一种数据类型,表示一个不可变的等差数列(算术级数)

range(stop)          # 0, 1, 2, 3, 4, …stop-1,初值为0,步长为1的等差数列
range(start, stop[, step]) # start, start+step, start+2*step…,步长为step的等差数列

range生成的内容r[i] = start + step * i,当step为正数,要求i>=0且r[i] =0且 r[i] > stop。
range()具有一些特性:
1. start、stop、step都必须是整数,可以是负数,但不能是浮点数等其它类型。
2. 如果start参数缺省,默认值为0;如果step参数缺省,默认值为1
3. 当step是正整数时,产生的序列递增;当step为负整数时,生成的序列递减。
4. range()函数产生一个左闭右开的序列,如range(4)生成一个序列:0, 1, 2, 3。
5.它是不可变的序列类型,可以进行判断元素、查找元素、切片等操作,但不能修改元素;
6.它是可迭代对象,却不是迭代器。

print(range(5))               # 输出range(0, 5)
print(list(range(5)))         # 输出[0, 1, 2, 3, 4]
print(*range(5))              # 0 1 2 3 4
print(*(range(100)))          # 整数递增等差数列0 1 2 3 4 5... 99
print(*(range(0,100,2)))      # 偶数0 2 4 6... 98
print(*(range(1,100,2)))      # 奇数1 3 5 7... 99
print(*range(3, 100, 3))      # 3 6 9 12 ...93 96 99
print(*range(0, 100, 5))      # 0 5 10 ... 85 90 95
print(*range(100, -1, -5))    # 整数递减数列100 95 90...10 5 0

for i in range(5):
    print(i,end = ' ')        # 0 1 2 3 4
print(tuple(range(0,-5,-1)))  # (0, -1, -2, -3, -4)
print(list(range(1, 11)))     # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(list(range(0, 40, 5)))  # [0, 5, 10, 15, 20, 25, 30, 35]
print(list(range(0)))         # range(0)输出[]
print(list(range(1,0)))       # 步长为1,stop<start时,输出[]

range()函数相对于列表和元组的优点在于占用内存固定,占用内存较小,他仅存储start、stop和step值,在需要时通过计算生成序列中的每个值。
range([start, ]stop[, step])常与for和控制变量一起使用,用于遍历range()生成的对象并控制循环的次数。start和 step是可选参数,缺省时,start=0, step=1。

for <variable> in range([start,]stop[,step]):
    <语句块>
my_sum = 0                # 设定初值为0,用于累加
for i in range(5, 10):    # range()生成序列5,6,7,8,9,依次赋值给i
    my_sum = my_sum + i   # 每次循环将新的i值加到my_sum上
print(my_sum)             # 输出my_sum的值 35

# Python写法
print(sum(range(5, 10)))  # 35, pythonic风格

变量my_sum 在循环体中的赋值号右侧出现,所以在进入循环之前应该先创建一个对象0作为my_sum 的初值。

在Python中,等差数列n 项和的问题也可以直接用sum()函数结合range()函数来实现。range(1,n+1) 可生成1,2,……,n的序列,将range()函数直接作为sum()函数的参数,可以直接获得range()函数所生成序列中全部元素的和。

n = int(input())             # 将输入的字符转成整型,10
print(sum(range(1, n + 1)))  # sum()函数可返回序列参数的元素的和,55


print(sum(range(1, int(input()) + 1)))  # sum()函数可返回序列参数的元素的和

改变range()函数中start、stop、step的值,便可以计算不同等差数列中连续n项和了。如:

print(sum(range(50, 80, 5))) # 序列50 55 60 65 70 75的和为375

修改my_sum = my_sum + i 为 product = product * i 就可以计算前n项的积,用于求前n项的积时,变量product的初值应该赋值为1。

product = 1                 # 设定初值为1,用于累加
for i in range(5, 11, 2):   # range()生成序列5,7,9,依次赋值给i
    product = product * i   # 每次循环将新的i值乘到product上
print(product)              # 输出product的值 315

math库中有一个用于计算阶乘的函数factorial(n)。

# 用math库中的math.factorial(n)函数直接返回阶乘结果
import math

print(math.factorial(6))    # factorial() 为计算阶乘的函数,720

能力点4:获得文件中的数据

2.4 遍历文件

实例3.5 遍历读文件

第3章 流程控制 - 图9
苏幕遮.txt

苏幕遮 范仲淹
碧云天,黄叶地,秋色连波,波上寒烟翠。
山映斜阳天接水,芳草无情,更在斜阳外。                            
黯乡魂,追旅思。夜夜除非,好梦留人睡。
明月楼高休独倚,酒入愁肠,化作相思泪。
poem = open('../data/txt/苏幕遮.txt','r',encoding='utf-8')
for line in poem:
    print(line)
poem.close()
with open('../data/txt/苏幕遮.txt','r',encoding='utf-8') as poem:
    for line in poem:
        print(line)
苏幕遮 范仲淹

碧云天,黄叶地,秋色连波,波上寒烟翠。

山映斜阳天接水,芳草无情,更在斜阳外。                            

黯乡魂,追旅思。夜夜除非,好梦留人睡。

明月楼高休独倚,酒入愁肠,化作相思泪。

实例3.6 利用文件中的数据计算

his.txt

95
68.25
99
100
...
94
85.5
88

计算平均成绩

sum_of_score = 0          # 总成绩初值为0
num = 0                   # 成绩数量初值
score = open('../data/txt/his.txt', 'r', encoding='utf-8')
for line in score:        # 遍历文件,每行数据转浮点数累加得到总成绩
    sum_of_score = sum_of_score + float(line)
    num = num + 1

avg = sum_of_score / num  # 计算平均成绩
print(round(avg, 2))      # 79.66,保留2位小数
score.close()             # 关闭文件

统计不及格人数:

score = open('../data/txt/his.txt', 'r', encoding='utf-8')
num = 0                   # 不及格人数初值为0
for line in score:        # 遍历文件,每行为一个成绩,字符串
    if float(line) < 60:  # 成绩低于60为不及格
        num = num + 1     # 不及格人数加1
print(num)                # 输出不及格人数 34
score.close()             # 关闭文件

2.5 算法

2.5.1:迭代算法

迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程
最常见的迭代法是牛顿法。其他还包括最速下降法、共轭迭代法、变尺度迭代法、最小二乘法、线性规划、非线性规划、单纯型法、惩罚函数法、斜率投影法、遗传算法、模拟退火等等。 
利用迭代算法解决问题的三步:

  1. 确定迭代变量,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。
  2. 建立迭代关系式,从变量的前一个值推出其下一个值的公式(或关系),通常可以顺推或倒推的方法来完成。
  3. 对迭代过程进行控制,设定终止条件

    实例 3.7 兔子繁殖问题

    兔子从出生后第3个月起每个月都会生一对兔子,小兔子成长到第三个月后每个月又会生一对兔子。初始有一对小兔子,假如兔子都不死,用户输入一个月份数,计算并在一行内输出从1到n月每个月的兔子数量。

    图 3.3 兔子繁殖规律.png
    图 3.3 兔子繁殖规律

各月的兔子数量形成的数列是:
1,1,2,3,5,8,13,……
斐波那契数列以如下被以递推的方法定义:
F(1)=1
F(2)=1
F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N*)
计算a,b指向数据的和作为新的一项,同时将变量b指向新产生的数据项,将变量a指向倒数第二项,也就是原来变量b指向的那个数据,这个操作可以用同步赋值语句a,b=b,a+b实现。
3.4 变量同步赋值.png
图 3.4 变量同步赋值

n = int(input())       # int()将input()接收的字符串转整数,例如输入12
a, b = 1, 1            # 设定数列前两项的初值
for i in range(n):     # i 只用于控制循环次数,n值为12
    print(a, end=' ')  # 每次循环输出一个值,不换行
    a, b = b, a + b    # 迭代,b的值赋给a,把a,b的和赋值给b
# 1 1 2 3 5 8 13 21 34 55 89 144

实例3.8 迭代法开平方

import math


x = int(input())             # 输入整数
x1 = x / 2                   # 初值
x2 = (x1 + x / x1) / 2       # 迭代公式
while abs(x1 - x2) > 1e-5:   # 迭代精度
    x1 = x2
    x2 = (x1 + x / x1) / 2   # 迭代公式
print(x2)                    # 2.23606797749979
print(math.sqrt(x))          # 2.23606797749979

实例3.9 二分法猜数游戏

import random

x = random.randint(1, 1024)

算法:

  1. 随机产生一个给定范围内整数,[1,1024]
  2. 循环10次猜数
    1. 输入猜的整数
    2. 如果猜中
      1. 输出猜 中了
      2. 终止循环
    3. 否则如果猜的数大于产生的数
      1. 输出猜的数大了
    4. 否则
      1. 输出猜的数小了
  3. 其10次没猜中,输出你没猜中 ```python import random

x = random.randint(1, 1024) # 随机产生一个0-1024之间的整数 for i in range(10): # 允许猜10次 guess = int(input()) # 输入猜的整数 if guess == x: print(‘猜中了’) # 猜中则终止循环 break elif guess > x: print(‘猜的数大了’) else: print(‘猜的数小了’) else: print(‘你没猜中哦’)

<a name="rzAZk"></a>
## 2.5.2: 枚举法:
枚举算法的思想是:<br />将问题的所有可能的答案一一列举,然后根据条件判断此答案是否合适,保留合适的,舍弃不合适的。<br />基本思路如下:<br />(1)确定枚举对象、范围和判定条件。<br />(2)逐一枚举可能的解并验证每个解是否是问题的解。<br />算法步骤:<br />(1)确定解题的可能范围,不能遗漏任何一个真正解,同时避免重复。<br />(2)判定是否是真正解的方法。<br />(3)为了提高解决问题的效率,使可能解的范围将至最小

<a name="f3whI"></a>
## 应用实例:皮箱密码破解
![image.png](https://cdn.nlark.com/yuque/0/2022/png/1157448/1647180725721-4b6a386d-69ac-4b98-bce3-7009f6794d45.png#clientId=uf8ef049f-ffd0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=437&id=ue2674f76&margin=%5Bobject%20Object%5D&name=image.png&originHeight=632&originWidth=1009&originalType=binary&ratio=1&rotation=0&showTitle=false&size=100373&status=done&style=none&taskId=ue31619bf-bc8e-49ab-b3e2-cbf60a0bf01&title=&width=697)

1. 先固定第1位为0
   2. 第2位设0
      1. 第3位从0试到9
   3. 重复b并依次设1-9

2.重复1并依次设第1位为1-9

```python
import random  # 导入随机数模块

keys = random.randint(100, 999)  # 随机产生一个三位整数
print(keys)  # 查看当前生成的随机数,每次结果不同,此处设为651

for i in range(10):          # 猜测百位上的数字
    for j in range(10):      # 猜测十位上的数字
        for k in range(10):  # 猜测个位上的数字
            # 三个数字拼接为一个三位整数,若此三位数与随机产生的相同,则找到密码
            if i * 100 + j * 10 + k == keys:  
                print(f'密码是{i}{j}{k}')      # 密码是651

实例 3.10 百钱买百鸡

image.png第3章 流程控制 - 图13
暴力枚举法
也叫做穷举法,条件范围不大的时候就可以用枚举法

for cock in range(1, 101):                 # 公鸡数量不为0且小于或等于100
    for hen in range(1, 101):              # 母鸡数量不为0且小于或等于100
        for chicken in range(3, 101, 3):   # 小鸡数量大于0小于等于100且是3的倍数
            # 鸡的总数为100,钱的总数为100
            if cock + hen + chicken == 100 and 5 * cock + 3 * hen + chicken // 3 == 100:
                print(cock, hen, chicken)  # 遇到满足条件的数字组合就输出

33万次运算,如果数值很大的话,这个运算就会很慢,所以我们可以用“剪枝”这个方法
剪枝的思想就是剪掉不可能出现的情况,避免计算机多余的运算

#方法2:缩小问题规模,减少循环层数
for cock in range(1, 21):           # 公鸡数量不超过20
    for hen in range(1, 34):        # 母鸡数量不超过33
        chicken = 100 - cock - hen  # 小鸡数量可由公鸡和母鸡数量计算得到
        if chicken % 3 == 0 and 5 * cock + 3 * hen + chicken // 3 == 100:
            print(cock, hen, chicken)

输出:
4 18 78
8 11 81
12 4 84
从结果中可以发现这样的一个规律:公鸡是4的倍数,母鸡是7的递减率,小鸡是3的递增率,为了确认这一规律,推导一下这个不定方程:
x + y + z = 100
5x + 3y + z/3 = 100
消去z可得:7x + 4y = 100
由此可得:
y = 25 - (7/4)x
z = 75 + (3/4)x
因为0 < y < 100,且是自然数,则可得知 x 必为4的整数倍的正整数才能保证y 和 z 都是整数; x 最大值必小于16 才能保证y 值为正数,所以x值只能取4、8、12。这样只循环3次就可以找到所有可能的解,可以继续优化代码提高效率:

for cock in range(4, 16, 4):    # cock 初值为4,小于16,步长为4
    hen = 25 - cock // 4 * 7    # 整除“//”符号保证运算结果仍为整数
    chicken = 75 + cock // 4 * 3
    print(cock, hen, chicken)

实例 3.11 计算圆周率

用π/4=1-1/3+1/5-1/7+…+1/(2n-1)公式可以计算圆周率求π的近似值,直到最后一项的绝对值小于10-6为止。
公式中每一项的分母数字正好相差2,符号正负交替,可以利用循环结构求解,因循环次数不确定,可用while关键字实现。

pi, i = 0, 1               # 设定初值,i 用于改变分母,f用于改变符号
sign = 1                   # sign用于改变符号
while 1 / i >= 1e-6:       # 最末项大于等于10-6时执行循环体中的语句
    pi = pi + sign * 1 / i # 累加,每次循环加上最后一项
    sign = - sign          # 每循环一次改变一次符号,实现正负项交替
    i = i + 2              # 每个分母的数字相差2
print(pi * 4)              # 公式是π/4,乘4得以π的计算值

while关键字也常用于构造无限循环,while后面加True或一个结果为True的表达式,使循环判断条件永远为True,此时循环可以无限的执行下去,除非在循环体内遇到break语句或return语句才能终止循环

# 用while True构建无限循环,当最后一项小于或等于给定值时结束循环
pi, i = 0,1                # 设定初值,pi为圆率初值,i为分母
sign = 1                   # sign用于改变符号
while True:
    if 1 / i < 1e-6:       # 当最后一项小于10-6时结束循环
        break              # 略过当前循环未执行的次数,提前终止循环  
    pi = pi + sign * 1 / i # 累加,每次循环加上最后一项
    sign = - sign          # 每循环一次改变一次符号,实现正负项交替
    i = i + 2              # 每个分母的数字相差2
print(pi * 4)              # 公式是π/4,乘4得到π的值

3. 分支结构

3.1 if…elif…else关键字

Python语言用if…elif…else关键字或其组合来选择要执行的语句。分支语句的基本结构如下:

if 条件测试表达式1:
    语句块1
elif 条件测试表达式2:
    语句块2
elif 条件测试表达式3:
    语句块3
    ……
else:
    语句块n

if…elif…else语句将整个区间分为若干个区间,当满足其中某一个区间的条件时,一定不会再满足后续的其他条件,程序即终止判定。

image.png
图 3.5 分支结构示意图
if、elif和else是Python中分支结构的关键词,分支结构由1个if、0个或多个elif和1个或零个else组成。
由这三个关键词开始的语句最后以冒号结束
同时要求其后面满足该条件时要执行的语句块缩进
同一组分支结构里的每个关键词必须要对齐。
if 和 elif 子句后都有条件测试表达式,当表达式的值为真(非0数字、非空对象或True)时执行对应的语句块。
else 子句后面无条件测试表达式,直接以冒号结束。

实例 3.12 百分制分数转五分制

输入一个整数,当输入不在[0,100]区间时输出提示“Data error!”;当输入满足要求的前提下,用多分支结构实现百分制转五分制。实现规则是根据分数所在区间[0,60)、[60,70)、[70,80)、[80,90)、[90,100],分别输出字符“E”、“D”、“C”、“B”、“A”。

# 用多分支将输入的百分制整数转五分制的字符
score = int(input())
if score > 100 or score < 0:  # 不合理分数时输出“Data error!”
    print('Data error!')
elif score >= 90:             # 区间为90 =< score <= 100
    print('A')
elif score >= 80:             # 区间为80 =< score < 90
    print('B')
elif score >= 70:             # 区间为70 =< score < 80
    print('C')
elif score >= 60:             # 区间为60 =< score < 70
    print('D')
else:                         # 区间为0 =< score < 60
    print('E')

实例3.13 鸡兔同笼

‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬
‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪大约在1500年前,《孙子算经》中就记载了这个有趣的问题。书中是这样叙述的: 今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何? 这四句话的意思是: 有若干只鸡兔同在一个笼子里,从上面数,有35个头,从下面数,有94只脚。问笼中各有多少只鸡和兔?‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‪‬
请编一个程序,用户在同一行内输入两个整数,代表头和脚的数量,编程计算笼中各有多少只鸡和兔,假设鸡和兔都正常,无残疾。如无解则输出Data Error!

第3章 流程控制 - 图15image.pngimage.png

head, foot = 35, 94                       # 两个数按顺序分别命名为head和foot
# head, foot = map(int, input().split())  # 输入用空格分隔的两个数,切分开,映射为整数,分别命名
rabbit = foot // 2 - head                 # 计算兔数量,半其足,除去头的数量即为兔的数量
chicken = head - rabbit                   # 鸡的数量
print(chicken, rabbit)
head, foot = map(int, input().split())  # 格式化输入
# 确认头和脚的数量都是非负数,并且所有动物抬起两只脚之后地上的脚的数量还是非负偶数
if head >= 0 and foot >= 0 and (foot - head * 2) % 2 == 0 and foot - head * 2 >= 0:
    print(head - (foot - head * 2) // 2, (foot - head * 2) // 2)
else:
    print('Data Error!')

3.2 条件表达式

用二分支结构程序将用户输入的两个数中的较大者命名为max_number并输出,可以用以下代码实现。

# 输入用空格分隔的二个整数,输出其中较大的值
# split()根据空格将输入切分为列表,元素类型为字符串
# map()将列表所有元素映射成整型,分别赋值给m 和n
m, n = map(int, input().split())
if m > n:
   max_number = m
else:
   max_number = n
print(max_number)

这种二分支结构的程序,可以用条件表达式语句表述。所谓的条件表达式,是用一条语句完成一个二分支结构的程序语句。
语法格式:

x if 条件 else y

if…else将语句分成三部分,是一个三元操作符的条件表达式。
首先对条件表达式求值:
若值为True,则计算表达式x的值并返回其运算结果;
当条件表达式结果为False时,对表达式y进行计算并返回其运算结果。
用条件表达式实现输出两个数中最大值的代码如下:

m, n = map(int, input().split())
max_number = m if m > n else n  # 如果m > n 为True, max_number = m,否则max_number = n
print(max_number)

用条件表达式也实现进入系统时验证用户名:

username = input()
print("用户名正确") if username == 'admin'  else print("用户名错误")

4. 流程跳转语句

4.1 pass 语句

pass相当于一个空语句,他不执行任何操作,可用在语法上需要语句,却不需要执行任何操作时,如:

while True:
    pass         # 忙-等待键盘中断

也常用作为类、函数或条件体的占位符,或者创建占位程序、保证格式完整、保证语义完整等。

def initlog():
    pass        # 占位,提醒这是一个待完成的函数定义

4.2 continue

应用不多
continue语句应用于while或for循环语句中。
一般置于条件判定语句块中,当满足某条件时触发该语句的执行。
continue语句作用是跳过本次循环中continue语句后面剩余语句的执行,提前进入下一次循环。

能力点5:提前终止循环

提前终止一重循环可以直接用break
想提前终止二重循环一般要结合标记变量或continue

4.3 break 语句

continue语句和break语句都应用于while或for循环语句中。
一般置于条件判定语句块中,当满足某条件时触发该语句的执行。
break语句用于跳过当前循环中未执行的次数,提前结束语句所在的循环。
在循环次数未达到设定次数或未达到循环终止条件时跳出循环。
例如枚举时,找到一个满足条件的结果就终止循环。
它会终结最近的外层循环,如果循环有可选的 else 子句,也会跳过该子句。
如果一个 for 循环被 break 所终结,该循环的控制目标会保持其当前值。

示例3.14 正整数a+b

# 产生正整数加法的题目,用户输入计算结果,判断输入正确与否,输入回车时结束程序
while True:   # 无限循环,每次产生一个新问题
    a = input()  # 输入字符串'5',转整数5
    if a == '':  # 如果直接输入回车
        print('程序结束')
        break    # 结束程序
    b = input()  # 输入字符串'10',转整数10
    print(f'{a}+{b}=', end='')  # 5+10=15,格式化输出
    result = int(input())  # 用户输入计算结果
    if result == int(a) + int(b):
        print('恭喜你,回答正确')
    else:
        print('回答错误,你要加油哦!')
# 产生正整数四则运算的题目,用户输入计算结果,判断输入正确与否,输入回车时结束程序
while True:   # 无限循环,每次产生一个新问题
    a = input('请输入一个整数')  # 输入字符串'5',转整数5
    if a == '':  # 如果直接输入回车
        print('程序结束')
        break    # 结束程序
    b = input('请输入一个整数')  # 输入字符串'10',转整数10
    for sign in '+-*/':
        print(f'{a}{sign}{b}=', end='')  # 5+10=15,格式化输出
        result = float(input())  # 用户输入计算结果
        if result == eval(f'{a}{sign}{b}'):
            print('恭喜你,回答正确')
        else:
            print('回答错误,你要加油哦!')

实例 3.15 自身以外的最大因数

输入一个大于1的整数,输出其自身以外的最大因数。
一个整数的因数可能有很多个,可以构建一个循环,用遍历的方法从大到小逐一判定,找到的第一个因数时便为最大因数,输出该数后,用break结束循环,不再进行判定。

num = int(input())               # 输入一个整数,例如:100
for i in range(num - 1, 0, -1):  # 遍历小于num且大于1的整数
    if num % i == 0:             # 如num可被i整除,则i为num的因数
        print(i)                 # 输入100时,输出50
        break                    # 中止循环,退出循环

break只能提前结束当前循环,当循环层数多于一层时,不能直接用break跳出多重循环。一般的方法可以用一个标记变量,用break退出一重循环后,根据标记变量的值决定是否继续退出外层循环。

实例 3.16 百钱买百鸡进阶

公鸡五元一只,母鸡三元一只,小鸡三只一元。用户输入想买的鸡的数量和付出的钱数
计算公鸡、母鸡和小鸡的数量。
如果有解,输出公鸡最少,小鸡最多的一组;
如果无解则输出“无解”。

num, money = map(int, (input().split()))  # 切分空格分隔的输入转整型
flag = 0                                  # 设定标记,0 为无解
for cock in range(1, num):                # 从小到大遍历公鸡数量
    for hen in range(1, num):             # 从小到大遍历母鸡数量
        chicken = num - cock - hen        # 小鸡数量计算得到
        if chicken % 3 == 0 and chicken > 0 and 5 * cock + 3 * hen + chicken // 3 == money:
            flag = 1                      # 找到解后改变标记
            print(cock, hen, chicken)     # 输出找到的第一组解
            break                         # 结束内层循环
    if flag == 1:                         # 根据flag值判断是否找到解
        break       # 找到解时结束外层循环,否则继续执行外层循环
if flag == 0:       # 两层循环都结束时,如flag值仍为0,表明未找到解
    print('无解')

程序中有两重循环,在内层循环中找到一个满足条件的解并输出后,按题目要求应该终止程序的执行。
提前终止循环可以用break关键字,但该关键字只能终止当前层次的循环,无法终止外层循环的执行。
外层循环不能无条件终止,需要先判断是否已经在内层中找到了满足条件的解。
所以可以在内层找到解时设定一个标记,上述程序中设置变量flag作为标记,一旦找到了满足条件的解,就改变flag变量的值。
在外层循环中根据flag的值是否发生了变化判定是否找到了解,一旦找到了一个满足条件的解,就用break提前终止外层循环。

5. for(while)的else 子句

Python中的for、while循环都有一个可选的else子句,在循环迭代正常完成之后执行(包括执行0次)。
当循环体中遇到break、return或异常时,程序将略过else子句。
只有当循环以正常方式结束时,else子句中的语句才能被执行。

实例 3.17 最大素数

用户输入一个大于 1 的正整数n,计算并输出不大于 n 的最大的素数。

  1. 从num到1逆序遍历:
    1. 判定他是否存在从 2 到 i-1 之中是否还存在其因子:
      1. 如果存在一个能整除的数字:
        1. 则数字 i 必然不是素数,不需要继续判断,用break跳出循环
      2. 如果顺利完成从 2 到 i-1 的遍历,也未发现其因子存在时,说明该数是素数
        1. 输出这个数
        2. 用break结束循环
          num = int(input())
          for i in range(num,1,-1):  # 产生从num 到2的整数递减序列
          for j in range(2,i):   # 产生从2到i-1序列,j逐个取值为2到i-1
          if i % j == 0:     # 若i能被2到i-1中的数整除,i不是素数
          break          # 找到一个整除的数就提前结束内层循环
          else:                  # i不能被2到i-1中的任意数整除时是素数
          print(i)           # 输出 i 值
          break              # 只输出最大的一个素数,提前结束外层循环
          

          除了break以外,函数中的return语句也会起到相同的作用。
          例如定义一个判断素数的函数,当n能被[2, n-1]中的某个数整除时,会执行return False语句。
          return语句一定是函数中最后一条语句,一旦执行到return语句,程序就会把函数值返回给主调程序,并跳过return语句后面的所有语句结束函数的调用。
def is_prime(n):          # 判断n是否是素数的函数
    if n < 2:             # 小于2的数都不是素数
        return False      # 返回False,不是素数
    for i in range(2,n):  # 遍历查找在2到n-1之中是否存在n的因子
        if n % i == 0:    # 有其他因子存在时,不是素数,返回False
            return False
    else:                 # else是for的子句,未找到因子时执行此语句
        return True       # 返回True,表示这个数n是素数

num = int(input())        # 输入一个整数
for i in range(num,1,-1): # 产生从num 到2的整数序列
    if is_prime(i):       # 调用prime()函数判定i是否是素数
        print(i)          # 输出找到的素数
        break             # 结束循环,只输出最大一个值

while…else用法类似于for…else,仅当while因条件为假而退出(即没有被break中断)时运行else分支下的语句块。
continue与for/else结构组合可用于跳出二重循环,实例3.9的程序可修改如下:

num, money = map(int, (input().split()))  # 切分空格分隔的输入转整型
flag = 0                                  # 设定标记,0 为无解
for cock in range(1, money):              # 从小到大遍历公鸡数量
    for hen in range(1, money):           # 从小到大遍历母鸡数量
        chicken = num - cock - hen        # 小鸡数量计算得到
        if chicken % 3 == 0 and chicken > 0 and 5 * cock + 3 * hen + chicken // 3 == money:
            flag = 1                      # 找到解后改变标记
            print(cock, hen, chicken)     # 输出找到的第一组解
            break                         # 结束内层循环
    else:         # 内层循环未找到解时,未遇到break,执行此语句
        continue  # 跳过本次循环后续语句,即跳过break执行下次循环
    break         # 内循环找到解会跳过else子句执行break,结束外层循环
else:             # 两层循环都结束时,若未遇到break,执行此分支,未找到解
    print('无解')

for循环的else分支触发条件是循环正常结束。
如果循环内被break终止,就不执行else。
所以这个逻辑是:如果循环内遇break,不触发else,则执行下一句外层循环中的break;
如果正常结束,执行else分支里的continue,直接跳转到外层循环的下一轮,跳过了第二个break。

能力点6:异常与错误处理能力

程序运行出错主要有三种:
一是语法错误,pycharm有提示,依提示修改
二是逻辑错误,程序可以执行,但结果不对,主要由于处理逻辑有问题导致
三是异常,会返回*Error的提示,根据出错提示修改对应的语句;或网络搜索问题出现原因和解决方法

6. 异常处理

异常即是一个事件,该事件会在程序执行过程中发生,影响了程序的正常执行。一般情况下,在Python无法正常处理程序时就会发生一个异常。
在Python中,异常也是一个对象,表示一个错误。当Python程序发生异常时需要捕获处理他,否则程序会终止执行。
Python提供了异常处理的方法,利用try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理。

6.1 程序中的错误

Python中的错误分为三类:语法错误、逻辑错误和运行时错误。
(1)语法错误(SyntaxError,也称解析错误)是指不遵循语言的语法结构引起的错误,程序无法正常编译/运行。语法错误属于编译阶段的错误,会导致解析错误。有语法错误的程序无法正确的编译或运行。一般是指由于程序语句、表达式、函数等存在书写格式错误或语法规则上的错误。
常见的语法错误包括:程序存在遗漏了某些必要的符号(冒号、逗号或括号)、关键词拼写错误、缩进不正确、全角符号和空语句块(需要用 pass 语句)等。
这种错误一般会在IDLE或其他IDE中会有明显的错误提示,如图3.6所示,这段代码中存在4处语法错误。“C”在程序里应该是用半角引号,这里用了全角;if语句结尾应该有半角的冒号,这里缺失;关键词“print”拼写错误;最后一行缩进不正确。
图 3.6 语法错误.png
图 3.6 语法错误
(2)逻辑错误(语义错误)是指程序可以正常运行,但其执行结果与预期不符。与语法错误不同的是,存在逻辑错误的程序从语法上来说是正确的,但会产生意外的输出或结果,并不一定会被立即发现。逻辑错误的唯一表现就是错误的运行结果。
常见的逻辑错误包括:运算符优先级考虑不周、变量名使用不正确、语句块缩进层次不对、在布尔表达式中出错等。
例如:当输入的用户名为“admin”或“root”,且密码为“asd*-+”时,输出登录成功。

username, password = input().split() # 切分空格分隔的输入,分别赋值
if username == 'admin'or username == 'root' and password == 'asd*-+':
    print("登录成功")

这段程序没有语法错误,但由于or的优先级低于and,一旦or左边结果为True,右边会被短路,不做处理,直接输出“登录成功”。这里可以用括号改变优先级或分成两个if语句来写或,确保逻辑的正确性。

# 用括号改变优先级确保逻辑正确
username, password = input().split()
if (username == 'admin'or username == 'root')  and password == 'asd*-+':
   print("登录成功")

# 或分两个if语句进行判定
username, password = input().split()
if username == 'admin'or username == 'root':
   if password == 'asd*-+':
      print("登录成功")

(3)运行时错误是指程序可以运行,但是在运行过程中遇到错误,导致程序意外退出。当程序由于运行时错误而停止时,也会说程序崩溃了,一般我们说的异常便是运行时错误,有时也会把所有错误都归于异常。

a = 8
b = 0
print(a / b)
print(a / b)<br />ZeroDivisionError: division by zero
pi = 3.14                         # 圆周率
diameter = 4                      # 直径
area = pi * ((dimeter / 2) ** 2)  # 计算圆的面积
area = pi * ((dimeter / 2) ** 2) # 计算圆的面积<br />NameError: name 'dimeter' is not defined. Did you mean: 'diameter'?

6.2 异常

异常是在程序执行过程中发生的一个事件,该事件会影响程序的正常执行。一般情况下,在Python无法正常处理程序时或者说程序运行时候发生错误而没有被处理时就会发生一个异常。这些异常会被Python中的内建异常类捕捉, 异常的类型有很多,在前面的学习过程中,遇到过SyntaxError、 NameError 、TypeError、 ValueError等多个错误提示信息,这些都是异常。
当程序发生异常时需要捕获他并进行一些处理,使其平稳结束,否则程序会终止执行甚至直接崩溃。本节主要学习异常的一些处理方法和利用异常进行程序设计。
在程序设计过程中,要尽可能考虑全面,避免类似异常的存在,同时,尽可能对可能产生的异常进行处理,使程序具有更好的健壮性和容错性,避免程序崩溃。也可以利用异常处理的方法实现程序的不同的功能。
Python中有许多内置的异常,有一个内置异常的完整层次结构,每当解释器检测到某类错误时,就能触发相对应的异常。在程序设计过程中,可以编写特定的代码,专门用于捕捉异常,如果捕捉到某类异常,程序就执行另外一段代码,执行为该异常定制的逻辑,使程序能够正确运行,这种处理方法就是异常处理。

6.3 try…except子句

在Python中,可以使用try、except、else和finally这几个关键词来组成一个包容性很好的程序,通过捕捉和处理异常,加强程序的健壮性。用try可以检测语句块中的错误,从而让except语句捕获异常信息并处理。
try…except语法如下:

try:
    <语句块1>        #需要检测异常的代码块
except <异常名称1>:
    <语句块2>        #如果在try部份引发了异常名称1时执行的语句块
[except <异常名称2>:
    <语句块3>]       #如果在try部份引发了异常名称2时执行的语句块
[else:
    <语句块4>]       #没有异常发生时执行的语句块
[finally:
    <语句块5>]

except语句和finally语句都不是必须的,但是二者必须要有一个,否则就没有try的意义了。
except语句可以有多个,Python会按except语句的顺序依次匹配指定的异常。

  1. 程序首先执行try子句
    1. 如果try里面的程序没有触发异常
      1. 跳过except子句
      2. 执行else里面的语句
    2. 如果在执行try子句的过程中发生异常
      1. 根据错误类型选择执行对应的except里面的语句,这里面可以是错误信息或者其他的可执行语句。
      2. except可以有多个,分别用于处理不同类型的异常
      3. 每个except只捕捉一个异常,一旦捕捉到异常就不会再进入其他except语句块
      4. except 子句 可以用带圆括号的元组来指定多个异常,except (RuntimeError, TypeError, NameError):
      5. 如果发生的异常与 except 子句 中指定的异常不匹配,则它是一个 未处理异常,执行将终止并输出error消息。
      6. except 或 else 子句执行期间也会触发异常,该异常会在 finally 子句执行之后被重新触发
    3. 若有finally子句,无论是否发生异常,都执行finally语句块
      1. finally放在最后,其内容通常是做一些后事的处理
      2. 比如关闭文件、资源释放之类的操作
      3. 如果 finally 子句中包含 break、continue 或 return 等语句,异常将不会被重新引发
      4. finally语句块是无论如何都要执行的
      5. 如果执行 try 语句时遇到 break,、continue 或 return 语句,则 finally 子句在执行 break、continue 或 return 语句之前执行。
      6. 如果 finally 子句中包含 return 语句,则返回值来自 finally 子句的某个 return 语句的返回值,而不是来自 try 子句的 return 语句的返回值。 ```python def div(a, b): try: return f”{a} / {b} = {a / b:.2f}” except ZeroDivisionError: return ‘除数为0,不能做除法运算’ finally: return ‘奇怪的事情发生了’

if name == ‘main‘: m, n = map(float, input().split()) print(div(m, n))

<a name="kzyxn"></a>
## 实例 3.18 四则运算
任意输入两个数字,输出其加、减、乘、除的结果。<br />题目很简单,但要注意到0不能做除数,所以当第二个数字为0时,程序要能够对异常输入进行处理,使程序不至于因除零异常而崩溃。
```python
a,b = input().split()
try:
    print("{} + {} = {:.2f}".format(a,b, float(a) + float(b)))
    print("{} - {} = {:.2f}".format(a,b, float(a) - float(b)))
    print("{} * {} = {:.2f}".format(a,b, float(a) * float(b)))
    print("{} / {} = {:.2f}".format(a,b, float(a) / float(b)))
except ZeroDivisionError:
    print('除数为0,不能做除法运算')

输入:25 0
输出:
25 + 0 = 25.00
25 - 0 = 25.00
25 * 0 = 0.00
除数为0,不能做除法运算

此题中非常肯定的知道只有当输入的 “b”值为“0”时,才会陷入异常,这种问题明确的情况下,尽可能用if…else 语句进行处理。

a,b = input().split()
print("{} + {} = {:.2f}".format(a,b, float(a) + float(b)))
print("{} - {} = {:.2f}".format(a,b, float(a) - float(b)))
print("{} * {} = {:.2f}".format(a,b, float(a) * float(b)))
if b == '0':
    print('除数为0,不能做除法运算')
else:
    print("{} / {} = {:.2f}".format(a,b, float(a) / float(b)))

Python允许在一个程序里同时对多类异常进行捕捉,触发哪个异常就执行哪个异常对应的语句。表3.6列出了Python中常见的异常名称及其描述,可以参考Python文档查看所有异常类及其子类。
表 3.6 常见异常

异常名称 描述
Exception 常规异常的基类,可以捕获任意异常
SyntaxError 语法错误
NameError 未声明/未初始化的对象(没有属性)
SystemError 一般的解释器系统错误
ValueError 传入无效的参数,或传入一个调用者不期望的值,即使值的类型是正确的
IndentationError 缩进错误(代码没有正确对齐)
ImportError 导入模块/对象失败(路径问题或名称错误)
ModuleNotFoundError 模块不存在
ZeroDivisionError 除(或取模)
OverflowError 数字运算超出最大限制
AttributeError 对象没有这个属性
IndexError 索引超出序列边界,如x只有10个元素,序号为0-9,程序中却试图访问x[10]
KeyError 映射中没有这个键(试图访问字典里不存在的键)
TypeError 对类型无效的操作
TabError Tab和空格混用
RuntimeError 一般的运行时错误
try:
    import turtle             # import tutle  时输出“模块名称有误”
    size = eval(input())
    print(size)               # 参数写成sizee时会输出“变量未定义”
    turtle.circle(size)
    turtle.done()             # done写成one时,会输出“属性不存在”
except ModuleNotFoundError:   # 遇到不同类型的异常给出不同的错误提示
    print('模块名称有误')
except NameError:
    print('变量未定义')
except AttributeError:
    print('属性不存在')
except SyntaxError:
    print('存在语法错误')

Python内置了的“Exception”类可以捕捉到所有内置的、非系统退出的异常,以及所有用户定义的异常。当需要输出程序遇到的异常时,可以使用以下方法:

try:
    import turtle  # import tutle  时输出No module named 'tutle'

    size = eval(input())
    print(size)    # size写成sizee时输出name 'sizee' is not defined
    turtle.circle(size)
    # 写成circe时输出 module 'turtle' has no attribute 'circe'
    turtle.done()
except Exception as e:
    print(e)

6.4 else子句

没有触发异常时执行的语句

6.5 finally子句

如果try中的异常没有在“Exception”中被指出,那么系统将会抛出默认错误代码(Traceback),并且终止程序,接下来的所有代码都不会被执行。但如果有finally关键字存在,则会在程序抛出默认错误代码之前,执行finally中的语句。这个方法在某些必须要结束的操作中颇为有用,如释放文件句柄,或释放内存空间等。

6.6 异常处理的应用

在一些特殊情况下,可以应用异常来实现一些特定的功能。例如正整数A+B的问题,利用其他语言实现可能需要近100行代码,而用Python结合异常处理来实现,仅需不到20行就可以实现。

实例 3.19 正整数A+B

题的目标很简单,就是求两个正整数A和B的和,其中A和B都大于0。稍微有点麻烦的是,输入并不保证是两个正整数。
输入在一行给出A和B,其间以空格分开。问题是A和B不一定是满足要求的正整数,有时候可能是超出范围的数字、负数、带小数点的实数、甚至是一堆乱码。‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‫‪‪‪‪‪‪‪‪
我们把输入中出现的第1个空格认为是A和B的分隔。题目保证至少存在一个空格,并且B不是一个空字符串。‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‫‪‪‪‪‪‪‪‪如果输入的确是两个正整数,则按格式“A + B = 和”输出。如果某个输入不合要求,则在相应位置输出“?”,显然此时和也是“?”。

ls = input().split(' ',maxsplit=1) # 根据空格切分输入为列表,切分一次
try:
    a = int(ls[0])  # 列表第一个元素转整型,转换类型不成功则触发异常
    if a <= 0:      # 如转换成功,说明是整数,再判定是否为负数
        a = '?'
except ValueError:
    a = '?'        # 当异常被触发时,表明不是整数,用?代替
try:
    b = int(ls[1]) # 列表第二个元素转整型,转换类型不成功则触发异常
    if b <= 0:     # 如转换成功,说明是整数,再判定是否为负数
        b = '?'
except ValueError:
    b = '?'        # 当异常被触发时,表明不是整数,用?代替
if a == '?' or b == '?':
    print('{} + {} = {}'.format(a, b, '?'))
else:
    print('{} + {} = {}'.format(a, b, a + b))

虽然try…except可以捕捉和处理程序中的异常,但不能过于依赖这种方法。在程序设计过程中,首先应该尽可能排除语法错误与逻辑错误,防御性方式编码比捕捉异常方式更好,应尽量采取这种编程方式,提升性能并且使程序更健壮。
不要试图用try语句解决所有问题,这将会极大的降低程序的性能。只有在错误发生的条件无法预知的情况下,才使用try…except进行处理。
在程序设计过程中,一般情况下异常处理与程序主要的功能是没有关系的,过多的应用异常处理,会导致代码可读性变差。要尽量减少try/except语句块中的代码量,try语句块的体积越大, 期望之外的异常就越容易被触发,越容易隐藏真正的错误,从而带来严重后果。
使用finally子句来执行那些无论try语句块中有没有异常都应该被执行的代码,常用于终止处理程序,这对于清理资源常常很有用,例如关闭文件。
第3章 流程控制.pptx