零基础入门学习 Python - 基础篇
本笔记内容主要来自 B 站教程:【Python 教程】《零基础入门学习 Python》
我稍微补充了一些内容,为了更方便理解,也做了一定的注释。
>>>
:表示执行,下方为输出的结果
⭐
:重点内容(比较常用)
重点内容目录
准备工作
首先你需要搭建 python 的环境,具体搭建方法可以看这里:
Python 小知识
文档中符号的意义
*other和other
它们表示的参数意义稍有不同,other 表示只能传入一个参数,*other 可以时多个参数。
更具体的可以看函数的参数
/
*
KISS 原则
全称是 Keep It Simple and Stupid,也就是 “Python 之禅” 里面提到的 “简洁胜于复杂”。
唯一标识
在 py 中每个对象都有 3 个属性:唯一标识、类型、值
唯一标识创建时就有,不能修改且无重复
迭代器和可迭代对象
一个迭代器肯定是一个可迭代对象。
最大的区别是:可迭代对象咱们可以对其进行重复的操作,而迭代器则是一次性的!
迭代器的值读取后,迭代器就变成空了
将可迭代对象转换为迭代器:iter()
函数。
BIF 里面有一个 next() 函数,它是专门针对迭代器的。
它的作用就是逐个将迭代器中的元素提取出来:
x = [1, 2, 3]
y = iter(x)
next(y) # 还可以这样:next(y, "都已经被取出"),来避免报异常
1
方法和函数
方法隶属于类,通过类和对象调用方法。例如在list.append(x)
中,list 是列表对象;函数不属于任何类,直接调用即可,例如list(iterable)
基础知识
1、IDLE 编辑器
刚开始学习推荐直接使用 python 套件自带的IDLE
工具。
这个编辑器拥有两种模式:交互模式和编辑模式。
默认打开的是交互模式,就是你输入代码,会给你反馈;
另一种是编辑器模式,通过新建文件来创建。
常用的快捷键
Alt + P = 直接自动输入上一句
Ctrl + C = 停止运行
2、新手常见错误
- 标点符号必须是英文符号
- 缩进必须正确(不同的缩进表示不同的层级)
- 函数拼写必须正确
3、变量 variable
变量名不可以以数字开头,支持中文;
字母是分大小写的;
两个变量值要更换的话,可以不借用第三个变量,直接用如下的代码来实现:
x = 3
y = 5
x,y = y,x
4、字符串 string
需要使用单引号、双引号括起来字符串内容
(单双引号可以解决文本内本身存在引号的情况)
使用转义字符,也可以避免引号被错误识别的尴尬。
输入路径的时候,推荐使用反斜杠转义字符。例:
print("D:\\three\\two\\one\\now")
也可以直接加一个r
来实现同样的效果,如:
print(r"D:\three\two\one\now")
如果语句最后还有反斜杠,表示还没有结束,如:
print('春暖花开\n\
你快回来')
>>>
春暖花开
你快回来
长字符串(三引号)
三个单引号,或者三个双引号来表示
例:
print('''春暖花开
你快回来''')
>>>
春暖花开
你快回来
这样就不用用反斜杠来表示还没有结束了
字符串的加减
字符串的相加我们叫拼接,就是将字符串组合成一个新的长字符串
字符串的乘法,表示重复
例:
print('春暖'+'花开')
>>>
春暖花开
5、比较运算符
返回值为 True/False
6、分支与循环
分支
判断单个条件:
if 条件:
某条语句或某个代码块
else:
某条语句或某个代码块
判断多个条件:⭐
if 第1个条件:
某条语句或某个代码块
elif 第2个条件:
某条语句或某个代码块
elif 第3个条件:
某条语句或某个代码块
else:
某条语句或某个代码块
例:
score = input("请输入您的分数:")
score = int(score)
if 0 <= score < 60:
print("D")
elif 60 <= score < 80:
print("C")
elif 80 <= score < 90:
print("B")
elif 90 <= score < 100:
print("A")
else:
print("S")
条件表达式⭐
条件成立时执行的语句 if 条件 else 条件不成立时执行的语句
相当于将一个完整的 if-else 结构整合成一个表达式来使用
例:
score = input("请输入您的分数:")
score = int(score)
level = ("D" if 0 <= score < 60 else
"C" if 60 <= score < 80 else
"B" if 80 <= score < 90 else
"A" if 90 <= score < 100 else
"S")
print(level)
分支结构的嵌套
理论上来说可以无限嵌套
例:
age = input("请输入您的年龄:")
age = int(age)
ismale = input("请确认是男生吗?(y/n):")
if age < 18:
print("不欢迎未成年进入!")
else:
if ismale == "y":
print("欢迎光临~")
else:
print("请仔细考虑后,再确认进入")
循环
Python 有两种循环语句:while 循环和 for 循环
while 循环
while 条件:
某条语句或某个代码块
break
使用 break 语句可以跳出一层循环
例:
while True:
a = input("主人我可以退出循环吗?(y/n)")
if a == "y":
break
print("我好累呀~")
例:
counts = 3
while counts > 0:
temp = input("不妨猜一下我的年纪:")
guess = int(temp)
if guess == 8:
print("你好聪明")
break
else:
if guess < 8:
print("小啦")
else:
print("大啦")
counts = counts - 1
print("游戏结束")
continue
跳出本一轮循环,回到循环体的条件判断位置,然后继续下一轮循环(如果条件还满足的话)
i = 0
while i < 10:
i += 1
if i % 2 == 0:
continue
print(i)
注意它和 break 语句两者的区别:
- continue 语句是跳出本次循环,回到循环的开头
- break 语句则是直接跳出循环体,继续执行后面的语句
else
当循环的条件不再为真的时候,便执行 else 语句的内容
(break 跳出后 else 将不再执行)
day = 0
while day <= 6:
a = input("今天你有好好学习吗?(Y/N):")
if a == 'N':
break
day += 1
else:
day = str(day)
print("你已经坚持" + day + "天啦!真棒!!")
嵌套
循环也能玩嵌套
例:
i = 1
while i <= 9:
j = 1
while j <= i:
print(j,"*",i,"=",j*i,end=" ")
j += 1
print()
i += 1
无论是 break 语句还是 continue 语句,它们只能作用域一层循环体
for 循环
它的语法结构如下:
for 变量 in 可迭代对象:
某条语句或某个代码块
什么是可迭代对象?
所谓可迭代对象,就是指那些元素能够被单独提取出来的对象。
比如我们学过的字符串,它就是一个可迭代对象。
什么叫迭代呢?
比如说让你每一次从字符串 "Sheji" 里面拿一个字符出来,那么你依次会拿出 'S'、'h'、'e'、'j'、'i' 五个字符,这个过程我们称之为迭代。
例:
for each in "shejibiji":
print(each)
如果需要用 while 也可以实现:
i = 0
while i < len("shejibiji"):
print("shejibiji"[i])
i += 1
因为整数不是可迭代对象,可以引入 range()函数
range() 函数
记住这个范围是不包含结束值的
range() 会帮你生成一个数字序列,它的用法有以下三种:
- range(stop) - 将生成一个从 0 开始,到 stop (不包含)的整数数列
- range(start, stop) - 将生成一个从 start 开始,到 stop(不包含)的整数数列
- range(start, stop, step) - 将生成一个从 start 开始,到 stop(不包含)结束,步进跨度为 step 的整数数列
注意:无论你使用哪一种,它的参数都只能是整数。
例:
sum = 0
for i in range(1001):
sum += i
print(sum)
>>>
500500
7、随机模块 random
伪随机数,是可以重现的。
默认情况下,random 是使用系统时间来作为随机种子
导入模块:import random
# 先引入模块,然后再输入单独的语句进行测试
import random
print( random.randint(1,10) ) # 产生 1 到 10 的一个整数型随机数
print( random.random() ) # 产生 0 到 1 之间的随机浮点数
print( random.uniform(1.1,5.4) ) # 产生 1.1 到 5.4 之间的随机浮点数,区间可以不是整数
print( random.choice('tomorrow') ) # 从序列中随机选取一个元素
print( random.randrange(1,100,2) ) # 生成从1到100的间隔为2的随机整数
a=[1,3,5,6,7] # 将序列a中的元素顺序打乱
random.shuffle(a)
print(a)
8、数字类型
整数 integers
长度不限制
浮点数
精度上有误差,比如:
i = 0
while i < 1:
i = i + 0.1
print(i)
>>>
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
1.0999999999999999
所以拿浮点数来比较要小心。
decimal
模块可以解决这个问题,实现精确计算浮点数。
复数
复数包含了一个实部和一个虚部,如:
1 + 2j
(1+2j)
复数在数学和工程学中非常有用,复数提供了一个强大的数学框架,用于解决现实世界中涉及多维度和周期性的问题。通过将问题表达为复数形式,可以简化数学运算,并提供深入的理解和直观的解决方案。
数字类型之间的运算
地板除 // :
取比目标结果小的最大整数(向下取整)
例:
# 计算水仙花数(三位数,各位的立方之和等于三位数本身)
i = 100; r = 0; s = 0; t = 0
while i < 1000:
s = i // 100 # 取百位上的数
r = (i - s * 100) // 10 # 取十位上的数
t = i - s * 100 - r * 10
if i == (s ** 3 + r ** 3 + t ** 3):
print("i = ", str(i))
pow() 函数:
支持第 3 个参数,如果传入第 3 个参数,那么会将幂运算的结果和第 3 个参数进行取余数运算
pow(2,3,5)
>>>
3
9、布尔类型 bool
True 和 False(首字母一定要大写!)
结果是 True 的情况非常多,但 False 却是屈指可数,下面这些几乎就是结果为 False 的所有情况:
- 定义为 False 的对象:None 和 False
- 值为 0 的数字类型:0, 0.0, 0j, Decimal(0), Fraction(0, 1)
- 空的序列和集合:'', (), [], {}, set(), range(0)
10、逻辑运算符
对于 and 和 or 运算符,它的计算结果不一定是 True 或者 False。
这要看它的操作数是什么了,如果你给到操作数的是两个数值,那么它的运算结果也是数值:
3 and 4
>>>
4
3 or 4
>>>
3
短路逻辑
and 和 or 这两个运算符都是遵从短路逻辑的。
短路逻辑的核心思想就是:从左往右,只有当第一个操作数的值无法确定逻辑运算的结果时,才对第二个操作数进行求值。
其实就是从左到右,如果有数可以判断结果,就直接出结果。
在上面的案例中,and 需要验证左右两边的 3 和 4,才能知道真假,所以出了 4;or 因为验证到 3 时,就可以判断结果,所以结果直接输出了 3。
运算符优先级
这个表格从低到高列出了 Python 的运算符优先级
优先级 | 运算符 | 描述 |
---|---|---|
1 | lambda | Lambda 表达式 |
2 | if - else | 条件表达式 |
3 | or | 布尔“或” |
4 | and | 布尔“与” |
5 | not x | 布尔“非” |
6 | in, not in, is, is not, <, <=,>, >=, !=, == | 成员测试,同一性测试,比较 |
7 | | | 按位或 |
8 | ^ | 按位异或 |
9 | & | 按位与 |
10 | <<, >> | 移位 |
11 | +, - | 加法,减法 |
12 | *, @, /, //, % | 乘法,矩阵乘法,除法,地板除,取余数 |
13 | +x,-x, ~x | 正号,负号,按位翻转 |
14 | ** | 指数 |
15 | await x | Await 表达式 |
16 | x[index], x[index:index],x(arguments...), x.attribute | 下标,切片,函数调用,属性引用 |
17 | (expressions...), [expressions...],{key: value...}, {expressions...} | 绑定或元组显示,列表显示,字典显示,集合显示 |
11、列表 list
列表 是 Python 中最基本的数据结构。
创建列表只需要用方括号括起来,中间用逗号隔开即可。
list = [1, 2, 3, 4, 5, '我最厉害']
访问列表中的元素,可以使用下标索引的方法,下标索引从 0 开始。
list[1]
Python 还支持你 “倒着” 进行索引:
list[1]等同于list[-5]
列表切片
将原先的单个索引值改成一个范围即可实现切片。
支持放入两到三个索引值,形如:
[开始索引:结束索引:间隔数]
开始索引结束索引如果是开头或者末尾,可以不写。
如:list[3:]
,表示从下标 3 到末尾
例:
list = [1, 2, 3, 4, 5, '我最厉害']
list[0:3:2]
>>>
[1, 2, 3]
list[1:6:2]
>>>
[2, 4, '我最厉害']
间隔数如果为负数,则为倒序。
间隔数为负数时,末尾为列表开头,这个时候索引也要倒写才对。
例:
list = [1, 2, 3, 4, 5, '我最厉害']
list[5:0:-1]
>>>
['我最厉害', 5, 4, 3, 2]
列表的增删
append() 方法: 在列表的末尾添加一个指定的元素
extend() 方法: 在列表的末尾允许一次性添加多个元素
注意:extend() 方法的参数必须是一个可迭代对象,然后新的内容是追加到原列表最后一个元素的后面。
如:
list.extend([6, 7])
还可以使用切片语法,来实现同样的需求。
如:
list[len(list):] = [8, 9]
insert() 方法: 允许你在列表的任意位置添加数据,它的参数有两个,第一个参数指定的是插入的位置,第二个参数指定的是插入的元素。
如:
list.insert(1, 2)
表示在下标 1 的位置插入 2+= 运算符: 也可以实现列表的增加
如:
list += [6, 7]
但是这个方法有一个缺点,就是它会直接修改原列表,而不会返回一个新的列表。
如果你想要一个新的列表,可以使用
+
运算符。如:
list = list + [6, 7]
remove() 方法: 将列表中指定的元素删除。
这里有两点要注意的:
如果列表中存在多个匹配的元素,那么它只会删除第一个
remove() 方法要求你指定一个待删除的元素,如果指定的元素压根儿不存在,那么程序就会报错
del 语句: 删除列表中指定位置的元素
如:
del list[1]
pop() 方法: 删除某个指定位置上的元素,它的参数就是元素的下标索引值。
如果你没有指定一个参数,那么它“弹”出来的就是最后一个元素。
如:
list.pop()
如果你指定了参数,那么它就会删除指定位置的元素。
如:
list.pop(2)
clear() 方法: 清空列表中的所有元素
如:
list.clear()
,这个方法不需要传入参数copy() 方法: 复制列表
count() 方法: 计算某个元素在列表中出现的次数
如:
list.count(1)
,表示统计 1 在列表中出现的次数
列表的改
替换列表中的元素跟访问元素类似,都是使用下标索引的方法,然后使用赋值运算符就可以将新的值给替换进去了。
如果有连续的多个元素需要替换,可以利用切片来实现。
list = [1, 2, 3, 0, 0]
list[3:] = [4, 5]
list
>>>
[1, 2, 3, 4, 5]
实现原理:
sort() 方法
可以实现数字从小到大排列。
reverse() 方法
实现反转列表。
例:
list = [1, 2, 3, 0, 0]
list.sort()
list
>>>
[0, 0, 1, 2, 3]
list.reverse()
list
>>>
[3, 2, 1, 0, 0]
两个还可以结合使用。
例:
list = [1, 2, 3, 0, 0]
list.sort(reverse = True)
list
>>>
[3, 2, 1, 0, 0]
列表的查
count() 方法
查找列表中元素的数量。
例:
list = [1, 2, 3, 0, 0]
list.count(0)
>>>
2
index() 方法
查找列表中元素的索引值。
可以有两个可选的参数: start
和 end
,就是指定查找的开始和结束的下标位置
例:
list = [1, 2, 3, 0, 0]
list[list.index(0)] = 9
list
>>>
[1, 2, 3, 9, 0]
列表的加法和乘法
列表的加法,其实也是拼接,所以要求加号(+)两边都应该是列表。
列表的乘法,则是重复列表内部的所有元素若干次。
例:
s = [1, 2, 3]
t = [4, 5]
s + t
>>>
[1, 2, 3, 4, 5]
s * 3
>>>
[1, 2, 3, 1, 2, 3, 1, 2, 3]
谨慎乘法的使用,它是重复引用,不等于 copy
列表的嵌套
Python 是允许列表进行嵌套的。
虽然列表都是以一行来显示,但是我们创建的时候是可以竖着写的,会看起来更好理解。
例:
t = [[1, 2, 3],
[2, 3, 4],
[3, 4, 5]]
t
>>>
[[1, 2, 3], [2, 3, 4], [3, 4, 5]]
# 每一行都有逗号,千万别忘了
嵌套的访问
1)访问每一个元素,可以这样:
t = [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
for i in t:
for each in i:
print(each, end=' ')
print()
>>>
1 2 3
2 3 4
3 4 5
# end 前面那个逗号别忘了
2)访问列表中的列表,用一个下标表示。
例:
t = [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
t[0]
>>>
[1, 2, 3]
for i in t:
print(i)
>>>
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
3)访问嵌套列表中的某一个数,用两个下标来表示行列即可。
t = [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
t[2][2]
>>>
5
创建并初始化嵌套列表
可以利用 for 语句来创建并初始化列表,如:
A = [0] * 3
for i in range(3):
A[i] = [0] * 3
print(A)
>>>
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
# 必须要先定义A为列表以及它的范围,不然会报错
即使两个列表里面的内容是一样的,它们也是不相同的。
因为 python 对不同的对象存储机制是不同的。即使列表内容相同,也会被存在不同的位置。
变量不是盒子!赋值的原理其实是引用,等于将变量与数据进行挂钩:
浅拷贝和深拷贝
copy() 方法
复制一个列表。
可以使用切片实现同样的效果。
例:
list = [1, 2, 3, 9, 0]
list2 = list.copy()
list2
>>>
[1, 2, 3, 9, 0]
list3 = list2[:]
list3
>>>
[1, 2, 3, 9, 0]
上面这两种拷贝的方法,在 Python 中都称为浅拷贝。
浅拷贝只是拷贝了外层对象,如果包含嵌套列表,那拷贝的只是其引用。
例:
x = [[1, 2, 3], [4, 5, 6]]
y = x.copy()
x[1][1] = 0
x
>>>
[[1, 2, 3], [4, 0, 6]]
y
>>>
[[1, 2, 3], [4, 0, 6]]
# 可以看到copy的嵌套列表,里面的数据被一同给修改了
深拷贝
要实现深拷贝,要用到copy模块
这个模块有两个函数:copy()
和deepcopy()
1)copy()
import copy
x = [[1, 2, 3], [4, 5, 6]]
y = copy.copy(x)
x[1][1] = 0
x
>>>
[[1, 2, 3], [4, 0, 6]]
y
>>>
[[1, 2, 3], [4, 0, 6]]
# 可以看出这个时候还是浅拷贝
2)deepcopy()
深拷贝 copy 的时候,是将对象中所有引用的子对象一并拷贝,所以不会互相干扰。
例:
x = [[1, 2, 3], [4, 5, 6]]
y = copy.deepcopy(x)
x[1][1] = 0
x
>>>
[[1, 2, 3], [4, 0, 6]]
y
>>>
[[1, 2, 3], [4, 5, 6]]
# 深拷贝的对象,不在收到影响
默认的拷贝方式都是浅拷贝,这是为了效率的考虑,所以使用的时候要注意。
列表推导式
列表推导式的基本语法:[expression for target in iterable]
结果是列表!所以一定要有方括号。
因为需要一组数据来填充,所以需要 for 来搭配使用。
左边的 expression 是表达式,相当于循环体。
因为列表推导式在 python 解释器里面,是以更快的 C 语言来运行的,所以从程序的执行效率上来说,列表推导式的效率通常是要比循环语句快上一倍左右的速度。
列表推导式和循环实现也是不一样的:循环是通过迭代逐个修改原列表中的元素,而列表推导式则是直接创建一个新的列表,然后再赋值回原先的变量名。
例:
x = [i + 1 for i in range(10)]
x
>>>
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 等同于:
x = []
for i in range(10):
x.append(i + 1)
x
>>>
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
利用列表推导式处理矩阵也是非常方便,比如获取矩阵主对角线上的元素(就是从左上角到右下角这条对角线上的元素),可以这样:
x = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
y = [x[i][i] for i in range(len(x))]
y
>>>
[1, 5, 9]
列表推导式[高阶]
带条件筛选功能的列表推导式
列表推导式其实还可以添加一个用于筛选的 if 分句,完整语法如下:
[expression for target in iterable if condition1]
例:
x = [i for i in range(10) if i % 2 == 0]
x
>>>
[0, 2, 4, 6, 8]
列表推导式执行顺序:先执行 for 循环,再执行 if,最后才执行语句 i。
多层嵌套的列表推导式
列表推导式还可以变得更复杂一些,那就是实现嵌套,语法如下:
[expression for target1 in iterable1
for target2 in iterable2
...
for targetN in iterableN]
比如我们想展开一个二维列表,就可以这样:
x = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
y = [col for row in x for col in row]
y
>>>
[1, 2, 3, 4, 5, 6, 7, 8, 9]
# 等同于
x = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
for row in x:
for col in row:
y.append(col)
y
>>>
[1, 2, 3, 4, 5, 6, 7, 8, 9]
每层嵌套还可以附带一个用于条件筛选的 if 分句:
[expression for target1 in iterable1 if condition1
for target2 in iterable2 if condition2
...
for targetN in iterableN if conditionN]
比如我们想列出一个列表,x 可以被 2 整除,y 可以被 3 整除,x 和 y 在 10 以内,那么就可以这样写:
list = [[x, y] for x in range(10) if x % 2 == 0 for y in range(10) if y % 3 == 0]
list
>>>
[[0, 0], [0, 3], [0, 6], [0, 9], [2, 0], [2, 3], [2, 6], [2, 9], [4, 0], [4, 3], [4, 6], [4, 9], [6, 0], [6, 3], [6, 6], [6, 9], [8, 0], [8, 3], [8, 6], [8, 9]]
尽管列表推导式可以使得代码非常简洁,执行效率也比传统的循环语句要快得多得多。
不过,如果由于使用了过分复杂的列表推导式,从而导致后期的阅读和维护代码的成本变得很高,那么就得不偿失了。
12、元组 tuple
元组既能像列表那样同时容纳多种类型的对象,也拥有字符串不可变的特性。
元组使用的是圆括号,而且这个圆括号是可以省略掉的。
x = (1, "设计笔记", 3.14)
x
>>>
(1, '设计笔记', 3.14)
x = 1, "设计笔记", 3.14
x
>>>
(1, '设计笔记', 3.14)
# 不推荐省略圆括号
元组和列表的不同点
- 列表是使用方括号,元组则是圆括号(也可以不带圆括号)
- 列表中的元素可以被修改,而元组不行
- 列表中涉及到修改元素的方法元组均不支持
- 列表的推导式叫列表推导式,元组的 “推导式” 叫生成器表达式
元组和列表的共同点
- 都可以通过下标获取元素
- 都支持切片操作
- 都支持 count() 方法和 index() 方法
- 都支持拼接(+)和重复(*)运算符
- 都支持嵌套
- 都支持迭代
打包和解包
生成一个元组我们有时候也称之为元组的打包。
将它们一次性赋值给三个变量名的行为,我们称之为解包:
x= (1, "设计笔记", 3.14)
a, b, c = x
a
>>>
1
b
>>>
'设计笔记'
c
>>>
3.14
需要注意的一点是:赋值号左侧的变量名数量,必须跟右侧序列的元素数量一致,否则通常都会报错。
选读内容
当元组只有一个元素的时候
如果这样写:x = (520)
是错误的。
(可能是因为这样太容易和赋值混淆了)
我们需要这样写:x = (520,)
多重赋值的真相
x, y = 10, 20
背后的实现逻辑应该是这样:
_ = (10, 20)
x, y = _
# _表示临时变量,如果变量不重要,就可以用这个符号来表示
元组不可变,元组中的列表是可变的
元组中的元素虽然是不可变的,但如果元组中的元素是指向一个可变的列表,那么我们依然是可以修改列表中的内容的。
13、字符串常用函数
按照规则生成新的字符串
转化大小写
capitalize() 第一个字母大写
casefold() 所有字母小写
title() 首字母大写
x = "www.shejibiji.com"
x.title()
>>>
'Www.Shejibiji.Com'
swapcase() 反转大小写
upper() 全大写
lower() 全小写(仅限英文字符)
左中右对齐
fillchar=' '表示填充的字符,默认是空格
center(width, fillchar=' ')
ljust(width, fillchar=' ')
rjust(width, fillchar=' ')
zfill(width) 用 0 填充,左对齐
'520'.zfill(5)
>>>
'00520'
14、字符串方法
查找
count(sub[, start[, end]]) 查找出现的数量
find(sub[, start[, end]]) 查找下标值 (找不到返回-1)
rfind(sub[, start[, end]])
index(sub[, start[, end]]) 查找下标值(找不到会抛出异常)
rindex(sub[, start[, end]])
替换
expandtabs([tabsize=8])
使用空格替换制表符(Tab)并返回新的字符串。
可以用来格式化代码,把使用了制表符的都给统一成空格。
_ = """
print("我使用了Tab制表符")
print("我是用了4个空格")"""
New = _.expandtabs(4)
print(New)
>>>
print("我使用了Tab制表符")
print("我是用了4个空格")
# 现在就都统一成4个空格了
replace(old, new, count=-1)
返回一个将所有 old 参数指定的子字符串替换为 new 的新字符串。
translate(table)
返回一个根据 table 参数(用于指定一个转换规则的表格)转换后的新字符串。
需要使用 str.maketrans(x[, y[, z]])
方法制定一个包含转换规则的表格。
table = str.maketrans("设计笔记", "biji") # 长度要相同
"I Love 设计笔记".translate(table)
>>>
'I Love biji'
# 等同于
"I Love 设计笔记".translate(str.maketrans("设计笔记", "biji"))
这个 str.maketrans() 方法还支持第三个参数,表示将其指定的字符串忽略
判断
下面这 14 个方法都是应对各种情况的判断,所以返回的都是一个布尔类型的值 —— 要么是 True
,要么是 False
。
startswith(prefix[, start[, end]])
用于判断 prefix 参数指定的子字符串是否出现在字符串的起始位置。
endswith(suffix[, start[, end]])
用于判断 suffix 参数指定的子字符串是否出现在字符串的结束位置。
istitle()、isupper()、islower()、
判断字母大小写问题的。
isascii()、
是否只是由 ASCII 字符组成。
isspace()
是否全是空格(转义字符等也是空格)。
isprintable()
是否全都可以打印(转义字符不支持打印)。
isalpha()
是否全是字母。
isdecimal()、isdigit()、isnumeric()
这三个都是表示是否全是数字。
这三个方法有区别,从左到右范围不大扩大,isnumeric()甚至支持中文数字一二三。
isalnum()
集大成者,只要 isalpha()、isdecimal()、isdigit() 或者 isnumeric() 任意一个方法返回 True,结果都为 True。
isidentifier()
判断该字符串是否一个合法的 Python 标识符。
支持同时调用多个方法,顺序为从左到右。
截取
这几个方法都是用来截取字符串的。
其中三个方法都有一个 chars=None 的参数,None 在 Python 中表示没有,意思就是去除的是空白。
那么这个参数其实是可以给它传入一个自定义字符串的。
lstrip(chars=None)/rstrip(chars=None)/strip(chars=None)
分别为:左侧/右侧/左右侧不要留白
例:
'www.shejibiji.com'.strip('wcom.')
>>>
'shejibiji'
# 虽然传入的是一个字符串'wcom.',但它是按照单个字符来去除的。
如果只是希望踢掉一个具体的子字符串,应该怎么办?
那么你可以考虑 removeprefix(prefix)
和 removesuffix(suffix)
这两个方法。
它们允许你指定将要删除的前缀或后缀:
'www.shejibiji.com'.removeprefix('www.')
>>>
'shejibiji.com'
拆分
拆分字符串,言下之意就是把字符串给大卸八块。
partition(sep)
rpartition(sep)
将字符串以 sep 参数指定的分隔符为依据进行切割,返回的结果是一个 3 元组(3 个元素的元组),partition(sep) 和 rpartition(sep) 方法的区别是前者是从左往右找分隔符,后者是从右往左找分隔符。
例:
'www.shejibiji.com'.partition('.')
>>>
('www', '.', 'shejibiji.com')
注意:它俩如果找不到分隔符,返回的仍然是一个 3 元组,只不过将原字符串放在第一个元素,其它两个元素为空字符串。
split(sep=None, maxsplit=-1)⭐
rsplit(sep=None, maxsplit=-1)
将字符串以 sep 参数指定的分隔符为依据进行切割,但是可以分割成多个,并将结果以列表的形式返回。
例:
"苟日新,日日新,又日新".split(",")
>>>
['苟日新', '日日新', '又日新']
splitlines(keepends=False)
将字符串进行按行分割,并将结果以列表的形式返回
keepends 参数用于指定结果是否包含换行符,True 是包含,默认 False 则表示是不包含
例:
"苟日新\r日日新\r\n又日新\n".splitlines()
>>>
['苟日新', '日日新', '又日新']
"苟日新\r日日新\r\n又日新\n".splitlines(True)
>>>
['苟日新\r', '日日新\r\n', '又日新\n']
拼接
join(iterable) 方法
是用于实现字符串拼接的,需要拼接的元素用元组或者列表包起来。
字符串是作为分隔符使用,然后 iterable
参数指定插入的子字符串。
例:
'.'.join(('www', 'shejibiji', 'com')) # 左右是两个括号,别漏掉了!
>>>
'www.shejibiji.com'
# 虽然也可以用+号来拼接,但是其效率会稍微慢一点,推荐使用join方法
格式化字符串
在字符串中,格式化字符串的套路就是使用一对花括号({})来表示替换字段,就在原字符串中先占一个坑的意思,然后真正的内容被放在了 format()
方法的参数中。
例:
shejibiji = '设计笔记'
"{}是一个非常不错的网站".format(shejibiji)
>>>
'设计笔记是一个非常不错的网站'
# 多语种就可以用这个方法实现了吧!
在花括号里面,还可以写上数字,表示参数的位置;
同一个索引值是可以被多次引用的;
还可以通过关键字进行索引;
位置索引和关键字索引可以组合使用。
例:
"{1}是一个非常不错的网站,网站的标语为:{slogan} - {1}{0}".format('shejibiji.com', '设计笔记', slogan="但行好事,乐于分享")
>>>
'设计笔记是一个非常不错的网站,网站的标语为:但行好事,乐于分享 - 设计笔记shejibiji.com'
参数中的字符串都是被当成元组来看待,所以支持下标来表示
但测试发现,无法真的把元组单程参数
如果我只是想单纯的输出一个纯洁的花括号,那应该怎么办呢?
有两种办法可以把这个纯洁的花括号安排进去:
"{}, {}, {}".format(1, "{}", 2)
>>>
'1, {}, 2'
"{}, {{}}, {}".format(1, 2)
>>>
'1, {}, 2'
字符串格式化语法
类似这样:"{:^}".format(1, 2)
这个:
是必须的,左边是位置或关键字索引,右边是格式化选项。
例:
"{1:>10}{0:<10}".format("第1个参数", "第2个参数")
>>>
' 第2个参数第1个参数 '
# {1:>10} 其中1为位置索引,表示取下标为1的值;>表示对其方向为右对齐;10表示显示宽度
下面是格式化语法汇总,各种选项都可以放入:
右边。
format_spec ::= [[fill]align][sign][#][0][width][grouping_option][.precision][type]
fill ::= <any character>
align ::= "<" | ">" | "=" | "^"
sign ::= "+" | "-" | " "
width ::= digit+
grouping_option ::= "_" | ","
precision ::= digit+
type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"
以上所解锁的新知识,可以直接在字符串的 format() 方法上使用,也可以用于 Python3.6 后新添加的 f-字符串。
上面的([fill])
表示填充,可以定义填充的元素;
([align])
表示对齐方式,四个符号的意义可看下图:
符号([sign])
选项仅对数字类型有效,可以使用下面 3 个值:
符号([sgrouping_option])
表示千分位的分隔符。
例:
"{:,}".format(19999)
>>>
'19,999'
符号([precision])
选项是一个十进制整数,对于不同类型的参数,它的效果是不一样的:
- 对于以 'f' 或 'F' 格式化的浮点数值来说,是限定小数点后显示多少个数位
- 对于以 'g' 或 'G' 格式化的浮点数值来说,是限定小数点前后共显示多少个数位
- 对于非数字类型来说,限定最大字段的大小(换句话说就是要使用多少个来自字段内容的字符)
- 对于整数来说,则不允许使用该选项值
例:
"{:.2f}".format(3.1415)
>>>
'3.14'
"{:2f}".format(3.1415)
>>>
'3.141500'
# 注意区分宽度和十进制整数的区别。第二个例子2为宽度,因为宽度不够,所以被忽视了,输出默认精度6。
"{:.2}".format("日了狗")
>>>
'日了'
# 非整数类型,限定字段数量
类型([type])
选项决定了数据应该如何呈现。
以下类型适用于整数:
"{:b}".format(80) # b是将参数以二进制的形式输出
>>>
'1010000'
以下类型值适用于浮点数、复数和整数(自动转换为等值的浮点数)如下:
'f'
定点表示法,就是表示定小数点位数:
"{:f}".format(800)
>>>
'800.000000'
"{:.2f}".format(800)
>>>
'800.00'
# 默认为6,我们可以改精度为2
[(0)]
用于 width 前,表示用可以感知正负数的 0 填充位置:
"{:04}".format(80)
>>>
'0080'
"{:04}".format(-80)
>>>
'-080'
# 4为宽度,前面加0,表示用0来填充位置,是负数时,也会被感知到
[(#)]
用于需要输出的进制前,来自动追加前缀:
"{:#x}".format(800)
>>>
'0x320'
# 十六进制数320前面被加上0x来提示此数为十六进制。
Python 事实上支持通过关键参数来设置选项的值,比如下面代码通过参数来调整输出的精度,比如上面那个例子,我们改一下:
"{:#{type}}".format(800, type="x")
'0x320'
# 注意后面的值只有整数可以直接写,如果是特别的值请用引号括起来
f-字符串(f-string)
需要版本 Python3.6+,大多数情况下,还是推荐使用 format,兼容性更高
f-string 可以直接看作是 format()
方法的语法糖,它进一步简化了格式化字符串的操作并带来了性能上的提升。
注:语法糖(英语:Syntactic sugar)是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
我们可以使用 f-字符串来简化下前面的例子:
shejibiji = '设计笔记'
f"{shejibiji}是一个非常不错的网站"
>>>
'设计笔记是一个非常不错的网站'
# f大小写都可以
type="x"
f"{800:#{type}}" # 等同于"{:#{type}}".format(800, type="x")
>>>
'0x320'
* 格式化%输出
Python2.6 开始,新增了一种格式化字符串的函数 str.format(),来代替以前的 % 。
所以这一节了解下即可。
例:
name = 'Sheji'
age = 20
rate = 99.999
print('我的名字是:%s,我今年%d岁,自我感觉良好,打分的至少有%.2f分' % (name, age, rate))
>>>
我的名字是:Sheji,我今年20岁,自我感觉良好,打分的至少有100.00分
# 浮点数可以定义小数点后面有效数字
%
后面的s
和d
不是随意定义的,表示其输出字符串格式,具体可以看下表:
16、序列
列表、元组、字符串的共同点
- 都可以通过索引获取每一个元素
- 第一个元素的索引值都是 0
- 都可以通过切片的方法获得一个范围内的元素的集合
- 有很多共同的运算符
因此,列表、元组和字符串,Python 将它们统称为序列。
加号(+)和乘号(*)
序列之间的加法表示将两个序列进行拼接;乘法表示将序列进行重复,也就是拷贝
关于 “可变” 和 “不可变” 的思考
字符串是不可变序列,如果用*复制,新的结果 id 是不同的。
元组和列表是可变学列,复制的结果 id 是相同的。
是(is)和不是(is not)
包含(in)和不包含(not in)
del 语句
用于删除一个或多个指定的对象。
例:
x = [1, 2, 3, 4, 5]
del x[1:4] # 等同于 x[1:4] = []
x
[1, 5]
x = [1, 2, 3, 4, 5]
del x[::2] # 不可用 x[::2] = [] 表示
x
[2, 4]
list()、tuple() 和 str()
list()、tuple() 和 str() 这三个 BIF 函数主要是实现列表、元组和字符串的转换。
min() 和 max()
min()和 max()这两个函数的功能是:对比传入的参数,并返回最小值和最大值。
min(iterable, *[, key, default])
min(arg1, arg2, *args[, key])
max(iterable, *[, key, default])
max(arg1, arg2, *args[, key])
# default可传入如果没有找到元素时显示的内容
如果是字符,按照字母表来排序,大写字母小于小写字母:
例:
x = ["a", "A", "z", "Z"]
min(x)
>>>
'A' # 大写字母更小
len() 和 sum()
sum() 函数用于计算迭代对象中各项的和。
它有一个 start 参数,用于指定求和计算的起始数值,比如这里我们设置为从 100 开始加起:
x = [1, 2]
sum(x, start=100) #从100开始累加
>>>
103
sorted() 和 reversed()
sorted() 函数将重新排序 iterable 参数中的元素,并将结果返回一个新的列表。
sorted() 函数也支持 key 和 reverse 两个参数,用法跟列表的 sort() 方法一致:
t = ["Shejibiji", "Apple", "Book", "Banana", "Pen"]
sorted(t, key=len, reverse=True) # 按照长度,从大到小排列
>>>
['Shejibiji', 'Banana', 'Apple', 'Book', 'Pen']
# line2,等同于 t.sort(key=len, reverse=True)
reversed()
函数将返回参数的反向迭代器。
要分清楚reverse
和reversed
。
例:
s = [1, 2, 5, 8, 0]
reversed(s)
<list_reverseiterator object at 0x000001D246775B40> # 返回反向迭代器
list(reversed(s)) # 可以使用 list() 函数将其转换为列表
[0, 8, 5, 2, 1]
all() 和 any()
all() 函数是判断可迭代对象中是否所有元素的值都为真; any() 函数则是判断可迭代对象中是否存在某个元素的值为真。
enumerate()
enumerate() 函数用于返回一个枚举对象,它的功能就是将可迭代对象中的每个元素及从 0 开始的序号共同构成一个二元组的列表。
(其实就是加上下标序号)
seasons = ["Spring", "Summer", "Fall", "Winter"]
list(enumerate(seasons))
>>>
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
zip()
zip() 函数用于创建一个聚合多个可迭代对象的迭代器。
做法是将作为参数传入的每个可迭代对象的每个元素依次组合成元组,即第 i 个元组包含来自每个参数的第 i 个元素。
x = [1, 2, 3]
y = [4, 5, 6]
zip(x, y)
>>>
<zip object at 0x000001E86B328C40> # 返回迭代器
list(zip(x, y))
>>>
[(1, 4), (2, 5), (3, 6)] # 返回元组
这里有一点需要大家注意的,就是如果传入的可迭代对象长度不一致,那么将会以最短的那个为准。
如果那些值对于我们来说是有意义的,我们可以使用 itertools
模块的 zip_longest()
函数来代替:
x = [1, 2, 3]
y = [4, 5, 6]
z = [7]
import itertools
list(itertools.zip_longest(x, y, z))
>>>
[(1, 4, 7), (2, 5, None), (3, 6, None)] # 用None补齐了
map()
map() 函数会根据提供的函数
对指定的可迭代对象的每个元素进行运算,并将返回运算结果的迭代器。
list(map(pow, [1, 2, 3], [1, 2, 3])) # pow()方法返回x的y次方的值
>>>
[1, 4, 27]
# 等同于[pow(1, 1), pow(2, 2), pow(3, 3)]
如果可迭代对象的长度不一致,跟 zip() 函数一样,都是在最短的可迭代对象终止时结束。
filter()
与 map() 函数类似,filter() 函数也是需要传入一个函数作为参数,不过 filter() 函数是根据提供的函数,对指定的可迭代对象的每个元素进行运算,并将运算结果为真的元素,以迭代器的形式返回。
(和 map 很像,但返回的是结果为真的元素)
例:
list(filter(str.islower, "ShejiBiji")) # 判断是否为小写,返回真的值
['h', 'e', 'j', 'i', 'i', 'j', 'i']
17、字典
字典是 Python 中唯一实现映射关系的内置类型。
字典的关键符号是大括号({}
)和冒号(:
)。
这里就是两对映射关系,我们将冒号的左边称为字典的 “键”,右边称为字典的 “值”。
在字典中,只要我们提供键,就可以获取其对应的值。方法跟序列类似,只不过这次在方括号中,咱们使用的是键,而非索引值:
d = {"鹿":"马", "S":"傻"}
d["鹿"]
# 一定要是双引号,注意区分大小写
创建字典
这里推荐四种方法,其它方法属于混用这四种,不列举了:
a = {"鹿":"马", "S":"傻"}
b = dict(鹿="马", S="傻") # 一定要记得这里是“=”号,Key不需要引号
c = dict([("鹿", "马"), ("S", "傻")]) # 使用用列表作为参数,列表中的每个元素是使用元组包裹起来的键值对
d = dict(zip(["鹿", "S"], ["马", "傻"]))
a == b == c == d
>>>
True
全部六种方法可以看这张图:
字典的增(初始化)
首先是 fromkeys(iterable[, value])
方法,这个可以算是字典中最特殊的方法,它可以使用 iterable
参数指定的可迭代对象来创建一个新字典,并将所有的值初始化为 value
参数指定的值:
a = dict.fromkeys("biji", 0)
a
>>>
{'b': 0, 'i': 0, 'j': 0}
a["b"] = 1 # 可修改字典的值
a
>>>
{'b': 1, 'i': 0, 'j': 0}
a["s"] = 4 # 如果找不到对应的键,那么就会变成增加一个新的键值对
a
>>>
{'b': 1, 'i': 0, 'j': 0, 's': 4}
如果不指定 value 参数,则采用默认值 None。
字典的删
pop()
删除字典中的指定元素我们可以使用 pop() 方法,支持添加一个default
值,来应对异常情况:
a = {'b': 1, 'i': 0, 'j': 0, 's': 4}
a.pop("b")
>>>
1
a
>>>
{'i': 0, 'j': 0, 's': 4} # 此时键b的值已经被删除了
a.pop("b", "未发现此值") # 没有这个默认值,找不到值时就会报异常
>>>
'未发现此值'
popitem()
在 Python3.7 之前,它是随机删除一个键值对,在 Python3.7 之后,它删除的是最后一个加入字典的键值对。
del
del 关键字也可以删除一个指定的字典元素。
如果 del 直接加上字典的变量名就是将整个字典给干掉。
a = {'i': 0, 'j': 0, 's': 4}
del a['i']
a
>>>
{'j': 0, 's': 4}
clear()
清空字典中的内容。
字典的改
只需要指定一个存在于字典中的键,就可以修改其对应的值。
如果我们想要同时修改多个键值对,可以使用字典的 update()
方法,可以同时给它传入多个键值对,也可以直接给它传入另外一个字典,或者一个包含键值对的可迭代对象。
例:
a = dict.fromkeys("sheji")
a
>>>
{'s': None, 'h': None, 'e': None, 'j': None, 'i': None}
a.update({'s': 1, 'h': 2}) # 记得是字典!需要大括号括起来
a.update(e='3', j='4', i='5') # 键值对即可,无需大括号
a
>>>
{'s': 1, 'h': 2, 'e': '3', 'j': '4', 'i': '5'}
字典的查
最简单的查方法就是你给它一个键,它返回你对应的值。
如果指定的键不存在于字典中,那么会报错(用 get 去避免)。
例:
a = dict.fromkeys("she", "nb")
a["s"]
>>>
'nb'
a.get("j", "未发现此值") # get() 方法可以定义default参数
>>>
'未发现此值'
get()
使用 get() 方法,它可以传入一个 default 参数,指定找不到键时返回的值。
setdefault()
查找一个键是否存在于字典中,如果在,返回它对应的值;如果不在,给它指定一个新的值。
例:
a = dict.fromkeys("she", "nb")
a.setdefault("S", "6")
>>>
'6'
a
>>>
{'s': 'nb', 'h': 'nb', 'e': 'nb', 'S': '6'} # 因为未找到值,增加新的值
对比前面直接复制的操作,这么做的一个显而易见的好处就是不会破坏到已经存在的键值对。
items()、keys() 和 values()
这三个方法分别用于获取字典的键值对、键和值三者的视图对象。
什么是视图对象呢?
这个名字听着挺新鲜,字面上的解释是:视图对象就是字典的一个动态视图,这意味着当字典内容改变时,视图对象的内容也会相应地跟着改变。
其实就是删除上面其中一个键,对应的其它值也会被相应删除
copy()
为了方便地实现浅拷贝,字典也提供了一个 copy() 方法,用法一样。
我们可以对字典做些什么?
比如,使用 len()
函数来获取字典的键值对数量;
使用 in
和 not in
来判断某个键是否存在于字典中;
字典也可以转化为列表,使用 list()
函数就可以了;
那么 iter()
函数也可以作用于字典,它会将字典的键构成一个迭代器;
list(d)
得到的是字典中所有的 “键” 构成的列表,要得到所有的 “值”,应该使用 list(d.values())
。
d = {'F':70, 'i':105, 's':115, 'h':104, 'C':67}
list(d.values())
[70, 105, 115, 104, 67]
那么 iter()
函数也可以作用于字典,它会将字典的键构成一个迭代器。
在 Python3.8 之后的版本中,咱们可以使用 reversed()
函数对字典内部的键值对进行逆向操作。
reversed(d)
其实相当于 reversed(d.keys())
的缩写,那么如果我们想要获得值的逆向序列,可以这么做:list(reversed(d.values()))
嵌套
字典也是可以嵌套的,某个键的值是另外一个字典,并不是什么稀奇的事儿。
例:
d = {"吕布": {"语文":60, "数学":70, "英语":80}, "关羽": {"语文":80, "数学":90, "英语":70}}
d["吕布"]["数学"]
>>>
70
嵌套的也可以是一个列表,第二次索引,我们当然也得换成下标索引:
d = {"吕布": [60, 70, 80], "关羽": [80, 90, 70]}
d["吕布"][1]
>>>
70
字典推导式
将键和值给掉了个位置,例:
d = {'F':70, 'i':105, 's':115, 'h':104, 'C':67}
b = {v:k for k,v in d.items()}
d
>>>
{'F': 70, 'i': 105, 's': 115, 'h': 104, 'C': 67}
b
>>>
{70: 'F', 105: 'i', 115: 's', 104: 'h', 67: 'C'}
c = {v:k for k,v in d.items() if v > 100}
c
>>>
{105: 'i', 115: 's', 104: 'h'}
例:
# 摩斯密码对比表
# 一个支持输入莫斯密码,自动输出结果的程序
c_table = {".-":"A", "-...":"B", "-.-.":"C", "-..":"D",
".":"E", "..-.":"F", "--.":"G", "....":"H",
"..":"I", ".---":"J", "-.-":"K", ".-..":"L",
"--":"M", "-.":"N", "---":"O", ".--.":"P",
"--.-":"Q", ".-.":"R", "...":"S", "-":"T",
"..-":"U", "...-":"V", ".--":"W", "-..-":"X",
"-.--":"Y", "--..":"Z", ".----":"1", "..---":"2",
"...--":"3", "....-":"4", ".....":"5", "-....":"6",
"--...":"7", "---..":"8", "----.":"9", "-----":"0"}
print("支持两种符号:.和-,密码中间请使用空格分割")
code = input("请输入摩斯密码:")
split_code = code.split(" ") #split是分割并输出列表
result = [c_table[each] for each in split_code]
print(result)
18、集合
和字典一样,集合中所有元素都应该时独一无二并且无序的!
集合具有随机性
创建集合
创建一个集合通常有三种方法:
- 使用花括号,元素之间以逗号分隔:{"Sheji", "biji"}
- 使用集合推导式:{s for s in "Sheji"}
- 使用类型构造器,也就是 set():set("Sheji")
例:
{s for s in "Sheji"}
>>>
{'e', 'S', 'i', 'h', 'j'}
set("Sheji")
>>>
{'e', 'S', 'i', 'h', 'j'}
把列表放入集合后,可以很方便的去重:
set([1, 1, 3, 2, 4, 4])
>>>
{1, 2, 3, 4}
# 使用此方法可以很方便的判断一个列表里面是否有重复元素
x = [1, 1, 3, 2, 4, 4]
len(x) == len(set(x))
>>>
False
集合的方法
copy() 方法
isdisjoint(other) 方法
检测两个集合之间是否毫不相干。
这个参数它并不要求必须是集合类型,可以是任何一种可迭代对象。
issubset(other) 方法
检测该集合是否为另一个集合的子集。
issuperset(other)方法
检测该集合是否为另一个集合的超集。
(对于两个集合 A、B,如果集合 B 中任意一个元素都是集合 A 中的元素,我们就说这两个集合有包含关系,称集合 A 为集合 B 的超集)
例:
s = set("Sheji")
s.issuperset("ji")
>>>
True
s.issubset("Shejibiji")
>>>
True
除了检测子集和超集,我们还可以计算当前集合和其它对象共同构造的并集、交集、差集以及对称差集。
并集 union(*other)
交集 intersection(*other)
差集 difference(*other)
例:
s = set("Sheji")
s.union("Biji")
>>>
{'B', 'e', 'h', 'S', 'j', 'i'}
s.intersection("Biji")
>>>
{'i', 'j'}
s.difference("Biji") # 我有他没有的
>>>
{'e', 'h', 'S'}
对称差集 symmetric_difference(other)
排除掉 A 集合和 other 容器中共有的元素后,剩余的所有元素组成集合
例:
s = set("Sheji")
s.symmetric_difference("Biji")
>>>
{'B', 'e', 'S', 'h'}
上面这 6 种常见的操作,Python 也提供了相应的运算符,可以直接进行运算。
方法 | 运算符 |
---|---|
子集 | <= |
真子集 | < |
超集 | >= |
真超集 | > |
并集 | | |
交集 | & |
差集 | - |
对称差集 | ^ |
注意:使用运算符的话,符号两边都必须是集合类型的数据才可以,不然会报错。
例:
s = set("Sheji")
s <= set("Biji")
>>>
False
s | set("Biji")
>>>
{'B', 'e', 'h', 'S', 'j', 'i'}
s & set("Biji")
>>>
{'i', 'j'}
s - set("Biji")
>>>
{'e', 'S', 'h'}
s ^ set("Biji")
>>>
{'B', 'e', 'S', 'h'}
冻结的集合
Python 将集合细分为可变和不可变两种对象,前者是 set(),后者是 frozenset()
。 被冻结的集合(frozenset())是不支持修改的。
仅适用于 set() 对象的方法
update(*others) 方法
使用 others 容器中的元素来更新集合。
intersection_update(*others)、difference_update(*others) 和 symmetric_difference_update(other)
分别是使用前面讲过的交集、差集和对称差集的方式来更新集合。
add(elem) 方法
单纯地往集合里添加数据。
update 是迭代字符串传入字符,add 是将整个字符串当成一个元素传入
例:
x = set("Sheji")
x.add("Biji")
x
{'e', 'Biji', 'S', 'i', 'h', 'j'}
remove(elem) 或者 discard(elem) 方法
集合中删除某个元素。
remove()指定元素不存在会抛出异常,discard()会静默处理
pop() 方法
随机从集合中弹出一个元素。
clear() 方法
将集合清空。
可哈希
想要正确地创建字典和集合,是有一个刚性需求的 —— 那就是字典的键,还有集合的元素,它们都必须是可哈希的。
如果一个对象是可哈希的,那么就要求它的哈希值必须在其整个程序的生命周期中都保持不变。
通过 hash() 函数,可以轻松获取一个对象的哈希值:
hash(1)
>>>
1
hash(1.000)
>>>
1
hash(1.1)
>>>
230584300921369601
# 两个值如果相等,hash值是一样的,不等的话会有不同的hash
python 中大部分不可变的对象都是可 hash 的,可变的对象是不可 hash 的。
比如字符串、元组就是可 hash。
根据这些,就知道冻结的集合,因为是可 hash 的,是可以嵌套入另一个集合的。
例:
x = {'1', '2', '3'}
x = frozenset(x)
y = {x, '4'} # 直接放入普通集合的话就会报错
y
>>>
{frozenset({'2', '3', '1'}), '4'}
19、函数
Python 函数的主要作用就是打包代码。
有两个显著的好处:
- 可以最大程度地实现代码重用,减少冗余的代码
- 可以将不同功能的代码段进行封装、分解,从而降低结构的复杂度,提高代码的可读性
函数的创建与调用
我们使用 def
语句来定义函数,紧跟着的是函数的名字,后面带一对小括号,冒号下面就是函数体,函数体是一个代码块,也就是每次调用函数时将被执行的内容。
调用这个函数,只需要在名字后面加上一对小括号。
例:
def myfunc(): #这就创建了一个函数
pass
myfunc() #调用也非常简单
# pass是一个空语句,表示不做任何事情,用来做占位符
函数的返回值
有时候,我们可能需要函数干完活之后能给一个反馈,这在 BIF 函数中也很常见。
只需要使用 return
语句,就可以让咱们自己定制的函数实现返回:
def plus(x, y):
return x * y
plus(10, 10)
>>>
100
# 还可以加入判断语句。
# 另外如果函数执行了return语句,就不会再理会后面的其它语句,包括其它return语句,所以下面的例子可以简写成那样,而不用再加else。
def div(x, y):
if y == 0:
return "除数不可以为0"
return x / y
div(2, 0)
>>>
'除数不可以为0'
如果一个函数没有通过 return 语句返回,它也会自己在执行完函数体中的语句之后,悄悄地返回一个 None
值。
函数的参数
从调用角度来看,参数可以细分为:形式参数(parameter)和实际参数(argument)。
其中,形式参数是函数定义的时候写的参数名字(比如下面例子中的 name 和 times);
实际参数是在调用函数的时候传递进去的值(比如下面例子中的 "Python" 和 5)。
例:
def myfunc(name, times):
for i in range(times):
print(f"I Love {name}.")
myfunc("Biji", 3)
>>>
I Love Biji.
I Love Biji.
I Love Biji.
位置参数
函数中位置固定的参数。
关键字参数
使用名字来传入参数,可以有效减少失误。
位置参数必须要在关键字参数之前!
默认参数
允许定义默认参数,但要注意默认参数要放在最后。
例:
def pbiji(c, a="我", b="爱"):
return "".join((a, b, c)) # 两个括号!
pbiji("笔记")
'我爱笔记'
# 用到了字符串的拼接方法 join()
收集参数 *args
表示可以传入多个参数,用*表示即可。
收集参数其实是元组,所以支持返回值打包解包操作
例:
def myfunc(*args, n):
print("共有{}个参数".format(len(args)))
print("第{}个参数是:{}".format(n, args[n]))
myfunc(1, 2, 3, 4, 5, n=2)
>>>
共有5个参数
第2个参数是:3
收集参数也可以是字典,只要用两个*表示即可:
def func(**a):
print(a)
func(x=1, y=2, z=3)
>>>
{'x': 1, 'y': 2, 'z': 3}
当然也可以混合起来一起使用,如:
def myfunc(a, *b, **c):
print(a, b, c)
myfunc(1, 2, 3, x=4, y=5)
>>>
1 (2, 3) {'x': 4, 'y': 5}
解包参数
在形参上使用*表示打包参数;
在实参上使用*表示解包参数。
例:
args = (1, 2, 3, 4)
def myfunc(a, b, c, d):
print(a, b, c, d)
myfunc(*args) # 没有*号就会提示需要输入四个参数
>>>
1 2 3 4
作用域 scope
局部作用域
如果一个变量定义的位置是在函数的里面,那么它的作用仅限于该函数中,我们将它成为局部作用域。
全局作用域
如果是在任何函数外面定义一个变量,那么它的作用就是全局的。
在函数中,局部变量会覆盖同名的全局变量
myfunc(*args)
1 2 3 4
x = 520
def myfunc():
x = 1314
print(x)
myfunc() # 函数中,局部变量会覆盖同名的全局变量
>>>
1314
print(x) # 在函数外,打印的还是全局变量
>>>
520
# 虽然同名,但这两个x实际上是不同的变量。
*global 语句
不推荐!很容易出问题导致难以找到错误。
x = 520
def myfunc():
global x # 加上一句,这里的变量就是全局变量了
x = 1314
print(x)
myfunc()
>>>
1314
print(x)
>>>
1314
嵌套函数
函数也是可以嵌套的,不过嵌套的函数是不可以在外部被调用的。
例:
def funA():
x = 520
def funB():
x = 1314
print("In funB, x = ", x)
funB() # 不在函数中调用B的话,函数B将不会启作用
print("In funA, x = ", x)
funA()
>>>
In funB, x = 1314
In funA, x = 520
nonlocal 语句
可以在函数内部修改外部函数的参数值。
def funA():
x = 520
def funB():
nonlocal x # 在上个案例中加上一句
x = 1314
print("In funB, x = ", x)
funB()
print("In funA, x = ", x)
funA()
>>>
In funB, x = 1314
In funA, x = 1314
#结果就发生了改变
LEGB 规则
知道了这个规则,就掌握了 python 的解析机制。
L:local 局部作用域
E:Enclosed 嵌套函数的外层作用域
G:Global 全局作用域
B:Build-In 内置作用域
优先级从上到下。
LEG + B ,这样比较容易记
闭包
闭包的深层原理:
利用嵌套函数的外层作用域具有记忆能力这个特性,让数据保存在外部函数或者变量中。
将内层函数作为返回值给返回,就可以从外部间接调用到内层函数
从一个例子开始讲解:
def funA():
x = 880
def funB():
print(x)
return funB # 不再是调用funB,而是返回funB
funA()
>>>
<function funA.<locals>.funB at 0x0000016A92626680>
# 返回的应该是funB这个函数,所以就可以做一些事情:
funA()()
>>>
880
funny = funA()
funny()
>>>
880
嵌套函数的外层作用域是可以通过某种形式给保存下来的,尽量这个函数已经调用完了。
利用嵌套函数的外层作用域具有记忆能力这个特性,可以将内层函数作为返回值给返回。
例:
def power(exp):
def exp_of(base):
return base ** exp
return exp_of
square = power(2)
cube = power(3)
square(4)
>>>
16
cube(4)
>>>
64
# 由于外层作用域的参数被记住,从而实现了不同的函数功能(平方和立方)
例:
def outer():
x = 0
y = 0
def inner(x1, y1):
nonlocal x, y
x += x1
y += y1
print(f"现在,x = {x},y = {y}")
return inner
move = outer()
move(1, 2)
>>>
现在,x = 1,y = 2
move(1, 2)
>>>
现在,x = 2,y = 4
# 通过这个例子,可以很明显看出,外层作用域被记住并保存了
嵌套函数的外层作用域会通过某种形式保存下来,“闭包”应运而生:在一个函数内部中,对外部作用域的变量进行引用,并且一般内部函数作为外部函数的返回值,那么内部函数就被认为是闭包。
闭包又称工厂函数,通俗地说就是“来料加工,批量生产”;由于参数不同,得到了不同的“生产线”。
利用闭包的特性,使用 nonlocal 语句可以实现带有记忆功能的函数,十分有趣!
闭包可应用于游戏开发,如实现游戏角色的移动,保护角色移动位置,不被其他函数轻易修改。
例:
origin = (0, 0) # 定义原点
legal_x = [-100, 100] # 限定X轴的移动范围
legal_y = [-100, 100] # 限定y轴的移动范围
def create(pos_x=0, pos_y=0):
def moving(direction, step): # 定义方向和步数,
nonlocal pos_x, pos_y
new_x = pos_x + direction[0] * step
new_y = pos_y + direction[1] * step
if new_x < legal_x[0]:
pos_x = legal_x[0] - (new_x - legal_x[0]) # 小于x轴范围时
elif new_x > legal_x[1]:
pos_x = legal_x[1] - (new_x - legal_x[1]) # 大于x轴范围时
else:
pos_x = new_x
if new_y < legal_y[0]:
pos_y = legal_y[0] - (new_y - legal_y[0]) # 小于y轴范围时
elif new_y > legal_y[1]:
pos_y = legal_y[1] - (new_y - legal_y[1]) # 大于y轴范围时
else:
pos_y = new_y
return pos_x, pos_y
return moving
# 我们先定义这样的移动程序,然后就可以开始玩了
move = create()
print("准备移动,现在的位置为:", move([0, 0], 0))
>>>
准备移动,现在的位置为: (0, 0)
print("开始移动,移动后的位置为:", move([0, 1], 10))
>>>
开始移动,移动后的位置为: (0, 10)
print("开始移动,移动后的位置为:", move([0, 1], 120))
>>>
开始移动,移动后的位置为: (0, 70)
写在内部是因为只有在内部才有用,外部根本不需要,也不想让他们使用,比如上面的角色位置,这种场景就是封装。
装饰器
能不能把一个函数作为参数传递给另一个函数呢?当然可以!
例:
import time
def time_master(func): # 先定义了一个时间统计的函数,并将函数作为参数
print("开始运行程序...")
start = time.time()
func()
stop = time.time()
print("结束程序运行...")
print(f"一共耗费了 {(stop-start):.2f} 秒.") # 看知识点:格式化字符串,{:.2f} 表示小数点两位
def myfunc(): # 随便定义一个简单的函数
time.sleep(2)
print("Hello Python.")
time_master(myfunc) # 将函数myfunc作为参数传入时间统计的函数
>>>
开始运行程序...
Hello Python.
结束程序运行...
一共耗费了 2.06 秒.
那有没有办法打包上面的时间统计函数,而更加方便的去使用呢?
当然可以,我们这个时候可以利用装饰器,来实现这个目的。
例:
import time
def time_master(func):
def call_func():
print("开始运行程序...")
start = time.time()
func()
stop = time.time()
print("结束程序运行...")
print(f"一共耗费了 {(stop-start):.2f} 秒.")
return call_func
@time_master
def myfunc():
time.sleep(2)
print("Hello Python.")
myfunc()
>>>
开始运行程序...
Hello Python.
结束程序运行...
一共耗费了 2.13 秒.
# 如果不用语法糖,其实可以这样写:
import time
def time_master(func):
def call_func():
print("开始运行程序...")
start = time.time()
func()
stop = time.time()
print("结束程序运行...")
print(f"一共耗费了 {(stop-start):.2f} 秒.")
return call_func
def myfunc():
time.sleep(2)
print("Hello Python.")
myfunc = time_master(myfunc) # 等同于语法糖那句
myfunc()
装饰器可以实现在不修改原来代码的前提下增加新功能。
(通过将原来代码作为函数,传入装饰器,返回新的结果)
拿函数作为参数,调用装饰器函数,实则还是利用了闭包的特性
@装饰器
其实是一个语法糖,等价于“函数 = 装饰器(函数)”;
此外,多个装饰器可以用在同一个函数上,还可以通过函数的多层嵌套调用给装饰器传递参数。
(多个@装饰器时,执行顺序是从下往上)
例:
def square(func):
def inner():
x = func()
return x ** 2
return inner
def add(func):
def inner():
x = func()
return x + 3
return inner
@square
@add
def test():
return 2
print(test()) # 顺序为(2+3)** 2
>>>
25
装饰器也可以传入参数。
例:
import time
def logger(msg):
def time_master(func):
def inner():
start = time.time()
func()
stop = time.time()
print(f"[{msg}]一共耗费了 {(stop-start):.2f} 秒.")
return inner
return time_master
@logger(msg='A') # 等同于funA = logger(msg='A')(funA)
def funA():
print("正在运行程序funA...")
time.sleep(1)
@logger(msg='B')
def funB():
print("正在运行程序funB...")
time.sleep(2)
funA()
funB()
>>>
正在运行程序funA...
[A]一共耗费了 1.08 秒.
正在运行程序funB...
[B]一共耗费了 2.03 秒.
lambda 表达式
lambda 表达式也称匿名函数,是一种比较特殊的函数实现方式,也是“一行流”代码的核心与常客。
语法为:
lambda arg1, arg2, arg3, ...argN : expression
属于极致精简之后的,它相当于这样:
def <lambda>(arg1, arg2, arg3, ...argN):
return expression
例:
# 两种不同的写法,lambda明显要简单很多
def squareX(x):
return x ** 2
squareX(3)
>>>
9
squareY = lambda y : y ** 2
squareY(3)
>>>
9
由于 lambda 是一个表达式,它就可以用在很多常规函数无法应用的地方。
例:
y = [lambda x : x ** 2, 2, 3]
y[0](5)
>>>
25
# 实际开发中,不推荐这样写!
有了 lambda,很多时候我们就可以简化我们的代码,比如:
def _(x):
return ord(x) + 10
list(map(_, "Python"))
>>>
[90, 131, 126, 114, 121, 120]
# 可以用lambda简写上面的代码
maped = map(lambda x : ord(x) + 10, "Python")
list(maped)
>>>
[90, 131, 126, 114, 121, 120]
lambda 虽然应用场景更多,但是因为只有一行代码,往往无法实现复杂的函数。
生成器 yield
定义生成器只需在函数中用yield
表达式代替return
语句,每次在执行到 yield 表达式时就生成一个数据,暂停并保留状态,下一次调用则从下一条语句开始继续执行 (每次调用提供一个数据!)。
例:
def counter():
i = 0
while i <= 5:
yield i
i += 1
counter()
>>>
<generator object counter at 0x000001999FA50EB0>
for i in counter():
print(i)
>>>
0
1
2
3
4
5
生成器就像一台制作机器,每调用一次只提供一个数据,并记住当时的状态,很神奇!
生成器是一种特殊的迭代器,不走回头路同时也支持next()
函数。
生成器对象不支持下标索引。
生成器表达式
例:
(i ** 2 for i in range(10))
>>>
<generator object <genexpr> at 0x000001999FA50900>
# 这样就有了一个生成器
这种利用推导的方式获得生成器的方法,我们称之为生成器表达式。
我们可以取出生成器的值:
c = (i ** 2 for i in range(10))
for i in c:
print(i)
>>>
0
1
4
9
16
25
36
49
64
81
生成器表达式和列表推导式,最大的不同:
列表推导式是一下子将所有的数据生产出来并放到一个列表中;
生成器表达式却像是一只母鸡,一次只生产一个蛋。
递归
所谓递归,就是函数调用自身的过程。
例:
def funC():
print("I Love Python")
funC()
funC()
>>>
I Love Python
I Love Python
...
# 会一直重复,使用 Ctrl+C 强制终止执行
使用递归必须要有结束条件,并且每次调用都要向着这个结束条件去推进。
使用递归固然优雅,但在数据量较大时效率很低。数据量小时用递归,数据量大时用迭代!
我们可以对比下迭代和递归两种方式:
# 求正整数的阶乘结果
# 迭代写法
def factIter(n):
result = n
for i in range(1, n):
result *= i
return result
# 递归写法
def factRecur(n):
if n == 1:
return 1
else:
return n * factRecur(n-1)
factIter(5)
>>>
120
factRecur(5)
>>>
120
factIter(10)
>>>
3628800
factRecur(10)
>>>
3628800
递归很适合用在类似如 1 + 2 +3 或者 100 _ 99 _ 98 这种比较有规律的运算上。
汉诺塔(递归案例)
# hanoi
def hanoi(n, x, y, z):
if n == 1:
print(x, '-->', z)
else:
hanoi(n-1, x, z, y)
print(x, '-->', z)
hanoi(n-1, y, x, z)
n = int(input('请输入汉诺塔的层数:'))
hanoi(n, 'A', 'B', 'C')
函数文档
函数文档一定要在函数的最顶端。
例:
def exchange(dollar, rate=6.5):
"""
功能:汇率转换,美元 -> 人民币
参数:
- dollar 美元数量
- rate 汇率,默认值是 6.5
返回值:
- 人民币的数量
"""
return dollar * rate
help(exchange)
>>>
Help on function exchange in module __main__:
exchange(dollar, rate=6.5)
功能:汇率转换,美元 -> 人民币
参数:
- dollar 美元数量
- rate 汇率,默认值是 6.5
返回值:
- 人民币的数量
exchange(20)
>>>
130.0
类型注释
类型注释是给人看的,程序默认是不会检测的。
例:
def times(s:list[int], n:int = 3) -> list:
return s * n
times([1, 2, 3])
>>>
[1, 2, 3, 1, 2, 3, 1, 2, 3]
# :后面的为类型注释
内省
也叫自省,是程序运行时进行自我检测的机制,通过一些特殊的属性来实现,如__name__
获取函数名。
如果想获取我们之前定义的exchange函数文档:
print(exchange.__doc__)
功能:汇率转换,美元 -> 人民币
参数:
- dollar 美元数量
- rate 汇率,默认值是 6.5
返回值:
- 人民币的数量
高阶函数模块
当一个函数接受另一个函数作为参数的时候,我们称这种函数为高阶函数。
官方也提供了相关的高阶函数模块:functools
functools.reduce()
functools.partial() 偏函数
functools.wraps() 装饰器
因为默认的装饰器,名称会变成call_func
,通过这个可以还原函数原名称。
20、文件读写(永久存储)
打开文件
你必须先用 Python 内置的open()
函数打开一个文件,创建一个 file 对象,相关的方法才可以调用它进行读写。
语法:file object = open(file_name [, access_mode][, buffering])
不同模式打开文件的完全列表:
模式 | 描述 |
---|---|
t | 文本模式 (默认)。 |
x | 写模式,新建一个文件,如果该文件已存在则会报错。 |
b | 二进制模式。 |
+ | 打开一个文件进行更新(可读可写)。 |
U | 通用换行模式(不推荐)。 |
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
下图很好的总结了这几种模式:
模式 | r | r+ | w | w+ | a | a+ |
---|---|---|---|---|---|---|
读 | + | + | + | + | ||
写 | + | + | + | + | + | |
创建 | + | + | + | + | ||
覆盖 | + | + | ||||
指针在开始 | + | + | + | + | ||
指针在结尾 | + | + |
写入对象
f.write(text,/)
将字符串写入到文件对象中,并返回写入的对象数量(字符串的长度,非字节长度)
f.writelines(text,/)
将一系列字符串写入到对象中(不会自动添加换行符,所以需要人为加在每个字符串的末尾)
写好并不会立刻写入,需要关闭文件,才可以写入数据
例:
f = open('test.txt', 'w')
f.write("我爱python!\n")
f.writelines(["这是第二行\n", "这是第三行"])
f.close() # 关闭文件后,数据才会真的写到文件中
读取修改对象
f.read(size=-1, /)
从文件对象中读取指定数量的字符(或者遇 EOF 停止);当未指定该参数,或该参数为负数,读取所有剩余的所有字符。
f.tell()
返回当前文件指针在文件对象中的位置。
f.seek(offset, whence=0, /)
修改文件指针的位置,从 whence 参数指定的位置(0 表示起始位,1 表示当前位,2 表示文件末尾)偏移 offset 个字节,返回值是新的索引位置。
例:
f.tell()
>>>
31
f.seek(0)
>>>
0
f.readline()
>>>
'我爱python!这是第二行这是第三行'
f.tell()
>>>
31
f.flush()
将文件对象中的缓存数据写入到文件中(不一定有效)
这样就不用关闭文件就可以写入数据
f.truncate(pos=None, /)
将文件对象截取到 pos 的位置,默认是截取到文件指针当前指定的位置(字节位置)。
中文占 2 个字节位置
例:
f = open("H:/Documents/私人文档/PythonTest/test.txt", "w+")
f.write("我爱python!我爱python!")
>>>
18 # 返回对象数量18
f.tell()
>>>
22 # 指针位置为22,因为中文占2个字符
f.truncate(11) # 截取时位置为字符位置!
>>>
11
f.seek(0)
>>>
0
f.read()
>>>
'我爱python!' # 正确截取到了结果
处理路径模块 pathlib
面向对象的文件系统路径,python3.4+
cwd()
获取当前路径
from pathlib import Path
Path.cwd()
>>>
WindowsPath('H:/Documents/私人文档/PythonTest')
p = Path('H:/Documents/私人文档/PythonTest')
q = p / "test.txt"
q
>>>
WindowsPath('H:/Documents/私人文档/PythonTest/test.txt')
is_dir()
判断一个路径是否为一个文件夹,is_file()
判断文件
exists()
检测一个路径是否存在:
q.exists()
>>>
True
Path('H:/400').exists()
>>>
False
name
获取路径的最后一个部分
stem/suffix
获取文件名/获取文件后缀
parent
获取父级目录
parents
获取逻辑祖先路径构成的序列
ps = p.parents
for each in ps:
print(each)
>>>
H:\Documents\私人文档
H:\Documents
H:\
ps[0] # 可以读取对应序列
>>>
WindowsPath('H:/Documents/私人文档')
parts
将路径的各个组件拆分成元组
stat()
获取文件或文件夹信息
p.stat()
>>>
os.stat_result(st_mode=16895, st_ino=3096224743869049, st_dev=1054987233, st_nlink=1, st_uid=0, st_gid=0, st_size=4096, st_atime=1651732503, st_mtime=1651635036, st_ctime=1651061793)
p.stat().st_size
>>>
4096
绝对路径和相对路径
./doc
:表示当前路径下的文件夹 doc
../doc
:表示上一层路径下的文件夹 doc
resolve()
将相对路径转化成绝对路径
iterdir()
获取当前路径下所有子文件和子文件夹(返回生成器)
for each in p.iterdir():
print(each)
>>>
H:\Documents\私人文档\PythonTest\.idea
H:\Documents\私人文档\PythonTest\for.py
H:\Documents\私人文档\PythonTest\game.py
H:\Documents\私人文档\PythonTest\hanoi.py
我们就可以利用这种方法,把所有文件名,组成一个列表:
[x for x in p.iterdir() if x.is_file()]
[WindowsPath('H:/Documents/私人文档/PythonTest/for.py'), WindowsPath('H:/Documents/私人文档/PythonTest/game.py'), WindowsPath('H:/Documents/私人文档/PythonTest/hanoi.py'),
# 判断语句只留下了文件
mkdir()
创建文件夹
例:
n = p / "Test/A/B/C"
n.mkdir(parents=True, exist_ok=True)
"""
exist_ok=True:文件夹已存在也是OK的
parents=True:如果最后创建的文件夹父路径不存在也可以
"""
open()
打开文件
rename()
重命名文件
如果不指定路径,还会顺便把文件也放到根目录去了
replace()
替换指定的文件或文件夹
也会修改文件位置
rmdir() / unlink()
删除文件夹;删除文件
如果文件夹不是空的,还需要先删除文件,才可以删除文件夹
glob()
功能强大的查找功能
from pathlib import Path
Path.cwd()
>>>
WindowsPath('H:/Program Files/Py')
p = Path('.') # Python根目录的意思
list(p.glob("*.txt")) # 查找所有后缀为.txt的文件
>>>
[WindowsPath('LICENSE.txt'), WindowsPath('NEWS.txt')]
查找下一级目录下所有文件:
list(p.glob("*/*.txt"))
查找该目录下所有子目录的文件:
list(p.glob("**/*.txt"))
with 语句和上下文管理器
用了这个不用关闭文件就可以实现写入。
这样即使程序报错,也能实现写入操作。
(默认程序报错被打断,写入操作也会停止)
with open("test.txt", "w+") as f:
f.write("I Love Python.")
1 / 0
>>>
14
Traceback (most recent call last):
File "<pyshell#12>", line 3, in <module>
1 / 0
ZeroDivisionError: division by zero
# 虽然程序报错了,但是内容还是正常写入到文件中了
*pickle 模块
实现了基本的数据序列和反序列化。
通过 pickle 模块的序列化操作我们能够将程序中运行的对象信息保存到文件中去,永久存储。
通过 pickle 模块的反序列化操作,我们能够从文件中创建上一次程序保存的对象。
异常
SyntaxError:语法错误
处理异常
try-except
语法:
try:
检测范围
except [expression [as identifier]]:
异常处理代码
例:
try:
1 / 0
except ZeroDivisionError as e:
print(e)
>>>
division by zero
try-except-else-finally
例:
try:
1 / 0
except:
print("报错!")
else:
print("一切安好!")
finally:
print("检测结束")
>>>
报错!
检测结束
可以利用try-finally
,finally不管是否报错都会执行的机制,来实现即使有报错,程序也能正常关闭的操作。
异常的嵌套
raise-from
自爆异常!注意它不可以生成不存在的异常。
raise ValueError("我说我有错!")
>>>
Traceback (most recent call last):
File "<pyshell#31>", line 1, in <module>
raise ValueError("我说我有错!")
ValueError: 我说我有错!
assert
和 raise 类似,主动引发异常!
但是它只会报AssertionError
的异常!
例:
s = "Python"
assert s == "Pyhon!"
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
assert s == "Pyhon!"
AssertionError
利用异常来实现 goto
python 默认是不可以一次性跳出多个循环的,我们可以利用异常来实现这个需求:
try:
while True:
while True:
for i in range(10):
if i > 3:
raise
print(i)
print("被跳过~")
print("被跳过~")
print("被跳过~")
except:
print("我一下子就出来啦!")
>>>
0
1
2
3
我一下子就出来啦!
类和对象
类
类是一群具有 相同特征 或者 行为的事物的一个统称,是抽象的,不能直接使用。
- 特征 被称为 属性
- 行为 被称为 方法
类 就相当于 制造飞机是的图纸,是负责创建对象的。
设计一个类,通常要满足三个要素:
- 类名 这类事物的名字,满足大驼峰命名法
- 属性 这类事物具有什么样的特征
- 方法 这类事物具有什么样的行为
self 代表类的实例,而非类
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。
用 self 只是因为按照惯例而已,所以可以换成其它的字母。
例:
class A:
def pri(biji):
print(biji)
print(biji.__class__)
t = A()
t.pri()
>>>
<__main__.A object at 0x000001671F0BDB10>
<class '__main__.A'>
# 从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。
类的初始化
在类的内部,使用 def
关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self
, 且为第一个参数,self 代表的是类的实例。
类有一个名为 __init__
的特殊方法(构造方法),该方法在类实例化时会自动调用。
它是专用用来定义一个类具有哪些属性的方法!
class person:
name = ''
age = 0
# 定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
def __init__(self, n, a, w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("{0}说:我{1}岁。".format(self.name, self.age))
# 实例化类
p = person('biji', 10, 40)
p.speak()
>>>
biji说:我10岁。
p.__dict__ # 查看类的属性
>>>
{'name': 'biji', 'age': 10, '_person__weight': 40}
对象
对象 是由类创建出来的一个具体存在,可以直接使用。
由哪一个类创建出来的对象,就拥有在哪一个类中定义的:
- 属性
- 方法
对象就相当于用图纸制造的飞机
在程序开发中,应该先有类,再有对象
类和对象的关系
类是模板,对象是根据类这个模板创建出来的。
类只有一个,而对象可以有很多个。
- 不同的对象之间属性可以有很多个
类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少。
例如:
小明今年 18 岁,身高 1.75,每天早上跑完步就会去吃东西;
小美今年 16 岁,身高 1.65,不跑步,喜欢吃东西。
我们就可以定义这样一个person类:
对象 = 属性 + 方法
整数、列表等都是对象,所以 python 中对象是无处不在的。
继承
python 的类是支持继承的,它可以使用现有类的所有功能,并且无需再重新编写代码的情况下对这些功能扩展。
通过继承的类我们一般称为子类。
如果子类拥有同样的属性和方法,就会实现覆盖。
**isinstance()**用于检测对象是否属于某个类;
**issubclass()**用于检测子类是否属于某个类。
多重继承
访问顺序为从左到右
class A:
x = 520
def hello(self):
print('你好,我是A~')
class B:
x = 888
y = 100
def hello(self):
print("您好,我是B~")
class C(A, B):
pass
c = C()
c.x
>>>
520
c.hello()
>>>
你好,我是A~
c.y
>>>
100
组合
可以将多个类组合到一起。
class Turtle:
def say(self):
print('我是一只小乌龟,我不会说话')
class Cat:
def say(self):
print('我是一只小猫,我会喵喵喵~')
class Dog:
def say(self):
print('我是一只小狗,我会汪汪汪~')
class Garden():
t = Turtle()
c = Cat()
d = Dog()
def say(self):
self.t.say()
self.c.say()
self.d.say()
g = Garden()
g.say()
>>>
我是一只小乌龟,我不会说话
我是一只小猫,我会喵喵喵~
我是一只小狗,我会汪汪汪~
绑定(self)
为什么在之前例子中,需要加self
呢?
这样做的目的是:实现实例对象和类的方法进行绑定。
self
其实就是实例对象本身。
因为:在实例中,类的方法确实是共享的,但是实例的属性却可以是自己的。
例:
class C:
def set_x(self, v):
self.x = v
c = C()
c.set_x(250)
c.x
>>>
250
# 现在c拥有了自己的属性,但是C的x还是没值的。
# 我们可以尝试给C的x属性一个值:
C.x = 100
C.x
>>>
100
c.x
>>>
250
# c的值还是250,和类没有关系
在 python 中,要给对象设置属性,非常容易,但不推荐!
只需要在类的外部代码中直接通过.
设置一个属性即可。
上例中,我们可以加一句:
c.y = 999
# 这样c就多了一个y的属性,不推荐这样操作,属性还是应该封装在类的内部
由哪一个对象调用的方法,方法内的self
就是哪一个对象的引用
class Cat:
def drink(self):
print('{0}小猫爱喝水'.format(self.name))
tom = Cat()
tom.name = 'Tom'
tom.drink()
>>>
Tom小猫爱喝水
# 因为时tom调用的方法,所以self现在就是tom的引用,所以我们输出self.name就是有内容了。
- 在类封装的方法内部,
self
就表示当前调用方法的对象自己 - 调用方法时,程序员不需要传递
self
参数 - 在方法内部
- 可以通过
self.
访问对象的属性 - 也可以通过
self.
调用其他的对象方法
- 可以通过
为什么不推荐在类的外部添加对象属性
因为如果在运行时,没有找到属性,程序就会报错。
对象应该包含哪些属性,应该封装在类的内部!
小技巧:
最小的类可以当字典来使用。
例:
class C():
pass
c = C() #推荐使用空类生成实例来模拟字典
c.x = 1
c.y = 20
c.z = [88, 99 ,520]
c.__dict__
>>>
{'x': 1, 'y': 20, 'z': [88, 99, 520]}
类的方法
生命周期
- 一个对象从调用
类名()
创建,生命周期开始 - 一个对象的
__del__
方法一旦被调用,生命周期结束 - 在对象的生命周期内,可以访问对象属性,或者让对象调用方法
__del__ 方法
当一个 对象被从内存中销毁 前,会自动调用__del__
方法。
del
关键字可以删除一个对象。
例:
class Cat:
def __init__(self, n):
self.name = n
def speak(self):
print('我是{}小猫咪'.format(self.name))
def __del__(self):
print('我将在这里被销毁')
tom = Cat('Tom')
tom.speak()
print('-' * 30)
>>>
我是Tom小猫咪
------------------------------
我将在这里被销毁
# __del__是在最后被执行的!
# 如果我们改一下,对象改成这样:
tom = Cat('Tom')
tom.speak()
del tom
print('-' * 30)
>>>
我是Tom小猫咪
我将在这里被销毁
------------------------------
# 因为我们执行了删除tom的操作,所以__del__这次不是最后被执行了。
__str__方法
在 python 中,使用print
输出对象变量,默认情况下会输出这个变量引用的对象是由哪一个类创建的对象,以及在内存中的地址(十六进制表示)
如果在开发中,希望print
输出对象变量时,能够打印自定义内容,就可以利用__str__
这个内置方法了。
注意:__str__
方法必须返回一个字符串
例:
class Cat:
def __init__(self, n):
self.name = n
def speak(self):
print('我是{}小猫咪'.format(self.name))
def __str__(self):
return "我是小猫:[{0}]".format(self.name)
tom = Cat('Tom')
print(tom)
可以用这个来打印所有内部对象的属性,而不用在外部在使用.属性
来查询。
封装
封装是面向对象编程的一大特点
面向对象编程的第一步——将属性和方法封装在一个抽象的类中
外界使用类对象,然后让对象调用方法
对象方法的细节都被封装在类的内部
构造函数
__init__方法
实现实例化的同时个性化定制。
(其实就是实例化的时候,可以传入自定义的参数)
例如:
class C:
def __init__(self, x, y):
self.x = x
self.y = y
def add(self):
return self.x + self.y
c = C(2, 3)
c.add()
>>>
5
调用未绑定的父类方法
我们可以直接调用父类的方法,但要注意避免钻石继承,也就是因为重复调用父类方法,而导致父类方法重复执行,而出现我们不期待的结果。
例如:
## 调用父类方法
class C:
def __init__(self, x, y):
self.x = x
self.y = y
def add(self):
return self.x + self.y
class D:
def __init__(self, x, y, z):
C.__init__(self, x, y)
self.z = z
def add(self):
return C.add(self) + self.z
d = D(10, 11, 12)
d.add()
33
## 钻石继承
class A:
def __init__(self):
print("我是A")
class B1(A):
def __init__(self):
A.__init__(self)
print("我是B1")
class B2(A):
def __init__(self):
A.__init__(self)
print("我是B2")
class C(B1, B2):
def __init__(self):
B1.__init__(self)
B2.__init__(self)
print("我是C")
c = C()
>>>
我是A
我是B1
我是A
我是B2
我是C
## 上面的我是A出现了两次
要解决这个问题官方也有完美的方案,就是super()
函数,它可以在父类中搜索指定的方法,并自动绑定好 self 参数。
例如:
"""
使用super()函数,修改代码时,要改的几个地方:
1、子类中不用再出现父类名称,改为super()
2、父类的参数直接为空,不用再写self
3、多次调用也不用再重复写,只要一次即可(具体案例看C类)
"""
class A:
def __init__(self):
print("我是A")
class B1(A):
def __init__(self):
super().__init__()
print("我是B1")
class B2(A):
def __init__(self):
super().__init__()
print("我是B2")
class C(B1, B2):
def __init__(self):
super().__init__()
print("我是C")
c = C()
>>>
我是A
我是B2
我是B1
我是C
## 此时就不会重复了
多态
根据不同的对象执行不同的操作
重写就是实现多类型的多态
私有变量
就是指通过某种手段,使得对象中的属性或方法无法被外部所访问。
(并不是真的私有,只是按照规则更改了名字)
推荐学习
教程观看进度: