Python作为解释型语言,运行速度偏慢,尤其是和编译型的C语言对比。但是Python拥有灵活的数据结构和丰富的扩展模块,因此编程过程更加方便快捷。
可以在help函数派生的环境下查询关键字。第一步,在shell中输入:
help()
然后在help>子环境中输入:
help>keywords
首字符必须是字母或下划线
区分大小写
二进制(binary)数的前缀 0b/0B
八进制(boctal)数的前缀 0o
十六进制(hexadecimal)数的前缀 0x
对于较大整数,Python支持用下划线分隔数的表示方法,如:
a = 12_345_000 表示 12345000
浮点数可以使用字母e参与表示,如:
1.23e9 表示 1.23 乘以 10 的 9 次方
比较两个浮点数是否相等的方法,如:
设置一个较小值 epsilon,例如 epsilon = 1e-10,进而判断两个浮点数差值是否小于 epsilon ,如果是,则认定二者相等,反之二者不相等。
注意:不要试图用“ == ”测试两个浮点数是否相等。
c = 3 + 5j #表示一个复数
c = complex(3, 5) == 3 + 5j #True
c.real = 3.0 #点语法
c.imag = 5.0 #点语法
使用成对的单引号或双引号定界字符串没有本质的区别,但是设计不止一种表示方式的好处在于,能够在某种情况下赋予用户更大的灵活性。
"It's a python"
'It\'s a python' #用到转义字符
常用的转义字符,如:
"\b" #Backspace 退格键
"\n" #Linefeed 换行
"\r" #将光标退回到本行的开头位置
关于三引号“```”的说明,如:
# 正规注释一般由“#”引导一行文本构成
s1 = ```
I LIKE PYTHON
SO DOES HE ```
# 三引号定界的一段文本实际上创建了一个字符串常量,不少程序员习惯用三引号定界的文本来注释程序。
#并且,三引号定界的字符串如果放在函数定义的函数体之前,这段文本则会成为该函数的帮助文档。
not 非运算
and 与运算
or 或运算
None 是类型 NoneType 的唯一值。
None 并不等同于 0 或空字符串
两个整数相除,结果是浮点数。
可以理解为:当运算在两个不同类型之间进行时,参与运算的数自动地向更宽泛的类型转换,类型宽泛性次序为:布尔型<整数<浮点数<复数
当一个表达式中出现多个操作符时,求值顺序依赖其优先级。优先级顺序从高到低,如:
括号
乘方
乘法和除法
加法和减法
7 = 3 * 2 +1 式中,“7”是被除数,“3”是除数,“2”是商,“1”是余数。
** 表示指数运算,// 表示整除求商,% 表示求余,如:
2 ** 3 # ** 表示指数运算,2 ** 3 == 8
7 // 3 # // 表示整除求商,7 // 3 == 2
7 % 3 # % 表示求余,7 % 3 == 1
divmod(7, 3) # divmod(7, 3) == (2, 1)
布尔值除进行算术运算外,还可以进行逻辑运算,逻辑运算符包括 not,and 和 or,优先级如:
not 一元操作,执行“非”运算,得到和操作数相反的逻辑值
and 二元操作,执行“与”运算,只有当两个操作数均为True时,结果为True
or 二元操作,执行“或”运算,只有当两个操作数都为False时,结果为False其他类型的变量参与逻辑运算时,会发生自动的类型转换
“+”表示连接两个字符串,“*”表示将字符串延展若干倍,如:
s1 = "Python"
s2 = "I love" + s1
>> s2
'I love Python'
s3 = s1 * 3
>> s3
'PythonPythonPython'
当整数和浮点数进行比较时,整数会被隐式地转换为浮点数
字符串的比较是从首位开始,逐一比较ASCII值
Python支持链式的比较运算,如:
>> 3 < 5 < 9 == 9 <= 10
True
判断元素是否存在与容器中可用关系运算符 in(not in),如:
>> "p" in "python"
True
>> "Py" not in "python"
True
判断两个变量是否为同一对象的运算符 is(is not),即判断两个变量是否位于同一内存地址,如:
a = 2 + 3
b = 5 * 1
>> a is b
True
>> id(a) == id (b) # id() 返回对象的地址
True
print("I have " + 3 + "mobiles") # TypeError
print("I have " + str(3) + "mobiles") # Success
查看帮助,如:
help(int)
Python 中赋值操作本质上是建立对象的引用,或理解为给对象贴标签。b = a 即b与a指向同一个对象,a 和 b “互为别名”(地址相同),是同一个对象的两个标签。如:
a = [0,1,2]
b = a
a[1] = 5
print(b)
>>[0,5,2]
Python 也允许 a,b 所指向的内存地址不同,但值可以相同。即浅拷贝,如:
a = [0, 1, 2]
b = a[:]
# 因为 a,b 地址不相同,因此改变 a 或 b 的值时对另一个没有影响
Python 交换赋值,如:
a = 2
b = 3
a, b = b, a
print(a, b)
>> 3 2
Python *在赋值中的应用,如:
a, *b, c, d = [0, 1, 2, 3, 4, 5]
print(a, b, c, d) #a,c,d根据位置分别获得值0,4,5,而 b 获得其余值
>> (0, [1, 2, 3], 4, 5)
map(func, *iterables) 根据提供的函数对指定序列作映射
function – 函数
iterable – 一个或多个序列
如:
def square(x)
return x ** 2
map(square, [1,2,3,4,5])
>>[1,4,9,16,25]
str.split(self, /, sep=None, maxsplit=-1) 指定分隔符对字符串进行切片
self – 实例
sep – 分隔符,如" ", “#” 等
maxsplit – 分隔次数,默认-1,即分隔所有
prompt = "请输入两个个整数,用空格分开:\n"
inp = input(prompt)
nums = map(int, inp.split())
print("和为:", sum(nums))
>> 70 30
和为:100
在上例中,我们称split为方法(method),而int、map为函数(function)
方法是定义在类中的函数,必须使用点语法进行调用,调用者必须是类的对象
《原子核物理》成绩计算程序
f = open("C:/Users/ChaoWang/Desktop/Score.txt", "r", encoding='utf-16')
line = f.readline()
result = []
while line:
line = line.split(sep="\t")
line = [x.strip() for x in line]
temp = 0
for i in range(2, 10):
if line[i] == "A+":
temp = temp + 100
elif line[i] == "A":
temp = temp + 95
elif line[i] == "A-":
temp = temp + 90
elif line[i] == "B+":
temp = temp + 85
elif line[i] == "B":
temp = temp + 80
elif line[i] == "B-":
temp = temp + 75
line.append(temp / (len(line) - 2))
print(f'{line[1]}的平时成绩是:{line[-1]}')
line = f.readline() # 读取一行
print(result)
f.close()
使用 eval 从字符串中直接提取数值,但必须是合法的元组对象(用逗号隔开),如:
prompt = "请输入两个整数,用逗号分开:\n"
inp = input(prompt)
nums = eval(inp)
print(nums)
print("和为:", sum(nums))
例3-4 从磁盘上读取数据、计算并写回,如:
import math
f = open(r"c:\a.txt")
num = f.read()
f.close()
result = math.sqrt(int(num))
f = open(r"c:\b.txt", 'w')
f.write(str(result))
f.close()
我们可以用逗号隔开的两个对象拼接为一个整体的字符串,但是当对象数量较多时,操作会显得繁琐。为此,可以采用 Python 提供的字符串格式化方案,如:
n1 = 2
n2 = 1
n3 = 0
# 方案1,代码续前
print("I have %d dogs, %d cats, and %d fish." % (n1, n2, n3))
# 使用 % 进行字符串格式化(如:%d,%s等)
# 注意:前面待替换数目和后面变量数目要严格一致
# 方案2,使用f前缀
print(f'I have {n1} dogs, {n2} cats, and {n3} fish')
可以使用引号定界的字符串代替注释,对于多行注释可以使用三引号
在程序调试的过程中,常用注释来临时关闭某一行代码。与其将其径直删除,把它设为注释并且保留才是常规的做法
代码分行的两种方法:
- 在代码截断处,上一行末尾插入反斜杠 \
- 考虑将截断处放在一套 (), [], {} 这样的定界符之内 —— 推荐使用
score = 95
if score >= 90:
print("excellent")
Python 的条件语法规则有几点值得注意
- if 后的判断条件 score >= 90 并不需要用 () 括起来
- if 语句后需要加冒号
- 处于相同缩进的若干语句构成语句块。当语句块结束后要退出缩进
- 当语句块并不长时,可以使用“if 条件表达式: 语句块”的紧凑语法
if…else… 语句的逻辑是:判断条件表达式的值,如果为 True,则执行语句块1的内容,否则执行语句块2的内容,如:
score = 60
if score >= 60:
print("excellent")
else:
print("failed")
对于多分支的情况,Python 以 elif 来实现,如:
score = 55
if score >= 90:
print("excellent")
elif score >= 60:
print("pass")
else:
print("failed")
此外,Python 中还支持一种…if…else…的条件表达形式,如:
Passed = “Yes” if score >= 60 else "No"
# 当条件为 True 时,右侧表达式的值为 Yes,反之为 No
内置函数range是for循环之基,大量的循环基于range返回值展开,因此先了解range函数是很有必要的。range函数根据参数返回特定的range对象,该对象蕴含两个整数之间的序列,如:
print(range(5))
>> range(5)
print(list(range(5)))
>> [0, 1, 2, 3, 4]
print(range(3, 9))
>> range(3,9)
print(len(range(3, 9)))
>> 6
print(list(range(3, 9, 2))) # range(start, stop[, step])
>> [3, 5, 7]
print(list(range(10, 1, -3)))
>> [10, 7, 4]
range 函数的特点,如:
- range 函数返回的的不是具体序列的值,而是惰性生成的range对象,可以使用 list 函数将 range 对象强制展开
- range 函数的形式之一是 range(stop),唯一参数为终点 stop ,函数返回从 0 到 stop 但不包含 stop 的序列,即 [0, stop)
- range 函数的形式之二是 range(start, stop[,step]),函数返回从 start 到 stop 但不包含 stop 的序列,即 [0, stop)
- range() 对象的参数都是整数,因此得到的序列也都是整数,如果需要构造含浮点数的序列可以用如下两个方案:
例 4-1 使用列表推导式(list comprehension)构造序列,相当于循环创建列表的简化,如:
size = int((5 - 3.14)/0.2)+1 # size == 10
rst = [3.14 + i * 0.2 for i in range(size)]
print(rst)
# 最后一个数为 3.14 + 9 * 0.2 = 4.94
>> [3.14, 3.3400000000000003, 3.54, 3.74, 3.9400000000000004, 4.140000000000001, 4.34, 4.54, 4.74, 4.94]
例 4-2 使用 numpy.arrage 函数构造序列,如:
import numpy as np
rst = np.arange(3.14, 5, 0.2)
print(rst)
>> [3.14 3.34 3.54 3.74 3.94 4.14 4.34 4.54 4.74 4.94]
- 当步长为 1 时,range 对象蕴含的元素数目等于 stop - start
- range() 对象是可迭代的(iterable)对象,这意味着 range 对象可以用 for 循环遍历访问(迭代)。但并不是迭代器(iterator),即访问一次 range 对象之后,其中的元素并不会因为访问而消耗掉,而元素的一次访问性是迭代器的特性
for 循环的逻辑是设置一个使循环持续进行的条件,如果该条件 满足,则持续执行循环体,for 循环的语法如下:
for 循环变量 in 序列:
语句块1
[else:
语句块2]
# 推荐使用不带 else 语句的 for 循环
例 4-3 求 1 ~ 100 的和_v1
total = 0
for x in range(1, 101): # x 是 1 ~ 100 的循环变量
total = total + x
print(total)
推荐使用不带 else 的 for 循环和 while 循环,如:
s = "abcdefghijk"
find = False
for i in range(len(s)):
if s[i] == "l":
print("找到了,位置在:", i)
find = True
break
if not find:
print("没找到")
例 4-5 打印输出九九乘法表。外层循环变量 i 处理行,内层循环变量 j 处理列。因为第 i 行有 i 个口诀,因此 j 的范围是[1, i+1),如:
for i in range(1, 10):
for j in range(1, i+1):
s = f'{i} * {j} = {i*j} '
print("%-6s" % s, end = " ")
print("\n")
例 4-7 水仙花数是指该数各位数的立方和等于其本身。求三维水仙花数(思路1:遍历 100 ~ 999 的数),如:
for i in range(100, 1000):
# 分别取得百位、十位和个位 a, b, c
a, temp = divmod(i, 100) # a = 商,temp = 余数
b, c = divmod(temp, 10)
if a ** 3 + b ** 3 + c ** 3 == i:
print(i, end=" ")
例 4-7 水仙花数是指该数各位数的立方和等于其本身。求三维水仙花数(思路2:遍历 百、十、个位),如:
for a in range(1, 10):
for b in range(0, 10):
for c in range(0, 10):
if a * 100 + b * 10 + c == a ** 3 + b ** 3 + c ** 3:
print(a * 100 + b * 10 + c, end=",")
break 语句用在循环体之内,用途是推出本层循环。break 语句常和条件判断组合使用,表示在特定的条件下退出当前循环。
get_solution = False
for chick in range(1, 35 + 1):
for rabbit in range(1, 35 + 1):
if chick + rabbit == 35 and chick * 2 + rabbit * 4 == 94:
print(chick, rabbit)
get_solution = True
break
if get_solution:
break
当循环不止两层时,这样逐层推出的方式十分繁琐。此时可以利用定义函数的方法,利用函数中的return语句从内层循环直接退出所有循环,如:
def calcCR(head, foot):
for chick in range(1, head + 1):
for rabbit in range(1, head + 1):
if chick + rabbit == 35 and chick * 2 + rabbit * 4 == 94:
return chick, rabbit
print(calcCR(35,94))
>> (23, 12)
while 循环执行的逻辑是:每次进入循环前检测条件表达式,如果为 True ,执行循环体,如果为 False ,执行 else 子句。while 循环的语法如下:
while 条件表达式:
语句块 1
[else:
语句块 2
]
# 建议不使用带有 else 子句的非主流用法
例 4-11 求 1 ~ 100 的和(方法3)
total = 0
x = 1 # 循环计数器初始值
while x <= 100:
total = total + x
x += 1
print(total)
while 循环适用于循环体执行次数不明确的情形。典型地,循环体会修改某些关键变量,使得条件表达式的值从起初的 True 变为后来的 False,循环退出。
另一种典型应用是:使用 while True 引导循环,在这种写法下必须使用退出循环语句,避免其成为死循环,如:
例 4-12 求 1 ~ 100 的和(方法4)。使用带 break 的无限循环
total = 0
x = 1
while 1: # 无限循环
total = total + x
if x >= 2:
break
x += 1
print(total)
今天去了兴庆
continue 语句用在循环体之内,用途是提前结束本轮循环进入下一轮循环。常与条件判断语句组合使用。
例4-14 模拟 shell 环境。用’==>'提示用户输入,用户输入字符串之后,回应“你输入了” + 用户所输入内容;当用户输入的字符串以 # 开头时,不作回应;当用户输入’quit’后立即退出
while 1:
inp = input("==>")
if inp[0] == '#':
continue # 退出本次循环
if inp == 'quit':
break # 退出循环
print("你输入了" + inp)
print("Byebye")
4-15 猜数字游戏。游戏规则说明:外层循环不断产生4位整数供用户猜想,用户随机猜出一个结果后,程序反馈正确数字的个数(位置不一定正确)和准确数字的个数(数字和位置都正确),用户根据程序反馈继续猜测。用户输入“next”表示放弃本次猜测本次出现的数字,输入“exit”表示结束程序。
自己编写的程序:
import random
while 1:
inp = input("请输入猜测的四位整数")
if inp == "next": # list对象和字符串不能直接比较
continue
elif inp == "exit":
break
else:
Correct_number = 0
Accurate_number = 0
Guessing_integers = random.randint(1000, 9999)
Guessing_integers = list(str(Guessing_integers))
inp = list(str(inp))
for number1 in range(len(Guessing_integers)):
for number2 in range(len(inp)):
if Guessing_integers[number1] == inp[number2]:
Correct_number = Correct_number + 1
if number1 == number2:
Accurate_number = Accurate_number + 1
print(Guessing_integers, "正确数字的个数为:", Correct_number, "准确数字的个数为:", Accurate_number)
4-15 猜数字游戏。游戏规则说明:外层循环不断产生4位整数供用户猜想,用户随机猜出一个结果后,程序反馈正确数字的个数(位置不一定正确)和准确数字的个数(数字和位置都正确),用户根据程序反馈继续猜测。用户输入“next”表示放弃本次猜测本次出现的数字,输入“exit”表示结束程序。
书上编写的程序(定义函数来实现且使用了 in 操作符):
import random
def compare(s1, s2):
correct_number, accurate_number = 0, 0
for i in range(len(s2)):
if s2[i] in s1:
correct_number += 1
if s2[i] == s1[i]:
accurate_number += 1
return correct_number, accurate_number
while 1:
the_num = random.randint(0, 9999)
the_str = str(the_num).zfill(4) # 方法返回指定长度的字符串,原字符串右对齐,前面填充0
print("4 位整数已就绪,请猜一猜:\n")
time = 0
while 1:
inp = input(">")
time += 1
if inp == "next":
break
if inp == "exit":
exit() # 结束程序
if inp == "peep": # 显示随机生成的数字
print(the_str)
continue
if len(inp) == 4 and inp.isnumeric(): # 检测字符串是否只由数字组成
m, n = compare(inp, the_str)
print("Time(%d): %d correct %d accurate" % (time, m, n))
if n == 4:
print("Great!")
break
else:
continue
exit() 结束程序
isnumeric()方法 检测字符串是否只由数字组成
汇总型循环适用于这样一些场景:统计一份学生成绩单中及格者的人数,或统计一段文本中各种类型的字符的数目等。对于这样的需求,一般是完全遍历整个对象,并且设置临时变量来存储所需要的中间结果,直至遍历完成后获得最终结果。
例 4-16 统计一份成绩单中及格者的人数和平均分
score = [70, 54, 50, 90, 89, 78, 67, 45, 31, 43]
count, total = 0, 0
for s in score:
if s > 60:
count += 1
total += s
assert count > 0 # assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。
average = total / count
print(average)
assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常
发现型循环适用于这样的场景:查找一份学生清单中的最高分。对于这样的需求,编程思路是:首先记录第一个学生的分数为最高分,然后遍历整个清单,当存在比之前记录的最高分更高的分数时,用新的高分替换旧的高分,直至遍历完成。
例 4-19 给某班 48 名考生随机排座位。假设考生学号为 1~48,考场为 8 行 6 列,要求给所有考生按照格式 1:3:4(表示学号为1的考生位于第3行第4列)输出座位代码。
import random
num = 48
nRow = 8
label = list(range(1, num + 1)) # 1~48 号考生的标签
random.shuffle(label) # 将序列的所有元素随机排序。
for i in range(1, num + 1):
row_no = label[i-1] % nRow + 1 # 根据label计算行号,+1 表示 1 起点
col_no = label[i-1] // nRow + 1 # 根据label计算列号,+1 表示 1 起点
s = f'{i}:{row_no}:{col_no}'
print(s)
在程序的编写过程中,由于一系列原因可能导致运行结果不符合预期。可以通过在关键点插入辅助性语句进行程序的调试。
思路 1:使用 print 函数输出关键变量的状态。在大批量执行的循环语句中,如果要频繁输出关键信息,会令测试者目不暇接。为此,可考虑使用间歇性的 print 输出呈现信息,如:
x = 3
y = 5
for i in range(1000):
# 省略了代码
if i == 100:
print(x, y)
# 省略了代码
上述做法的缺点是,在调试阶段插入的 print() 语句在发布阶段必须删除
思路 2:插入 assert 断言语句。该语句在 expression 为 True 时,不作任何响应,在 expression 为 False 时抛出异常,如:
assert expression
assert 1 == 2
>> Traceback (most recent call last):
File "D:/PythonProject/main.py", line 303, in <module>
assert 1 == 2
AssertionError
Process finished with exit code 1
一个好的程序,不可以因为用户的输入不符合预期就直接报错进而退出
异常捕捉可以用 try/expect 语句
解释器在语法检查阶段会发现语法错误并报告错误所在行
在代码已经通过了语法检查之后,程序在运行阶段报错。有如下几种手段改进完善程序:
使错误尽可能复现
通过对用户输入进行检查来加强代码的鲁棒性
程序根本不报错,但是运行结果不符合预期,存在 Bug。对于 Python 而言,一种典型的语义错误是由于没有正确使用代码缩进。
设置断点,并且全速运行至断
单步执行、进入和跳出函数、全速运行
函数可以减少程序的重复,缩减篇幅。函数定义,如:
def 函数名(形参列表): #可以没有形参
函数体
return 表达式1 # 如果函数有返回的结果(没有返回结果可以不用或者仅写一个return,即返回None)
return 表达式2
当某行代码的缩进退回到和 def 对齐时表示函数结束
函数体不能为空,未设计完成时可以使用占位语句 pass 代替
函数的定义必须先于调用
在内存中创建函数对象之后,可以通过 id、type 观察函数对象的地址、类型,如:
def calc(a, b):
return a ** 2 - b ** 2
print(id(calc)) # 输出对象地址
print(type(calc)) # 输出对象类型
>> 1994280620528
>> <class 'function'>
也可以将该对象赋值给某变量,赋值之后,该变量指向函数,即可以通过新的变量名来调用函数,如:
def calc(a, b):
return a ** 2 - b ** 2
va = calc
print(va(1, 2))
>> -3
今天调整一下生物钟,晚上十点半之后写博客每次都很迟睡。明天看完第五章。
在自定义函数中撰写“帮助”文档
一调整就是五天没更新
通过在函数体和函数头之间使用 三引号 插入字符串的方式,自定义函数中撰写帮助文档
def myFun():
"""这是函数的帮助文档"""
pass
在 Pycharm 编辑器中,选中函数名,点击 view -> Quick Documentation 即可查看函数的帮助文档
例 5-1 实现银行卡号的 Luhn 检验,检验过程如下:
def CheckCard(n):
"""对银行卡卡号进行 Luhn 检验,合法返回 True,否则返回 False"""
counter1, counter2 = 0, 0 # 与奇数位相关的计数,与偶数位相关的计数
list_n = list(str(n))
n_len = len(list_n)
if n_len % 2 == 0: # 银行卡号的位数为偶数
i = -1
while 1:
if i < -n_len:
break
else:
counter1 += int(list_n[i]) # 奇数位
if int(list_n[i-1])*2 < 10: # 偶数位
counter2 += int(list_n[i - 1])*2
else:
counter2 += int(list_n[i-1])*2 - 9
i = i - 2
else:
i = -1
while 1:
if i < -n_len:
break
else:
counter1 += int(list_n[i]) # 奇数位
if i == -n_len:
pass
else:
if int(list_n[i-1])*2 < 10: # 偶数位
counter2 += int(list_n[i-1])*2
else:
counter2 += int(list_n[i-1])*2 - 9
i = i - 2
print("银行卡号有效") if (counter1 + counter2) % 10 == 0 else print("银行卡号无效")
当程序规模变得庞大时,定义大量的函数名字在一定程度上是一个负担,并且思路还会被繁琐的函数定义打断.可以用 lambda 定义匿名函数(即用即抛的函数,比较简短),语法如下:
lambda 形参1, 形参2, ...: 表达式
匿名函数常与 map 搭配使用,用于将不复杂的特殊运算映射到某序列之上
print(list(map(lambda x: x**2 + 1, [1, 2, 3])))
>> [2, 5, 10]
例 5-3 使用匿名函数给 names 列表,按照 姓 给字典顺序排序
names = ["Chao Wang", "Wenquan Wei", "Zebing Li"]
names.sort(key = lambda name: name.split()[-1].lower())
print(names)
>> ['Zebing Li', 'Chao Wang', 'Wenquan Wei']
列表中的 sort 方法对列表元素进行排序,将 key 指定为匿名函数,在函数中通过 split 方法取得姓名分割后的列表,通过 [-1]取最后的元素,通过 lower() 确保排序为不区分大小写的字典排序
所谓递归调用就是在函数内部调用自身
对于递归有没有什么好的理解方法? - 帅地的回答 - 知乎
https://www.zhihu.com/question/31412436/answer/683820765
例 5-4 求自然数 n 的阶乘
def factorial(n):
if n <= 1:
return 1 # 递归的出口
else:
return n*factorial(n-1)
print(factorial(10))
当我们试图调用factorial(1000)时,Pycharm编辑器会报错,如下:
RecursionError: maximum recursion depth exceeded in comparison
即超出了最大递归深度
例 5-5 求斐波那契数列的第n项。斐波那契数列形如1,1,2,3,5,8…这样的无限序列,所有项均为前面两项之和
def Fibonacci(n):
if n == 1 or n == 2
return 1
else:
return Fibonacci(n-1) + Fibonacci(n-2)
上列代码当参数越来越大时会逐渐变慢,因为递归过程会一直从 n 溯源到 1,再返回 n。
为解决上述问题,可以使用 functools 模块下的 lru_cache 函数,“lru”表示 Least-recently-used(最近最少使用),是一种缓存技术,用其修饰函数可以显著提升访问性能。
from functools import lru_cache
@ lru_cache(maxsize = 100)
def Fibonacci(n):
if n == 1 or n == 2
return 1
else:
return Fibonacci(n-1) + Fibonacci(n-2)
要使用模块所提供的内容,须要先导入模块,导入完成后,对其函数的调用或变量的使用应使用“模块名.函数名()”这样的点语法
导入方式 1:
import math, random
# 可以将不同的模块名称用逗号隔开,一并导入
print(math.sqrt(16))
print(math.pi)
由于模块的名称在代码中会频繁被使用,人们还常在模块导入时以“as + 别名”语法,得到更简短的名字空间,便于使用,如:
import math as ms
import numpy as np
这种使用 as 别名不是新增一个名字空间,而是创建一个以别名为名字的空间,原先的名字并不存在对应的空间。也就是说执行 import math as ms 之后,只可以使用 ms.sqrt,而math.sqrt 这样的调用并不合法。
导入方式 2:
from math import sqrt
print(sqrt(16))
>> 4.0
print(math.sqrt(16))
>> NameError: name 'math' is not defined
import math
dir(math) # 函数可以查看包含的内容
help(math.log) # 函数可以查看帮助信息
random模块比较重要的函数有:
random() 函数,返回 0 到 1 之间均匀分布的随机数
uniform(a, b) 函数,返回介于 a, b 两个数之间的均匀分布的随机数
randint(a, b) 函数,返回介于 a, b 两个数之间,并且包含两端的随机整数
import random
random.seed(2.10)
print(random.random())
sleep()函数,执行以秒为单位的延时。这个函数可以用来构造某些需要延时执行或定期执行的任务
time()函数,用来获取当前时间的时间戳,即1970年1月1日8时0分0秒至当前时间的秒数。常在部分代码前后分别获取时间戳的额方法,来计算代码运行所消耗的时间。
例 5-11 获取代码的运行时间
import time
t0 = time.time()
for i in range(100_0000):pass
t1 = time.time()
print(t1 - t0)
代码中,for 循环内实际上什么都没有做,但是它测试了循环条件100万次,所以消耗了CPU资源,占用了时间
例 5-12 sys 和 os 模块的简单应用
import sys
import os
print(sys.version)
print(sys.path) #系统路径
print(os.name) # 操作系统名称
import module1py
# 调用模块
s1 = "student"
s1[2] = 'U'
>> TypeError
通过切片方式可以访问字符串的多个元素,语法格式如下:
s[start:end:step]
# start 表示切片的起始位置,end 表示切片的截至位置,step 表示切片的步长
# 当 step 为负值时,切片从右向左进行,如果start省略,表示从最右侧开始;如果 end 省略,表示到最左侧结束。
s1 = "12345"
print(s1[::2])
print(s1[::-1])
print(s1[2:0:-1]) # 起始元素是第2个元素,即3。终止元素是第0个元素即1
>> 135
>> 54321
>> 32
假设要完成这样的任务:将字符串s1的每个元素打印输出,中间用逗号隔开。
方案1:
s1 = "student"
for i in range(len(s1)):
print(s1[i], end=',')
方案2:
s1 = "student"
for s in s1:
print(s, end=',')
因为字符串是可迭代对象,如遍历操作无需使用索引,可采用直接遍历的方式。
>> dir(str)
字符串方法的使用方式,如:
s1.upper()
# s1 是字符串类型的对象
# upper()是方法
join 方法可以通过连接符将多个字符串拼接为一个整体,而 split 方法完成相反的操作,将一个字符串拆解为多个单体。
join 方法,如:
firstname = 'Tom'
lastname = 'Lucy'
s1 = "-"
s2 = ""
name = s1.join([firstname, lastname])
print(name)
>> Tom-Lucy
split 方法,如:
name = "Good-Boy, NiceGirl"
x = name.split(sep=',')
print(x)
>> ['Good-Boy', ' NiceGirl']
例 6-2 从长字符串中解析出电子邮箱地址
sentence = '''If you have any question
, please contact me via [email protected]
'''
sentenceSplit = sentence.split(sep=' ')
for temp in sentenceSplit:
if temp.find("@") > 0:
break
email = temp
print(email)
>> 410435553@qq.com
上一节例 6-2 获取邮箱地址的程序虽然能正常工作,但是如果仅仅为了获得一个含有@字符的串,就将全文进行 split 切割,十分浪费资源,特别是邮箱字符串之后还存在着大段文字的时候。
新的思路:首先定位@的位置,然后确定其附近两个空格的位置,以此获取所需要的邮箱地址信息。
例 6-3 从长字符串中解析出电子邮箱地址
import string
sentence = '''If you have any question
, please contact me via [email protected], Thanks.
'''
emailSign = sentence.find("@")
emailEnd = sentence.find(" ", emailSign) # 第2锚点
temp = sentence[emailSign::-1]
emailBegin = len(sentence) - (temp.find(" ") + len(sentence) - emailSign)
email = sentence[emailBegin:emailEnd]
email = email.strip(string.punctuation)
print(email)
列表和元组是 Python 中重要的组合型数据类型。其中列表是可变类型,它可以存储各种类型的元素,并实现增删改。而元组是不可变类型,可以用来存储无需修改的对象。
列表作为一种有序序列,有诸多特性和字符串类似,如:
创建列表,主要有以下两种方式:
一是描述法,直接用定界符 [] 把若干元素封装起来,如:
list1 = []
list2 = [1, 2, 5]
二是构造函数法,即使用 list 函数将其他类型对象转换为列表,如:
list3 = list()
list4 = list("apple")
list5 = list(range(8))
print(list3, list4, list5)
>> [] ['a', 'p', 'p', 'l', 'e'] [0, 1, 2, 3, 4, 5, 6, 7]
对象的可变性是 Python 数据的重要特性,前面所讲的简单类型如整数、浮点数、布尔型都是不可变(immutable)对象,而列表是可变(mutable)对象。
注:同样是序列类型的字符串是不可变的,意味着字符串一旦创建完毕,就不可修改。
修改列表中的元素,如:
list1 = [1, 2, 5]
print(list1)
list1[0] = 9
print(list1)
>> [1, 2, 5]
>> [9, 2, 5]
修改字符串中的元素,如:
string1 = "apple"
string1[0] = "z"
>> TypeError: 'str' object does not support item assignment
列表支持 +、+=、、=操作,分别表示连接和延展。与字符串类似。
列表支持 in、not in 这样的成员检查,如:
list1 = [1, 2, 3]
print(4 in list1, 4 not in list1)
>> False True
列表的 append 方法,可在列表的末尾添加1个元素。
insert 方法,在列表的指定位置添加1个元素。
extend 方法,把另一个列表合并到当前列表的尾部。
append 方法,如:
list1 = [1, 2, 3]
list1.append(4)
print(list1)
>> [1, 2, 3, 4]
insert 方法,如:
list1 = [1, 2, 3]
list1.insert(1, 4)
print(list1)
>> [1, 4, 2, 3]
extend 方法,如:
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list1.extend(list2)
print(list1)
>> [1, 2, 3, 4, 5, 6]
列表的 remove 方法,可删除列表中指定的值,当存在多个值时只删除第一个,当值不存在时报错。
pop 方法可删除指定位置的元素,参数为index。未列明参数时,默认删除最后一个元素。
clear 方法可清除列表的所有对象,使列表变为空的列表,无参数。
此外,还可以使用 del 语句来删除指定的元素或整个列表。
A = list("123456")
A.remove("2")
print(A)
C = A.pop(0)
print(A)
del A[0:2]
print(A)
>> ['1', '3', '4', '5', '6']
>> ['3', '4', '5', '6']
>> ['5', '6']
要修改列表,只要通过索引的方式定位元素,然后重新赋值即可。
A = list("123456")
A[0] = 777
print(A)
>> [777, '2', '3', '4', '5', '6']
B = list("123456")
B[-1:-3:-1] = 5, 6
print(B)
>> ['1', '2', '3', '4', 6, 5]
C = list("123456")
C[-1:-3:-1] = 5, 6, 7
print(C)
>> ValueError: attempt to assign sequence of size 3 to extended slice of size 2
例 7-2 使用冒泡排序法对 [14, 77, 84, 10, 3, 99] 进行从小到大的排序。
def bubbleSort(listX):
length = len(listX)
for i in range(0, length-1):
for j in range(0, length-1-i):
if listX[j] > listX[j+1]:
listX[j], listX[j+1] = listX[j+1], listX[j]
list1 = [14, 77, 84, 10, 3, 99]
bubbleSort(list1)
print(list1)
>> [3, 10, 14, 77, 84, 99]
函数执行效果和预期相同,但是本代码存在资源浪费,因为经过不长的几轮循环后,排序事实上已经完成,但CPU仍在空跑。可以加入判断排序是否已经完成的标志位,一旦为True,则及时退出函数,如:
# 整轮对调循环都没有发生一次对调,则认为排序已经完成
def bubbleSort(listX):
length = len(listX)
for i in range(0, length-1):
isSwap = False
for j in range(0, length-1-i):
if listX[j] > listX[j+1]:
listX[j], listX[j+1] = listX[j+1], listX[j]
isSwap = True
if not isSwap:
return
list1 = [14, 77, 84, 10, 3, 99]
bubbleSort(list1)
print(list1)
>> [3, 10, 14, 77, 84, 99]
使用 count 方法可以获取指定元素出现的次数,如:
list1 = [1, 2, 5, 2]
print(list1.count(1))
print(list1.count(2))
>> 1
>> 2
列表的 index 方法可以获取指定元素首次出现的位置,如:
list1 = [1, 2, 5, 2]
print(list1.index(1))
print(list1.index(2))
>> 0
>> 1
使用 sort 方法对列表进行排序,操作完成之后,列表本身被修改。若要保持原列表不变,同时得到排序之后的新列表,可以使用 sorted 函数。
list1 = [1, 2, 5, 2]
list1.sort()
print(list1)
>>[1, 2, 2, 5]
list1.sort(reverse = True) # 降序
print(list1)
>> [5, 2, 2, 1]
list2 = sorted(list1)
print(list1)
print(list2)
>> [5, 2, 2, 1]
>> [1, 2, 2, 5]
在编程中,常需要遍历列表。方式1:根据索引访问,方式2:不使用索引访问,如:
list1 = [1, 2, 5, 2]
# 方式1:
for i in range(len(list1)):
print(list1[i], end='')
# 方式2:
for x in list1:
print(x, end='')
对于列表 list1 而言,可以使用 [:]、s.copy、copy.copy(list1)、copy.deepcopy(list1)实现列表的复制,现探究这些操作的区别。
和原数据是否指向同一对象 | 第一层为基本数据类型 | 原数据中包含子对象(如二维列表) | |
---|---|---|---|
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 |
深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |
list1 = [1, 2, 3]
list2 = list1
list1 = [1, 2, 3]
list2 = list1[:]
print(list1, list2)
list1[2] = 5
print(list1, list2)
>> [1, 2, 3] [1, 2, 3]
>> [1, 2, 5] [1, 2, 3]
list1 = [1, 2, [3, 4]]
list2 = list1[:]
print(list1, list2)
list1[2][0] = 5
print(list1, list2)
>> [1, 2, [3, 4]] [1, 2, [3, 4]]
>> [1, 2, [5, 4]] [1, 2, [5, 4]]
注: 列表和字符串的 [:] 不完全相同,列表是浅拷贝,字符串是引用
import copy
list1 = [1, 2, [3, 4]]
list2 = copy.deepcopy(list1)
print(list1, list2)
list1[2][0] = 5
print(list1, list2)
>> [1, 2, [3, 4]] [1, 2, [3, 4]]
>> [1, 2, [5, 4]] [1, 2, [3, 4]]
列表推导式是代替复杂循环的一种简洁语法形式。各语句之间是嵌套关系,左边第二个语句是最外层,依次往右进一层,左边第一条语句是最后一层(遵从从外而内的写法)。
list1 = []
for i in range(1, 11):
list1.append(i**2)
print(list1)
>> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
上例的 for 循环可改写为:
list1 = [x**2 for x in range(1,11)]
print(list1)
>> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
例 7-3 生成公差为 0.65 的等差序列,从 3 开始,共 7 个元素
list1 = [3+x*0.65 for x in range(7)]
print(list1)
>> [3.0, 3.65, 4.3, 4.95, 5.6, 6.25, 6.9]
例 7-4 带条件过滤的列表推导式
list1 = [1, 2, 3, 4, 5, 6, 7, 8]
list2 = [x for x in list1 if x % 2 != 0]
print(list2)
>> [1, 3, 5, 7]
list1 = [x * x for x in range(1, 11) if x % 2 == 0]
print(list1)
>> [4, 16, 36, 64, 100]
例 7-5 使用列表推导式构造笛卡尔积
list1 = [m+n for m in 'ABC' for n in 'XYZ']
print(list1)
>> ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
例 7-6 搭配方法调用的列表推导式
list1 = ['Hello', 'World']
list2 = [s.lower() for s in list1]
print(list2)
>> ['hello', 'world']
例 7-7 将某列表中的所有元素 0 ,均改为 zero ,其余不变
import random
list1 = [random.randint(-3, 3) for x in range(20)]
print(list1)
list1 = ['zero' if x == 0 else x for x in list1]
print(list1)
>> [0, 2, -2, 2, 3, -3, 1, 1, -1, -2, 0, 3, 0, -1, -3, 0, 2, 3, -1, 2]
>> ['zero', 2, -2, 2, 3, -3, 1, 1, -1, -2, 'zero', 3, 'zero', -1, -3, 'zero', 2, 3, -1, 2]
# 另一种方案,目前看不懂什么意思
import random
list1 = [random.randint(-3, 3) for x in range(20)]
print(list1)
list1 = map((lambda x: 'zero' if x == 0 else x), list1)
print(list1)
>> [-3, -1, 1, 3, -3, -3, -1, 0, 2, -1, -3, -2, -2, -3, 1, 3, -3, 1, 3, -3]
>> <map object at 0x000001C26C216700>
例 7-8 矩阵的转置,如:
matrix = [[1,2,3], [4,5,6]]
matrix = [[matrix[r][c] for r in range(len(matrix))] for c in range(len(matrix[0]))]
# matrix[0] == [1,2,3]
print(matrix)
>> [[1, 4], [2, 5], [3, 6]]
这段代码通过双重 for 循环构造了嵌套的列表推导式,最终实现了转置。但是,当代码变得复杂的时候,我们要评估使用这种“高端”写法的必要性,这种写法的优点显而易见——简洁,缺点也显而易见——增加程序设计时间且降低程序可读性。
例 7-9 结构扁平化(将列表内部元素无差别的展开)
# list1 中含有元组、列表、字典(均为sublist1)
list1 = [(1,2), [3,4,5], (6,7), {8,9}]
list2 = [x for sublist1 in list1 for x in sublist1]
print(list2)
>> [1, 2, 3, 4, 5, 6, 7, 8, 9]
构造嵌套列表推导式的方法非常常用,但要注意两个 for 循环的顺序,应遵从从外而内的写法。本例中,外就是 for sublist1 in list1,内就是 for x in sublist1
元组是Python内置的又一重要数据结构,与列表不同,元组是不可变的(一旦创建,其内部元素便不能修改)。从形式上看,元组使用()将元素括起,元素之间用逗号分隔,元组中的元素可以是单一的类型也可以是混杂的类型。如果在程序中确定某序列无须改变,则建议采用元组以保障程序高效可靠。
A = (1,) #只有一个元素时必须加逗号,否则表示整数1
B = (1, 2)
print(A, B)
>> (1,) (1, 2)
可以使用 tuple 函数将序列转化为元组
A = tuple() #创建空的元组
B = tuple("Anaconda") #('A', 'n', 'a', 'c', 'o', 'n', 'd', 'a')
C = tuple(range(2, 8))
print(A, B, C)
>> () ('A', 'n', 'a', 'c', 'o', 'n', 'd', 'a') (2, 3, 4, 5, 6, 7)
例 7-10 测试元组和列表的访问性能
import timeit, sys
N = 100_0000
#它接受一个参数为每个测试中调用被计时语句的次数,默认为一百万次;返回所耗费的秒数。
t1 = timeit.timeit('A = [1, 2, "OK", False]', number=N)
t2 = timeit.timeit('B = (1, 2, "OK", False)', number=N)
print(t1, t2)
>> 0.1182347 0.02775139999999998
s1 = sys.getsizeof([1, 2, "OK", False])
s2 = sys.getsizeof((1, 2, "OK", False))
print(s1, s2)
>> 88 72
值得一提的是,当元组的元素为列表时,这个列表中的元素可以被修改,从而以一种特别的方式修改元组
A = (1, 3, [1, 5], 4)
A[2][0] = 3
print(A)
>> (1, 3, [3, 5], 4)
此外,元组变量可以被整体重新赋值,这是显然的。除了元组的内容不可改变之外,元组的操作和列表有如下相似之处:
前面所学的由 [] 定界的列表推导式实现了把冗长的循环语句简化,但其存在耗占内存过多的问题,为此Python提供另一种生成器(generator)表达式解决方案,既实现了形式上的简洁,又具有生成器不耗占内存的优势。
将 [x**2 for x in range(1, 11)] 外面的 [] 改为 () 即可
A = (x**2 for x in range(1, 11))
print(list(A))
print(list(A)) #第二次访问时为空,这是因为生成器具有重要的特性——一旦被访问即被消耗
>> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>> []
字典是另一种可变容器模型,且可存储任意类型对象。字典的访问速度比列表更快,但其所占据的空间比列表更大。字典的每个键值 key=>value 对用冒号 “:” 分割,每个对之间用逗号“,” 分割,整个字典包括在花括号 {} 中 ,格式如下所示:
dict1 = {key1:value1, key2:value2, key3:value3, ...}
dict = {'Name': 'Runoob', 'Age': 7, 'Class': 'First'}
print("dict['Name']: ", dict['Name'])
>> dict['Name']: Runoob
print("dict['Age']: ", dict['Age'])
>> dict['Age']: 7
字典的创建可以采用 {} 定界描述的方法,也可以采用 dict 函数构造的方法(dict 可以把形如字典形式的列表或元组转换为字典)。如:
# {} 定界描述的方法
team = {"赵":22, "钱":34, "孙":28}
# dict 函数构造的方法
team = dict(赵=22, 钱=34, 孙=28)
字典的 keys 方法返回字典的键列表
字典的 values 方法返回字典的值列表
字典的 items 方法返回字典的 键值对 列表
dict = {'Name': 'Runoob', 'Age': 7, 'Class': 'First'}
print("dict['Name']: ", dict['Name'])
dict = {'Name': 'Runoob', 'Age': 7, 'Class': 'First'}
dict["Name"] = "Bamboo"
print(dict["Name"])
>> Bamboo
字典的 dt1.update(dt2) 方法把字典 dt2 的数据(键值对)更新到调用者字典中
字典的 dt1.pop(“Name”) 方法删除指定的键值对
字典的 dt1.clear() 方法清除字典的数据
例 8-2 统计一段字符中各个字符出现的个数,为达成这一需求,固然也可以使用链式判断的方法,或者列表存储的方法,但是过程都比较繁琐,因为事前并不确定字符串中可能存在的哪些字符。
方式1:对字符进行逐个判断,若不在字典中则设置为1,若已在字典中则将值加1
sentence = "where there is a will, there is a way"
d = {}
for c in sentence:
if c not in d: # 按”键“判断其是否存在
d[c] = 1
else:
d[c] += 1
print(d.items())
>> dict_items([('w', 3), ('h', 3), ('e', 6), ('r', 3), (' ', 8), ('t', 2), ('i', 3), ('s', 2), ('a', 3), ('l', 2), (',', 1), ('y', 1)])
方式2:利用了 get 方法的特性,不存在时返回缺省值 0,存在时返回实际值
sentence = "where there is a will, there is a way"
d = {}
for c in sentence:
d[c] = d.get(c, 0) + 1
print(d.items())
>> dict_items([('w', 3), ('h', 3), ('e', 6), ('r', 3), (' ', 8), ('t', 2), ('i', 3), ('s', 2), ('a', 3), ('l', 2), (',', 1), ('y', 1)])
在编写循环结构时,如果仅展示序列对象的元素,可以使用 for…in…循环结构,如果需要同时展示元素的序号以及元素的内容,可使用如下代码:
A = [1, 2, "345"]
for i, name in enumerate(A):
print(i+1, name)
>> 1 1
>> 2 2
>> 3 345
多重循环时,可以使用 product 函数构造笛卡尔积,从而减少嵌套的层数,即将多重循环扁平化。
product(list1,list2)依次取出list1中每1个元素,与list2中的每1个元素,组成元组,将所有元组组合成一个列表返回。
from itertools import product
A = list(product([1,2,5], [8,3]))
print(A)
>> [(1, 8), (1, 3), (2, 8), (2, 3), (5, 8), (5, 3)]
例 10-1 鸡兔同笼问题
# 原方法:
def calcCR(head, foot):
for chick in range(1, head + 1):
for rabbit in range(1, head + 1):
if chick + rabbit == 35 and chick * 2 + rabbit * 4 == 94:
return chick, rabbit
# 改写后的方法:
from itertools import product
def calcCR_new(head, foot):
for chick,rabbit in product(range(1,head+1), range(1,head+1)):
if chick+rabbit == head and chick*2 + rabbit*4 == foot:
return chick,rabbit
print(calcCR_new(10, 30))
>> (5, 5)
编程实践中,常须对序列的成员构成情况进行判断。这样的场景下,善用 any 和 all 函数可以编写出简洁、可靠的代码。
例 10-2 使用 any 函数判断成员情况
A = [7, 25, 36, 18, 31]
print(any(10 <= i <= 20 for i in A))
>> True
例 10-3 使用 all 函数判断成员情况
例子有误
前面所学的 eval 函数的功能是求形如字符串的表达式的值,exec 函数的功能是执行一段程序代码。相比于 eval 而言,exec 可以执行更复杂的 Python 代码。
A = eval("3+3")
print(A)
>> 6
exec('print("Hello world")')
exec("print(3+3)")
exec("""for i in range(5):
print("iter time: %d" %i)
""")
>> Hello world
6
iter time: 0
iter time: 1
iter time: 2
iter time: 3
iter time: 4
P160
迭代器可以看作是一个特殊的对象,每次调用该对象时会返回自身的下一个元素,从实现上来看,一个迭代器对象必须是定义了__iter__()方法和next()方法的对象。字符串,列表或元组对象等可迭代对象都可用于创建迭代器。
list1 = [1, 2, 3, 4] # 列表
iter1 = iter(list1)
print(next(iter1))
>> 1
print(next(iter1))
>> 2
遍历迭代器可以使用常规的 for 语句也可以使用 next() 函数:
# 使用常规 for 语句进行遍历
list1 = [1, 2, 3, 4]
iter1 = iter(list1)
for x in iter1:
print(x, end=" ")
>> 1 2 3 4
# 使用 next() 函数进行遍历
import sys
list1 = [1, 2, 3, 4]
iter1 = iter(list1)
while True:
try:
print(next(iter1))
except StopIteration:
sys.exit()
>>
1
2
3
4
创建一个迭代器的方法:
把一个类作为迭代器使用需要在类中实现两个方法:__iter__()
与__next__()
Python 的构造函数为 init(), 它会在对象初始化的时候执行
__iter__()
方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 next() 方法并通过 StopIteration 异常标识迭代的完成。
__next__()
方法(Python 2 里是 next())会返回下一个迭代器对象。
创建一个返回数字的迭代器,初始值为 1,逐步递增 1:
class MyNumbers:
def __iter__(self):
self.a = 1
return self
def __next__(self):
x = self.a
self.a += 1
return x
myclass = MyNumbers()
myiter = iter(myclass)
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
>> 1
2
3
4
5
StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__()
方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代
class MyNumbers:
def __iter__(self):
self.a = 1
return self
def __next__(self):
if self.a <= 20:
x = self.a
self.a += 1
return x
else:
raise StopIteration
myclass = MyNumbers()
myiter = iter(myclass)
for x in myiter:
print(x)
列表推导式使用 []
定界,元素立即生成,因而占据内存。生成器表达式使用()
定界,在系统中保存算法,而非元素,元素惰性生成,仅占据少量内存。
生成器表达式是一种迭代器,可使用for
循环遍历,也可以使用next
函数寻访,但是寻访之后元素不可复现。
当有待构造的序列数目较大时,应考虑使用生成器表达式。
from collections.abc import Iterable, Iterator
import sys
list1 = [x*x for x in range(10)]
print(isinstance(list1, Iterable)) # 列表是可迭代对象
print(isinstance(list1, Iterator)) # 列表不是迭代器
generator1 = (x*x for x in range(10))
print(isinstance(generator1, Iterable)) # 生成器是可迭代对象
print(isinstance(generator1, Iterator)) # 列表不是迭代器
print(sys.getsizeof(list1), sys.getsizeof(generator1)) # 184 112
构造惰性生成的 generator 符合Python风格,但是普通的 generator 功能有限,难以实现复杂的逻辑。如需构造斐波那契序列那样有着复杂逻辑的序列时,可以使用函数生成器。
函数生成器这样构造:
将普通定义的函数中输出元素的关键语句用yield
改写,yield意味着收获,其取得的一系列元素就是蕴含在生成器对象中的一个又一个元素。
# 普通求解斐波那契序列的函数
def fib(num):
n = 1
a, b = 0, 1
while n <= num:
print(b)
a, b = b, a+b
n = n + 1
return "done"
print(fib(5))
>>
1
1
2
3
5
done
yield
语句,整个代码就构造了函数式生成器def fib(num):
n = 1
a, b = 0, 1
while n <= num:
yield b # yield 语句
a, b = b, a+b
n = n + 1
return "done"
print(fib(5)) #
print(list(fib(5))) #[1, 1, 2, 3, 5]
>>
<generator object fib at 0x000001FD06B7C9E0>
[1, 1, 2, 3, 5]
函数生成器是一个对象,这个对象常常被 for 循环遍历访问,或被 list 函数展开。这两者的本质都是调用 __next__()
方法访问对象的一个又一个元素。函数生成器被寻访的过程中,遇到 yield 就输出一个元素并中断,指导下次被 __next__()
方法访问时继续执行,在这一过程中 return 语句被架空,并不会执行。最后,即所有的数据都被取光时,报 StopIteration 错,此时函数生成器中的 return 语句执行。
例 10-7 使用函数生成器构造无限序列。对于 蕴含无数个元素的序列,断然是无法用[]
或()
来描述表示的,但可以用函数生成器构造他们,这进一步说明函数生成器存储的不是实体,而是算法。
def getn():
n = 1
while True:
yield n
n += 1
generator1 = getn()
print(next(generator1))
print(next(generator1))
print(next(generator1))
>>
1
2
3
例 10-8 设计模仿 map 函数的 myMap 和 myNewMap 函数
ori = range(10)
# 方式1,使用系统 map
result1 = map(lambda x: x*x, ori)
print(list(result1))
def myMap(func, num):
rst = []
for x in range(num):
rst.append(func(x))
return rst
rst2 = myMap(lambda x: x*x, 10)
print(list(rst2))
>> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
def myNewMap(func, li):
for x in li:
yield func(x)
ori = range(10)
rst3 = myNewMap(lambda x: x*x, ori)
print(list(rst3))
>> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
itertools 模块中,配置了很多有用的工具。如:
itertools.combinations():获得对象的组合,即不考虑元素顺序
itertools.permutations():获得对象的排列,即考虑元素顺序
itertools.groupby():对连续的元素进行分组
不同维度的切片中间用 逗号 隔开
import numpy as np
A = np.arange(9).reshape(3,3)
print(A)
>>
[[0 1 2]
[3 4 5]
[6 7 8]]
print(A[0:1, :]) # 输出[0,1) 行,所有列,等同于输出第0行。
>> [[0 1 2]]
print(A[0:1, 0:2]) # 输出[0,1) 行,[0,2)列,等同于输出第0行的第0、1列。
>> [[0 1]]
对于二维数组而言,当两个维度的切片方式都为整数时,得到标量;当两个维度中间有一个整数时,得到的是一维数组;当两个维度中都含有:
时,得到二维数组。