python从入门到进去
- 第一章、软件和工具的安装
- 一、安装 python 解释器
- 二、安装 pycharm
- 第二章、初识 python
- 一、注释可分三种
- 二、打印输入语句
- 三、变量
- 1、基本数据类型
- 1.1、整数数据类型 int
- 1.2、浮点数数据类型 float
- 1.3、布尔数据类型 boolean
- 1.4、字符串数据类型 string
- 2、组合数据类型
- 2.1、列表数据类型list
- 2.2、元组数据类型tuple
- 2.3、字典数据类型(dictionary) dict (java中的map
- 2.4、集合数据类型set
- 3、序列的推导式
- 3.1、列表的推导式
- 3.2、字典的推导式
- 3.3、集合的推导式
- 3.4、元组的推导式
- 4、变量的作用域
- 四、运算符与表达式
- 1. 算术运算符
- 2. 赋值运算符
- 3. 比较(关系)运算符
- 4. 逻辑运算符
- 5. 位运算符
- 6. 成员运算符
- 7. 身份运算符
- 8. 运算符的优先级
- 第三章、基础语法
- 一、条件判断
- 1. 单分支判断
- 2. 双分支判断
- 3. 多分支判断
- 4. match 匹配不同的值运行相应的逻辑,相当于java中的switch
- 二、循环语句
- 1、for循环
- 2、while循环
- 3. break 跳出距离最近的循环
- 4. 与 else 的结合
- 5. continue 跳过本次最近循环,执行下次循环
- 6. pass
- 三、异常
- 1、常见的异常类型
- 2、try-except语句
- 3、与 else 语句结合,当没有出现异常的时候执行
- 3、 finally 表示一定会执行的代码
- 4、 raise 手动抛出一个异常
- 四、函数
- 1、函数的定义
- 2、函数的调用
- 3、函数的参数
- 3.1、位置形参
- 3.2、默认形参(缺省形参)
- 3.3、可变参数
- 3.4、参数控制
- 4、函数的返回值
- 5、匿名函数
- 6、闭包
- 7、装饰器(相当于Java的代理模式
- 7.1、使用装饰器
- 7.2、自定义函数装饰器
- 7.3、还可以自定义带有参数的装饰器:
- 7.4、类装饰器
- 五、模块
- 1、模块的简单使用
- 2、import 语句
- 3、from … import 语句
- 4、__name__属性
- 5、包
- 6、\_\_init__.py 的 \_\_all__
- 六、迭代器与生成器
- 1、可迭代对象
- 2、迭代器
- 3、生成器
- 第四章、面向对象
- 一、类
- 1、类的创建
- 2、类的属性——变量
- 3、类的属性——类函数
- 4、类的属性——静态函数
- 5、类的属性——对象函数
- 6、类中函数的调用权限
- 7、类的属性的增删改查
- 二、对象
- 1、对象的创建
- 2、对象的使用
- 3、对象的属性和函数
- 三、基础魔术函数
- 1、_\_init__(self)
- 2、_\_del__(self)
- 3、_\_new__(cls, *args, **kwargs)
- 3、_\_repr__(self) 和 _\_srt__(self)
- 4、_\_doc__ 表示类的注释信息
- 5、_\_doc__ 表示当前操作的对象在哪个模块
- 6、_\_doc__ 表示当前操作的对象的类是哪个
- 7、_\_call__(self, *args, **kwargs)表示通过 “对象()”来调用的方法
- 8、_\_dict__查看类或者对象的所有属性
- 9、\_\_getitem__ ,\_\_setitem__ ,\_\_delitem__ 让对象有字典的功能
- 四、封装
- 1、私有属性
- 2、获取私有属性
- 3、修改私有属性
- 五、继承
- 1、如何继承
- 2、继承之后的使用
- 3、继承之重写和扩展
- 4、子类调用父类的属性
- 5、多继承
- 6、继承的传递
- 六、单例模式
- 1、通过 \_\_new__ 实现
- 2、通过 @classmethod 类方法实现
- 3、通过装饰器来实现
- 3、通过元类来实现
- 第五章、文件读写
- 一、文件的打开
- 二、文件的读取
- 三、文件的写入
- 四、文件的关闭
- 五、打开一个可读可写的文件
- 六、文件目录的简单操作-os模块
- 1、文件重命名操作-os.rename("原名","新名")
- 2、创建文件夹-os.mkdir("目录地址")
- 3、删除文件夹-os.rmdir("目录地址")
- 4、删除文件-os.remove("目录地址")或os.unlink("目录地址")
- 5、获取当前路径-os.getcwd()
- 6、切换当前路径-os.chdir("切换命令")
- 7、获取路径下的目录列表-os.listdir("指定路径")
- 8、更多用法
- 第六章、多线程
- 一、线程
- 1、线程的创建与启动
- 2、线程的同步与锁
- 3、线程间通信
- 4、线程池
- 5、自定义创建新线程
- 二、进程
- 1、进程的创建与启动
- 2、进程属性与方法
- 3、进程间的通信与同步
- 4、进程池(Pool)
- 5、示例代码
- 三、协程
- 1、greenlet创建协程:
- 2、gevent创建协程:
- 3、asyncio创建协程:
- 四、进程-线程-协程,之前的异同
- 1、相同点:
- 2、不同点:
第一章、软件和工具的安装
一、安装 python 解释器
通过 官网 下载 python
光标放在 Downloads 上选择自己的系统点击右边的软件进行下载
二、安装 pycharm
打开 官网 下载软件
选择自己系统的工具下啦到最后选择免费版
下载之后傻瓜式安装就行了
第二章、初识 python
一、注释可分三种
# 这是一行注射
print("hello word")# 行内注释
"""
这是多行注释
"""
二、打印输入语句
输出函数 print( *objects, sep = ’ ', end = ‘\n’ , file = None , Flush = False)
参数 | 解释 |
---|---|
*objects | 可变参数可以输出多个入参用 “,” 分开 |
*sep | 表示用 “,” 分开中间插入的部分 ,默认空格 |
end | 输出语句最后添加的部分,默认"\n"换行 |
print("2024","我要","发大财",sep='',end='!!!\n')
格式化输出
格式化字符 | 解释 |
---|---|
%s | 字符串占位符 |
%d | 整数占位符 ,%02d 表示显示整数显示两位以上,不够用"0"填补 |
%f | 浮点数占位符,%0.2f表示保留小数点后两位,四舍五入 |
%% | 输出% |
s = "字符串"
i = 3
f = 2.34
print("%s是字符串,%f是个整数的数字,%0.1f是个保留一位小数点的值" % (s,i,f))
输入函数 input()
默认接收的都是str字符串,使用 函数Type() 可以查看
a = input("请输出你想打印的话:")
print(a,"类型为:",Type(a))
如果需要转换可以使用类型转换函数,比如转换为int类型进行计算 int()
a = input("请输出你想打印的一个数:")
a = int(a) + 1
print("这个数比较你输入的大",a)
三、变量
1、基本数据类型
类型 | 解释 |
---|---|
int | 整数类型,可通过int()转换 |
float | 浮点数类型,可以通过float()转换 |
bool | 布尔类型,可以通过bool()转换,0,0.0,None或者空容器为False,True和False首字母大写 |
str | 字符串类型,可以通过str()转换 |
# 定义一个变量
a = 1
# 定义多个变量同一个值
a = b = c = d = 100
# 定义多个变量不同的值
a,b,c,d = 1,2,3,4
变量可以通过重新赋值进行修改,不受类型的限制
a = "str"
print("我是字符串a",a)
a = 1
print("我是整数a",a)
a = 1.4
print("我是浮点数a",a)
1.1、整数数据类型 int
整数的创建有两种方式
# 第一种方式 通过 = 直接创建
i1 = 3
# 第二种方式通过 int() 创建
i2 = int(2.9)
print(i2) # 2
i2 = int("4")
print(i2) # 4
i2 = int("4.4")
print(i2) # 异常 ValueError
python设置了一些小整数保存在内存中,-5 - 256 。 在使用这个范围内的整数的时候,这些整数是的内存地址是相等的,可以通过 id() 查看
1.2、浮点数数据类型 float
整数的创建有两种方式
# 第一种方式通过 = 直接创建
f1 = 0.1
# 第二种方式通过 float() 创建
f2 = float("0.2")
print(f2) # 0.2
f2 = float("1")
print(f2) # 1.0
浮点类型的数在进行计算的时候会出现丢失精度的问题
f1 ,f2 = 0.1 ,0.24
print(f1 + f2) # 0.33999999999999997
可以通过 round(number, ndigits) 进行四舍五入的计算,number表示参与计算的值,ndigits表示保留几位小数点
f1 ,f2 = 0.1 ,0.24
print(round((f1 + f2),2)) # 0.34
也可以通过引入 math 包 利用 math的向下取整和向上取整来取值
import math # 引入math 包
f1 ,f2 = 3.1 ,0.24
# 向上取整
print(math.ceil(f1 + f2)) # 4
# 向下取整
print(math.floor(f1 + f2)) # 3
1.3、布尔数据类型 boolean
bool类型有两种创建方式
# 第一种方式通过 = 直接创建
b = True
# 第二种方式通过 bool()创建
pring(bool(0)) # False
print(bool(0.00)) # False
print(bool(1)) # True
print(bool(2)) # True
print(bool("True")) # True
print(bool("False")) # True
print(bool("abc")) # True
print(bool("")) # False
print(bool(" ")) # True
False的值有:None、0、0.0、False、所有的空容器(集合list,字符串,元祖,字典)
1.4、字符串数据类型 string
1.4.1、字符串的创建有三种方式
# 通过双引号或者单引号单行创建
s1 = "hello word"
# 通过str()创建
s2 = str()
# 通过三引号多行创建
s3 = """
hello
word
!
"""
1.4.2、字符串的转义字符 \
s1 = "小明说:\"早上好\""
print(s1) # 小明说:"早上好"
如果不想让字符串中的斜杠进行转义可以在字符串前面加一个 r
s1 = "abc\fgd"
s2 = r"abc\fgd"
print(s1)
print(s2)
# 输出
abcgd
abc\fgd
1.4.3、字符串的加法,就是把两个字符串加到一起,不能加其他数据类型,会报 TypeError 异常
s1 = "hello"
s2 = "word"
print(s1 + s2) # helloword
print(s1 + 100) # 报异常
1.4.4、字符串的乘法,就是把字符串乘多个然后加起来,只能乘整数类型,否则会报 TypeError 异常
s1 = " hello "
print(s1 * 5) # hello hello hello hello hello
1.4.5、字符串的索引
字符串会为每一个字符从左到右起一个索引从 0 开始,也可以从右往左从 -1 开始
s = "hello,word"
print(s[0],s[-4]) # h w
取值的时候如果索引超过字符串范围会报 IndexError 异常
1.4.6、切片运算,一个左闭右开的范围取值运算,可以设置步长(取下一个多远的值)
s = "hello,word"
print(s[2:5]) # llo
# 设置步长为2
s = "123456789"
print(s[1::2]) # 2468
步长可以设置为负数表示从右往左取值
s = "123456789"
#反转字符串
print(s[::-1]) # 987654321
2、组合数据类型
类型 | 解释 |
---|---|
list 列表 | 使用 []定义,中间用逗号分隔有索引从0开始,可修改长度和值 |
tuple 元组 | 使用 ()定义与列表类似,区别是元素不能修改 |
string 字符串 | 字符串也是组合数据的一种 |
dict 字典 | dict字典是无序的对象集合,相当于java中的map。用{}定义,可以用于保存健值对,中间用 : 隔开,健是唯一的,只能是字符串、数字或者元组。值可以是任何数据类型,可重复 |
set 集合 | 无序,不能重复取交集并集,用的也是 {} 定义 |
2.1、列表数据类型list
2.1.1、列表的创建分为两种方式,列表中的元素可以是任意类型的数据
# 第一种方式 通过 = [] 的方式创建
l = [1,2,3,4,"a",True,4.5]
# 第二种方式 通过 list() 创建
l1 = list("123456abc") # 只支持 string类型
print(l1) # [1,2,3,4,5,6,a,b,c]
list(5) # 会报 TypeError 异常
2.1.2、列表的元素
列表和str字符串类似可以通过索引取值,而且列表中的值还可以修改
l = [1,2,3,4,5,6]
print(l[2]) # 3
l[3] = 9
print(l) # [1,2,3,9,5,6]
2.1.3、列表的常用操作
两个列表相加,就是把两个列表中的元素放到一个列表中
l1 l2 = [1,2,3] , [3,4,5,6]
print(l1 + l2) # [1,2,3,3,4,5,6]
print(l1 + 3) # 报 TypeError 异常
列表的乘法运算,就是列表中的元素个数乘积放一个列表中
l = [1,2,3]
print(l * 3) #[1,2,3,1,2,3,1,2,3]
print(l * 3.2) # 报 TypeError 异常
包含元素判断 in 和 not in
l = [1,2,3]
#判断列表 l 中是否包含 3
print(3 in l ) # True
print("3" in l ) # False
# 判断列表 l 中是否包不含 4
print(4 not in l) # True
列表的大小的比较,是从左往右依次比较大小,如果想到则比较下一个元素
l1 l2 = [1,2,3] , [1,3]
print(l1 < l2) # True
# 先比较第一个元素 l1[0]=1,l2[0]=1,想到比较第二个元素;l1[1]=2,l2[1]=3,因为2<3所以 l1<l2
获取列表的长度:len();最大值:max();最小值:min()。
l = [1,2,3,6]
print(len(l)) #4
print(max(l)) #6
print(min(l)) #1
列表的切片操作
l = [1,2,3,4,5,6]
print(l[::2]) # [1, 3, 5]
print([1,2,3,4,5,6][-1:1:-1]) # [6, 5, 4, 3]
删除列表 del
l = [1,2,3,6]
del l
列表的遍历使用 while 或者 for ,可以使用 enumerate() 同时遍历列表的索引和值
l = [1,2,3,6]
i = 0
while i < len(l):
print(l[i])
i+=1
# 或者
for i in l:
print(i)
# 打印结果为
# 1
# 2
# 3
# 6
for k , v in enumerate(l):
print(k,v)
# 打印结果为
# 0 1
# 1 2
# 2 3
# 3 6
介绍一下 range() 迭代器:系统提供的一个内置函数,它有三个入参range(start,end,[step=1]),生成一个左闭右开的等差序列,start:开始的索引包括索引本身,默认为0可不填,end:截止的索引不包含索引本身,step:步长,默认为1可不填,-1表示倒叙。生成的序列不能被修改
print(list(range(10))) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(list(range(1,10))) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(list(range(1,10,2))) # [1, 3, 5, 7, 9]
print(list(range(10,0,-1))) # [10, 8, 6, 4, 2]
因为range是个迭代器无法直接打印所以转换成list方便展示其成员。
range一般都是和 for - in 结合遍历使用
l = [1,2,3,4,5,6,7]
for i in range(0,len(l),2):
print(i,":",l[i])
# 输出为
0 : 1
2 : 3
4 : 5
6 : 7
2.1.4、列表的常用方法
方法名 | 介绍 |
---|---|
pop(a) | 取出指定a索引的值,集合的长度 -1 ,有返回值 |
index(a) | 查找入参a在集合中的索引,从左到右取第一个,如果不在会报 ValueError 异常 |
remove(a) | 删除集合中值为a的元素,从左到右删除第一个,如果没有会报 ValueError 异常 |
copy(a) | 拷贝集合 |
append(a) | 集合添加a元素到尾部 |
extend([a,b]) | 添加列表中的元素a和b到列表中,可以是str类型 |
insert(a,b) | 插入一个元素b,放到指定的a索引位置,需要两个入参 |
count(a) | 获取集合中指定元素的个数 |
reverse() | 没有入参和反参,反转列表 |
sort() | 没有入参和反参,对列表进行排序,列表中的元素类型为int,float,boole可以一起比较,不能和str一起比较会报TypeError;元素全部是str的可以排序 |
clear() | 没有入参和反参,清空列表中的所有元素 |
l = [1,2,3,4,5,6,7,8,9]
print("集合的值为:%s ,长度为:%d ,类型为:%s" % (l, len(l), type(l)) )
# 集合的值为:[1, 2, 3, 4, 5, 6, 7, 8, 9] ,长度为:9 ,类型为:<class 'list'>
i0 = l.pop(0) # 取出指定索引的值,集合的长度 -1 ,有返回值
print("集合的值为:%s ,长度为:%d,取出的值为:%s" % (l, len(l),i0) )
# 集合的值为:[2, 3, 4, 5, 6, 7, 8, 9] ,长度为:8,取出的值为:1
i1 = l.index(3) # 查找入参在集合中的索引,如果不在会报 ValueError 异常
print("集合的值为:%s ,长度为:%d,返回值为:%s" % (l, len(l),i1) )
# 集合的值为:[2, 3, 4, 5, 6, 7, 8, 9] ,长度为:8,返回值为:1
rl =l.remove(3) # 删除集合中指定value的元素,从左到右删除第一个,如果没有会报 ValueError 异常
print("集合的值为:%s ,长度为:%d,返回值为:%rl" % (l, len(l),rl) )
# 集合的值为:[2, 4, 5, 6, 7, 8, 9] ,长度为:7,返回值为:Nonel
cpl = l.copy() # 拷贝集合
print("集合的值为:%s ,长度为:%d ,类型为:%s" % (cpl, len(cpl), type(cpl)) )
# 集合的值为:[2, 4, 5, 6, 7, 8, 9] ,长度为:7 ,类型为:<class 'list'>
la =l.append("500") # 集合添加元素到尾部
print("集合的值为:%s,返回值为:%s" % (l,la))
#集合的值为:[2, 4, 5, 6, 7, 8, 9, '500'],返回值为:None
le = l.extend([3,4,5]) # 添加列表中的元素到列表中
print(l,le) # [2, 4, 5, 6, 7, 8, 9, '500', 3, 4, 5] None
le = l.extend("34") # 添加 "3" , "4" 到列表中
print(l,le) # [2, 4, 5, 6, 7, 8, 9, '500', 3, 4, 5, '3', '4'] None
li = l.insert(3,1) # 插入数字 1 到索引为 3 的位置上
print(l,li) # [2, 4, 5, 1, 6, 7, 8, 9, '500', 3, 4, 5, '3', '4'] None
lc = l.count(4) # 获取列表中存在元素 4 的个数
print(l,lc) # [2, 4, 5, 1, 6, 7, 8, 9, '500', 3, 4, 5, '3', '4'] 2
lr = l.reverse() # 反转列表
print(l,lr) # ['4', '3', 5, 4, 3, '500', 9, 8, 7, 6, 1, 5, 4, 2] None
l = [3,8,5,40,5,3,17,1,4.5,True]
ls = l.sort() # 对列表进行排序,需要列表中的数据类型相同
print(l,ls) # [1, True, 3, 3, 4.5, 5, 5, 8, 17, 40] None
l = [3,8,5,40,5,3,17,1,4.5,True,"a"]
ls = l.sort()
print(l,ls) # 会报 TypeError 异常
l = ["3","2","0.1"]
ls = l.sort() # 全是 str 数据类型的列表才可以排序不报错
print(l,ls) # ['0.1', '2', '3'] None
lc = l.clear() # 清空列表中的所有元素
print(l,lc) # [] None
2.2、元组数据类型tuple
2.2.1、元组的创建分两种方式
# 第一种方式 通过 = () 直接创建
t = (1,2)
print(t,type(t)) # (1,2) <class 'tuple'>
需要注意的是通过小括号创建当只有一个元素的时候需要自元素后边加一个逗号,因为当只有一个元素的时候默认会当成int类型
t = (1)
print(t,type(t)) # 1 <class 'int'>
t = (1,)
print(t,type(t)) # (1,) <class 'tuple'>
# 第二种方式 通过使用 tuple() 进行创建,入参只有一个,只能容器类的数据(str,列表,元组等)
t = tuple("123456")
print(t,type(t)) # ('1', '2', '3', '4', '5', '6') <class 'tuple'>
2.2.2、元组的常用操作
元组和列表的区别是元素不能修改
t = (0,1,2,3,4)
print(t[2]) # 2
t[2] = 20 # 会报 TypeError 异常
元组也是支持加法和乘法的
t1 = (1,2,3)
t2 = (3,4,5)
print(t1 + t2) # (1, 2, 3, 3, 4, 5)
print(t1 * 2) # (1, 2, 3, 1, 2, 3)
元组的常用操作方法只有两个count和index
t = (0,1,2,30,4,3)
ti = t.index(3) # 获取元素是3第一个索引
print(t,ti)
tc = t.count(4) # 取元素4在元组中的个数
print(t,tc)
2.3、字典数据类型(dictionary) dict (java中的map
2.3.1 字典的创建分两种,一种是使用 {} 一种是使用 dict()
d1 = {1:"a",2:list("abcd")}
d3 = dict(a=1,b=2)
d4 = dict([("c",4),("d",5)])
2.3.2 字典的增删改查
d1 = {1:"a",2:list("abcd"),3:"hn",4:185}
# 通过key 查 value
v4 = d1[4]
# 或者
v4 = d1.get(4)
# 新增
d1["5"] = "5的值"
d1[("tuple","类型的key")] = {"字典类型":"的值"}
# 修改
d1[4] = "4号新值"
# 或者使用update方法(也可以新增元素
d2.update({4:"4号新值"})
# 删除元素
d1.pop(4)
# 清空元素
d1.clear()
# 删除字典
del d1
字段的 in 方法判断的是 key 是否存在
2.3.3 字典的遍历
# 遍历 k,v 简直对
for k,v in d1.items():
print(k,v)
# 遍历所有的 key
for i in d1:
print(i,d1[i])
# 或者
for k in d1.keys():
print(k)
# 遍历所有的 value
for v in d1.values():
print(v)
2.3.4 字典的常用方法
items() 变成kv健值对类型
d2 = {1:11,2:22}
print(d2)
print(d2.items())
# 输出
{1: 11, 2: 22}
dict_items([(1, 11), (2, 22)])
pop() 弹出指定k 的元素,弹出之后字典就没有这个元素了
d = {1:11,2:22,3:33,4:44}
d.pop(4)
popitem() 从队尾弹出一个元素
d = {1:11,2:22,3:33,4:44}
d.popitem()
copy() 拷贝出一个一摸一样的字典
d3 = d2.copy()
2.4、集合数据类型set
2.4.1 集合的创建有两种形式,使用{} 和 set()
s1 = {"a","b","c",3,4,5}
s2 = set("123") # 通过字符串创建
s3 = set([10,20,30]) # 通过列表创建
s4 = set((1,3,4)) # 通过元组创建
s5 = set({"name":"大宝","age":18}) # 通过字典创建,取 key 值
2.4.2 集合的遍历
for i in s3:
print(i)
2.4.3 集合的常用方法
add() 添加一个元素
s1.add("add")
remove() 删除指定的元素
s1.remove("add")
pop() 从头部弹出一个元素
s1.pop()
2.4.4 取交集(&)和并集(|)
s1 = {"a","b","c",3,4,5}
s2 = set("123adc")
s3 = s1 & s2
s4 = s1 | s2
print(s3)
print(s4)
# 输出
{'a', 'c'}
{'b', 3, 4, 5, '1', 'a', '3', '2', 'c', 'd'}
3、序列的推导式
Python 推导式是一种独特的数据处理方式,可以从一个数据序列构建另一个新的数据序列的结构体。
Python 推导式是一种强大且简洁的语法,适用于生成列表、字典、集合和生成器。
3.1、列表的推导式
列表推导式格式为:
新列表 = [元素的操作 for 变量 in 旧列表 if 筛选]
旧列表:可以是列表、元组、或者集合
其中 if 筛选 不是必须有的,如果写了则不符合 if 筛选 的元素不会进入新的列表中
举例:
names = ["ra","ab","abc","abcd","abcde"]
# 过滤掉长度小于等于2的字符串,并将剩余的字符串加上 .test
new_names1 = [name+".test" for name in names if len(name) >2]
print(new_names1)
# 过滤掉长度小于等于3的字符串,并将剩余的字符串转换成大写字母
new_names2 = [name.upper() for name in names if len(name) > 3] # upper() 字符串转换成大写
print(new_names2)
# 输出
['abc.test', 'abcd.test', 'abcde.test']
['ABCD', 'ABCDE']
格式二:
新列表 = [结果1 if 判断条件 else 结果2 for 变量 in 旧列表]
这种格式不会丢弃元素,只不过是通过不同的判断条件执行不同的结果,放到新列表中
举例:
names = ("ra","ab","abc","abcd","abcde")
# 长度大于2的字符串前边加上 “结果1”,否则前面加上 “结果2”,放入新列表中
new_name = ["结果1"+name if len(name) > 2 else "结果2"+name for name in names ]
print(new_name)
#输出
['结果2ra', '结果2ab', '结果1abc', '结果1abcd', '结果1abcde']
3.2、字典的推导式
格式为:
新字典 = {k:v for 变量 in 旧列表 if 筛选} # if 筛选 非必须
举例:
names = {"ra","ab","abc","abcd","abcde"}
# 过滤出长度大于2的字符串,生成 key 是字符串本身,value 是字符串长度的字典
name_dict = {name:len(name) for name in names if len(name) > 2}
print(name_dict)
# 输出
{'abcde': 5, 'abc': 3, 'abcd': 4}
3.3、集合的推导式
格式是:
# 格式一:
新集合 = {元素的操作 for 变量 in 旧列表 if 筛选} # if 筛选 非必须
# 格式二:
新集合 = {结果1 if 判断条件 else 结果2 for 变量 in 旧列表}
举例:
names = {"ra","ab","abc","abcd","abcde"}
# 筛选出以 ”a“ 开头的字符串并乘以2组成新的集合
name_set = {name*2 for name in names if name.startswith("a")}
print(name_set)
# 已 ”a“ 开头的字符串乘以2,否则乘以3组成新的集合
name_set2 = {name*2 if name.startswith("a") else name*3 for name in names}
print(name_set2)
# 输出
{'abcdeabcde', 'abcabc', 'abcdabcd', 'abab'}
{'abcdeabcde', 'abab', 'rarara', 'abcdabcd', 'abcabc'}
3.4、元组的推导式
元组推导式和列表推导式的用法也完全相同,只是元组推导式是用 () 圆括号将各部分括起来,而列表推导式用的是中括号 [],另外元组推导式返回的结果是一个生成器对象。需要使用 tuple()
转换成元组
格式是:
# 格式一:
新元组生成器 = (元素的操作 for 变量 in 旧列表 if 筛选) # if 筛选 非必须
# 格式二:
新元组生成器 = (结果1 if 判断条件 else 结果2 for 变量 in 旧列表)
举例:
names = ["ra","ab","abc","abcd","abcde"]
# 筛选出以 ”a“ 开头的字符串并乘以2组成新的元组
name_tuple1 = (name*2 for name in names if name.startswith("a"))
print(name_tuple1,"=====》",tuple(name_tuple1))
# 以 ”a“ 开头的字符串乘以2,否则乘以3组成新的元组
name_tuple2 = (name*2 if name.startswith("a") else name*3 for name in names)
print(name_tuple2,"=====》",tuple(name_tuple2))
# 输出
<generator object <genexpr> at 0x0000029E98C7A8E0> =====》 ('abab', 'abcabc', 'abcdabcd', 'abcdeabcde')
<generator object <genexpr> at 0x0000029E98C7A9B0> =====》 ('rarara', 'abab', 'abcabc', 'abcdabcd', 'abcdeabcde')
4、变量的作用域
变量分为全局变量和局部变量两种
全局变量:在类中设置的变量,函数中和函数外都能使用
局部变量:在函数中设置的变量,只能在函数中使用
a = 10 # 全局变量
def f():
a = 100 # 局部变量
print("局部变量a",a)
f()
print("全局变量a",a)
# 输出
全局变量a 10
局部变量a 100
函数中设置相同名字的局部变量是不会修改全局变量的,如果想要修改全局变量则可以通过关键字 global 变量名
先声明在本函数中使用的是全局变量
a = 10 # 全局变量
def f():
global a # 声明在这个函数中使用的a 是全局变量
print("函数中的全局变量",a)
a = 100 # 局部变量
print("局部变量a",a)
f()
print("全局变量a",a)
# 输出
函数中的全局变量 10
局部变量a 100
全局变量a 100
最后需要注意的是可变的数据类型(如list、set、dict)他们的元素是可以在函数中修改的。
四、运算符与表达式
1. 算术运算符
运算符 | 描述 | 实例 |
---|---|---|
+ | 加 | 10+10=20 |
- | 减 | 10-20=-10 |
* | 乘 | 10*20=200 |
/ | 除 | 10/20=0.5 |
// | 取整除 | 返回商的整数部分 9//2 =4 |
% | 取余数 | 9 % 2 = 1 |
** | 幂 | 2**3 = 8 |
1.1. 算术运算符的优先级(首先括弧优先级最高
幂运算 >>>> 乘除 >>>> 加减
( ** ) > >>> ( * / % // ) >>>> ( + - )
2. 赋值运算符
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值 | c = a+b |
+= | 加法赋值 | c+=a 等效于 c = c +a |
-= | 减法赋值 | c-=a 等效于 c = c-a |
*= | 乘法赋值 | c*=a 等效于 c = c*a |
/= | 除法赋值 | c/=a 等效于 c = c / a |
//= | 取整除赋值 | c//=a 等效于 c = c // a |
%= | 取模赋值 | c%=a 等效于 c = c % a |
**= | 幂赋值 | c**=a 等效于 c = c**a |
3. 比较(关系)运算符
运算符 | 描述 |
---|---|
== | 比较两个值是否相等 |
!= | 比较两个值是否不相等 |
> | 比较左边值是不是大于右边值 |
< | 比较左边值是否小于右边值 |
>= | 比较左边值是否大于等于右边值 |
<= | 比较左边值是否小于等于右边值 |
4. 逻辑运算符
运算符 | 表达式 | 描述 |
---|---|---|
and | x and y | x 和 y 是否都是 True,有一个False 则返回False |
or | x or y | x 和 y 有一个 True 则为 True ,都为 False 则为 False |
not | not x | x 为 True 则返回False ;如果为 False 则返回 True |
5. 位运算符
运算符 | 描述 | 实例 | 功能 |
---|---|---|---|
& | 按位与运算符 | 5&7 | 参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 |
| | 按位或运算符 | 5|7 | 参与运算的两个值,如果两个相应位有一个为1时,则该位的结果为1,否则为0 |
^ | 按位异或运算符 | 5^7 | 参与运算的两个值,如果两个相应位不同时,则该位的结果为1,则该位的结果为1,否则为0 |
- | 按位取反运算符 | -5 | 对数据的每个二进制位运算进行取反操作,把1变成0,把0变成1 |
<< | 左移动运算符 | 9<<2 | 运算术的各二进制位全部向左移动若干位,由符号右侧的数字指定移动的位数,高位丢弃,低位补0 |
>> | 右移动运算符 | 9>>2 | 运算术的各二进制位全部向右移动若干位,由符号右侧的数字指定移动的位数,低位丢弃,高位补0 |
6. 成员运算符
右边必须是组合数据类型
运算符 | 描述 | 实例 |
---|---|---|
in | 如果在指定的序列中找到值则返回True,否则返回False | 3 in (1,2,3) 返回True |
not in | 同 in 取 反 | 3 not in (1,2,3) 返回 False |
7. 身份运算符
运算符 | 描述 | 实例 |
---|---|---|
is | 判断两个标识符是否引用同一个对象,是返回True,否则返回False | a = 1 , b = 1 , (a is b) 返回True |
is not | 同 is 取 反 | a = 1 , b = 2 , (a is not b) 返回True |
8. 运算符的优先级
运算符 | 描述 |
---|---|
** | 幂(最高优先级) |
~ + - | 按位取反,一元运算符(正负号) |
* / % // | 乘、除、取余数、取整除 |
+ - | 加、减 |
<< >> | 左移、右移 |
& | 按位与 |
< > >= <= | 比较运算符 |
== != | 等于运算符 |
= += -= *= /= //= %= **= | 赋值运算符 |
is is not | 身份运算符 |
in not in | 成员运算符 |
and or not | 逻辑运算符 |
第三章、基础语法
一、条件判断
1. 单分支判断
age = 19
if age >= 18:
print("可以进网吧")
print("欢迎光临")
2. 双分支判断
sex = "男"
if sex == "男":
print("进男厕所")
else:
print("进女厕所")
3. 多分支判断
score = 87
if score == 100:
print("A+")
elif score > 90:
print("A")
elif score > 80:
print("B")
elif score > 60:
print("C")
else:
print("D")
4. match 匹配不同的值运行相应的逻辑,相当于java中的switch
sex = "外星人"
match sex:
case "男":
print("进男厕所")
case "女":
print("进女厕所")
case _: # 表示匹配值
print("你是变态吧")
二、循环语句
1、for循环
for i in range(10):
print(i)
2、while循环
while n < 10:
print(n)
n+=1
3. break 跳出距离最近的循环
for i in range(3):
n = 0
print("i",i)
while n < 10:
if n==2:
break
print("n",n)
n+=1
# 输出结果
i 0
n 0
n 1
i 1
n 0
n 1
i 2
n 0
n 1
4. 与 else 的结合
else 的下级代码:没有通过break退出循环,循环结束后会执行的代码
for i in range(5):
if i == 30:
print("我不喜欢三")
break
print("i:",i)
else:
print("没有执行break")
# 输出
i: 0
i: 1
i: 2
i: 3
i: 4
没有执行break
5. continue 跳过本次最近循环,执行下次循环
for i in range(5):
if i == 3:
print("我不喜欢三")
continue
print("i:",i)
# 输出
i: 0
i: 1
i: 2
我不喜欢三
i: 4
6. pass
作用:当语句要求不希望任何命令或者代码来执行时使用
说明:pass语句表示一个空操作,在执行时没有任何的响应,pass的位置最终应该有代码来执行,只不过暂时写不出来,相当于占位符
使用:可以使用在流程控制和循环语句中
for i in range(5):
if i == 3:
print("我不喜欢三")
break
print("i:",i)
else:
pass
# pass这里如果什么都不写原本是会编译报错的
三、异常
1、常见的异常类型
2、try-except语句
try:
print("可能出现异常的代码")
except:
print("代码出现了异常")
except 可以捕获到异常信息
try:
print("可能出现异常的代码")
except Exception as e:
print("代码出现了异常,异常信息为:",e)
可以设置多个except 可以捕获指定的类型的异常信息
try:
print("可能出现异常的代码")
except ZeroDivisionError as e:
print("出现了数字异常,异常信息为:",e)
except Exception as e:
print("代码出现了异常,异常信息为:",e)
3、与 else 语句结合,当没有出现异常的时候执行
try:
print("可能出现异常的代码")
except ZeroDivisionError as e:
print("出现了数字异常,异常信息为:",e)
except Exception as e:
print("代码出现了异常,异常信息为:",e)
else:
print("未出现异常情况")
3、 finally 表示一定会执行的代码
try:
print("可能出现异常的代码")
except ZeroDivisionError as e:
print("出现了数字异常,异常信息为:",e)
except Exception as e:
print("代码出现了异常,异常信息为:",e)
else:
print("未出现异常情况")
finally:
print("一定会执行的代码模块")
4、 raise 手动抛出一个异常
try:
print("可能出现异常的代码")
raise TypeError("我手动抛出了一个类型异常")
except ZeroDivisionError as e:
print("出现了数字异常,异常信息为:",e)
except Exception as e:
print("代码出现了异常,异常信息为:",e)
else:
print("未出现异常情况")
finally:
print("一定会执行的代码模块")
# 输出
可能出现异常的代码
代码出现了异常,异常信息为: 我手动抛出了一个类型异常
一定会执行的代码模块
四、函数
1、函数的定义
使用关键字 def 确定函数名称、参数名称、参数个数、编写函数体(用于实现函数功能的代码
def a():
print("我是函数a")
2、函数的调用
通过函数名称进行调用函数,定义好的函数只表示这个函数疯涨了一段代码而已,如果不主动调用函数,函数是不会执行的
从上到下函数要先定义,再调用
def a():
print("我是函数a")
a()
3、函数的参数
形参:就是函数定义时小括号里的参数,是用来接收参数用的,在函数内部作为变量使用。
实参:函数调用的时候小括号里的参数,是用来把数据传递到函数内部用的。
3.1、位置形参
def a(num1 , num2): # 形参
print("我是函数a")
print("参数1:",num1)
print("参数2:",num2)
a(1,"e") # 实参
print("*"*10)
a([1,2,"3"],2.4) # 实参
# 输出
我是函数a
参数1: 1
参数2: e
**********
我是函数a
参数1: [1, 2, '3']
参数2: 2.4
3.2、默认形参(缺省形参)
在函数的定义的时候可以对形参设置一个默认值,则这个参数在函数调用的时候可以不传递值,取默认值,也可以传入值,则取传入的值
def defaultTest(num1,num2 =3):
return num1 * num2
print(defaultTest(2)) # 2*3=6
print(defaultTest(2, 5)) # 2*5=10
在设置多个形参的时候,默认形参后边的形参也必须是默认形参
如果有多个缺省参数,在调用函数传递实参的时候会按照顺序赋值的
如果要给指定的形参赋值,则在函数调用的时候就需要指定实参的属性
def userTest(name,age=20,gender="nan"):
print("你好,我叫%s,我今年%d岁了,我是一名%s生"%(name,age,gender))
userTest("ali",gender="男")
3.3、可变参数
参数的个数是若干个的,可变参数必须是在形参的最后
3.1 tuple类型的可变参数
def tupleTotal(*args): # 接收的是一个 tuple
r = 0
for i in args:
r +=i
return r
print(tupleTotal(1, 2, 3, 4)) # 10
3.2 字典类型的可变参数
def dictTest(**kwargs): # 接收的是一个字典
for k,v in kwargs.items():
print(k,"的值为:",v)
dictTest(**{"name":"李宁","age":23}) #调用的时候也要加上 **
# 输出
name 的值为: 李宁
age 的值为: 23
3.4、参数控制
3.4.1 关键字控制
声明函数时,如果想要控制一些参数必须用关键字传入,则可以使用 *
控制
如果单独出现星号 *,则星号 * 后的参数必须用关键字传入:
def f(a,b,*,c,d):
return a+b+c+d
print(f(1, 2, 3, 4)) # 会报错 TypeError
# 正确写法
print(f(1, 2, c=3, d=4)) 或者 print(f(1, 2, d=3, c=4))
3.4.2 强制位置控制
/
用来指明函数形参必须使用指定位置参数,表示斜杠前的参数不能使用关键字参数的形式,必须根据位置传参。
在没有控制之前
def f(a,b,c,d):
return a+b+c+d
print(f(b=1, c=2, d=3, a=4))
控制之后
def f(a,b,/,c,d):
return a+b+c+d
print(f(b=1, c=2, d=3, a=4)) # 报错 TypeError
前俩个 a 和 b 必须不能使用关键字参数,必须通过位置传参
print(f(1, 2, d=3, c=4)) 或者 print(f(1, 2, 3, 4))
4、函数的返回值
返回值是函数完成工作之后最后给调用者的一个结果,使用 return 关键字,调用者可以使用变量来接收函数的返回结果
def mysum(a,b):
return a+b
result = mysum(5,2)
print(result) # 7
5、匿名函数
lambda函数是一种快速定义单行的最小函数,可以用在任何需要函数的地方,让代码更加简洁
lambda函数的写法是 lambda 参数: 函数逻辑
lambda的语法:
lambda arguments: expression
- lambda是 Python 的关键字,用于定义 lambda 函数。
- arguments 是参数列表,可以包含零个或多个参数,中间用逗号隔开但必须在冒号(:)前指定。
- expression 是一个表达式,用于计算并返回函数的结果。
# 1、无参 lambda 函数
f1 = lambda : "无参lambda函数"
print(f1()) # 无参lambda函数
# 2、有参 lambda 函数
f2 = lambda a : a + "我是有参lambda函数"
print(f2("你好,")) # 你好,我是有参lambda函数
# 3、多参 lambda 函数
f3 = lambda a, b, c: a**b+c
print(f3(2, 3, 4)) # 12
lambda 函数通常与内置函数如 map()、filter() 和 reduce() 一起使用,以便在集合上执行操作。
- map():映射函数,第一个入参是一个函数,第二个参数是一个变量,返回值为变量经过函数后的map对象
# 将列表中的元素乘以三次幂
m = map(lambda x:x**3,[1,2,3,4,5])
print(list(m)) # [1, 8, 27, 64, 125]
- filter():过滤函数,第一个入参是一个判断函数,第二个入参是变量,返回值为通过过滤条件的filter对象
# 使用 lambda 函数与 filter() 一起,筛选偶数:
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers)) # 输出:[2, 4, 6, 8]
下面是一个使用 reduce() 和 lambda 表达式演示如何计算一个序列的累积乘积:
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# 使用 reduce() 和 lambda 函数计算乘积
product = reduce(lambda x, y: x * y, numbers)
print(product) # 输出:120
# 1 * 2 * 3 * 4 * 5 = 120 的结果。
6、闭包
定义:
①两个函数的嵌套,
②内层函数使用外层函数的入参(内层函数对外层函数有一个非全局变量的引用),
③外层函数返回内层函数本身
格式:
def wai(a):
def nei(b):
print("外层函数入参:",a)
print("内层函数入参:",b)
return a+b
return nei
n = wai(100) # 调用外层函数,返回一个内层函数
print(n) # <function wai.<locals>.nei at 0x0000023CAECB8900>
nr = n(2) # 外层函数入参: 100
# 内层函数入参: 2
print(nr) # 102
闭包如果想要修改外层函数的参数,可以使用关键字 nonlocal
:表示定义此变量使用的是本地变量
################# 没有使用 nonlocal 之前 ###################
def wai():
n = 100 # n 为非全局变量
def nei(b):
n = 200
return n+b
return nei
n = wai()
print(n(2)) # 202
################# 使用 nonlocal 之后 ###################
def wai():
n = 100 # n 为非全局变量
def nei(b):
nonlocal n
n += 200
return n+b
return nei
n = wai()
print(n(2))
7、装饰器(相当于Java的代理模式
装饰器是一种函数,它接受一个函数作为参数,并返回一个新的函数或修改原来的函数。
装饰器的语法使用 @decorator_name 来应用在函数或方法上。
Python 还提供了一些内置的装饰器,比如 @staticmethod 和 @classmethod,用于定义静态方法和类方法。
装饰器的应用场景:
- 日志记录: 装饰器可用于记录函数的调用信息、参数和返回值。
- 性能分析: 可以使用装饰器来测量函数的执行时间。
- 权限控制: 装饰器可用于限制对某些函数的访问权限。
- 缓存: 装饰器可用于实现函数结果的缓存,以提高性能。
7.1、使用装饰器
装饰器通过 @ 符号应用在函数定义之前,例如:
@time_logger
def target_function():
pass
等同于:
def target_function():
pass
target_function = time_logger(target_function)
7.2、自定义函数装饰器
# 自定义一个装饰器 decorator 形参是一个方法
# 在装饰器中定义一个方法用于接收被装饰的函数的入参,返回被装饰的函数的返参,在调用目标函数的前后可以对被装饰的函数进行增强
def decorator(func):
def wrapper(*args, **kwargs):
print("函数的入参为:",*args,**kwargs)
result = func(*args, **kwargs)
print("函数的原返回值为:",result)
return str(result).upper() # 把函数的返回值转换成大写
return wrapper
# 使用 @decorator 标在目标函数的头上,表示装饰这个函数
@decorator
def greet(name):
return f"Hello, {name}!"
print("函数最终的返回值",greet("Alice"))
print("*"*50)
print("函数最终的返回值",greet({"name":"miya","age":15}))
# 输出
函数的入参为: Alice
函数的原返回值为: Hello, Alice!
函数最终的返回值 HELLO, ALICE!
**************************************************
函数的入参为: {'name': 'miya', 'age': 15}
函数的原返回值为: Hello, {'name': 'miya', 'age': 15}!
函数最终的返回值 HELLO, {'NAME': 'MIYA', 'AGE': 15}!
7.3、还可以自定义带有参数的装饰器:
# 创建一个修饰器,传入一个函数名,记录指定函数名的入参和返参
def repeat(name):
def decorator(func):
def wrapper(*args, **kwargs):
print("函数:",name,"的入参为:",*args,**kwargs)
result = func(*args, **kwargs)
print("函数:",name,"的原返回值为:",result)
return name+"_"+result
return wrapper
return decorator
# 使用 @decorator 标在目标函数的头上,表示装饰这个函数
@repeat("greet")
def greet(name):
return f"Hello, {name}!"
print("函数最终的返回值",greet("Alice"))
print("*"*50)
print("函数最终的返回值",greet({"name":"miya","age":15}))
# 输出
函数: greet 的入参为: Alice
函数: greet 的原返回值为: Hello, Alice!
函数最终的返回值 greet_Hello, Alice!
**************************************************
函数: greet 的入参为: {'name': 'miya', 'age': 15}
函数: greet 的原返回值为: Hello, {'name': 'miya', 'age': 15}!
函数最终的返回值 greet_Hello, {'name': 'miya', 'age': 15}!
7.4、类装饰器
除了函数装饰器,Python 还支持类装饰器。类装饰器是包含 call 方法的类,它接受一个函数作为参数,并返回一个新的函数。
class DecoratorClass:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("在调用原始函数之前/之后执行的代码")
result = self.func(*args, **kwargs)
print("在调用原始函数之后执行的代码")
return result
@DecoratorClass
def my_function(a,b,c):
print("执行函数my_function",(a+b+c))
return a+b+c
my_function(1, 2, 3)
# 输出
在调用原始函数之前/之后执行的代码
执行函数my_function 6
在调用原始函数之后执行的代码
五、模块
简单理解就是一个.py
文件就是一个模块,在一个文件中可以通过import
引入另外一个模块,使用被引入的模块的变量和方法
1、模块的简单使用
创建一个my_module的python文件
i = 100
s = "my_module的字符串"
l = list("my_module的列表")
def hello():
print("你好我是hello方法")
return "hello"
def add(a,b):
return a+b
在另一个python中使用 import 引入这个模块
import my_module
print(my_module.s) # my_module的字符串
i=my_module.i
print(my_module.l) # ['m', 'y', '_', 'm', 'o', 'd', 'u', 'l', 'e', '的', '列', '表']
print(my_module.hello()) # 你好我是hello方法
# hello
print(my_module.add(i, len(my_module.l))) # 112
2、import 语句
在一个模块中引入另外一个模块,语法如下:
import module1[, module2[,... moduleN]
当我们使用 import 语句的时候,Python 解释器会从搜素路径列表
中依次去寻找所引入的模块,可以通过sys.path
查看路径目录列表:
import sys
print(sys.path)
# 输出
['D:\\python\\PycharmProjects\\pythonProject001',
'D:\\python\\PycharmProjects\\pythonProject001',
'D:\\python\\PycharmProjects\\pythonProject001\\.venv\\Scripts\\python313.zip',
'C:\\Users\\14911\\AppData\\Local\\Programs\\Python\\Python313\\DLLs',
'C:\\Users\\14911\\AppData\\Local\\Programs\\Python\\Python313\\Lib',
'C:\\Users\\14911\\AppData\\Local\\Programs\\Python\\Python313',
'D:\\python\\PycharmProjects\\pythonProject001\\.venv',
'D:\\python\\PycharmProjects\\pythonProject001\\.venv\\Lib\\site-packages']
如果不存在则编译报错
3、from … import 语句
Python 的 from 语句让你从模块中导入一个指定的部分到当前命名空间中,语法如下:
from modname import *
这样别导入的变量或者函数可以直接在这个模块中使用,而不是像只导入模块那种通过模块.函数
的形式调用
from my_module import l,hello
l.append("我可以直接使用")
print(l) # ['m', 'y', '_', 'm', 'o', 'd', 'u', 'l', 'e', '的', '列', '表', '我可以直接使用']
hello() # 你好我是hello方法
4、__name__属性
一个模块被另一个程序第一次引入时,其主程序将运行。如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用__name__属性来使该程序块仅在该模块自身运行时执行。
#!/usr/bin/python3
# Filename: using_name.py
if __name__ == '__main__':
print('程序自身在运行')
else:
print('我来自另一模块')
运行输出:
程序自身在运行
导入到另一个模块中使用
import using_name
print(using_name.l)
说明: 每个模块都有一个__name__属性,当其值是’main’时,表明该模块自身在运行,否则是被引入。
5、包
包是一种管理 Python 模块命名空间的形式,采用"点模块名称",比如一个模块的名称是 A.B, 那么他表示一个包 A中的子模块 B 。
在python3之前:目录只有包含一个叫做 __init__.py 的文件才会被认作是一个包。
python3之后没有 __init__.py文件的目录也为被认定为包,可以引入模块,但是建议依然遵循之前的规则。
导入方式可以分为两种:
- 单独使用 import,格式:
import 包.包.模块
# 使用
包.包.模块.变量
包.包.模块.函数
这样的弊端就是在使用的时候要使用带包名的前缀
- 配合使用 from…import,格式:
from 包.包 import 模块
# 使用
模块.变量
模块.函数
使用的时候不需要那些冗长的前缀
from bao02 import my_module02
print(my_module02.add(1, 2)) # 3
print(my_module02.l) # ['m', 'y', '_', 'm', 'o', 'd', 'u', 'l', 'e', '的', '列', '表']
6、__init__.py 的 __all__
当我们使用 from sound.effects import * 的时候会导入这个包下的所有模块,但是我们如果想指定这个时候导入哪些模块的时候可以在这个包的__init__.py中定义__all__列表属性:
__all__ = ["my_module01","my_module02"]
注意不要带 .py
from bao01 import *
print(my_module02.add(my_module01.i, len(my_module01.l))) # 112
在上边的例子中,在包:bao01的 __init__.py 文件中设置 __all__ = ["my_module01","my_module02"]
则在使用 from bao01 import *
引入 bao01 的时候只会引入 my_module01 和 my_module02 这两个模块,不会引入 my_module 模块。
需要注意的是 my_module 模块并不是不会被引入其他模块了,可以通过具体的路径来引入:
from bao01 import my_module
my_module.hello() # 你好我是hello方法
六、迭代器与生成器
1、可迭代对象
1.1、内置可迭代对象
像 字符串,集合,列表,元组,字典,这样能够通过 for in 去迭代的对象就是被称为迭代对象,上边这些是python中天然的可迭代对象,那么我们自己创建的对象如果也像成为可迭代对象的话需要重写 __iter()__
魔术函数,并且这个函数返回一个迭代器对象。
可迭代对象本质:
其实都是collections.abc(容器数据类型)模块里的 Iterable (可迭代对象)类创建出来的实例
可以通过 isinstance("对象",Iterable)
查看对象是否是可迭代对象
from collections.abc import Iterable
l = [1,2,3]
print(isinstance(l,Iterable)) # True
print(isinstance((2,3,4),Iterable)) # True
print(isinstance("abcd",Iterable)) # True
1.2、自定义可迭代对象
知道了可迭代对象的特性之后,除了系统自带的可迭代对象外,我们自己创建的对象如果也想有可迭代的属性,则需要通过重写
__iter()__
魔术函数,并必须返回一个迭代器对象。
class A:
def __iter__(self):
self.n = 1
return self
print(isinstance(A(),Iterable)) # True
2、迭代器
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
iter("可迭代对象"):创建一个迭代器对象
next("迭代器对象"):从第一个元素开始每执行一次获取下一个元素
list=[1,2,3,4] # 可迭代对象
it = iter(list) # 创建迭代器对象
print (next(it)) # 输出迭代器的下一个元素 ( 1
print (next(it)) # 2
迭代器对象也可以使用 for
循环进行遍历
#!/usr/bin/python3
list=[1,2,3,4]
it = iter(list) # 创建迭代器对象
for x in it:
print (x, end=" ")
#输出
1 2 3 4
迭代器对象所有元素取出之后继续执行 next() 时会报 StopIteration
异常,根据这个特性我们也可以使用 while 循环遍历元素
list=[1,2,3,4]
it = iter(list) # 创建迭代器对象
while True:
try:
print(next(it))
except StopIteration:
print("取完了")
break
自定义迭代器对象
自定义迭代器对象需要有两个魔法函数 __iter__
和 __next__
,iter函数返回迭代器对象,next函数返回迭代器对象的下一个值,需要注意的是规范需要设置取值范围,当超过指定范围的时候需要抛出 StopIteration
异常
# 创建一个初始值为 1 依次递增 1 的迭代器对象,最大取到 5
class A:
def __iter__(self):
self.num = 1
return self
def __next__(self):
num = self.num
if num > 5: # 当超过 5 的时候抛出 StopIteration 异常
raise StopIteration
self.num+=1
return num
a = A()
it = iter(a) # 创建一个迭代器对象
for i in iter(a):
print(i,end=" ")
# 输出
1 2 3 4 5
3、生成器
在Python中,yield
关键字用于从一个函数中返回一个生成器(generator)。生成器是一种迭代器,它允许你按需生成值,而不是一次性将所有值加载到内存中。这对于处理大量数据或需要惰性求值的场景非常有用。
以下是yield的一些关键特性和用法:
3.1、基本用法
使用yield的函数称为生成器函数。调用生成器函数时,会返回一个生成器对象,而不是立即执行函数体。生成器对象可以使用next()函数或在循环中迭代来逐个获取值。
def simple_generator():
yield 1
yield 2
yield 3
gen = simple_generator()
print(next(gen)) # 输出: 1
print(next(gen)) # 输出: 2
print(next(gen)) # 输出: 3
3.2、生成器推导式
同列表推导式一样,把列表推导式中的 [ 换成 ( 就可以创建生成器推导式
# 列表推导式
list = [i*2 for i in range(5)]
print(type(list)) # <class 'list'>
print(list) # [0, 2, 4, 6, 8]
# 生成器推导式
it = (i*2 for i in range(5))
print(type(it)) # <class 'generator'>
print(it) # <generator object <genexpr> at 0x000001CD288FA9B0>
for i in it:
print(i,end=" ") # 0 2 4 6 8
3.3、在循环中使用
yield通常在循环中使用,以按需生成一系列值。
def range_generator(start, end):
current = start
while current < end:
yield current
current += 1
for num in range_generator(0, 5):
print(num) # 输出: 0 1 2 3 4
3.4、保存函数状态
当生成器函数产生一个值后,它的执行状态会被保存,直到下一次调用next()。这意味着函数中的局部变量和控制流状态在后续调用时仍然存在。
def counter():
count = 0
while True:
count += 1
yield count
gen = counter()
print(next(gen)) # 输出: 1
print(next(gen)) # 输出: 2
print(next(gen)) # 输出: 3
3.5、结合send()方法(有入参)
除了next(),生成器对象还支持send()方法。send(value)会向生成器发送一个值,并在生成器函数上次暂停的地方恢复执行,同时将发送的值赋给yield表达式的结果。
def echo_generator():
while True:
received = yield
print(f"Echo: {received}")
gen = echo_generator()
next(gen) # 启动生成器
gen.send("Hello") # 输出: Echo: Hello
gen.send("World") # 输出: Echo: World
注意,在第一次使用生成器之前,必须先调用next(gen)来启动生成器,否则第一次调用send()会抛出异常。
3.6、使用yield from
yield from是Python 3.3引入的语法,用于在生成器函数中委托另一个生成器。
def sub_generator():
yield 'a'
yield 'b'
def main_generator():
yield 'x'
yield from sub_generator()
yield 'y'
for item in main_generator():
print(item) # 输出: x a b y
总结
yield关键字在Python中提供了一种强大而灵活的机制来创建迭代器,它允许函数在多个调用之间保持状态,并且按需生成值。这使得处理大量数据或需要惰性计算的场景变得更加高效和简洁。
第四章、面向对象
一、类
在python中通过 class 来定义一个类
1、类的创建
定义一个类有三种格式,写法不同其他都相同
# 第一种格式
class A(object):
name = "A"
def f(self):
print("name:",self.name)
return self.name
# 第二种格式
class B():
name = "B"
def f(self):
print("name:",self.name)
return self.name
# 第三种格式
class C:
name = "c"
def f(self):
print("name:",self.name)
return self.name
2、类的属性——变量
在类中可以设置成员变量
class A(object):
name = "A"
age = 18
gender = "男"
print(A.name) # A
以上都是普通的变量,通过类名或者对象可以直接获取,如果不希望外部可以直接获取可以设置成私有变量,通过 "__变量名"
设置
class A(object):
name = "A"
age = 18
gender = "男"
__weight = 120
print(A.name) # A
print(A.__weight) # AttributeError 异常
可以通过类内部的函数获取私有变量
class A(object):
__weight = 120
def getweight(self):
return self.__weight
a = A()
print(a.getweight()) # 120
3、类的属性——类函数
类的成员属性函数可以分两种,
一种是通过类直接调用的,
一种是需要通过类生成的对象才可以调用的
# 通过类直接调用的方法
class A(object):
def f():
print("函数f")
print(A.f()) # 通过类名直接调用
一般我们设计一个类的方法的时候规范是都要使用 @classmethod
注解的
@classmethod
:注解的函数表示为类函数,此函数的规范最少有一个参数,cls
:表示类本身,cls可以掉类的属性
class A(object):
name = "A"
@classmethod
def f(cls):
print("函数f")
print(cls.name)
print(A.f()) # 函数f
# A
4、类的属性——静态函数
在类内部的方法上使用 @staticmethod
注解,可以表示这个函数是静态函数,静态函数既不需要传对象参数,也不需要传类参数(self,cls)
静态函数的作用主要是可以设置类的属性
class A:
__name = "a" # 私有属性
@staticmethod # 静态函数
def sm(r):
print("我是静态函数sm,name为:",A.__name,"入参为:",r) # 类内部可以获取私有属性
A.__name = "name新值" # 可以修改类的私有属性
return "我是"+A.__name+"静态函数的返参"+r # 可以把私有属性返回
print(A.sm("类直接调用")) # 我是静态函数sm,name为: a 入参为: 类直接调用
# 我是name新值静态函数的返参类直接调用
print(A().sm("通过对象调用")) # 我是静态函数sm,name为: name新值 入参为: 通过对象调用
# 我是name新值静态函数的返参通过对象调用
5、类的属性——对象函数
对象函数规定第一个入参为 self
表示对象本身
class A(object):
__name = "A"
def f(self):
print(self.__name)
print("f函数")
return "f函数的返参"
a = A()
print(a.f()) # A
# f函数
# f函数的返参
print(A.f()) # TypeError
类的函数同样也可以设置成私有函数
class A:
def __f(self):
print("我是一个私有函数")
return "私有函数的返参"
def f(self):
print("我是对象函数f")
return self.__f()
a = A()
fr = a.f() # 我是对象函数f
# 我是一个私有函数
print(fr) # 私有函数的返参
a.__f() # AttributeError
6、类中函数的调用权限
函数 | 特性 | 类可直接调用 | 对象可直接调用 |
---|---|---|---|
普通函数 | 没有装饰器和入参 | 是 | 否 |
静态函数 | @staticmethod 装饰器,没有入参 | 是 | 是 |
类函数 | @classmethod装饰器,入参(cls) | 是 | 是 |
对象函数 | 没有装饰器,入参(self) | 否 | 是 |
class A:
def f1():
print("普通函数")
@staticmethod
def f2():
print("静态函数")
@classmethod
def f3(cls):
print("类函数")
def f4(self):
print("对象函数")
A.f1() # 类调普通函数
A.f2() # 类调静态函数
A.f3() # 类调类函数
A.f4() # 类调对象函数 报错
a = A()
a.f1() # 对象调普通函数 报错
a.f2() # 对象调静态函数
a.f3() # 对象调类函数
a.f4() # 对象调对象函数
ps:类调用对象函数可以通过传入一个对象来进行调用
class A:
def f(self):
print("对象函数")
a = A() # 实例化一个对象
A.f(a) # 通过类调用对象函数,传如一个对象 ( 对象函数
7、类的属性的增删改查
通过 __dict__
可以查看类的所有属性
class A:
name = "A"
def f(self):
print("f函数")
return "f函数的返参"
# 查看
print(A.__dict__) # 查看所有属性
# {'__module__': '__main__', '__firstlineno__': 23, 'name': 'A', 'f': <function A.f at 0x0000028D37F48900>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
print(A.__dict__["name"]) # 查看指定属性
# 新增
A.age = 20
print(A.age) # 20
# 修改
A.name = "AA"
print(A.name) # AA
# 删除
del A.name
print(A.name) # AttributeError
设置属性总结一点就是,如果本身有这个属性则是修改属性的值,如果没有则是新增一个属性
重点
:给类设置完属性之后生成的对象是有这个属性值的
class A:
name = "A"
def fun(self):
print("fun函数,age:",self.age) # 打印对象的 age 值
return "fun函数返参"
A.age = 20 # 给 A 类设置属性
A.f = fun # 给 A 类设置对象函数
a = A() # 通过类 A 生成对象 a
print(a.age) # 通过对象 a 调用 age 属性 ( 20
af = a.f() # 通过对象 a 调用 f 函数 ( fun函数,age: 20
print(af) # 打印 f 函数返参 ( fun函数返参
二、对象
通过类创建对象,类相当于是一个模板,通过这个模板可以创建多个对象
1、对象的创建
对象创建的格式
对象 = 类()
2、对象的使用
通过对象可以调用类的非私有变量和方法(类方法和对象方法都可以调用
class A:
name = "A"
def f(self): # 对象函数
print("f函数")
return "f函数的返参"
@classmethod
def f2(cls): # 类函数
print("f2函数")
return "f2函数的返参"
a = A()
print(a.name) # 调用变量 A
fr = a.f() # 调用对象函数 f函数
print(fr) # f函数的返参
f2r = a.f2() # 调用对象函数 f2函数
print(f2r) # f2函数的返参
注意:
对象函数只能通过对象调用
类函数可以通过类名调用,也可以通过对象调用
3、对象的属性和函数
对象除了通过类的属性生成自己的属性外还可以通过自己手动设置属性和函数
通过 __dict__
可以查看对象的属性(不包含类的属性
class A:
age = 20
def f(self):
print("f函数",self.name)
return "f函数的返参"
def f2(n):
print("我是手动添加的函数",n)
return "手动添加的函数的返参"
a=A()
# 查看对象 a 的所有属性 (手动设置的属性
print(a.__dict__) # {}
# 手动添加属性
a.name = "我是手动添加的属性"
print(a.name) # 打印手动添加的属性 ( 我是手动添加的属性
a.f() # 执行函数,这个函数打印了新添加的属性 ( f函数 我是手动添加的属性
# 手动添加函数
a.af2 = f2 # 给对象添加函数
af2r = a.af2("aaa") # 调用手动添加的函数 ( 我是手动添加的函数 aaa
print(af2r) # 手动添加的函数的返参
# 查看对象 a 的所有属性 (手动设置的属性
print(a.__dict__) # {'name': '我是手动添加的属性', 'af2': <function f2 at 0x000001CE79FA3C40>}
三、基础魔术函数
在python中通过 __函数名__
来表示魔法函数,这些函数在类和对象中起着非常关键的作用,就行表演魔术一样,所以被称为魔术函数,通过 dir() 函数,可以查看都有哪些魔术函数
print(dir(object))
# 输出
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
1、__init__(self)
入参:对象
返参:无
在类实例化创建对象的时候调用,创建一个调用一次,入参是一个对象,一般用于给对象的属性初始化赋值
class A:
def __init__(self):
self.name = "lgh" # 给对象添加属性
self.age = 20
print("init函数调用")
a = A() # init函数调用
print(a.name,a.age) # lgh 20
init方法可以增加形参,在实例化对象的时候添加对应的实参来动态的给对象初始化属性
class A:
def __init__(self,name,age): # 增加形参
self.name = name # 给对象添加属性
self.age = age
print("init函数调用")
a = A("张三",23) # 创建对象的时候必须传入对应的参数
print(a.name,a.age) # 张三 23
2、__del__(self)
入参:对象
返参:无
在对象销毁的时候调用,销毁一个对象调用一次
class A:
def __init__(self,name):
self.name = name
print("对象 %s 创建了" % self.name)
def __del__(self):
print("对象 %s 销毁了" % self.name)
a1 = A("张三")
print("*"*20)
a2 = A("李四")
# 输出
对象 张三 创建了
********************
对象 李四 创建了
对象 张三 销毁了
对象 李四 销毁了
对象在程序运行结束直接会自动销毁,也可以通过 del
在程序中手动销毁对象
class A:
def __init__(self,name):
self.name = name
print("对象 %s 创建了" % self.name)
def __del__(self):
print("对象 %s 销毁了" % self.name)
a1 = A("张三")
del a1 # 手动删除 a1 对象
print("*"*20)
a2 = A("李四")
# 输出
对象 张三 创建了
对象 张三 销毁了
********************
对象 李四 创建了
对象 李四 销毁了
3、__new__(cls, *args, **kwargs)
入参:类本身和类的所有入参
返参:必须要有,返回一个对象
在类实例化对象的过程是先执行 new 函数,再执行 init 函数,init 函数的作用是给对象初始化赋值, new 函数的作用是创建一个对象并且返回,如果new函数没有返回一个对象,则对象创建失败,也就不会调用 init 函数
class A:
def __init__(self,name):
self.name = name
print("对象 %s 创建了" % self.name)
def __new__(cls, *args, **kwargs):
print("new函数调用")
# new函数没有返回值,则之后调用 new 函数,不会调用 init 函数
a1 = A("张三")
# 输出
new函数调用
正确的写法
class A(object):
def __init__(self,name):
self.name = name
print("对象 %s 创建了" % self.name)
def __new__(cls, *args, **kwargs):
print("new函数调用")
# 所有的类都是object类的子类,object的 new 函数也得返回一个对象,所以把这个对象返回就可以了,意思就是通过 object 创建一个对象
return object.__new__(cls)
# 也可以使用 supper().__new__(cls) 意义和上边是一样得,写法不同而已
#return super().__new__(cls)
a1 = A("张三")
# 输出
new函数调用
对象 张三 创建了
3、__repr__(self) 和 _srt_(self)
入参:对象
返参:必须要有,对象的介绍
相当于java中的 toString 方法,再打印对象的时候显示的信息,两个方法都存在的时候优先显示 str 函数的返参
当没有定义这两个函数的时候,打印对象的信息显示的是内存空间的地址
class A(object):
def __init__(self,name):
self.name = name
print("对象 %s 创建了" % self.name)
a = A("张三")
print(a)
# 输出
对象 张三 创建了
<__main__.A object at 0x000002629E9C7CB0>
定义 __repr__(self) 函数
class A(object):
def __init__(self,name):
self.name = name
print("对象 %s 创建了" % self.name)
def __repr__(self):
return "repr函数返回我是对象"+self.name
a = A("张三")
print(a)
# 输出
对象 张三 创建了
repr函数返回我是对象张三
当两个函数都定义,则显示 str 返回的内容 str 优先级高于 repr
class A(object):
def __init__(self,name):
self.name = name
print("对象 %s 创建了" % self.name)
def __repr__(self):
return "repr函数返回我是对象"+self.name
def __str__(self):
return "str函数返回我是对象"+self.name
a = A("张三")
print(a)
# 输出
对象 张三 创建了
str函数返回我是对象张三
4、__doc__ 表示类的注释信息
通过类名直接调用,表示类的注释信息,注意这个注释信息的格式有一定的要求:
- 必须是在所有属性前边
- 必须是多行注释(三个单引号或双引号
- 必须和属性缩进对齐
class A:
'''类的注释信息,注意必须是在所有属性前边 '''
name = "a"
def f(self):
print("f函数")
print(A.__doc__) # 类的注释信息,注意必须是在所有属性前边
5、__doc__ 表示当前操作的对象在哪个模块
通过对象调用,表示当前对象来自哪个模块
from 类 import A
a = A()
print(a.__module__) # 类
6、__doc__ 表示当前操作的对象的类是哪个
通过对象调用,显示该对象的类信息
from 类 import A
a = A()
print(a.__class__) # <class '类.A'>
7、__call__(self, *args, **kwargs)表示通过 “对象()”来调用的方法
对象的init方法是在 “类名()” 这种形式调用,而 call函数可以通过 “对象()” 这种形式调用
并且这个函数是可以传参的
class A:
def __init__(self):
print("__init__函数执行")
def __call__(self, *args, **kwargs):
print("__call__函数执行")
print(args)
a = A() # __init__函数执行
a(1,2,3) # __call__函数执行
# (1, 2, 3)
8、__dict__查看类或者对象的所有属性
对象和类都可以调用
class A:
name = "a"
def f(self):
print("f函数")
print(A.__dict__) # {'__module__': '__main__', '__firstlineno__': 135, 'name': 'a', 'f': <function A.f at 0x000002AD389B8900>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
a = A()
print(a.__dict__) # {}
a.age = 18
print(a.__dict__) # {'age': 18}
print(dir(A)) # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'f', 'name']
print(dir(a)) # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'age', 'f', 'name']
同样是查看类和对象的属性,dir()和 __dict__ 有什么区别呢,主要有以下区别:
- dir() 是一个基本函数,通过传参调用,__dict__是魔术函数,通过对象或者类直接调用
- dir()返回的是一个列表,__dict__返回的是一个字典
- dir()传入对象的时候返回信息会包括生成这个对象的类的属性,__dict__不会包括类的属性,只会显示对象的属性
- dir()所有对象都可以调用,__dict__并不是所有对象都拥有的属性,许多内建类型都没有 __dict__ 属性,例如 list
9、__getitem__ ,__setitem__ ,__delitem__ 让对象有字典的功能
- __getitem__(self, item):通过“对像[属性key]” 语法获取值的时候调用,需要有返回值,返回属性key对应的 value 值
- __setitem__(self, key, value):通过 “对像[属性key]” = value 语法给属性设置值的时候调用,无返回值
- __delitem__(self, key):通过 “del 对像[属性key]”语法删除属性key的时候调用,无返回值
注意:这三个魔术函数本身不能让对象具有字典的功能,需要在这三个函数中自己写逻辑
class A:
def __init__(self):
self.data = {} # 创建一个字典,用来放数据
def __getitem__(self, item):
print("__getitem__函数执行", item)
return self.data.get(item)
def __setitem__(self, key, value):
print("__setitem__函数执行", key, value)
self.data[key] = value
def __delitem__(self, key):
print("__delitem__函数执行", key)
del self.data[key]
a = A()
a["name"] = "张三" # 给对象设置属性 __setitem__函数执行 name 张三
name = a["name"] # 获取对象属性 __getitem__函数执行 name
print(name) # 张三
del a["name"] # __delitem__函数执行 name
print(a["name"]) # __getitem__函数执行 name
# None
四、封装
python和java一样是一个面向对象的一个编程,它也有封装的一个特性:将复杂的流程,信息包装起来内部处理,让使用者通过简单的调用就能实现。
1、私有属性
在类中可以通过使用 __属性名
或者 __函数名
来定义私有属性,私有属性不能被外部调用,只能在类中被调用
1.1 、普通属性
class A:
name = "aa"
def f(self):
return "f函数"+self.name
a = A()
print(a.name) # aa
print(a.f()) # f函数aa
1.2 、假的私有属性 通过 _属性
(单下划线)来定义,它只是一种标识这个属性是私有属性,但是对象其实是可以再外部调用的
class A:
_name = "aa"
def _f(self):
return "f函数"+self._name
a = A()
print(a._name) # aa
print(a._f()) # f函数aa
1.3 、私有属性
class A:
__name = "aa"
def __f(self):
return "f函数"+self.__name # 再函数内部可以获取到私有属性 __name
def f2(self):
return self.__f() # 在 函数 f2 内部可以调用私有函数 __f
a = A()
print(a.f2()) # f2函数aa
print(a.__name) # AttributeError
print(a.__f()) # AttributeError
2、获取私有属性
由于私有属性不能在外部直接获取,但是可以在类内部获取,所有可以设置共有函数返回私有属性的方式来获取私有属性
class A:
__name = "aa"
def get_name(self):
return self.__name # 在函数内部可以获取到私有属性 __name
a = A()
print(a.get_name()) # 通过get_name函数__name的值 ( aa
3、修改私有属性
和获取私有属性一样,通过共有函数在类内部修改私有属性
class A:
__name = "aa"
def set_name(self,new_name):
self.__name = new_name # 在函数内部修改私有属性 __name 的值
def get_name(self):
return self.__name
a = A()
a.set_name("bb")
print(a.get_name()) # 通过get_name函数__name的值 ( bb
五、继承
1、如何继承
同java一样python也有继承的特性,其实所有的类都继承于 object 在前边的类的创建哪里的第一种定义就展现出了python继承的特性
class A(object):
name = "aa"
def f1():
print("aa")
return name
同样我们也可以继承自己定义的类
class A(object):
name = "aa"
def f1(self):
print("aa")
return name
# B 类 继承 A 类
class B(A):
age = 20
2、继承之后的使用
在类 B 继承类 A 之后,B 实例化的对象就也有了类 A 的属性
class A(object):
name = "aa"
def f1(self):
print("aaaa")
class B(A):
age = 20
b = B()
print(b.name) # aa
b.f1() # aaaa
print(b.age) # 20
3、继承之重写和扩展
如果子类的方法或者属性和父类名字相同,则为重写,如果不同则为扩展
比如 B类继承于A类,A的属性 name = “a” ,B类的属性 name=“b” 则生成的b=B()对象的name是子类的"b"
class A:
name = "aa"
class B(A):
name = "bb"
b = B()
print(b.name) # bb
python继承的函数中是要是函数名相同则为重写,只通过函数名不通过参数区分
,如果参数不同则根据子类的参数为准,这点和 Java 有所区别
class A:
def f1(self):
print("aaaa")
class B(A):
# 方法名和父类中方法名相同,但参数不同的情况下
def f1(self,name):
print("bbbb",name)
b = B()
b.f1(33) # bbbb333
b.f1() # TypeError 报异常,认为缺少入参
4、子类调用父类的属性
在子类的函数中可以需要父类的属性,这个时候就需要通过 super()
关键字
class A:
name = "aa"
def f1(self):
print("aaaa")
class B(A):
def __init__(self):
self.a_name = super().name # 子类的 a_name 取 父类 name 属性的的值
def f2(self):
super().f1() # 子类调用 父类的 f1() 函数
print("bbb")
b = B()
print(b.a_name) # aa
b.f2() # aaaa
# bbb
需要注意的是子类中访问不了父类的私有属性
,如果想要访问也是需要通过父类的共有方法(如果父类提供共有方法的前提下)
class A:
__name = "我是私有变量name"
def __f1(self):
print("我是私有方法f1")
class B(A):
def get_name(self):
print(super().__name) # AttributeError
def get_f1(self):
super().__f1() # AttributeError
b = B()
b.get_name()
b.get_f1()
5、多继承
python中通过在定义类的时候再括号中添加多个类实现多继承,中间用 “,” 隔开,属性的优先级从左到右依次降低
class A:
name = "我是A类的属性name"
def f(self):
print("我是A类的函数f")
def ff(self):
print("我是A类的函数ff")
class B:
name = "我是B类的属性name"
def f(self):
print("我是B类的函数f")
def ff(self):
print("我是B类的函数ff")
class C(A,B): # 多继承
age = 20
def ff(self):
print("我是C类的函数ff")
c = C()
print(c.name) # 我是A类的属性name
c.f() # 我是A类的函数f
c.ff() # 我是C类的函数ff
同样在子类中使用 super() 调用相同名字的属性的时候也是遵循从右往左的优先级顺序
class A:
name = "我是A类的属性name"
def f(self):
print("我是A类的函数f")
class B:
name = "我是B类的属性name"
def f(self):
print("我是B类的函数f")
class C(A,B):
def __init__(self):
self.super_name = super().name
def getf(self):
super().f()
c = C()
c.getf() # 我是A类的函数f
print(c.super_name) # 我是A类的属性name
如果想要知道一个类的调用优先级,可以使用 __mro__
这个魔术函数来查看
格式为:类名.__mro__
print(C.__mro__)
#输出
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
如果想调用指定父类的属性
,可以通过引用父类名直接调用
class A:
name = "A类的name"
def f(self):
print("A类的函数f")
class B:
name = "B类的name"
def f(self):
print("B类的函数f")
class C(A,B):
def get_super_f(self):
super().f()
return super().name
def get_f(self):
B.f(self) # 类直接调用对象方法需要把对象传入进去
return B.name
c = C()
sf = c.get_super_f() # A类的函数f
print(sf) # A类的name
f = c.get_f() # B类的函数f
print(f) # B类的name
6、继承的传递
继承是可以传递的子类继承父类,父类也可以继承爷爷类,然后子类也会有爷爷类的属性,如果属性名相同则优先级是继承关系越近优先级越高
class A:
name = "我是爷爷类的name"
def f(self):
print("我是爷爷类的函数f")
def d(self):
print("我是爷爷类的函数d")
class B(A): # B 继承 A
def d(self):
print("我是父类的函数d")
class C(B): # C 继承 B
pass
c = C()
print(c.name) # 我是爷爷类的name
c.f() # 我是爷爷类的函数f
c.d() # 我是父类的函数d
print(C.__mro__) # (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
六、单例模式
定义:一个类只创建一个对象,这样的类被称为单例模式
正常通过类可以创建N个对象,如何控制类只能创建一个对象呢?一共有四种方式可以定义一个单例模式的类
1、通过 __new__ 实现
主要实现方法:在类中定义一个变量用来保存对象(建议设置成私有属性),在 new 方法中判断这个变量是否有值,如果有值则直接返回,如果没值则创建一个对象并且保存到这个变量中,返回变量,这样这个类就只会创建一个对象。
class A:
__a = None # 建议设置成私有属性
def __new__(cls, *args, **kwargs):
if cls.__a is None: # 如果变量为 none
cls.__a = super().__new__(cls) # 创建对象 并赋值
return cls.__a
def __init__(self,name):
self.name = name
a1 = A("张三")
a2 = A("李四")
# 同一个对象
print(id(a1)) # 1946691206320
print(id(a2)) # 1946691206320
# 创建的对象叫张三,后边的李四使用的还是张三这个对象,以先创建的为主
print(a1.name)
print(a2.name)
2、通过 @classmethod 类方法实现
主要实现方法:同 new 函数类似,也是定义一个变量来保存类对象,不同的是这次通过类方法来判断这个变量是否有值,没值则创建对象赋值并返回这个变量。
注意点:严格来说需要定义 __new__ 函数然后返回用于保存对象的变量,这样可以避免通过 对象=类() 的方式创建多个对象,使这个类只能创建一个对象。
class A:
__a = None
name = ""
def __new__(cls, *args, **kwargs):
return cls.__a
@classmethod
def get_obj(cls):
if cls.__a is None: # 如果变量为 none
cls.__a = super().__new__(cls) # 创建对象 并赋值
return cls.__a
def __init__(self):
print("执行init")
a1 = A()
print(a1) # None
a2 = A.get_obj()
a3 = A.get_obj()
print(a1) # <__main__.A object at 0x0000027573B67CB0>
print(a2) # <__main__.A object at 0x0000027573B67CB0>
a2.name = "张三" # 给a2对象的name赋值
print(a3.name) # 打印a3对象的name显示 ”张三“ 说明是同一个对象
a4 = A() # 由于 A 类的 __a 有值, new 函数返回有值的对象后 init 函数就执行了 ( "执行init"
print(a4.name) # 由于是同一个对象所以依然显示张三 ( 张三
3、通过装饰器来实现
主要实现方法:通过定义一个类装饰器,这个装饰器中定义一个字典,这个字典key是类,value是类的对象,通过判断这个key是否在这个字典中,如果不在字典中则通过类创建一个对象生成一个键值对保存到字典中,如果存在则不创建对象。最后通过类最为key返回这个字典中保存的value也就是对象数据,这样保证一个类只会实例化一个对象。
def my_singleton(clas):
__objs = {} # 定义一个字典用于保存类和对象的键值对,key 为类,value 为类的对象,通过判断key是否存在,不存在则创建对象并赋值保存,存在则不创建,最后返回value值也就是对象
def get_obj():
if clas not in __objs:
__objs[clas] = clas() # 创建一个对象,生成成一个键值对保存到 __objs 字典中
return __objs[clas]
return get_obj
@my_singleton
class A:
name = None
a1 = A()
a2 = A()
print(a1) # <__main__.A object at 0x000001E15E477CB0>
print(a2) # <__main__.A object at 0x000001E15E477CB0>
a1.name = "张三"
print(a2.name) #"张三"
3、通过元类来实现
hasattr(obj,name)
:用于判断对象是否包含对应的属性。obj:对象;name:属性名,包括变量和函数。获取不到私有属性
class A:
name = None
__age = None
def f1(self):
pass
def __f2(self):
pass
@classmethod
def f3(cls):
pass
@staticmethod
def f4():
pass
a = A()
print(hasattr(a, "name")) # True
print(hasattr(a, "__age")) # False
print(hasattr(a, "f1")) # True
print(hasattr(a, "__f2")) # False
print(hasattr(a, "f3")) # True
print(hasattr(a, "f4")) # True
print(hasattr(a, "abcde")) # False
主要实现方法:在 __new__ 函数中通过 hasattr() 函数判断类是否有指定对象属性,如果没有则创建赋值,如果有则不做处理,最终返回这个对象属性的值
class A:
def __new__(cls, *args, **kwargs):
if not hasattr(cls,"obj"): # 注意不能是私有属性
cls.obj = super().__new__(cls)
return cls.obj
a1 = A()
a2 = A()
print(a1) # <__main__.A object at 0x000001BB8D537CB0>
print(a2) # <__main__.A object at 0x000001BB8D537CB0>
a1.name = "张三"
print(a2.name) # 张三
同样使用类方法也可以实现
class A:
@classmethod
def get_obj(cls):
if not hasattr(cls,"obj"):
cls.obj = super().__new__(cls)
return cls.obj
def __new__(cls, *args, **kwargs):
if hasattr(cls,"obj"):
return cls.obj
a1 = A()
print(a1) # None
a2 = A.get_obj()
a3 = A.get_obj()
print(a1) # None
print(a2) # <__main__.A object at 0x0000024E6B267CB0>
print(a3) # <__main__.A object at 0x0000024E6B267CB0>
a4 = A()
print(a4) # <__main__.A object at 0x0000024E6B267CB0>
第五章、文件读写
Python open() 方法用于打开一个文件,并返回文件对象,在对文件进行处理过程都需要使用到这个函数,如果该文件无法被打开,会抛出 OSError。
注意:使用 open() 方法一定要保证关闭文件对象,即调用 close() 方法。
open() 函数常用形式是接收两个参数:文件名(file)和模式(mode)。
open(file, mode='r')
完整的语法格式为:
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
参数说明:
file: 必需,文件路径(相对或者绝对路径)。
mode: 可选,文件打开模式
buffering: 设置缓冲
encoding: 一般使用utf8
errors: 报错级别
newline: 区分换行符
closefd: 传入的file参数类型
opener: 设置自定义开启器,开启器的返回值必须是一个打开的文件描述符。
mode 参数有:
模式 | 描述 |
---|---|
t | 文本模式 (默认)。 |
x | 写模式,新建一个文件,如果该文件已存在则会报错。 |
b | 二进制模式。 |
+ | 打开一个文件进行更新(可读可写)。 |
U | 通用换行模式(不推荐)。 |
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
默认为文本模式,如果要以二进制模式打开,加上 b 。
open() 函数生成的是 file 对象,下边是 file 对象的常用方法
方法 | 描述 |
---|---|
file.read([size]) | 从文件读取指定的字节数,如果未给定或为负则读取所有。 |
file.readline([size]) | 读取整行,包括 “\n” 字符。 |
file.readlines([sizeint]) | 读取所有行并返回列表,若给定sizeint>0,则是设置一次读多少字节,这是为了减轻读取压力。 |
file.write(str) | 将字符串写入文件,返回的是写入的字符长度。 |
file.writelines(sequence) | 向文件写入一个序列字符串列表,如果需要换行则要自己加入每行的换行符。 |
file.close() | 关闭文件。关闭后文件不能再进行读写操作。 |
file.flush() | 刷新文件内部缓冲,直接把内部缓冲区的数据立刻写入文件, 而不是被动的等待输出缓冲区写入。 |
file.fileno() | 返回一个整型的文件描述符(file descriptor FD 整型), 可以用在如os模块的read方法等一些底层操作上。 |
file.isatty() | 如果文件连接到一个终端设备返回 True,否则返回 False。 |
file.next() | 返回文件下一行。 |
file.seek(offset[, whence]) | 设置文件当前位置 |
file.tell() | 返回文件当前位置。 |
file.truncate([size]) | 截取文件,截取的字节通过size指定,默认为当前文件位置。 |
file.readable() | 判断文件是否可读 |
一、文件的打开
在python中通过 open() 函数打开一个文件,函数内主要参数是文件的位置,字节编码和打开模式
f = open(file = r"D:\python\PycharmProjects\pythonProject001\aa.text",mode="r",encoding="utf-8")
如果想要创建一个文件,则mode设置成 w
f = open(file = r"D:\python\PycharmProjects\pythonProject001\bb.text",mode="w",encoding="utf-8")
二、文件的读取
通过open() 创建一个可读取的文件之后,通过 read()
读取文件的内容
需要注意的是在文件的读取是有连续性和不可重复性的,也就是读取过的内容不会被再次读取
'''
aa.text文件内容为:
第一行aa
第二行bb
第三行cc
'''
f = open(file= r"D:\python\PycharmProjects\pythonProject001\aa.text",mode="r",encoding="utf-8")
print(f.read())
# 执行结果为
第一行aa
第二行bb
第三行cc
读取指定字节数
print(f.read(9))
# 打印结果
第一行aa
第二行
可以通过 readline()
一次读取一行数据
'''
aa.text文件内容为:
第一行aa
第二行bb
第三行cc
'''
f = open(file= r"D:\python\PycharmProjects\pythonProject001\aa.text",mode="r",encoding="utf-8")
print(f.read(4)) # 第一行a
print(f.readline()) # a
print(f.readline()) # 第二行bb
如果想读取的数据返回一个集合可以使用 readlines()
'''
aa.text文件内容为:
第一行aa
第二行bb
第三行cc
'''
f = open(file= r"D:\python\PycharmProjects\pythonProject001\aa.text",mode="r",encoding="utf-8")
print(f.readlines()) # ['第一行aa\n', '第二行bb\n', '第三行cc']
三、文件的写入
打开一个可写的 file 后 通过 write()
写入内容
# 创建文件
fw = open(file = r"D:\python\PycharmProjects\pythonProject001\bb.text",mode="w",encoding="utf-8")
fw.write("这是我手动输入的内容") # 写入内容
# 读取文件内容
fr = open(file = r"D:\python\PycharmProjects\pythonProject001\bb.text",mode="r",encoding="utf-8")
print(fr.read()) # 这是我手动输入的内容
四、文件的关闭
文件打开之后我们操作完数据之后一定要关闭的,因此我们可以通过 try - finally 来保证文件一定执行关闭操作
try:
fr = open(file= r"D:\python\PycharmProjects\pythonProject001\aa.text",mode="r",encoding="utf-8")
print(fr.read())
finally:
fr.close()
一般我们可以通过使用 with
关键字来保证文件的关闭,它内部自动帮我们做了 try - finally 操作,具体用法如下
with open(file= r"D:\python\PycharmProjects\pythonProject001\aa.text",mode="r",encoding="utf-8") as f :
print(f.read())
五、打开一个可读可写的文件
在 Python 中,你可以使用内置的 open 函数来打开一个文件,并指定文件的打开模式为可读可写。你可以使用模式
① ‘r+’(读取和写入,文件必须存在),
② ‘w+’(写入和读取,会覆盖文件内容),
③ ‘a+’(追加和读取,在文件末尾添加内容)。
下面是一些示例代码,展示了如何使用这些模式:
使用 ‘r+’ 模式(文件必须存在):
try:
with open('example.txt', 'r+') as file:
content = file.read()
print("文件内容:", content)
file.seek(0) # 移动到文件开头
file.write("新的内容\n")
except FileNotFoundError:
print("文件不存在,无法使用 'r+' 模式打开")
使用 ‘w+’ 模式(会覆盖文件内容):
with open('example.txt', 'w+') as file:
file.write("这是一些新的内容\n")
file.seek(0) # 移动到文件开头
content = file.read()
print("文件内容:", content)
使用 ‘a+’ 模式(在文件末尾添加内容):
with open('example.txt', 'a+') as file:
file.write("追加的内容\n")
file.seek(0) # 移动到文件开头
content = file.read()
print("文件内容:", content)
六、文件目录的简单操作-os模块
python中对文件目录的操作主要是通过os模块的功能来实现的下面来演示os模块对目录的相关操作
1、文件重命名操作-os.rename(“原名”,“新名”)
import os
# 我对文件重命名
os.rename("abc.text","abc2.text")
2、创建文件夹-os.mkdir(“目录地址”)
注意一次只能创建一级目录,下边的例子中abc是新需要创建的目录
# 创建文件夹 , 一次只能创建一级目录
# os.mkdir("/Users/wangtianyu/PycharmProjects/pythonProject/abc")
3、删除文件夹-os.rmdir(“目录地址”)
# 删除文件夹
os.rmdir("/Users/wangtianyu/PycharmProjects/pythonProject/abc")
4、删除文件-os.remove(“目录地址”)或os.unlink(“目录地址”)
# 删除文件
os.remove("abc2.text")
os.unlink("abc.text")
5、获取当前路径-os.getcwd()
# 获取当前路径
print(os.getcwd()) # /Users/wangtianyu/PycharmProjects/pythonProject
6、切换当前路径-os.chdir(“切换命令”)
# 获取当前路径
print(os.getcwd()) # /Users/wangtianyu/PycharmProjects/pythonProject
os.chdir("../") # 同liunx命令,切换到上级目录
print(os.getcwd()) # /Users/wangtianyu/PycharmProjects
7、获取路径下的目录列表-os.listdir(“指定路径”)
函数如参不写默认取当前路径
print(os.listdir()) # 获取当前路径下的目录列表
# ['第二章.py', '第一章.py', 'os_test.py', '.venv', '.idea']
print(os.listdir("/Users/wangtianyu/PycharmProjects")) # 获取指定路径下的目录列表
# ['pythonProject']
8、更多用法
方法 | 解释 |
---|---|
os.access(path, mode) | 检验权限模式 |
os.chdir(path) | 改变当前工作目录 |
os.chflags(path, flags) | 设置路径的标记为数字标记。 |
os.chmod(path, mode) | 更改权限 |
os.chown(path, uid, gid) | 更改文件所有者 |
os.chroot(path) | 改变当前进程的根目录 |
os.close(fd) | 关闭文件描述符 fd |
os.closerange(fd_low, fd_high) | 关闭所有文件描述符,从 fd_low (包含) 到 fd_high (不包含), 错误会忽略 |
os.dup(fd) | 复制文件描述符 fd |
os.dup2(fd, fd2) | 将一个文件描述符 fd 复制到另一个 fd2 |
os.fchdir(fd) | 通过文件描述符改变当前工作目录 |
os.fchmod(fd, mode) | 改变一个文件的访问权限,该文件由参数fd指定,参数mode是Unix下的文件访问权限。 |
os.fchown(fd, uid, gid) | 修改一个文件的所有权,这个函数修改一个文件的用户ID和用户组ID,该文件由文件描述符fd指定。 |
os.fdatasync(fd) | 强制将文件写入磁盘,该文件由文件描述符fd指定,但是不强制更新文件的状态信息。 |
os.fdopen(fd[, mode[, bufsize]]) | 通过文件描述符 fd 创建一个文件对象,并返回这个文件对象 |
os.fpathconf(fd, name) | 返回一个打开的文件的系统配置信息。name为检索的系统配置的值,它也许是一个定义系统值的字符串,这些名字在很多标准中指定(POSIX.1, Unix 95, Unix 98, 和其它)。 |
os.fstat(fd) | 返回文件描述符fd的状态,像stat()。 |
os.fstatvfs(fd) | 返回包含文件描述符fd的文件的文件系统的信息,像 statvfs() |
os.fsync(fd) | 强制将文件描述符为fd的文件写入硬盘。 |
os.ftruncate(fd, length) | 裁剪文件描述符fd对应的文件, 所以它最大不能超过文件大小。 |
os.getcwd() | 返回当前工作目录 |
os.getcwdu() | 返回一个当前工作目录的Unicode对象 |
os.isatty(fd) | 如果文件描述符fd是打开的,同时与tty(-like)设备相连,则返回true, 否则False。 |
os.lchflags(path, flags) | 设置路径的标记为数字标记,类似 chflags(),但是没有软链接 |
os.lchmod(path, mode) | 修改连接文件权限 |
os.lchown(path, uid, gid) | 更改文件所有者,类似 chown,但是不追踪链接。 |
os.link(src, dst) | 创建硬链接,名为参数 dst,指向参数 src |
os.listdir(path) | 返回path指定的文件夹包含的文件或文件夹的名字的列表。 |
os.lseek(fd, pos, how) | 设置文件描述符 fd当前位置为pos, how方式修改: SEEK_SET 或者 0 设置从文件开始的计算的pos; SEEK_CUR或者 1 则从当前位置计算; os.SEEK_END或者2则从文件尾部开始. 在unix,Windows中有效 |
os.lstat(path) | 像stat(),但是没有软链接 |
os.major(device) | 从原始的设备号中提取设备major号码 (使用stat中的st_dev或者st_rdev field)。 |
os.makedev(major, minor) | 以major和minor设备号组成一个原始设备号 |
os.makedirs(path[, mode]) | 递归文件夹创建函数。像mkdir(), 但创建的所有intermediate-level文件夹需要包含子文件夹。 |
os.minor(device) | 从原始的设备号中提取设备minor号码 (使用stat中的st_dev或者st_rdev field )。 |
os.mkdir(path[, mode]) | 以数字mode的mode创建一个名为path的文件夹.默认的 mode 是 0777 (八进制)。 |
os.mkfifo(path[, mode]) | 创建命名管道,mode 为数字,默认为 0666 (八进制) |
os.mknod(filename[, mode=0600, device]) | 创建一个名为filename文件系统节点(文件,设备特别文件或者命名pipe)。 |
os.open(file, flags[, mode]) | 打开一个文件,并且设置需要的打开选项,mode参数是可选的 |
os.openpty() | 打开一个新的伪终端对。返回 pty 和 tty的文件描述符。 |
os.pathconf(path, name) | 返回相关文件的系统配置信息。 |
os.pipe() | 创建一个管道. 返回一对文件描述符(r, w) 分别为读和写 |
os.popen(command[, mode[, bufsize]]) | 从一个 command 打开一个管道 |
os.read(fd, n) | 从文件描述符 fd 中读取最多 n 个字节,返回包含读取字节的字符串,文件描述符 fd对应文件已达到结尾, 返回一个空字符串。 |
os.readlink(path) | 返回软链接所指向的文件 |
os.remove(path) | 删除路径为path的文件。如果path 是一个文件夹,将抛出OSError; 查看下面的rmdir()删除一个 directory。 |
os.removedirs(path) | 递归删除目录。 |
os.rename(src, dst) | 重命名文件或目录,从 src 到 dst |
os.renames(old, new) | 递归地对目录进行更名,也可以对文件进行更名。 |
os.rmdir(path) | 删除path指定的空目录,如果目录非空,则抛出一个OSError异常。 |
os.stat(path) | 获取path指定的路径的信息,功能等同于C API中的stat()系统调用。 |
os.stat_float_times([newvalue]) | 决定stat_result是否以float对象显示时间戳 |
os.statvfs(path) | 获取指定路径的文件系统统计信息 |
os.symlink(src, dst) | 创建一个软链接 |
os.tcgetpgrp(fd) | 返回与终端fd(一个由os.open()返回的打开的文件描述符)关联的进程组 |
os.tcsetpgrp(fd, pg) | 设置与终端fd(一个由os.open()返回的打开的文件描述符)关联的进程组为pg。 |
os.ttyname(fd) | 返回一个字符串,它表示与文件描述符fd 关联的终端设备。如果fd 没有与终端设备关联,则引发一个异常。 |
os.unlink(path) | 删除文件 |
os.utime(path, times) | 返回指定的path文件的访问和修改的时间。 |
os.walk(top[, topdown=True[, οnerrοr=None[, followlinks=False]]]) | 输出在文件夹中的文件名通过在树中游走,向上或者向下。 |
os.write(fd, str) | 写入字符串到文件描述符 fd中. 返回实际写入的字符串长度 |
os.path 模块 | 获取文件的属性信息。 |
第六章、多线程
一、线程
Python提供了threading模块来支持线程的创建、同步和通信。threading模块的设计基于Java的线程模型,但也有一些不同之处。例如,在Java中,锁和条件变量是每个对象的基础特性,而在Python中,这些被独立成了单独的对象(如threading.Lock()锁,threading.Condition()条件变量)。
1、线程的创建与启动
Python3中使用的是 threading.Thread
来创建线程,通过调用线程的start()
启动线程, 其中Thread函数的入参包括以下:
- group:线程组,暂时未使用,保留为将来的扩展。
- target:线程将要执行的目标函数。
- name:线程的名称。
- args:目标函数的参数,以元组形式传递。
- kwargs:目标函数的关键字参数,以字典形式传递。
- daemon:指定线程是否为守护线程。
import threading
import time
def threa_test():
time.sleep(1) # 新线程阻塞两秒,为了让主线程先执行完,方便看效果
print("新线程执行")
td = threading.Thread(target=threa_test) # 创建一个线程 注意传入的函数名不带括号
td.start() # 启动新线程
print("主线程执行")
# 执行结果
主线程执行
新线程执行
join(): a.join() a为子线,主线程进行阻塞,等待a执行完毕之后再执行主线程
import threading
import time
def threa_test():
time.sleep(1) # 新线程阻塞两秒,为了让主线程先执行完,方便看效果
print("新线程执行")
td = threading.Thread(target=threa_test) # 创建一个线程 注意传入的函数名不带括号
td.start()
print("主线程join前执行")
td.join()
print("主线程join后执行")
# 输出
主线程join前执行
新线程执行
主线程join后执行
daemon:守护线程 默认为False,设置为True表示主线程如果结束,则子线程也结束,不管子线程是否执行完
import threading
import time
def threa_test():
for i in range(5):
time.sleep(1) # 阻塞1秒
print("新线程执行",1)
td = threading.Thread(target=threa_test,daemon=True) # 设置为守护线程
# td.daemon = True 也可以通过设置属性的方式设置为守护线程
td.start()
time.sleep(3) # 主线程阻塞3秒
print("主线程执行完毕")
# 输出
新线程执行 1
新线程执行 1
主线程执行完毕
从上边的例子可以看出主线程执行完毕的时候子线程还没有输出完(只输出到了2),但使因为设置了守护线程也跟着主线程的结束而结束
2、线程的同步与锁
在多线程编程中,为了避免多个线程同时访问共享资源导致数据混乱或不一致的问题,可以使用互斥锁(Lock
)来实现线程同步。
在没有锁的情况下多线程会出现共享资源混乱的情况
import threading
import time
con= 10 # 假设库存为10
lock = Lock()
def pop_con():
global con
if con > 0: # 如果有库存
time.sleep(0.0001) # 阻塞 0.0001秒,模拟减库存操作
con -= 1 # 库存减一
print("卖出一个")
else:
print("卖完了")
if __name__ == "__main__":
for i in range(15): # 假设有15个用户同时下单
threading.Thread(target=pop_con,daemon=False).start()
# 输出
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖完了
卖完了
卖出一个
从上边的例子可以看出,库存只有 10 但是却卖出了13个物品,这样就出现了超卖问题
,所以在出现多个线程争抢同一个资源的时候就需要加上互斥锁,来保证不会出现超卖的现象
python中 threading 的 Lock 就是用来解决都线程共享资源的问题的
import threading
from threading import Lock
import time
con= 10 # 假设库存为10
lock = Lock()
def pop_con():
lock.acquire() # 加锁
global con
if con > 0:
time.sleep(0.0001) # 阻塞 0.001秒,模拟减库存操作
con -= 1
print("卖出一个")
else:
print("卖完了")
lock.release() # 释放锁
if __name__ == "__main__":
for i in range(15):
threading.Thread(target=pop_con,daemon=False).start()
# 输出
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖出一个
卖完了
卖完了
卖完了
卖完了
卖完了
加锁的时候必须要释放锁,所以我们释放锁的代码需要通过写到 try finally中
def pop_con():
try:
lock.acquire()
pass
finally:
lock.release()
因此我们使可以使用 whit 关键字来简化写法的,它自动帮我们做了加锁和解锁的操作
最终写法为
import threading
from operator import ifloordiv
from threading import Lock
import time
con= 10 # 假设库存为10
lock = Lock()
def pop_con():
with lock:
global con
if con > 0:
time.sleep(0.0001) # 阻塞 0.001秒,模拟减库存操作
con -= 1
print("卖出一个")
else:
print("卖完了")
if __name__ == "__main__":
for i in range(15):
threading.Thread(target=pop_con,daemon=False).start()
3、线程间通信
线程间通信可以使用队列(Queue)来实现。Queue是线程安全的数据结构,可以在多个线程之间安全地传递数据。
示例代码:
import threading
import queue
def producer(q):
for i in range(5):
q.put(i)
def consumer(q):
while not q.empty():
item = q.get()
print("Consumed:", item)
# 创建队列
q = queue.Queue()
# 创建生产者和消费者线程
producer_thread = threading.Thread(target=producer, args=(q,))
consumer_thread = threading.Thread(target=consumer, args=(q,))
# 启动线程
producer_thread.start()
consumer_thread.start()
# 等待线程执行完成
producer_thread.join()
consumer_thread.join()
在这个示例中,生产者线程向队列中放入数据,消费者线程从队列中取出数据进行消费,实现了线程间的通信。
Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。
这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。
Queue 模块中的常用方法:
- Queue.qsize() 返回队列的大小
- Queue.empty() 如果队列为空,返回True,反之False
- Queue.full() 如果队列满了,返回True,反之False
- Queue.full 与 maxsize 大小对应
- Queue.get([block[, timeout]])获取队列,timeout等待时间
- Queue.get_nowait() 相当Queue.get(False)
- Queue.put(item) 写入队列,timeout等待时间
- Queue.put_nowait(item) 相当Queue.put(item, False)
- Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
- Queue.join() 实际上意味着等到队列为空,再执行别的操作
4、线程池
使用线程池可以方便地管理线程,控制线程数量并提交任务。Python的concurrent.futures模块提供了ThreadPoolExecutor类来创建线程池。
示例代码:
from concurrent.futures import ThreadPoolExecutor
import time
def task(n):
print(f"Task {n} started")
time.sleep(2)
return f"Task {n} completed"
# 创建ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=3) as executor:
# 提交任务给线程池
future1 = executor.submit(task, 1)
future2 = executor.submit(task, 2)
future3 = executor.submit(task, 3)
# 获取任务执行结果
print(future1.result())
print(future2.result())
print(future3.result())
在这个示例中,创建了一个最大容纳3个线程的线程池,并提交了三个任务给线程池执行。
5、自定义创建新线程
我们可以通过直接从 threading.Thread 继承创建一个新的子类,并实例化后调用 start() 方法启动新线程,即它调用了线程的 run() 方法:
#!/usr/bin/python3
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, threadID, name, delay):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.delay = delay
def run(self):
print ("开始线程:" + self.name)
print_time(self.name, self.delay, 2)
print ("退出线程:" + self.name)
def print_time(threadName, delay, counter):
while counter:
if exitFlag:
threadName.exit()
time.sleep(delay)
print ("%s: %s" % (threadName, time.ctime(time.time())))
counter -= 1
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)
# 开启新线程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("退出主线程")
# 输出
开始线程:Thread-1
开始线程:Thread-2
Thread-1: Sun Feb 16 12:56:21 2025
Thread-2: Sun Feb 16 12:56:22 2025
Thread-1: Sun Feb 16 12:56:22 2025
退出线程:Thread-1
Thread-2: Sun Feb 16 12:56:24 2025
退出线程:Thread-2
退出主线程
二、进程
进程是资源分配的最小单元,线程是cpu调度的最小单元,一个进程包含多个线程。
ps:进程之间不共享全局变量
在Python中,进程可以通过multiprocessing模块来实现。multiprocessing模块支持子进程的创建、通信和同步等功能,提供了Process、Queue、Pipe、Lock等组件。
1、进程的创建与启动
创建进程是通过multiprocessing模块的Process类来创建的,主要包括以下参数:
- group:指定进程组
- name:进程名字
- target:子进程要执行任务的函数名
- args:子进程函数的入参,以元组的形式传递
- kwargs:目标函数的关键字参数,以字典形式传递。
下边就是创建进程的例子
import multiprocessing
import os
import time
def test():
for _ in range(5):
time.sleep(1)
print("子进程执行",os.getpid(),"父进程为:",os.getppid())
if __name__ == "__main__":
up = multiprocessing.Process(target=test)
up.start()
time.sleep(3)
print("主进程执行",os.getpid(),"父进程为:",os.getppid())
# 执行结果
子进程执行 22604 父进程为: 22400
子进程执行 22604 父进程为: 22400
主进程执行 22400 父进程为: 14620
子进程执行 22604 父进程为: 22400
子进程执行 22604 父进程为: 22400
子进程执行 22604 父进程为: 22400
os.getpid()
:获取进程号os.getppid()
:获取父进程号
2、进程属性与方法
属性:
- name:当前进程的别名。
- pid:当前进程的进程号(PID)。
- daemon:如果设置为True,则主进程结束时,子进程会随之结束。
方法:
- start():启动子进程实例。
- join(timeout):等待子进程执行结束。timeout是可选的超时时间。
- is_alive():判断进程是否还存活。
- terminate():立即终止子进程。
- run():进程启动时运行的方法,正是它调用的target指定的函数。
3、进程间的通信与同步
通信:
- 队列(Queue):multiprocessing.Queue提供了一个线程和进程安全的队列,用于在多个进程之间传递数据。
- 管道(Pipe):multiprocessing.Pipe返回一个连接的对象(Connection),用于两个进程之间的双向通信。
同步:
- 锁(Lock):multiprocessing.Lock提供了进程间的同步机制,防止多个进程同时访问共享资源。
- 事件(Event):multiprocessing.Event用于允许多个进程或线程等待某个事件的发生。
- 条件变量(Condition):multiprocessing.Condition允许一个或多个进程等待某个条件为真再继续执行。
- 信号量(Semaphore):multiprocessing.Semaphore是计数信号量,用于控制对共享资源的访问数量。
4、进程池(Pool)
当需要创建大量的子进程时,可以使用Pool来管理这些进程。Pool允许用户指定一个最大进程数,并自动管理这些进程的创建和销毁。
创建Pool:
- 使用Pool(processes=None, initializer=None, initargs=(), maxtasksperchild=None)来创建一个进程池。
- processes参数指定池中进程的最大数量。
- initializer和initargs参数用于在每个工作进程启动时执行一个指定的函数及其参数。
- maxtasksperchild参数指定每个工作进程可以执行的任务数,之后工作进程会被替换。
使用Pool:
- apply(func[, args[, kwds]]):同步执行一个函数,并等待结果。
- map(func, iterable[, chunksize]):将一个可迭代对象中的每个元素传递给一个函数,并收集结果。
- async_apply(func[, args[, kwds]]):异步执行一个函数。
- async_map(func, iterable[, chunksize]):异步地将一个可迭代对象中的每个元素传递给一个函数。
5、示例代码
以下是一个简单的示例代码,展示了如何使用multiprocessing模块创建进程、传递参数、等待进程结束以及使用队列进行进程间通信:
import multiprocessing
import time
def worker(interval, name, queue):
print(f"{name}【start】")
time.sleep(interval)
print(f"{name}【end】")
queue.put(f"{name}的结果")
if __name__ == "__main__":
queue = multiprocessing.Queue()
p1 = multiprocessing.Process(target=worker, args=(2, '两点水1', queue))
p2 = multiprocessing.Process(target=worker, args=(3, '两点水2', queue))
p3 = multiprocessing.Process(target=worker, args=(4, '两点水3', queue))
p1.start()
p2.start()
p3.start()
for p in [p1, p2, p3]:
p.join()
while not queue.empty():
print(queue.get())
以上代码创建了三个子进程,每个子进程执行worker函数,并将结果放入队列中。主进程等待所有子进程执行完毕后,从队列中读取并打印结果。
三、协程
协程又称微线程,是一种用户态的轻量级线程,其切换是由用户程序自己控制调度的,而非操作系统。因此,协程的切换开销非常小,适用于I/O密集型任务。协程能够保留上次调用时的状态,对协程来说程序员就是他的上帝,想让他执行到哪里就执行到哪里。
协程存在的意义:
- 开销:协程的切换开销非常小,因为它们是由程序员显式控制的,并且不涉及内核态与用户态之间的切换。相比之下,线程和进程的切换开销较大。
- 并发性:协程可以实现高效的并发执行,因为它们可以在单个线程内交替运行。然而,由于GIL(全局解释器锁)的存在,Python中的多线程在CPU密集型任务上可能并不会带来性能上的提升。对于I/O密集型任务,多线程和协程都可以显著提高程序的并发性和性能。多进程则不受GIL的限制,但进程间通信和同步的开销较大。
- 编程模型:协程的编程模型更加直观和易于理解,因为它们遵循了同步编程的直观逻辑,但具有异步执行的能力。多线程和多进程的编程模型则更加复杂,因为它们需要处理线程或进程间的同步和通信问题。
1、greenlet创建协程:
greenlet 是 Python 中一个轻量级的协程库,它提供了一种在单个线程中协作执行多个函数的方式。与更高级的协程库(如 asyncio)相比,greenlet 更加底层和简单,主要提供了基本的上下文切换功能。手动切换协程
greenlet 的核心是两个类:greenlet.greenlet 和 greenlet.getcurrent()。greenlet.greenlet 用于创建新的 greenlet,而 greenlet.getcurrent() 用于获取当前正在执行的 greenlet。
from greenlet import greenlet
def a():
print("男孩:我想对你说三个字")
g2.switch() # 手动切换协程
print("男孩:我饿了")
g2.switch() # 手动切换协程
def b():
print("女孩:我也爱你")
g1.switch() # 手动切换协程
print("女孩:好吧。。。")
g1 = greenlet(a) # 创建协程g1
g2 = greenlet(b) # 创建协程g2
g1.switch() # 启动协程
# 输出
男孩:我想对你说三个字
女孩:我也爱你
男孩:我饿了
女孩:好吧。。。
注意事项
- 手动切换:与 asyncio 不同,greenlet 不会自动管理协程的调度。你需要显式地调用 switch() 方法来切换 greenlet。
- 异常处理:在一个 greenlet 中抛出的异常不会自动传播到另一个 greenlet。你需要在每个 greenlet 内部处理异常。
- 死锁:如果两个 greenlet 互相等待对方切换,会导致死锁。因此,设计 greenlet 时需要小心避免这种情况。
- 虽然 greenlet 提供了基本的协程功能,但它通常不直接用于生产环境。更高级的库(如 gevent 和 asyncio)提供了更丰富的功能和更好的错误处理机制。gevent 是基于 greenlet 的一个高级库,它添加了事件循环和网络 I/O 的支持。
2、gevent创建协程:
Gevent是基于libev与greenlet实现的。其中,libev是一个高性能的事件循环库,greenlet是Python中的一个轻量级协程库。Gevent通过结合这两个库,实现了自动切换协程
的功能,从而提高了程序的执行效率。
1、基本使用:
- 创建协程: 使用gevent.spawn()函数可以创建一个协程。该函数接受一个函数作为参数,并可以传递任意数量的位置参数和关键字参数。创建协程后,它将立即开始执行。
- 启动: 使用join()方法可以等待协程完成。调用join()方法将阻塞当前线程,直到协程执行完毕。
- 获取返回值: 协程执行完毕后,可以使用value属性来获取其返回值(如果有的话)。
- 优势: Gevent的主要优势在于它能够自动切换协程,特别是在遇到IO操作时。当协程遇到IO阻塞时(如网络请求、文件读写等),Gevent会自动切换到其他协程,从而充分利用CPU资源。
import gevent
def func(name):
for i in range(3):
print(name,"打印:",i)
gevent.sleep(1) # 模拟io耗时操作
return "返参:"+name
g1 = gevent.spawn(func,"协程g1") # 创建协程g1
g2 = gevent.spawn(func,"协程g2") # 创建协程g2
g1.join() # 启动协程g1
g1.join() # 启动协程g2
print("g1执行最终返回结果:",g1.value) # 打印协程 g1 的返参
print("g2执行最终返回结果:",g2.value) # 打印协程 g1 的返参
# 执行结果
协程g1 打印: 0
协程g2 打印: 0
协程g1 打印: 1
协程g2 打印: 1
协程g1 打印: 2
协程g2 打印: 2
g1执行最终返回结果: 返参:协程g1
g2执行最终返回结果: 返参:协程g2
2、joinall 的使用
如果一次启动多个协程可以使用gevent.greenlet.joinall函数,入参为列表,元素为创建的协程,用法如下
import gevent
from gevent.greenlet import joinall
def func(name):
for i in range(3):
print(name,"打印:",i)
gevent.sleep(1) # 模拟io耗时操作
return "返参:"+name
g1 = gevent.spawn(func,"协程g1") # 创建协程g1
g2 = gevent.spawn(func,"协程g2") # 创建协程g2
joinall([g1,g2]) # 一次启动多个协程
3、 给程序打补丁 ( monkey)
为了使Gevent能够识别并处理程序中的IO阻塞操作,你需要使用monkey.patch_all()
函数来给程序打补丁。这个函数会替换Python标准库中的一些模块(如socket、ssl、time等),使它们支持Gevent的协程切换。
import gevent
from gevent import monkey
import time
monkey.patch_all() # 打补丁
def func(name):
for i in range(3):
print(name,"打印:",i)
time.sleep(1) # 这里使用的是time.sleep(),但由于打了补丁,所以也会被Gevent识别并处理
return "返参:"+name
g1 = gevent.spawn(func,"协程g1") # 创建协程g1
g2 = gevent.spawn(func,"协程g2") # 创建协程g2
g1.join() # 启动协程g1
g1.join() # 启动协程g2
print("g1执行最终返回结果:",g1.value) # 打印协程 g1 的返参
print("g2执行最终返回结果:",g2.value) # 打印协程 g1 的返参
# 输出
协程g1 打印: 0
协程g2 打印: 0
协程g1 打印: 1
协程g2 打印: 1
协程g1 打印: 2
协程g2 打印: 2
g1执行最终返回结果: 返参:协程g1
g2执行最终返回结果: 返参:协程g2
# 如果没有 monkey.patch_all() # 打补丁 输出的结果为:
协程g1 打印: 0
协程g1 打印: 1
协程g1 打印: 2
协程g2 打印: 0
协程g2 打印: 1
协程g2 打印: 2
g1执行最终返回结果: 返参:协程g1
g2执行最终返回结果: 返参:协程g2
3、 高级用法
- 使用锁: 在协程中,你可能需要使用锁来保护共享资源。Gevent提供了多种锁机制,如Semaphore(信号量)、Lock(互斥锁)等。
import gevent
from gevent import lock
from gevent.greenlet import joinall
rlock = lock.RLock()
def func(name):
with rlock:
for i in range(3):
print(name,"打印:",i)
gevent.sleep(1) # 这里使用的是time.sleep(),但由于打了补丁,所以也会被Gevent识别并处理
return "返参:"+name
g1 = gevent.spawn(func,"协程g1") # 创建协程g1
g2 = gevent.spawn(func,"协程g2") # 创建协程g2
joinall([g1,g2])
print("g1执行最终返回结果:",g1.value) # 打印协程 g1 的返参
print("g2执行最终返回结果:",g2.value) # 打印协程 g1 的返参
# 输出
协程g1 打印: 0
协程g1 打印: 1
协程g1 打印: 2
协程g2 打印: 0
协程g2 打印: 1
协程g2 打印: 2
g1执行最终返回结果: 返参:协程g1
g2执行最终返回结果: 返参:协程g2
- 使用队列: Gevent还提供了队列机制,用于在协程之间传递数据。你可以使用gevent.queue.Queue来创建一个线程安全的队列。
from gevent.queue import Queue
import gevent
q = Queue() # 创建一个队列
def producer():
for i in range(5):
q.put(i)
print(f'生产一个 {i}')
gevent.sleep(1)
def consumer():
for i in range(5):
item = q.get()
print(f'消费一个 {item}')
gevent.joinall([gevent.spawn(producer), gevent.spawn(consumer)])
# 输出
生产一个 0
消费一个 0
生产一个 1
消费一个 1
生产一个 2
消费一个 2
生产一个 3
消费一个 3
生产一个 4
消费一个 4
- 网络编程: Gevent非常适合用于网络编程。你可以使用它来创建高性能的TCP/UDP服务器和客户端。
from gevent import monkey; monkey.patch_all()
from socket import *
import gevent
def server(server_ip, port):
s = socket(AF_INET, SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind((server_ip, port))
s.listen(5)
while True:
conn, addr = s.accept()
gevent.spawn(talk, conn, addr)
def talk(conn, addr):
try:
while True:
res = conn.recv(1024)
print(f'Client {addr[0]}:{addr[1]} msg: {res}')
conn.send(res.upper())
except Exception as e:
print(e)
finally:
conn.close()
if __name__ == '__main__':
server('127.0.0.1', 8080)
上面的例子展示了如何使用Gevent创建一个简单的TCP服务器。当服务器接受到客户端的连接时,它会创建一个新的协程来处理该连接。
3、asyncio创建协程:
asyncio是Python标准库中的一个模块,提供了使用协程构建并发应用的工具。它使用一种单线程单进程的方式实现并发,应用的各个部分彼此合作,可以显式地切换任务。这一般会在程序阻塞I/O操作的时候发生上下文切换,如等待读写文件或请求网络。同时,asyncio也支持调度代码在将来的某个特定事件运行,从而支持一个协程等待另一个协程完成,以处理系统信号和识别其他一些事件。
3.1、协程的定义与运行
- 定义协程:协程的定义需要使用async def语句。例如,async def do_some_work(x): pass,这里的do_some_work便是一个协程函数。准确来说,do_some_work是一个协程函数,可以通过asyncio.iscoroutinefunction来验证。
- 运行协程:调用协程函数,协程并不会开始运行,只是返回一个协程对象。要让这个协程对象运行,有两种方式:
- 在另一个已经运行的协程中用await等待它。
- 通过ensure_future函数计划它的执行。
可以通过asyncio.get_event_loop()获取当前线程的事件循环,然后将协程对象交给loop.run_until_complete(),协程对象随后会在loop里得到运行。例如:
import asyncio
async def do_some_work(x):
print("Waiting " + str(x))
await asyncio.sleep(x)
loop = asyncio.get_event_loop()
loop.run_until_complete(do_some_work(3))
这段代码会输出“Waiting 3”,然后等待3秒钟后程序结束。
3.2、 await关键字与异步操作
在协程中,可以使用await关键字来等待其他异步函数或某个任务完成。await只能用在async定义的函数内部。例如,await asyncio.sleep(x)就是等待x秒。
3.3、 事件循环
事件循环是asyncio提供的“中央处理设备”,它负责注册、执行和取消延迟调用(超时),创建可用于多种类型的通信的服务端和客户端的Transports,启动进程以及相关的和外部通信程序的Transports,将耗时函数调用委托给一个线程池等。
事件循环是一种处理多并发量的有效方式。在asyncio中,程序会开启一个无限的循环,把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
3.4、 Future与Task
- Future:是一个数据结构,表示还未完成的工作结果。事件循环可以监视Future对象是否完成,从而允许应用的一部分等待另一部分完成一些工作。
- Task:是Future的一个子类,它知道如何包装和管理一个协程的执行。任务所需的资源可用时,事件循环会调度任务运行,并生成一个结果,从而可以由其他协程消费。
3.5、多个协程的并发执行
实际项目中,往往有多个协程需要同时在一个loop里运行。为了把多个协程交给loop,需要借助asyncio.gather函数。例如:
import asyncio
import random
async def async_task(name):
print(f"{name}开始工作...")
await asyncio.sleep(random.uniform(1, 3))
print(f"{name}工作完成!")
async def main():
tasks = []
for i in range(5):
task_name = f"任务{i + 1}"
tasks.append(async_task(task_name))
await asyncio.gather(*tasks)
asyncio.run(main())
这段代码创建了5个异步任务,并使用asyncio.gather来并发执行所有任务,直到所有任务完成。
3.6、 回调函数
假如协程是一个IO的读操作,等它读完数据后,希望得到通知以便下一步数据的处理。这一需求可以通过往Future添加回调来实现。例如:
def done_callback(futu):
print('Done')
futu = asyncio.ensure_future(do_some_work(3))
futu.add_done_callback(done_callback)
loop.run_until_complete(futu)
3.7、 asyncio的其他功能
除了上述基本功能外,asyncio还提供了许多其他功能,如:
- 异步上下文管理器:可以使用async with语句来管理异步资源的上下文。
- 信号处理:可以使用asyncio.signal_handler来添加信号处理函数。
- 子进程:可以使用asyncio.create_subprocess_exec等函数来创建和管理子进程。
3.8、 注意事项
- 确保在使用await时调用的是异步函数。
- 使用asyncio.sleep()来模拟等待时间,而不是使用普通的time.sleep(),因为后者会阻塞事件循环。
- 根据实际需要调整并发任务的数量和类型,以优化性能。
四、进程-线程-协程,之前的异同
1、相同点:
- 都是并发编程的实现方式:线程、进程和协程都可以用来实现程序的并发执行,提高程序的运行效率。
- 都可以共享资源(在一定程度上):进程间可以通过特定的通信方式进行数据交换,线程共享同一进程的内存空间,而协程则共享同一线程的资源。
2、不同点:
定义与资源拥有:
-
进程
- 定义:进程是操作系统资源分配的基本单位,每个进程都拥有独立的内存空间和系统资源。
- 资源拥有:进程拥有独立的内存空间、数据栈等系统资源。
-
线程
- 定义:线程是操作系统中调度和执行的基本单位,被包含在进程之中。一个进程中可以包含一个或多个线程,所有线程共享该进程的资源。
- 资源拥有:线程不拥有独立的系统资源,但拥有自己的执行堆栈和程序计数器。线程共享进程的内存空间和系统资源。
-
协程
- 定义:协程是一种用户态的轻量级线程,其调度完全由用户控制。协程拥有自己的寄存器上下文和栈。
- 资源拥有:协程不拥有独立的系统资源,只拥有在运行中必不可少的资源(如寄存器上下文和栈),并共享所属线程的资源。
执行与调度:
-
进程
- 执行:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动。
- 调度:进程的创建、销毁和调度开销相对较大,因为它们拥有独立的内存空间和系统资源。
-
线程
- 执行:线程是进程内的一个执行单元,可以并发执行。
- 调度:线程的调度开销较小,因为线程共享同一进程的内存空间和系统资源。线程是处理器调度的基本单位。
-
协程
- 执行:协程在单一线程内部实现并发,不存在多线程中常见的数据竞争等线程同步问题。当协程遇到I/O操作时,它会将控制权让给其他协程,直到I/O操作完成。
- 调度:协程的调度完全由用户控制,没有线程切换的开销。协程的切换由程序自身控制,因此切换速度非常快。
适用场景与性能
- 进程
- 适用场景:多进程一般使用在CPU密集型的程序上,因为进程拥有独立的内存空间和系统资源,可以充分利用多核CPU的并行计算能力。
- 性能:进程间的通信和同步相对复杂,开销较大。
- 线程
- 适用场景:多线程适合用于既有计算密集型任务,又有I/O密集型任务的场景。多线程可以完成一些I/O密集型并发操作,如文件读写、网络请求等。
- 性能:多线程的优势是切换快、资源消耗低。但由于存在数据共享,数据同步(如通过锁)会是多线程编程的一个常见问题。此外,由于Python全局解释器锁(GIL)的存在,多线程在执行CPU密集型任务时性能得不到明显提升。
- 协程
- 适用场景:协程适合用于I/O密集型任务。在遇到I/O操作时,协程会将控制权让给其他协程,从而充分利用等待时间。
- 性能:协程的执行效率极高,因为子程序切换不是线程切换,而是由程序自身控制。此外,协程不需要多线程的锁机制,因为只有一个线程在执行。这使得协程在I/O密集型任务上具有显著的性能优势。