https://blog.youkuaiyun.com/Aria_Miazzy/article/details/149385816
Python常用模块
- 运行时服务相关模块: copy / pickle / sys / ...
- 数学相关模块: decimal / math / random / ...
- 字符串处理模块: codecs / re / ...
- 文件处理相关模块: shutil / gzip / ...
- 操作系统服务相关模块: datetime / os / time / logging / io / ...
- 进程和线程相关模块: multiprocessing / threading / queue
- 网络应用相关模块: ftplib / http / smtplib / urllib / ...
- Web编程相关模块: cgi / webbrowser
- 数据处理和编码模块: base64 / csv / html.parser / json / xml / ...
一、基础知识
1、数据类型
1、常见的数据类型有:
* Number(数字):int、float、bool(0|1)、complex(复数)
* String(字符串):用单引号 ' 或双引号 " 括起来,同时使用反斜杠 \ 转义特殊字符。
* Bool(布尔):True、False
* List(列表):列表中元素的类型可以不相同,它支持数字,字符串甚至可以包含列表(所谓嵌套),使用中括号 [] 里,元素之间用逗号隔开。
* Tuple(元组):与列表类似,不同之处在于元组的元素不能修改,使用小括号 () 里,元素之间用逗号隔开。
* Set(集合): 是一种无序、可变的数据类型,用于存储唯一的元素,使用大括号 {} 表示,元素之间用逗号分隔。
* Dictionary(字典): 是一种映射类型,元素是通过键来存取的,而不是通过偏移存取是一种映射类型,使用{ }标识,它是一个无序的 键(key) : 值(value) 的集合。
* Byte(字节数组类型):是不可变的二进制序列(byte sequence),元素是整数值(0 到 255 之间的整数),而不是 Unicode 字符。
2、 Python3 有六个标准的数据类型:
* 不可变数据(Immutable,变量重赋值等于新建):Number(数字)、String(字符串)、Tuple(元组)
* 可变数据(Changeable,变量重赋值等于替换):List(列表)、Dictionary(字典)、Set(集合)、Bytes(字节数组)
3、Python3 中,所有非零的数字和非空的字符串、列表、元组等数据类型都被视为 True,
只有False、None、0(0、0.0、0j)、""、[]、{}、()、set() 等被视为 False
4、三目运算符是可以嵌套, 但需要注意 if 和 else 的配对使用,
形式为 x if 条件表达式 else y,先判断条件表达式真假,真则取x的值,否则取y的值。
例如:
a if a > b else c if c>d else d # 这种不好阅读,不建议这样写
a if a > b else ( c if c>d else d ) # 不建议
a if (a > b and a > c) else ( d if d > e else f ) # 不建议
3 if 4>3 else 4 值为3
3 if 4<3 else 4 值为4
2、运算符
* 算术运算符:+ 、-、*、/、%、//、**
* 比较(关系)运算符:==、!=、>、<、>=、<=
* 赋值运算符: =、+=、-=、*=、/=、%=、//=、**=、:= (海象运算符,Python3.8 版本新增运算符)
* 逻辑运算符: and、or、not(逻辑与、逻辑或、逻辑非)
* 位运算符: &、|、^、~、<<、>>
* 成员运算符: in、not in
* 身份运算符: is、is not(True, False, None)
* 条件运算符: 语句1 if 条件表达式 else 语句2
对浮点数做数值运算,结果也是浮点数
- 算术运算符:
a = 10 + 5 # 两个字符串相加,是将两个字符串进行 拼接
a = 10 - 5
a = 10 * 5 # 字符串 和 数字 相乘,是对字符串做复制操作,将字符串重复指定次数
a = 10 / 5
a = 10 // 3 # 只保留整数位
a = 10 ** 2 # 幂运算 几次方
a = 16 ** 0.5 # 16的平方根
- 赋值运算符:
a = 10
a += 5 <==> a = a + 5
a -= 5 <==> a = a - 5
a *= 5 <==> a = a * 5
a /= 5 <==> a = a / 5
a //= 5 <==> a = a // 5
a **= 5 <==> a = a ** 5
- 关系运算符:
* 关系成立返回True, 否则返回False
* 当两个字符串进行比较时,实际上比较的是字符串的unicode编码
* 比较unicode编码时,是逐位比较的
- 逻辑运算符:短路运算符
* not: 若not右边是布尔值,则加not直接进行取反。若右边是非布尔值,则会先将其转换为布尔值再取反
* and: 符号两侧的值都为 True 时,返回 True ; 只要有一个 False ,则返回 False
|-- and 是短路与运算符,是找 False
|--当第一个值为False,则返回第一个值,否则返回第二个值
* or: 符号两侧的值都为 False 时,返回 False ; 只要有一个 True ,则返回 True
|-- or 是短路逻辑或运算符,是找True
|-- 当第一个值为True,则返回第一个值,否则返回第二个值
* and/or/not 在处理非布尔值时,python会将其当成布尔值进行运算,最终再返回原值
a = 1 # 非布尔值,转换成布尔值是True
a= not a # False
b = "" # 非布尔值,转换成布尔值是False
b = not b # True
passwd = "" # False
if not passwd: # 如果是not False==》True, 则进入if条件
print("bbbbb")
# 逻辑与:找False
True and print("猜猜我会进来吗?") # 会打印print语句
False and print("猜猜我会进来吗?") # 不会打印print语句
result = 1 and 2 # True and True 返回第二个数 2,因为没找到False,只能返回第二个数了
result = 1 and 0 # True and False 返回False的 0
result = 0 and 2 # False and True 返回False的 0
result = "" and 2 # False and True 返回False的 ""
result = 0 and None # False and False 返回第一个False的 0
# 逻辑或: 找True
True or print("猜猜我会进来吗?") # 不会打印print语句
False or print("猜猜我会进来吗?") # 会打印print语句
result = 1 or 2 # True and True 返回第一个数 1
result = 1 and 0 # True and False 返回True的 1
result = 0 and 2 # False and True 返回True的 2
result = "" and 2 # False and True 返回True的 2
result = 0 and None # False and False 返回第二个False的 None,因为没有找到True,只能返回第二个数
- 三目运算符:
先对条件表达式进行求值判断,若结果为True, 则执行语句1。若结果为False, 则执行语句2
3、变量存储
变量中存储的不是对象的值,而是对象的id(内存地址),对象并没有直接存储到变量中。
- 当使用变量时,实际上就是通过对象id查找对象
- 变量中保存的对象,只有在为变量重新赋值时才会改变
- 变量和变量之间相互独立的,修改一个变量不会影响另一个变量

4、is 和 ==
* is 是判断两个标识符是不是引用自一个对象.类似 id(x) == id(y) , 如果引用的是同一个对象则返回 True,否则返回 False
* is not 是判断两个标识符是不是引用自不同对象. 类似 id(x) != id(y)。如果引用的不是同一个对象则返回结果 True,否则返回 False。
Python 中 id() 函数用于获取对象内存地址
* is 和 == 的区别:
is 判断两个变量是否是引用同一个内存地址
== 调用的是__eq__()方法判断两个变量是否相等,所以 a==b 等效于a.__eq__(b)
5、参数类型
* 描述:函数参数是函数定义中括号内的变量,在函数调用时传递给函数的值,在Python中函数的参数可分为以下几种类型:对象必需参数、关键字参数、默认参数、不定长参数、以及强制位置参数。
* 位置(必需)参数:须以正确的顺序传入函数,否则出现语法错误。例如 func(arg1,arg2)
* 关键字参数:使用关键字参数来确定传入的参数值,并且可以改变参数的顺序,关键字参数必须写在位置参数后面。例如,func(arg1,arg2),调用 func(arg1="全栈工程师修炼指南",arg2="Python")
* 默认参数:调用函数时,如果没有传递参数,则会使用默认设定的参数值。例如,func(arg1,arg2="python"),
* 不定长参数:传递参数可变可以是0或任意个,加了一个星号 * 的参数会以元组(tuple)的形式导入,加了两个星号 ** 的参数会以字典的形式导入。例如,func(*args, **kwargs )
6、推导式
"""列表生成式"""
"""
1、if 在for后面,则没有else
2、if 在for前面,则必须要加else
"""
list_d = [x for x in range(10) if x % 2 == 0]
list_e = [x if x % 2 == 0 else -x for x in range(10)]
print("列表生成式list_d", list_d) # [0, 2, 4, 6, 8]
print("列表生成式list_e", list_e) # [0, -1, 2, -3, 4, -5, 6, -7, 8, -9]
* 列表生成式:
基础型:[表达式 for 变量 in 列表]
[x**2 for x in range(10)]
筛选型:[表达式 for 变量 in 列表 if 条件]
[x for x in range(30) if x % 5 == 0]
筛选型:[表达式 for 变量 in 列表 if 条件...if 条件...]
[(x,y) for x in range(10) for y in range(10) if x % 2 == 0 if y % 2 != 0]
条件型:[结果值1 if 判断条件 else 结果值2 for 变量名 in 原列表 ]
list1 = ['python', 'go', 'java']
list2 = [ word.title() if word.startswith('p') else word.upper() for word in list1 ]
* 元组推导式:
( 表达式 for 变量 in Sequence )
(x for x in range(1,10))
( 表达式 for 变量 in Sequence if 条件)
* 集合推导式:
( 表达式 for 变量 in Sequence )
( 表达式 for 变量 in Sequence if 条件)
* 字典推导式:
{ key_expr: value_expr for 变量 in collection }
# 或者
{ key_expr: value_expr for 变量 in collection if 条件 }
7、any和 all内置函数
用于可迭代对象(列表、集合、元组、字典、字符串等)中的元素执行布尔运算
# any:
* 1、any()函数用于判断:给定的可迭代对象是否至少有一个元素为True。如果可迭代对象为空,返回False
* 2、any()函数在遍历可迭代对象时,只要找到一个True值就立即返回True,不再继续遍历剩余的元素
print(any([0, False, None])) # 输出False
print(any([0, 1, False])) # 输出True
print(any([])) # 输出False
print(any([""])) # 输出False,空字符串在布尔上下文中被视为False
# all:
* 1、all()函数用于判断给定的可迭代对象中的所有元素是否都为True。若可迭代对象为空,则返回True
* 2、all()函数在遍历可迭代对象时,只要找到一个False值就立即返回False,不再继续遍历剩余元素
print(all([1, 2, 3])) # 输出True
print(all([0, 1, 2])) # 输出False
print(all([])) # 输出True, 空集合被认为是“所有条件都满足”
print(all([1, 0, False])) # 输出False
8、类型转换
- int(): 将其他对象转换为整型
* 布尔值: True -> 1 False -> 0
* 浮点数: 取整,省略小数点后面的内容
* 字符串: 合法整数字符串,直接转换为对应数字。不合法的整数字符串,则报错。如: a= int("11.6")
* 对于其他不能转换为整型的对象,则直接抛出异常。如: a = int(None)
- float(): 将其他对象转换为浮点型
* 布尔值: True -> 1.0 False -> 0.0
* 字符串: 合法小数字符串,直接转换为对应数字。不合法的小数字符串,则报错。a = float('abc')
* 对于其他不能转换为浮点型型的对象,则直接抛出异常。如: a = float(None)
- str(): 将其他对象转换为字符串
- bool(): 将其他对象转换为布尔值
* 对所有表现空性的对象,都会转换为 False。其他的转换为 True
* 空性: 0(0.0, 0f), None, '', "", [], {}, {}(集合)
9、type()、isinstance()、issubclass()
- type() 函数:是用于求一个未知数据类型对象,不考虑继承关系
- isinstance() 函数:是用于判断一个对象是否是已知类型,考虑继承关系。
例如:isinstance(obj, int) 仅会在 obj.__class__ 为 int 或某个派生自 int 的类时为 True。
- issubclass() 来检查类的继承关系
例如: issubclass(bool, int) 为 True,因为 bool 是 int 的子类。 但是,issubclass(float, int) 为 False,因为 float 不是 int 的子类。
10、流程控制语句
1、if 语句
# 当条件表达式为 True 时,执行下面的代码块
if 条件表达式:
代码块
if 条件表达式1:
代码块
elif 条件表达式2:
代码块
...
else:
代码块
* 如果条件表达式为 True, 则执行当前代码块, 然后语句结束
* 如果条件表达式为 False,则继续向下执行,知道找到True的表达式,然后执行代码块,最后退出
* 如果所有的表达式都是False, 则执行 else 中的代码块
2、while语句
while 条件表达式:
代码块
else:
代码块
for i in 条件表达式:
代码块
else:
代码块
* 如果条件表达式为True,则执行循环体代码块,直到条件表达式为False,则结束循环
* 当循环正常完成所有迭代,则else语句才会执行。若循环被break打断,则else子句不会执行
* 三要素:初始化变量、条件表达式、更新表达式
4、循环嵌套
* 外层循环控制图形高度
* 内层循环控制图形长度
* 外层循环每执行一次,内层循环就要执行一圈
def prac_15():
for i in range(5):
for j in range(6):
print('*', end=" ")
# 每行执行完换行,print默认有换行符,不然就30个*号连成一行,每执行一轮内层循环就用print换行
print()
* * * * * *
* * * * * *
* * * * * *
* * * * * *
* * * * * *
def prac_15_1():
i = 0
while i < 5:
j = 0
while j < 10:
print("*", end=" ")
j += 1
i += 1
print() # 每行执行完换行,print默认有换行符,每执行一轮内层循环就用print换行
* * * * * * * * * *
* * * * * * * * * *
* * * * * * * * * *
* * * * * * * * * *
* * * * * * * * * *
# 九九乘法表
"""
1 * 1 = 1
1 * 2 = 2 2 * 2 = 4
1 * 3 = 3 2 * 3 = 6 3 * 3 = 9
1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16
1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 5 * 5 = 25
1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 5 * 6 = 30 6 * 6 = 36
1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 5 * 7 = 35 6 * 7 = 42 7 * 7 = 49
1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 5 * 8 = 40 6 * 8 = 48 7 * 8 = 56 8 * 8 = 64
1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36 5 * 9 = 45 6 * 9 = 54 7 * 9 = 63 8 * 9 = 72 9 * 9 = 81
"""
def prac_16():
for i in range(1, 10):
for j in range(1, 10):
if i >= j:
print(f"{j} x {i} = %2d" % (i*j), end=" ")
print()
def prac_17():
i = 1
while i < 10:
j = 1
while j < 10:
if j <= i:
print(f"{j} * {i} =", j * i, end="\t")
j += 1
i += 1
print()
"""
*
* *
* * *
* * * *
* * * * *
"""
def func1():
i = 0
while i < 5:
j = 0
while j < i+1:
print('*', end=" ")
j += 1
i += 1
print()
"""
*
* *
* * *
* * * *
* * * * *
"""
def func2():
for i in range(5):
for j in range(i + 1):
print('*', end=" ")
print()
"""
* * * * *
* * * *
* * *
* *
*
"""
def func3():
i = 0
while i < 5:
j = 0
while j < 5-i:
print('*', end=' ')
j += 1
i += 1
print()
"""
* * * * *
* * * *
* * *
* *
*
"""
def func4():
for i in range(5):
for j in range(5 - i):
print('*', end=" ")
print()
"""
* * * * *
* * * *
* * *
* *
*
0 0 1 2 3 4
1 0 1 2 3 4
2 0 1 2 3 4
3 0 1 2 3 4
4 0 1 2 3 4
"""
def func5():
i = 0
while i < 5:
j = 0
while j < 5:
if j >= i: # j < i
print('*', end=" ")
else:
print(' ', end=" ")
j += 1
i += 1
print()
def func5_1(row):
i = 0
while i < row:
j = 0
while j < row:
if i > j:
print(" ", end=" ")
else:
print("*", end=" ")
j += 1
i += 1
print()
"""
*
* *
* * *
* * * *
* * * * *
0 0<5-0-1=4 1<5-0-1 2<5-0-1 3<5-0-1 4<5-0-1
1 0<5-1-1=3 1<5-1-1 2<5-1-1 3<5-1-1 4<5-1-1
2 0<5-2-1=2 1<5-2-1 2<5-2-1 3<5-2-1 4<5-2-1
3 0<5-3-1=1 1<5-3-1 2<5-3-1 3<5-3-1 4<5-3-1
4 0<5-4-1=0 1<5-4-1 2<5-4-1 3<5-4-1 4<5-4-1
"""
def func6():
i = 0
while i < 5:
j = 0
while j < 5:
if j < 5-i-1:
print(' ', end=" ")
else:
print('*', end=" ")
j += 1
i += 1
print()
11、列表 list
# 序列通用操作:
# 所有的序列操作,跟索引有关,适用于所有序列
unpack() #解包
max()
min()
len()
sort()
add_list.count()
add_list.index()
'Bob' in name_list
'zkc' not in name_list
add_list[0]
a = "zkc"
a[0]
name_list = ['Bob', 'Jack', 'Lisa', 'Jerry']
add_list = ['zhangsan', 'lisi']
# + 和 *
fruits_1 = add_list + ['pick'] # 列表相加,生成新列表 ['zhangsan', 'lisi', 'pick']
fruits_2 = ['zkc'] * 3 # 生成 ['zkc', 'zkc', 'zkc']
print("测试列表的+", fruits_1)
print("测试列表的*", fruits_2)
# 查询
print(name_list[0])
print(name_list[-1])
# 增加
name_list.append('Tom') # 一次性向列表末尾添加
name_list.insert(2, 'Rose') # 向指定位置添加
name_list.extend(add_list) # 将可迭代对象中元素依次添加进列表
# 修改
name_list[2] = 'Herry'
# 删除
del name_list[1]
del name_list[:2] # 可以根据切片删除
user_name = name_list.pop() # 默认弹出列表末尾元素,删除的元素还可以访问
user_name = name_list.pop(2) # 默认弹出指定位置元素,删除的元素还可以访问
name_list.remove('Jack') # 根据元素删除
# 切片--遵循 左闭右开 原则
my_list_1 = name_list[:3] # ['Bob', 'Jack', 'Lisa']
my_list_2 = name_list[2:] # ['Jack', 'Lisa', 'Jerry']
my_list_3 = name_list[::-1] # ['Jerry', 'Lisa', 'Jack','Bob'] 负步长==列表倒序
my_list_4 = name_list[::2] # 步长为2 ['Bob', 'Lisa']
my_list_5 = name_list[1::2] # 步长为2 ['Jack', 'Jerry']
my_list_6 = name_list[::] # 全列表的分片,相当于创建了一个原列表的副本,全列表复制
12、元组 tuple
不可变序列, 也叫不可变列表,分片、访问等操作跟列表的一样。
元组和列表的区别:
除了 元组是不可变序列,不能赋值和添加,列表是可变序列,其他所有的操作都和列表一样
# 创建元组
my_tuple = ()
my_tuple1 = tuple()
my_tuple2 = ('name1', 'name2', 'name3')
my_tuple3 = 'name1', 'name2', 'name3' # 当确定元组中有数据时,也可以省略()
my_tuple4 = ('name1', ) # 元组中至少要有一个 ,
my_tuple5 = 'name1', # 元组中至少要有一个 ,
13、解包(解构)
* 解包:将序列中的每一个元素都赋值给一个变量
- 在解包时,"变量的数量" 必须和 **序列中** "元素的数量" 保持一致
- 在变量前添加 "*", 表示该变量 会获取 序列 中剩余的所有元素
- 不能同时出现两个 *
# 一、元组的解包
my_tuple = 10, 20, 30, 40
a, b, c, d = my_tuple # 将元组中的元素都赋值给一个变量
# 解包时,不需要的元素可以用 *变量名 来接收获取。
a, b, *c = my_tuple
print(a, b, c) # 10 20 [30, 40]
a, *b, c = my_tuple
print(a, b, c) # 10 [20, 30] 40
*a, b, c = my_tuple
print(a, b, c) # [10, 20] 30 40
# 例:交换两个变量的值:
# =左边是变量,右边是不带括号的元组
a = 300
b = 100
a, b = b, a # 通过解包,将a的值换成300,b的值换成100
# 二、列表解的包
my_list = [1, 2, 3, 4, 5]
a, b, c, d, e = my_list # 1 2 3 4 5
a, b, *c = my_list # 1 2 [3, 4, 5]
# 三、字符串的解包
my_str = "hello world"
m, n, t* = my_str # h e ['l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
14、字典 dict
# 创建字典
my_dict = dict()
my_dict = dict(name="zkc",hobby="read")
my_dict = {}
my_dict = {"name": "zkc", "hobby": "read", "class": "math"}
new_dict = {"user_id": "1001", "password": "123456", "name": "Jack"}
# 访问字典
my_name = my_dict['name'] # 当key不存在时,报错KeyError
my_name = my_dict.get('name', '') # 指定默认值。当key不存在时,返回默认值
# 修改字典
my_dict['name'] = "zkk" # key存在时,是修改
# 新增item
my_dict['date'] = "2025-07-01" # key不存在时,是新增
my_dict.update(new_dict) # 更新字典,key存在时更新,不存在时添加key-value
my_list = ["name", "username"]
my_dict1 = dict.fromkeys(my_list, "Mary") # 创建所有value相同的字典,第一个参数是可迭代对象, 第二个参数是value
name = my_dict.setdefault("new_name", "umesh") # 添加k-v,如果key存在,则返回value,如果key不存在,则新增k-v,并返回value
# 删除字典
del my_dict["name"] #根据key永久删除
value = my_dict.pop('name', '') # 删除该字典指定的key,并且返回value值. 若key不存在,返回默认值
key, value = my_dict.popitem() # 删除该字典末尾的键值对,并返回key, value组成的元组
# 清除字典
my_dict.clear()
# 遍历字典
for key, value in my_dict.iteams()
for key in my_dict.keys()
for value in my_dict.values()
15、集合 set
集合中只能存储不可变对象,并且元素是无序的、不可重复的。
my_set = set() # 空集合。不能使用{}
my_set = {"name", "hobby", "class"}
# 访问
for elm in my_set:
print(elm ** 2, end=' ')
# 添加元素
my_set.add('gender')
# 更新元素
# 将一个集合中的元素添加到当前集合中
# 参数可以为:序列、字典。字典值使用键值
new_set = set(range(10))
my_set.update(new_set)
my_set.update([1, 2, 3, 4, 5])
my_set.update((10, 20, 30, 40, 50))
my_set.update({1001: "a", 1002: "b"}) # 传递参数为字典时,只添加 key 值到集合中
# 删除元素
my_set.remove('name') # 删除指定元素
value = my_set.pop() # 随机删除并返回集合中的一个元素。不确定编辑器如何排入,所以是随机删除
# 情况集合
my_set.clear()
# 列表转集合
my_list = list(range(5))
my_set1 = set(my_list)
# 字典转集合
my_dict = {'a': 1, 'b': 2, 'c': 3}
my_set2 = set(my_dict)
# 元组转集合
my_tuple = (1, 2, 3, 4, 4,)
my_set3 = set(my_tuple)
# 集合的运算
aa = {1, 2, 3, 4}
bb = {3, 4, 5, 6}
cc = {1, 1, 2, 2, 2, 3, 3, 3, 3}
# 去重
print("去重后效果: ", cc)
# 交集 &
print("交集:", aa & bb) # {3, 4}
# 并集 |
print("并集: ", aa | bb) # {1, 2, 3, 4, 5, 6}
# 差集 -
print("属于aa但不属于bb的: ", aa - bb) # {1, 2}
print("属于bb但不属于aa的: ", bb - aa) # {5, 6}
# 异或集 ^ 获取两个集合中不是公共的部分
print("异或集: ", aa ^ bb) # {1, 2, 5, 6}
# 子集 <= 检查一个集合是否是另一个集合的子集
# 如果集合a的元素全部都在集合b中出现,那么集合a是集合b的子集
a = {1, 2, 3}
b = {1, 2, 3, 4, 5}
c = {1, 2, 3}
result = a <= b # True
result = a <= c # True
result = b <= a # False
# 真子集 < 检查一个集合是否是另一个集合的子集
# 集合b中含有集合a的所有元素,并且集合b中含有集合a中没有的元素,则集合a是集合b的真子集
result = a < b # True
result = a < c # False
二、函数
1、一切皆对象
函数是一个对象,对象是内存中用来存储数据的一块区域
python一切皆对象
- func: 函数对象
- func(): 函数调用
1、python中函数和类可以赋值给一个变量
2、python中函数和类可以存放到集合对象中
3、python中函数和类可以作为一个函数的参数传递给函数
4、python中函数和类可以作为返回值
def func1():
print("func1....")
def func2():
print("func2")
def func3():
print("func3")
def func4(fn_name):
fn_name()
class Cls4:
name = "cls4~~~~"
if __name__ == "__main__":
# 函数对象 赋值 给变量
a = func1
print("函数赋值给变量", a)
# 类对象 赋值 给变量
b = Cls4
print("类赋值给变量", b.name)
# 函数对象 作为 另一个函数 参数
func4(func1)
# 函数对象 放在 list 中
list1 = list()
list1.append(func1)
list1.append(func2)
list1.append(func3)
for fn in list1:
fn()
2、参数类型
在定义函数时,可以定义数量不等的形参

2.1、位置参数和不定长位置参数
1、位置参数是有顺序的,传递实参的顺序和形参定义的顺序是一致的
2、位置参数是要必传的,传递实参的数量和形参定义的数量是一致的
3、不定长位置参数:形参前加 * ,如 *args。将该参数位置所有的实参,封装到一个元组中(先装包-再解包)
4、不定长位置参数和其他参数配合使用,它前面的参数必须用位置参数传递,后面的参数必须用关键字参数传递
因为要告诉python,后面的参数是对应的哪个形参,不然python以为都是不定长参数里的元素
5、带 * 的形参只能有一个
"""
位置参数的数量跟形参数量一致。并且是有顺序的
"""
def func(a, b, c):
print(a, b, c)
func(1, 2, 4) # a是1,b是2,c是4
"""
可变位置参数放在函数参数的第一位时,其他参数必须时关键字参数
*agrs接收 从 当前位置 到 第一个关键字形式参数 之前 的所有参数,装包成 *args
装包: args = (1, 2, 3)
常见的解包:
a, b, c = args
a, *b = args # 将序列中不用的元素,全部用带*好的变量接收,a=1, *b=(2, 3)
在函数内部再进行 解包 或者 循环遍历
"""
def func(*args, name_a, name_b):
print(args) # (1, 2, 3)
m, n, k = *agrs # 解包
print(name_a) # zkc
print(name_b) # zkk
func(1, 2, 3, name_a='zkc', name_b='zkk')
"""
可变位置参数在形参的中间位置时,它当前位置前面的参数按位置参数传递,后面的参数用关键字参数传递
*args 接收 从 当前位置 到 第一个关键字形式参数 之前 的所有参数,装包成 *args
"""
def func2(name_a, *args, name_b):
print(name_a) # zkc
print(args) # (1, 2, 3)
m, n, k = *agrs # 解包
print(name_b) # zkk
func2('zkc', 1, 2, 3, name_b='zkk')
"""
可变位置参数在形参的末尾位置时,它当前位置之前的所有参数按照位置参数传递
"""
def func3(name_a, name_b, *args):
print(name_a)
print(name_b)
print(args)
m, n, k = *agrs # 解包
func('zkc', 'zkk', 1, 2, 3)
2.2、关键字参数和不定长关键字参数
1、关键字参数是通过 "k-v键值对" 形式来传递参数
2、关键字参数与顺序无关,是通过参数名来进行参数传递
3、位置参数和关键字参数可以混合使用,关键字参数必须放在位置参数的后面
4、不定长关键字参数: 形参前加 ** ,如 **kwargs。将该位置后面所有参数封装到一个字典中
5、不定长关键字参数放在形参列表的最后面
""" 关键字参数简单示例 """
def func(a, b ,c):
print(a, b, c)
func(b=1, c=2, a=3)
"""
关键字参数、位置参数和默认值参数混合使用,默认值参数在最后,关键字参数在默认值参数前面
"""
def func(a, b, c, d=20):
print(a) # 1
print(b) # 2
print(c) # 10
print(d) # 20
func(1, 2, c=10)
"""
关键字参数、位置参数和不定长关键字参数、不定长位置参数混合使用
"""
def func(a, b, *args, **kwargs):
print(a) # 1
print(b) # 2
print(args) # (3, 4, 5)
print(kwargs) # {'name': 'zkc', 'age': 18}
func(1, 2, 3, 4, 5, name='zkc', age=18)
2.3、默认值参数
允许在函数定义时指定默认值。一般将为变化频率低的参数设置默认值
1、调用函数时,如果实参中不传递该参数,将使用默认值
2、调用函数时,如果实参中传递参数,将使用传递的值,默认值不生效
3、默认值参数在位置参数后面
def func(a, b=10, c=20):
print(a)
print(b)
print(c)
func(1, 2, 3) # 此时实参传递了b,c,则不使用默认值10,20
func(1) # 此时实参没有传递b,c,则使用默认值10,20
def func(item, my_index=None):
"""
函数默认参数值设置
一般是不可变类型,如果是可变类型,一般设置为None,并在函数内部检查,根据需要创建一个新的对象
"""
if my_index is None:
my_index = []
my_index.append(item)
return my_index
def func(item, my_index=[]):
""" 这种不可取,多次调用时返回值会累加"""
my_index.append(item)
return my_index
2.4、命名关键字参数/强制关键字参数
- 定义:要求 * 后面所有参数必须以关键字参数进行传递,而不允许使用位置参数
- 作用:限制要传入的参数的名字,只能传已命名关键字参数
- 特征:命名关键字参数需要一个特殊分隔符*,而后面的参数被视为命名关键字参数
- 关键字参数 和 命名关键字参数 的区别:
前者可以传递任何名字的关键字参数,而后者只能传递 * 后面名字的关键字参数。
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*
def func(*, a, b, c):
print(a, b, c)
func(a=1, b=2, c=4)
def person(*, city, sex): # 使用分隔符 *,表示后面的参数必须使用关键字传递
print(city, sex)
person(city='USA',sex='男') # 输出:USA 男
def person(name, age, *, city, sex):
print(name, age, city, sex)
person('John',20,city='USA',sex='男') # 输出:John 20 USA 男
#person('John',20,city='USA',birthday='1996-02-11') 报错 TypeError: person() got an unexpected keyword argument 'birthday'
"""
命名关键字参数 和 关键字参数 区别:
"""
def person(name, age, *args, city, sex):
print(name, age,args, city, sex)
person('John',20, *[1,2,3,4],city='USA',sex='男')
#运行结果:
John 20 (1, 2, 3, 4) USA 男
2.5、参数解包
"""
元组解包:
t = (1, 2, 3)
*t = t
字典解包:
d = {'a': 1, 'b': 2, 'c': 3}
**d = d
1、传递实参时,在序列类型的参数前加 * ,会自动将序列中的元素依次作为参数传递
2、要求序列中元素个数和形参的个数保持一致
"""
def func(a, b, c):
print(a)
print(b)
print(c)
# 创建一个元组
t = (1, 2, 3)
func(*t)
# 创建一个字典
d = {'a': 1, 'b': 2, 'c': 3}
func(**d)
# 示例一
def func(x, y, z):
print(x, y, z)
func(*[1, 2, 3])
func(*(1, 2, 3))
func(**{"x": 1, "y": 2, "z": 3})
# 示例二
def func(x, y, z, *args):
print(x, y, z)
print(args)
func(5, 6, 7, *[1, 2, 3])
2.6、组合使用
参数定义的顺序必须是:必选参数–>默认参数–>可变参数–>命名关键字参数–>关键字参数
def person(name, age,clas='二班', *args, **kwargs):
print('name:{}, age:{}, clas:{}, args:{}, kwargs:{}'.format(name, age,clas,args, kwargs))
person('John',20) #输出:name:John, age:20, clas:二班, args:(), kwargs:{}
person('John',20,'4班',1,2) #输出:name:John, age:20, clas:4班, args:(1, 2), kwargs:{}
person('John',20,'4班',1,2,city='USA',sex='男') #输出:name:John, age:20, clas:4班, args:(1, 2), kwargs:{'city': 'USA', 'sex': '男'}
def f1(a, b, c=0, *, d, **kw):
print('a:{},b:{},c:{},d:{},kw:{}'.format(a,b,c,d,kw))
f1(1,2,d=4) # 输出:a:1,b:2,c:0,d:4,kw:{}
f1(1,2,3,d=4,e=5) #输出:a:1,b:2,c:3,d:4,kw:{'e': 5}
3、函数返回值
函数执行以后返回的结果
- return 可以返回任意类型的数据
- 当只写return 或者不写return时,表示返回值为None
- return 表示函数结束,后面的代码不会再执行
def sum(*args):
result = 0
for i in args:
result += i
return result
res = sum(123, 456, 789)
"""
下面函数输出:0, 1, 2 return表示函数执行结束,后面的代码不会在执行
"""
def test():
for i in range(5):
if i == 3:
return
print(i)
print("循环结束")
4、文档字符串
"""
这里函数参数的类型 和 函数返回值的类型都不是强制的,只是一个提示作用
"""
def func(a:int, b:bool, c:str='zkc') -> str:
'''
这是一个文档字符串
函数的作用是:XXXXXX
函数的参数:
a: 作用,类型,默认值
b: 作用,类型,默认值
c: 作用,类型,默认值
'''
return "hello"
# 通过help函数查看函数的说明
help(func)
5、作用域
5.1、作用域的概念
作用域指的是变量生效的区域
* 在 Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的,变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称
* 在 Python 中,只有在模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,
在其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问
* 内置作用域是通过一个名为 builtin 的标准模块来实现的,但是这个变量名自身并没有放入内置作用域内,所以必须导入这个文件才能够使用它,例如,查看下查看到底预定义了哪些变量 import builtins; dir(builtins)
1、全局作用域
- 全局作用域在程序执行时创建,在程序执行结束时销毁
- 所有函数以外的区域,都属于全局作用域
- 全局作用域中定义的变量,叫全局变量,全局变量可在程序的任意位置被访问
2、函数作用域
- 函数作用域在函数调用时创建,在调用结束时销毁
- 函数每调用一次,就会产生一个新的函数作用域
- 函数作用域中定义的变量,都是局部变量,只能在函数内部被访问
"""
局部变量 和 全局变量 简单案例
"""
b = 20 # 全局变量
def func():
a = 10 # 局部变量/函数变量
print('函数内部:', 'a = ', a) # a是局部变量,在函数内部能访问
print('函数内部:', 'b = ', b) # b是全局变量,可在任意位置被访问
func()
print('函数外部:', 'a = ', a) # 访问不到
print('函数外部:', 'b = ', b) # b是全局变量,可在任意位置被访问
"""
嵌套函数 的变量作用域
"""
a = 10
def func():
a = 20
def func1():
a = 30
print('func1 中', 'a = ', a) # a对于func1来说是全局变量。所以在func1中是可以访问的
func1()
func()
# 输出结果: func1 中 a = 10
5.2、作用域的查找
搜索顺序:Local -> Enclosing -> Global -> Built-in
- 当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用;如果没有,则继续到上一级作用域中寻找,以此类推。一直找到全局作用域,若依然没有找到,则抛出异常
- 从 内-->外 可以看到所有的变量,一层一层往外寻找,直到找到为止
- 从 外--内 只能看到全局的变量
"""
嵌套函数 的变量作用域
"""
a = 10
def func():
a = 20
def func1():
a = 30
print('func1 中', 'a = ', a) # a对于func1来说是全局变量。所以在func1中是可以访问的
func1()
func()
# 输出结果: func1 中 a = 10
- Local:最内层,包含局部变量,比如一个函数/方法内部
- Enclosing:包含了非局部(non-local)也非全局(non-global)的变量。
如两个嵌套函数,一个函数(或类)A 中又包含一个函数 B,那么对于 B 中来说 A 中的作用域就为 nonlocal
- Global:当前脚本的最外层,比如当前模块的全局变量
- Built-in: 包含了内建的变量/关键字等
nonlocal 关键字: 修改嵌套作用域(enclosing 作用域,外层非全局作用域)
关于多层嵌套函数中,用 nonlocal 关键字声明的变量只影响上一层的变量,再外一层的不受影响
"""
1、如果要在函数内部修改全局变量,则需要使用 global 关键字
2、global 关键字可以声明函数内部的变量是全局变量,此时修改的就是全局变量
"""
a = 10
def func():
global a
a = 20
print('函数内部', 'a = ', a)
func()
print('函数外部', 'a = ', a)
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam" # 全局变量
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
#### 运行输出 ####
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
# 局部作用域
x = 0
def outer():
x = 1
def inner():
x = 2
print(x)
inner()
outer()
# 执行结果为 2,因为此时直接在函数 inner 内部找到了变量 x
# 闭包函数外的函数中
x = 0
def outer():
x = 1
def inner():
i = 2
print(x)
inner()
outer()
#执行结果为 1,因为在内部函数 inner 中找不到变量 x,继续去局部外的局部——函数 outer 中找,这时找到了,输出 1
# 全局作用域
x = 0
def outer():
o = 1
def inner():
i = 2
print(x)
inner()
outer()
# 执行结果为 0,在局部(inner函数)、局部的局部(outer函数)都没找到变量 x,于是访问全局变量,此时找到了并输出。
# 非全局作用域
def outer():
num = 10
def inner():
nonlocal num # 使用 nonlocal 关键字 num 变量非全局作用域,将num修改为局部作用域
num = 100
print("inner 函数中,num = ",num)
num *= 10.24 # 类型自动转换为float
inner()
print("outer 函数中,num = ",num) # 因为inner中将num修改成局部作用域,所以覆盖了原来的10
outer()
# inner 函数中,num = 100
# outer 函数中,num = 1024.0
x = 0
def outer():
# "外层函数"
x = 3
def mid():
# "中层函数"
x = 2
def inner():
# "内层函数"
nonlocal x
x = 1
print("x_inn=", x) # x_inn = 1
inner()
print("x_mid=", x) # x_inn = 1 ,受到 nonlocal 关键字影响
mid()
print("x_out=", x) # x_inn = 3,不受到 nonlocal 关键字影响
outer()
print("x_glo=", x) # x_inn = 0,因outer函数内没有使用global关键字定义x_inn ,所以此处输出的还是 0
6、命名空间

6.1、命名空间的概念
指的就是变量存储的位置,每一个变量都需要存储到指定的命名空间中
也就是说,命名空间实际上是一个专门用来存储变量的字典
* 描述:
命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过"字典"来实现的,提供在项目中避免名字冲突的一种方法,各个命名空间是独立的,没有任何关系的,不同命名空间空间下变量可以重名而没有任何影响
* 例如:
计算机系统中的例子,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名
# 文件夹 A
A/1.txt
A/demo1.txt
A/demo2.txt
# 文件夹 B
B/2.txt
B/demo1.txt
B/demo2.txt
6.2、命名空间种类
每一个作用域都有一个对应的命名空间
* 局部命名空间(local namespace)
- 保存函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量
* 全局命名空间(global namespace)
- 保存模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量
* 内置命名空间(built-in namespace)
- Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等
6.3、获取命名空间
locals() 用来获取当前作用域的命名空间,返回一个字典
- 全局作用域中调用,则获取全局命名空间
- 函数作用域中调用,则获取局部命名空间
globals() 用来获取全局命名空间,返回一个字典
- 函数作用域中调用,则获取全局命名空间
name = 'zkc'
global_scope = locals()
def ns():
a = 10
local_scope = locals()
print('局部命名空间: ', local_scope)
print('全局命名空间: ', global_scope)
ns()
# 输出结果
全局命名空间: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001BF91B44820>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:\\zkc\\file\\code\\python-workspace\\shell\\param_test.py', '__cached__': None, 'name': 'zkc', 'global_scope': {...}, 'ns': <function ns at 0x000001BF91A83EB0>}
局部命名空间: {'a': 10}
"""
向字典中添加key-value,相当于在命名空间中创建了一个变量(一般不建议这样做)
"""
name = 'zkc'
global_scope = locals()
global_scope['age'] = 19
def ns():
a = 10
local_scope = locals()
local_scope['b'] = 2000
print('局部命名空间: ', local_scope)
print(local_scope['b'])
print('全局命名空间: ', global_scope)
print(global_scope['age'])
ns()
# 输出结果
全局命名空间: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000200B7CD4820>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:\\zkc\\file\\code\\python-workspace\\shell\\param_test.py', '__cached__': None, 'name': 'zkc', 'global_scope': {...}, 'age': 19, 'ns': <function ns at 0x00000200B7C13EB0>}
19
局部命名空间: {'a': 10, 'b': 2000}
2000
"""
在函数内部获取全局命名空间(一般不建议这样做)
"""
a = 20
def func():
global_ns = globals()
print('全局命名空间: ', global_ns)
global_ns['b'] = 30
print('全局命名空间: ', global_ns)
func()
# 输出结果:
全局命名空间: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002F608E04820>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:\\zkc\\file\\code\\python-workspace\\shell\\param_test.py', '__cached__': None, 'a': 20, 'func': <function func at 0x000002F608D43EB0>}
全局命名空间: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000002F608E04820>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:\\zkc\\file\\code\\python-workspace\\shell\\param_test.py', '__cached__': None, 'a': 20, 'func': <function func at 0x000002F608D43EB0>, 'b': 30}
6.4、命名空间的查找
- 命名空间查找顺序:
局部的命名空间 -> 全局命名空间 -> 内置命名空间
6.5、命名空间的声明周期
- 命名空间的生命周期
取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束, 因此,我们无法从外部命名空间访问内部命名空间的对象
7、递归和其他函数知识
7.1、递归
* 递归式函数
- 在函数中自己调用自己
- 整体思想:将一个大问题分解成一个个的小问题,直到问题无法分解时,再解决问题
* 递归式函数的要素:
- 1、基线条件
- 问题可以被分解为最小问题,当满足基线条件时,递归不再执行
- 2、递归条件
- 将问题继续分解的条件
循环和递归区别:
循环:编写简单,不容易理解
递归:编码复杂,容易理解
"""
无穷递归:
函数被调用时,程序内存会溢出,效果类似于死循环。 (千万不要这样写)
"""
def fn():
fun()
fun()
"""
创建一个函数,求任意数的阶乘 -- 循环方式 求阶乘
"""
def factorial(n):
result = n
for i in range(1, 10):
result *= i
return result
print(factorial(10))
"""
创建阶乘的递归函数 -- 递归方式 求阶乘
10! = 10 X 9!
9! = 9 X 8!
8! = 8 X 7!
...
1! = 1
"""
def factorial(n):
# 基线条件: 判断 n 是否为 1,如果为 1, 则不再递归
if n == 1:
return 1
# 递归条件
return n * factorial(n-1)
"""
创建一个函数power,来为任意数字做幂运算 n ** i
"""
def power(n, i):
if i == 0:
return 1
if i < 0:
return 1 / power(n, -i)
return n * power(n, i - 1)
print(power(10, -3))
"""
回文字符串:从前往后读和从后往前读是一样的 abcba
创建一个函数,检查任意字符串是否是回文字符串,是则返回True,否则返回False
检查abcdefgfedcba是不是回文
检查bcdefgfedcb是不是回文
检查cdefgfedc是不是回文
检查defgfed是不是回文
检查efgfe是不是回文
检查fgf是不是回文
检查g是不是回文
"""
def hui_wen(s):
# 基线条件,字符串长度小于2,则一定是回文
if len(s) < 2:
return True
elif s[0] != s[-1]: # 第一个字符和最后一个字符不相等,不是回文字符串
return False
return hui_wen(s[1:-1])
print(hui_wen('abcdefgfedcba'))
7.2、高阶函数
- 在python中,函数是 一等对象
- 一等对象 一般都会具有如下特点:
① 对象是在运行时创建的
② 能赋值给变量或者作为数据结构中的元素
③ 能作为参数传递
④ 能作为返回值返回
- 高阶函数
- 有两个特点:
① 接收一个或多个函数作为参数
② 将函数作为返回值返回
- 当我们使用一个函数作为参数时,实际上是将指定函数的代码传递进目标函数
list_1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 定义一个函数,用来检查任意数字是否为偶数
def fn2(i):
if i % 2 == 0:
return True
return False
# 定义一个函数,用来检查指定的数字是否大于5
def fun3(i):
if i > 5:
return True
return False
# 定义一个函数,用来检查能否被3整除
def fun4(i):
if i % 3 == 0:
return True
return False
def fn(func, my_list):
new_list = []
for n in my_list:
if func(n):
new_list.append(n)
return new_list
# 调用
print(fn(fn2, list_1))
print(fn(fn3, list_1))
print(fn(fn4, list_1))
1、函数式编程 map
"""
# 定义:
将一个函数映射到一个输入列表的所有元素上, 生成新的列表
# 语法格式:
map(function, list_of_input)
第一个参数是函数,第二个参数是一个或多个可迭代对象,可以是列表/元组/字符串
# 注意事项
map 返回的是迭代器,如果需要列表,要用 list()转换
map 不会修改原数据,而是返回新数据
map 比for循环更高效,因为map是惰性计算(不立即执行,只在需要时计算)
"""
item = [1, 2, 3, 4, 5]
# map使用lambda函数
item1 = list(map(lambda x: x ** 2, item))
# map处理多个可执行对象
item2 = list(map(lambda x, y: x + y, item, item1))
# map处理类型转换
item4 = ['1', '2', '3']
item3 = list(map(int, item4))
# 互换位置
item5 = [(1, 2), (3, 4), (5, 6)]
item6 = list(map(lambda x: (x[1], x[0]), item5))
2、函数式编程 filter
"""
# 定义:
过滤列表中的元素,返回一个由所有符合要求的元素所构成的列表
# 语法格式:
filter(function, list_of_input)
第一个参数是函数,第二个参数是一个或多个可迭代对象,可以是列表/元组/字符串
# func 返回 True:则保留该元素
# func 返回 False:则丢弃该元素
# func 是 None:则直接过滤掉 0, "", '', None, False 的值
# 注意事项
filter 返回的是迭代器,如果需要列表,要用 list()转换
filter 不会修改原数据,而是返回新数据
filter 比for循环更高效,因为filter是惰性计算(不立即执行,只在需要时计算)
# filter 和 列表推导式的用法
filter():适用于 函数式 编程 或 已有判断函数 的情况
列表推导式: 适用于 简单条件 或 需要更直观代码 的情况
"""
my_list = [1, None, 'hello', '', 3.14, False, []]
# 过滤my_list列表的空值
new_list1 = list(filter(lambda x: x is not None and x != '' and x != [], my_list))
new_list2 = list(filter(None, my_list))
3、函数式编程 reduce
"""
reduce:对列表中的元素进行累积操作,返回一个单一的结果
无初始值:第一次输入列表的前两个元素,再将前两个累加后的和与下一个元素相加,以此类推
有初始值:将初始值和输入列表的第一个元素相加,再将前两个累加的和与下一个元素相加,以此类推
"""
list_aa = list(range(1, 10))
res = reduce(lambda x, y: x + y, list_aa)
res1 = reduce(lambda x, y: x + y, list_aa, 10)
print("reduce===============", res) ## 45
print("reduce有初始值===============", res1) ## 55
# 使用 reduce() 和 lambda 表达式计算一个序列的累积乘积:
numbers = [1, 2, 3, 4, 5]
# reduce() 函数通过遍历 numbers 列表,并使用 lambda 函数将累积的结果不断更新
product = reduce(lambda x, y: x * y, numbers)
print("1 * 2 * 3 * 4 * 5 =",product) # 输出:120
4、sort 函数
- sort(): 用来对列表中的元素进行排序,默认是按照大小来排序。(永久排序,原列表顺序会改变)
item = ['aasda', 'ad', 'weqetfsfa', 'asd', '1dasda']
item.sort(key=lambda x: len(x))
item.sort(key=len)
- sorted(): 用来对序列中的元素进行排序。 (临时排序,原列表顺序不会改变)
a = sorted(item, key=lambda x: len(x), reverse=True)
item = ['aasda', 'ad', 'weqetfsfa', 'asd', '1dasda']
print('排序前:', item) # ['aasda', 'ad', 'weqetfsfa', 'asd', '1dasda']
a = sorted(item, key=len, reverse=True)
print(a) # ['ad', 'asd', 'aasda', '1dasda', 'weqetfsfa']
print('排序后:', item) # ['aasda', 'ad', 'weqetfsfa', 'asd', '1dasda']
7.3、匿名函数
lambda关键字用来创建匿名函数(隐函数),调用完成后,函数从内存中删除
- lambda 主体是一个表达式,而不是一个代码块, 仅仅能在lambda表达式中封装有限的逻辑进去。
- lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
- lambda 函数虽然看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
# 语法:
my_lambda = lambda 参数1, 参数2: 表达式
#调用
print(my_lambda(参数1, 参数2))
- 定义形式:
lambda 形参:返回值表达式 (语法糖)
- 相对于一般函数而言具有以下优势:
* 免去了函数定义过程,代码变得更加精简
* 省却定义函数名过程
* 省去了返回到函数定义出阅读代码过程,提高了代码的可读性
* 匿名函数一般作为参数使用,其他地方不会使用
# 无参lambda
my_lambda = lambda: "hello, Python"
# 有参lambda
my_lambda = lambda a, b: a * b
# 参数有默认值lambda
my_lambda = lambda a=10, b=25: a + b
def fn(a, b):
return a + b
等价于:lambda a, b: a + b
调用:print((lambda a, b: a + b)(10, 20))
或者:
a = lambda x: x ** 2
调用: a(10, 20)
7.4、闭包
将函数作为返回值返回,也是一种高阶函数
这种高阶函数,也叫闭包
通过闭包可以创建一些只有当前函数能访问的变量,可以将一些私有的数据藏到闭包中
总体来说平是为了数据安全
- 内嵌函数: 即一个函数中可以定义另一个函数。
- 闭包: 将函数内部和函数外部连接起来的桥梁,
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,其次是闭包会在父函数外部,改变父函数内部变量的值,若把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),此时有可能被黑客利用。
- r是一个函数,是调用fn()后返回的函数
- 这个函数在fn()内部定义,并不是全局函数
- 所以这个函数总是能访问到fn()函数内的变量
# 形成闭包的条件:
- 函数嵌套
- 将内部函数作为返回值返回
- 内部函数必须要使用外部函数的变量
def fn():
a = 10
# 函数内部再定义一个函数
def inner():
print('我是inner', a)
# 将内部函数inner作为函数值返回
return inner
r = fn()
r()
"""
如果不加外层函数,nums列表就是一个全局变量,在任何位置都能访问和修改
加了外层函数之后,nums变量只在averager()中能访问,在全局中访问不到函数内部的nums
"""
def make_averager():
# 创建一个列表,用来保存数据
nums = []
# 创建一个函数,用来计算平均值
def averager(n):
nums.append(n)
# 求平均值
return sum(nums)/len(nums)
return averager
averager = make_averager()
print(averager(10))
print(averager(10))
print(averager(10))
# 内嵌函数与闭包
# 内嵌函数
def fun1():
print("Fun1 主函数被调用")
def fun2():
print("Fun2 内嵌函数正在被调用\n")
return fun2() #内部函数(内嵌函数),只能由fun1()调用
fun1()
# Fun1 主函数被调用
# Fun2 内嵌函数正在被调用
# 闭包示例1
def funX(x):
def funY(y):
return x * y
return funY
i = funX(8) # i就是返回的funY对象
print("变量 i 的类型 :",type(i))
print("i(5) =",i(5)) # 返回值 40,由于前面已经赋值给x了,后面得就给了y=5。
# 变量 i 的类型 : <class 'function'>
# i(5) = 40
"""
# 其他方式:
def funX(x):
def funY(y):
return x * y
return funY(2)
>>> funX(3)
6
"""
# 闭包示例.nonlocal 关键字, 其中值保存在内存之中。
def funA():
x = 5 # 函数内部变量,局部变量。
def funB():
nonlocal x # 把x强制表示不是局部变量local variable,所以就不会每次被初始化
x += 13
return x
return funB
a = funA() #当 a 第一次被赋值后,只要没被重新赋值,funA()就没被释放,也就是说局部变量x就没有被重新初始化。
print("闭包中使用 nonlocal 关键字,第一次调用:",a(),"第二次调用:",a(),"第三次调用:",a())
# 闭包示例2.global 关键字, 值还是保存在内存之中。
x=1 # 函数外部的变量,全局变量。
def funC():
x = 5
def funD():
global x # 声明全局变量x,所以每次调用都不会被重新初始化。
x += 13
return x
return funD
b = funC() #当 a 第一次被赋值后,只要没被重新赋值,funC()就没被释放,也就是说全局变量x就没有被重新初始化。
print("闭包中使用 global 关键字,第一次调用:",b(),"第二次调用:",b(),"第三次调用:",b())
# 闭包中使用 nonlocal 关键字,第一次调用:18 第二次调用:31 第三次调用:44
# 闭包中使用 global 关键字,第一次调用:14 第二次调用:27 第三次调用:40
8、装饰器
- 装饰器:又叫修饰符
常用于有切面需求的场景,比如插入日志记录,方法添加,数据验证,性能测试,事务处理等等
- 作用:
再不修改原来函数的情况下,是来增强函数、类功能的一个函数
- 在定义函数时,可以通过 "@装饰器" 来使用指定的装饰器,来装饰当前的函数
- 可以同时为一个函数执行多个装饰器,按照从内向外的顺序被装饰
- python装饰器的4种类型:函数装饰函数、函数装饰类、类装饰函数、类装饰类
- 用functools.wraps装饰器,使得被装饰的函数的元信息不会丢失,避免被装饰内部函数的名称、注释等信息被改变
- 如果要修改的函数很多,修改起来比较麻烦
- 直接修改源函数,违反开闭原则(OCP)
- 程序的涉及,要求开放对程序的扩展,要关闭对程序的修改
8.1、装饰器定义
def wrapper(func): # wrapper 装饰器名称 fn:被装饰的函数
# inner: 被返回的函数,用来替代原函数
def inner(*args, **kwargs): # 给原函数同样的参数配置
res = func(*args, **kwargs) # func() 运行原函数,res是原函数的返回值
return res # 把原函数运行的结果反馈给调用方,在调用方看来,得到的结果依然是原函数的结果
return inner # 用来替代原函数
def func1():
pass
func1 = wrapper(func1) # 这行代码可以用python的语法糖 @ 替换
func1()
# 装饰器初始阶段
def log(func):
def wrapper(*args, **kwargs):
print("调用函数之前的操作1")
result = func(*args, **kwargs)
print("调用函数之后的操作2")
return wrapper
def calc_add(a: int, b: int) -> int:
print("正在计算a、b的和=====", a + b)
calc_add = log(calc_add) # 这里的calc_add已经不再是原来的calc_add对象了,而是将log函数返回的wrapper对象赋值给了calc_add。
calc_add(1, 2) # 这里相当于是调用的wrapper函数
# 装饰器成型阶段
# 将 calc_add = log(calc_add) 用 语法糖 "@" 替代
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("调用函数之前的操作1")
result = func(*args, **kwargs)
print("调用函数之后的操作2")
return result
return wrapper
@log
def calc_add(a: int, b: int) -> int:
return a + b
def add(a, b):
return a+b
def new_add(a, b):
print("函数开始执行···")
r = add(a, b)
print("函数执行结束···")
return r
r = new_add(123, 456)
print(r)
# 上面的方式已经在不修改源码的情况下对函数进行扩展了
# 但是,这种方式要求我们每扩展一个函数就要手动创建一个新的函数,太繁琐了
# 为了解决这个问题,我们创建一个函数,让这个函数可以自动的帮我们创建新的函数
def add(a, b):
return a+b
def mul(a, b):
return a*b
def fn():
print('我是fn')
def begin_end(func):
'''
# 用来对其他函数进行扩展,使其它函数在执行前后打印内容
# 只要调用这个函数,他都会给我们创建一个新函数new_function
参数:
func: 要扩展的函数
'''
# 创建一个新函数
def new_function(*args, **kwargs):
print("开始执行")
# 调用被扩展的函数
r = func(*args, **kwargs)
print("结束执行")
return r
# 返回新函数,有return func,则被装饰器装饰后的函数还是被赋值为原函数,后续还可以调用
return new_function
f = begin_end(add)
f2 = begin_end(add)
print(f) # <function begin_end.<locals>.new_function at 0x000001303975A320>
print(f2) # <function begin_end.<locals>.new_function at 0x000001F81A66A8C0>
f(123, 456)
8.2、高阶装饰器
先执行距离函数最近的装饰器(就近原则),再将第一个装饰器返回的函数 作为 下一个装饰器函数的参数,依次类推
def log1(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("调用函数之前的操作1")
result = func(*args, **kwargs)
print("调用函数之后的操作2")
return result
return wrapper
def log2(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("调用函数之前的操作3")
result = func(*args, **kwargs)
print("调用函数之后的操作4")
return result
return wrapper
'''
不用语法糖调用
'''
def calc_add(a: int, b: int) -> int:
print("正在计算a、b的和=====", a + b)
return a + b
calc_add2 = log2(calc_add) # 返回wrapper对象,赋值给calc_add2
calc_add1 = log1(calc_add2) # 这里的log1函数的参数calc_add参数是,第一步log2函数返回的wrapper函数对象赋值给的calc_add
print(calc_add1(1, 2))
'''
使用语法糖 @
'''
@log1
@log2
def calc_add(a: int, b: int) -> int:
print("正在计算a、b的和=====", a + b)
return a + b
8.3、不同种类的装饰器
8.3.1、函数装饰函数:不带参数装饰器函数
import time
import functools
def log_execution_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
res = func(*args, **kwargs)
end = time.perf_counter()
print('{} took {} ms'.format(func.__name__, (end - start) * 1000))
return res
return wrapper
@log_execution_time
def calculate_similarity(items):
pass
# 声明装饰器,将被装饰函数传入
def decorator_function(original_function):
@functools.wraps(original_function)
def wrapper(*args, **kwargs):
print("Start.在调用 original_function 函数之前的操作")
result = original_function(*args, **kwargs)
print("End.在调用 original_function 函数之后的操作")
return result
# 将装饰完之后的函数返回(返回的是函数对象)
return wrapper
# 使用装饰器, 装饰器通过 @ 符号应用在函数定义之前。
@decorator_function
def original_function(arg1, arg2):
print("original_function:",arg1,arg2)
pass
# 调用原函数,触发装饰器
original_function(1024, 2048)
# Start.在调用 original_function 函数之前的操作
# original_function: 1024 2048
# End.在调用 original_function 函数之后的操作
8.3.2、函数装饰函数:带参数装饰器函数
'''
@fn2('zkc', age=19)
这里的 @ 符号后面是函数调用,调用了fn2()这个函数,fn2函数返回了一个装饰器,跟前面的 @ 符号拼接成了
装饰器调用
'''
def fn2(*_out_args, **out_kwargs):
def begin_end(func):
# 创建一个新函数
def new_function(*args, **kwargs):
print(f"{out_kwargs['age']}岁的{out_args[0]}开始执行")
# 调用被扩展的函数
r = func(*args, **kwargs)
print(r)
print("结束执行")
return r
# 返回新函数
return new_function
return begin_end
@fn2('zkc', age=19)
def add(a, b):
return a + b
add(3, 6)
# 带参数的装饰器
# repeat 函数是一个带参数的装饰器,它接受一个整数参数 n,然后返回一个装饰器函数,此参数是用来控制装饰器的执行次数。
# 声明装饰器
def repeat(n):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# 使用装饰器
@repeat(5)
def greet(name):
print(f"Hello, {name}!")
# 调用原函数,触发装饰器
greet("WeiyiGeek")
8.3.3、函数装饰类:装饰器函数
def wrapClass(cls):
def inner(a):
print('class name:', cls.__name__)
return cls(a)
return inner
@wrapClass
class Foo:
def __init__(self, a):
self.a = a
def fun(self):
print('self.a =', self.a)
m = Foo('xiemanR')
m.fun()
# 在类初始化时进行数据验证
def validate(cls):
original_init = cls.__init__
def new_init(self, *args, **kwargs):
if args:
for arg in args:
if not isinstance(arg, (int, float)):
raise ValueError(f"Excepted arg int or float, got {type(arg).__name__}")
original_init(self, *args, **kwargs)
if kwargs:
for key, value in kwargs.items():
if not isinstance(value, (int, float)):
raise ValueError(f"Excepted kwarg int or float, got {type(value).__name__}")
original_init(self, *args, **kwargs)
cls.__init__ = new_init
return cls
@validate
class MyClass:
def __init__(self, x, y):
self.x = x
self.y = y
try:
instance = MyClass(x=10, y='20')
except ValueError as e:
print(e)
8.3.4、类装饰函数:类装饰器
# 类装饰器是包含 __call__ 方法的类,它接受一个函数作为参数,并返回一个新的函数。
class DecoratorClass:
# 首先,DecoratorClass 的 __init__ 方法应该只接受 count 参数
def __init__(self, count=3):
self.count = count
self.execution_count = 0
def __call__(self, func):
def wrapper(*args, **kwargs):
self.execution_count += 1
print(f"Execution count: {self.execution_count}")
result = None
for _ in range(self.count):
result = func(*args, **kwargs)
return result
return wrapper
# 使用装饰器
@DecoratorClass(count=5)
def my_function(name):
print(f"Hello, {name}!")
# 调用原函数,触发装饰器
my_function("公众号: 全栈工程师修炼指南")
my_function("公众号: 全栈工程师修炼指南")
# 输出:
Execution count: 1
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Execution count: 2
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
8.3.5、类装饰类:类装饰器
class ShowClassName(object):
def __init__(self, cls):
self._cls = cls
def __call__(self, a):
print('class name:', self._cls.__name__)
return self._cls(a)
@ShoClassName
class Foobar(object):
def __init__(self, a):
self.value = a
def fun(self):
print(self.value)
a = Foobar('xiemanR')
a.fun()
9、迭代器 和 生成器
# 可迭代协议 iterable protocol
* 如果一个对象中实现了 __iter__()方法, 称该对象为可迭代对象,如列表、字典
在python中,为了使一个”对象“可迭代:
1. 这个迭代器必须同时包含另一个方法叫做“__iter__”
2. 这个"__iter__"方法还得返回一个”迭代器“(可迭代),即返回迭代器本身self
# 迭代器协议 iterator protocol
* 如果一个对象中实现了__next__()方法, 称该对象为迭代器
9.1、迭代器(Iterator)
* Python 中实现了迭代协议,即包含 __iter__() 和 __next__() 方法的对象叫迭代器
* 迭代器是一种可以实现惰性计算的对象,可以被用来遍历可迭代对象中的元素
* 迭代器可以被 next() 函数调用,并且逐个返回元素,直到没有元素可返回时抛出 StopIteration 异常
* 迭代器不是一个容器,且判断一个容器是不是有迭代功能只需要查看iter()和 next()方法
* 迭代器只能往前不能后退,当容器中没有元素时,就抛出 StopIteration异常表示容器已经没有元素
* 迭代器只能迭代一次值,要是想再迭代,要重新实例化创建迭代器
* 惰性机制,必须调用__next__()或者被 next() 函数调用才会获取数据
迭代器的主要特点是它只在需要时才生成下一个值,这种延迟计算的方式使得迭代器在处理大数据集时非常高效,因为它不会一次性将所有数据都加载到内存中,而是按需生成和处理数据,它们不仅支撑着 for 循环迭代的基本机制,还在数据流式处理、懒加载、无限序列等场景中发挥着关键作用。
'''
自定义迭代器
要实现一个迭代器,需定义一个类并实现两个魔法方法:
__iter__():返回迭代器自身(通常是return self)
__next__():返回下一个元素,无元素时抛出StopIteration
'''
class CountIterator:
def __init__(self, start, end):
self.current = start # 初始值
self.end = end # 终止值
def __iter__(self):
return self # 返回迭代器自身
def __next__(self):
if self.current <= self.end:
result = self.current
self.current += 1 # 更新状态
return result
else:
raise StopIteration # 终止迭代
# 使用自定义迭代器
counter = CountIterator(1, 3)
for num in counter:
print(num) # 输出:1、2、3
class MyIterator:
def __init__(self, content):
self.content = content
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index < len(self.content):
result = self.content[self.index]
self.index += 1
return result
else:
raise StopIteration
my_iterator1 = MyIterator([1, 2, 3, 4])
print(next(my_iterator1)) # 输出1
print(next(my_iterator1)) # 输出2
print(next(my_iterator1)) # 输出3
print(next(my_iterator1)) # 输出4
print(next(my_iterator1)) # 抛出StopIteration异常
9.1.1、第一种方式:使用__iter__()和__next__()抽象方法
iter()和__next__()是迭代器对象的抽象方法,每个迭代器都实现了这两个方法
# 相当于for循环
from collections.abc import Iterable,Iterator
def func(data):
if isinstance(data, Iterable): # 判断数据是不是可迭代的/迭代器
it = data.__iter__()
while True:
try:
print(it.__next__())
except StopIteration as e:
break
func([1, 2, 3])
data = (1, 2, 4)
it1 = data.__iter__().__iter__().__iter__()... # 迭代器的迭代器还是自己
print(it1) # 不调用__next__()时,知识一个可迭代对象<list_iterator object at 0x0000025D2BA6FE20>
print(it1.__next__())
print(it1.__next__())
print(it1.__next__())
# 要想再次迭代data,重新调用迭代器
it2 = data.__iter__()
9.1.2、第二种方式:使用内置的 iter() 和 next() 函数
def func(data):
from collections.abc import Iterable
if isinstance(data, Iterable):
it = iter(data)
while True:
try:
iter_data = next(it)
print(iter_data)
except StopIteration as e:
break
func([1, 2, 3, 4, 5])
9.1.3、第三种方式:使用for循环
data = [1, 2, 3, 4, 5]
it = iter(data)
for i in it:
print(i)
it2 = data.__iter__()
for i in it2:
print(i)
9.2、生成器(generator)
生成器(Generator)是一种特殊的迭代器,它可以在需要时动态生成值,而不是一次性将所有值存储在内存中。生成器使用 yield 关键字来定义生成值的逻辑,每次调用生成器的 next() 方法时,它会从上一次的 yield 语句处继续执行,直到遇到下一个 yield 或者函数结束。
生成器在实现上更加简洁和高效,因为它不需要显式地维护整个序列,而是在每次迭代中动态生成下一个值,这种惰性计算的方式使得生成器非常适合处理大数据集或者无限序列。
9.2.1、生成器函数
函数中如果包含了yield关键字,通过yield来返回数据时,这个函数就是生成器函数
特点:
- 生成器的本质就是迭代器,所以是可迭代的,可以使用for循环
- 函数名():不是运行函数,而是在创建生成器
- 当函数调用到 yield 时,函数都会暂停运行并保存当前的所有运行信息,然后返回 yield关键字的值,并在下一次执行 next()方法时从当前位置继续执行
- 只有调用生成器的__next__()方法/next()函数,才能触发执行生成器,执行到yield位置,函数被挂起,会返回值并且保存当前的执行状态
- next() 和 send() 函数可以恢复生成器
- 一般不用 next()方式获取元素,而是使用for循环,因为容易触发StopIteration异常
- 当使用while循环时,需要捕获 StopIteration 异常
- __next__(): 是生成器中的方法
- next(): python内置函数
# 基本概念
collections.abc.Generator:
1. 是Python 标准库中定义的一个抽象基类(Abstract Base Class),提供了一系列的生成器对象应该支持的 接口 和 行为,除了迭代器应有的__iter__()和__next__(),还扩展了send(), throw(), close() 等协程相关操作
2. 内置的生成器对象(generator iterator)就实现了这个抽象基类的(更精确地说,是其对应的内置类 <class 'generator'> 实现了该抽象基类)
# generator函数 :
简称为generator,但是为了避免和generator iterator(有时候也会被称为generator)区分开来,最好是两者都使用全称。
1. 定义起来与普通函数相似,区别在于其在函数体内使用了yield表达式
2. 被调用时,即刻返回一个genrator iterator,且不会执行函数体中的任何逻辑,只有其返回的genrator iterator的__next__()被调用时会执行,执行到下一个yield表达式时暂停执行(挂起),并将该yield表达式的值作为__next__()的返回值返回给调用者,当yield后的所有代码执行完毕后抛出StopIteration 异常。
3. 在__next__()的调用过程中,如果执行了return语句,return返回的值不会作为函数的返回值。return语句会结束生成器函数的执行,并抛出 StopIteration 异常。该异常的 value属性存储了 return后面表达式的值。正常迭代时(for循环)这个异常被迭代器捕获以结束迭代。
# generator iterator:
1. 是迭代器iterator的一种,支持 迭代协议(实现了__iter__() 和__next__())
2. generator函数被调用时所创建的对象。其内部 不预先存储 任何要生成的值,仅保存当前 执行状态(如局部变量、指令指针等)。这是其 惰性 的一个体现: 惰性存储
3. 它的存在只是为了维护的是generator函数当前的执行状态(包括局部变量、代码执行位置等),在调用__next__()时,将generator函数的代码执行到下一个yield处并返回值给调用者。这是其 惰性 的另一个体现:惰性读取
''' 普通函数 '''
def func():
print("我是普通函数")
return 123
mm = func()
print(mm)
# 输出
我是普通函数
123
''' 生成器函数 '''
def func():
print("我是生成器函数1")
yield 123
print("我是生成器函数2")
yield 456
mm = func() # 创建生成器对象<generator object func at 0x0000021BBCB72030>,不是调用函数,所以print(mm)时候没有函数中的print输出,因为此时函数没有运行
print("开始第一次调用,执行到第一个yield")
g = mm.__next__()
print(g)
print("开始第二次调用,从第一个yield后开始执行,执行到第二个yield位置")
g1 = mm.__next__() # 获取函数返回值
print(g1)
# 输出
开始第一次调用,执行到下一个yield
我是生成器函数1
123
开始第二次调用,从第一个yield后开始执行
我是生成器函数2
456
9.2.1.1、使用__next__() 和 next() 方法触发生成器
def fn1(name):
print(name)
lst = ['a', 'b', 'c']
yield lst
print('456')
yield 'aaaaaaaa'
# 创建生成器
gen = fn1('zkc') # 创建生成器对象
print(gen.__next__()) # 调用__next__方法执行生成器。输出:zkc ['a', 'b', 'c']
print(next(gen)) # 调用next()函数执行生成器。输出:456 aaaaaaaa
'''
注意:
1. 此时上面共调用执行了两次生成器
2. 第一次调用从开始执行到第一个yield位置,记录当前位置并返回结果
3. 第二次调用从第一个yield记录的位置开始,调用到第二个yield位置,记录位置并返回当前结果
4. 这两次调用 gen 生成器执行完后,此时gen生成器没有可以再迭代的内容,已经空了
5. 因为生成器是特殊的迭代器,是只能调用__next__或者next()来出发,从前往后一直执行的,不能后退
'''
print(gen.__next__()) #这时候,再调用触发生成器时,会报错StopIteration错误
for i in gen: # 此时gen生成器是空的,所以for循环不执行
print('=======', i)
9.2.1.2、使用for循环触发生成器
# 当容器中的元素很多的时候,如果全放到容器中,会特别占用内存
# 这样是一次性生成有99999个元素的列表,会占用大量的内存
my_list = []
# 1.创建列表
for i in range(99999):
my_list.append(i)
# 2.使用列表
for iters in my_list:
print(iters)
# 用生成器优化,节省很大内存
def func():
for i in range(99999):
yield i
my_iter = func() # 创建生成器
for iters in my_iter:
print(iters)
9.2.1.3、高级特性(send()/throw()/close())
'''
send()方法向生成器函数中传入数据
1. 使用 send()方法时,必须在 yield 关键字前面添加一个变量,并打印这个变量。
2. 在使用send()方法之前,必须要先使用至少一次 __next__ 方法,因为生成器不可以在使用之前导入任何非None的参数。
3. send()是用来向生成器中导入参数并返回该参数的,与该参数一起返回的还有生成器中原先保存的数据。
'''
def receiver():
while True:
item = yield # 接收外部发送的值
print(f"收到: {item}")
r = receiver()
next(r) # 启动生成器
r.send("消息1") # 输出: 收到: 消息1
r.send("消息2") # 输出: 收到: 消息2
r.throw(ValueError("错误")) # 向生成器抛出异常
r.close() # 终止生成器
# send()方法示例
def fib(times):
n = 0
a, b = 0, 1
while n < times:
temp = yield b # 在 yield 前面添加一个变量
print(temp)
a, b = b, a + b
n += 1
f = fib(5)
print(f.__next__()) # 调用 __next__ 打印值
print(f.send("Super Man 超人")) # 使用send()方法传入值
print(f.send("lron Man 钢铁侠"))
# 结果
1
Super Man 超人
1
lron Man 钢铁侠
2
9.2.2、生成器表达式
- 列表推导式中的 [] 换成 () 就会变成一个生成器
- 生成器表达式 和 列表推导式 区别
- 列表推导式:会将列表中的所有元素都创建出来放到内存中,元素很多时,会占用内存
- 生成器表达式:只在内存中存了创建生成器的代码,当需要取数据进行for循环时,才进行一个一个迭代获取。节省内存
# 列表推导式
lst = [x + 1 for x in range(10) if x % 2 == 0]
# 生成器表达式,
# 将推导式的[] 换成(), 会创建一个生成器对象, 再用for循环迭代
# 其实没有元组推导式,因为元组中的数据是不允许变化的
# (x for i in range(10) if x % 2 == 0) 其实不是元组推导式,而是生成器表达式
my_gen = (x + 1 for x in range(10) if x % 2 == 0)
9.2.3、面试题
def func():
print("111")
yield 222
g = func() # 生成器
g1 = (i for i in g) # 生成器
g2 = (i for i in g1) # 生成器
'''
以上代码只是定义生成器,还没有开始调用执行生成器,所以在此时print(g)是没有任何输出,生成器有惰性机制,生成器只有在调用/触发的时候才会执行函数
'''
# list()函数中有for方法,所以list(g)开始调用生成器
'''
以下调用,不管调用顺序怎么变,为什么输出结果顺序不变?
因为生成器是从前往后一个方向执行的,不能向后执行
比如,第一次打印list(g2), 此时开始触发调用生成器g2,根据上面g2生成器代码,会先循环g1,此时触发g1生成器,
'''
print(list(g)) #开始调用g生成器,从生成器g中通过for循环取数据,取出来后生成列表[2, 2, 2],直到取完数据
print(list(g1)) # 开始调用g1生成器,从生成器g中通过for循环取数据,但是g中数据已经在上步取完了,所以g是空的,所以g1生成了一个空列表
print(list(g2)) # 开始调用g2生成器,从生成器g1中通过for循环取数据,但是g1是个空列表,所以g2也生成了一个空列表
# 输出
111
[2, 2, 2]
[]
[]
9.3、最佳场景案例
9.3.1、生成器案例
9.3.1.1、处理大文件(逐行读取,避免加载整个文件到内存):
def read_large_file(file_path):
with open(file_path, 'r') as f:
for line in f: # 文件对象本身是迭代器,此处用生成器进一步封装
yield line.strip()
# 遍历10GB大文件,内存占用始终极低
for line in read_large_file("huge_file.txt"):
process(line) # 逐行处理
简化复杂迭代逻辑(如多步骤数据处理流水线)
def data_pipeline(data):
# 第一步:过滤
filtered = (x for x in data if x > 0)
# 第二步:转换
transformed = (x*2 for x in filtered)
# 第三步:返回结果
yield from transformed # 简化生成器嵌套
for res in data_pipeline([-1, 2, -3, 4]):
print(res) # 4、8(2*2=4,4*2=8)
9.3.1.2、基于可迭代对象和生成器实现:自定义range
class Xrange(object):
def __init__(self, num):
self.__num = num
def __iter__(self):
count = 0
while count < self.__num:
yield count
count += 1
obj1 = Xrange(10)
for i in obj1:
print(i)
9.3.2、迭代器案例
9.3.2.1、处理100万条用户数据的CSV文件。
# 错误方式(列表推导式):
# 一次性加载所有数据到内存
all_users = [line.strip().split(',') for line in open('users.csv')] # 内存爆炸!
# 正确方式(生成器):
def load_users(file_path):
with open(file_path) as f:
for line in f:
yield line.strip().split(',')
# 按需处理数据
user_gen = load_users('users.csv')
for user in user_gen:
if user[2] == 'VIP': # 筛选VIP用户
send_promotion(user)
9.3.2.2、处理大型数据集:如读取大文件时,通过迭代器逐行读取(而非一次性加载整个文件),减少内存占用
class LogFileIterator:
def __init__(self, file_path):
self.file_path = file_path
self.file_obj = None
def __iter__(self):
# 打开文件并返回迭代器自身
self.file_obj = open(self.file_path, "r", encoding="utf-8")
return self
def __next__(self):
if self.file_obj is None:
raise StopIteration("File not opened")
# 读取下一行,文件结束时抛出 StopIteration
line = self.file_obj.readline()
if not line:
self.file_obj.close() # 关闭文件
raise StopIteration
return line.strip() # 去除行尾换行符
ger = LogFileIterator('00.iree.json')
for i in ger:
print(i)
9.3.2.3、基于可迭代对象和迭代器实现:自定义range
class IterObject(object):
def __init__(self, num):
self.__num = num
self.__count = -1
def __iter__(self):
return self
def __next__(self):
self.__count += 1
if self.__count >= self.__num:
raise StopIteration
return self.__count
class Xrange(object):
def __init__(self, max_num):
self.__max_num = max_num
def __iter__(self):
return IterObject(self.__max_num)
obj = Xrange(10)
for i in obj:
print(i)
9.4、迭代器和生成器区别
# 迭代器:
* 定义:
迭代器是实现了迭代协议(__iter__()和__next__()方法)的对象,用于逐个访问集合元素。
* 实现:
需显式定义类并实现协议方法,例如遍历列表的自定义迭代器类。
# 生成器
* 定义:
生成器是通过函数和yield关键字实现的特殊迭代器,无需手动定义类。
* 实现:
函数执行到yield时暂停并返回值,下次调用从暂停处继续。
# 核心特性对比:
* 内存效率:
生成器天然支持惰性求值,每次仅生成一个值,适合处理无限序列或大文件;迭代器若需预加载数据则内存占用较高。
* 代码复杂度:
生成器代码更简洁(如斐波那契数列生成),而迭代器适合需复杂状态管理的场景。
* 单向性:两者均只能向前遍历,不可回退
| 维度 | 迭代器(Iterator) | 生成器(Generator) |
|---|---|---|
| 实现方式 | 需手动定义__iter__()和__next__() | 用 yield 函数或生成器表达式,无需手动实现迭代方法 |
| 状态管理 | 需手动维护迭代状态(如current变量) | 自动保存函数内部状态(无需手动处理) |
| 代码复杂度 | 较高(需处理StopIteration) | 极低(无需手动处理异常) |
| 内存效率 | 惰性计算(较高效) | 惰性计算(同样高效,且更轻量) |
| 适用场景 | 自定义容器迭代逻辑 | 简化迭代逻辑、处理大数据 / 流数据 |
10、python 自省机制
- 自省(Introspection)是指程序在运行时能够检查自身对象的类型、属性、方法等信息的能力
- Python 是一门高度自省的语言,几乎所有对象都可以在运行时被检查和动态操作
# 常用的自省函数
dir(obj):返回对象的所有属性和方法名(列表)
id(obj):返回对象的唯一标识(内存地址,整数)
type(obj):返回对象的类型
help(obj):显示对象的帮助文档
isinstance(obj, cls):判断对象是否是某个类或其子类的实例
hasattr(obj, name):判断对象是否有某个属性/方法
getattr(obj, name):获取对象的某个属性/方法
callable(obj):判断对象是否可调用(如函数、方法、类等)
class Demo:
def foo(self):
pass
obj = Demo()
print(dir(obj)) # 查看所有属性和方法
print(type(obj)) # <class '__main__.Demo'>
print(isinstance(obj, Demo)) # True
print(hasattr(obj, 'foo')) # True
print(callable(getattr(obj, 'foo', None))) # True
print(id(obj)) # 对象的唯一标识
help(obj) # 查看帮助文档
class MyClass:
def __init__(self):
self.attribute = "value"
obj = MyClass()
print(getattr(obj, 'attribute')) # 输出: value
print(hasattr(obj, 'attribute')) # 输出: True
三、面向对象编程
1、面向对象编程相关概念
* 类:用来描述具有相同的属性和方法的对象的集合
* 方法:类中定义的函数
* 类变量:类变量在整个实例化的对象中是公用的。定义在类中且在函数体之外。类变量通常不作为实例变量使用。
* 实例变量:定义在方法中用构造方法初始化的属性叫做实例变量,只作用于当前实例的类。实例变量用于对每个实例都是唯一的数据,类变量用于类的所有实例共享的属性和方法
* 继承:即子类继承父类的属性和方法,可以重用父类的属性和方法。适用于 "是什么,is-a" 的情形。
* 封装:对外部隐藏对象的工作细节,例如,采用 get_name()/set_name()方法来获取/设置对象的名字
* 多态:允许不同类的对象可以通过相同的接口调用方法,从而实现相同的操作。例如,在 Dog类和Cat类中eat()方法,输出可以是不同的信息。
* 方法重用:指的是通过类的继承、组合和模块化等技术,使得代码可以被多个不同的程序或不同的部分重复使用,从而减少代码重复,提升开发效率和代码的可维护性。
* 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖
* 实例化:创建一个类的实例,类的具体对象
* 对象:通过类定义的数据结构实例。对象包括两个属性(数据成员类变量和实例变量)和方法(由函数构成)。
* 组合:在一个类定义中,直接实例化需要的类。如:self.x = Turtle(x)。适用于 "有什么,has-a" 的情形。
2、抽象基类(abc模块)
也称: “元类”
* 抽象基类(abstract base class,ABC):抽象基类就是类里定义了纯虚成员函数的类。纯虚函数只提供了接口,并没有具体实现。
* 抽象基类不能被实例化(不能创建对象),通常是作为基类供子类继承,子类中重写虚函数,实现具体的接口。
* 抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。
from abc import ABCMeta, abstractmethod
class CacheBase(object, metaclass=ABCMeta):
"""
被abstractmethod装饰的抽象方法,在子类必须要重写
"""
@abstractmethod
def get(self,key):
pass
@abstractmethod
def set(self,key,value):
pass
class RedisCache(CacheBase):
def get(self,key):
pass
def set(self,key,value):
pass
r = RedisCache()
r.get('zkc')
class Foo:
def A(self): # 告诉子类应该重新该方法,子类直接调用父类该方法会报错
raise NotImplementedError("sub class must override this method.")
class Bar(Foo):
def A(self):
print("重写了父类的方法")
b = Bar()
b.A()
3、类 和 对象
图3.0

# 什么是对象?
- 对象是内存中用来存储数据的一块区域
- 对象中可以存放各种数据(如:数字、布尔值、代码)
- 对象由三部分组成:
1. 对象标识(id)
2. 对象类型(type)
3. 对象的值(value)
# 面向对象(oop)
- 面向对象编程:语言中的所有操作都是通过一个个对象来进行的。
- 面向过程编程:将程序逻辑分解为一个个步骤,通过对每个步骤的抽象,来完成程序。
# 面向对象编程和面向过程编程的区别:
- 面向过程语言:面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再依次调用,类似流水线的工作原理。
- 面向对象语言:面向对象是把构成问题事务分解成各个对象,依靠各个对象之间的交互推动程序执行,进而实现问题的解决。建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在完整解决问题步骤中的行为。
# 喝可乐为例
* 面向过程:
- 我-起床-走到冰箱前-打开冰箱门-拿出可乐-关闭冰箱门-喝可乐
* 面向对象
- 我-创建对象-命令对象打开冰箱拿出可乐再关闭冰箱后给我-喝可乐
让对象去干活,我只负责创建可以干这个活的对象,操作对象,我不关心中间的过程,命令对象去干活
- 类和对象都是现实生活中的事物或者程序中内容的抽象
* 类 和 对象 是面向对象中的两个重要概念:
(1) 类:是对事物的抽象,比如人类,球类。
(2) 对象:是事物的实例,比如足球,篮球。球类可以对球的特征和行为进行抽象,然后可以实例化一个真实的球体出来。
- 类: 是对一群具有相同 "特征" 或 "行为" 的事物的一个统称,一个模板,是抽象的,不能直接使用
- 特征: 属性
- 行为: 方法
- 对象: 是由类创建出来的一个具体的存在,可以直接使用
- 由哪一个类创建出来的对象,就拥有在哪个类中定义的 "属性" 和 "方法"
* 类对象支持两种操作:属性引用和实例化。
(1)类对象创建后,类命名空间中所有的命名都是有效属性名。
(2)创建对象的过程称之为实例化,当一个对象被创建后,包含三个方面的特性:对象句柄、属性和方法。句柄用于区分不同的对象,对象的属性和方法,与类中的成员变量和成员函数对应。
(3)实例化:将类对象看作是一个返回新的类实例的无参数函数。
3.1、 类的定义
图3.1

# 上图中,p1 和 p2 是两个实例化对象,Person是类对象
p1 和 p2 中没有 name 属性
- 如图3.0,当使用 a = p1.name 时,编辑器先从当前实例对象中寻找name属性,没找到的话再去该实例的类对象中寻找,所以 a='swk'
- 如图3.1,当使用 p1.name = 'zbj'时,此时是在p1对象中设置name属性的值,因为p1中原先没有name属性,所以会新增一个name属性。再使用 a = p1.name 时,获取到的a = 'zbj'。因为编辑器在当前对象中找到了name属性
- 当使用 b = p2.name 时,编辑器还是从当前实例对象找,没有找到的话再去当前实例对象的类对象中寻找。因为刚才只改了 p1实例对象中的 name属性,p2中的没变,所以 b = 'swk'
# 类设计:
1. 类名:这类事物的名字,满足大驼峰命名法
2. 属性:这类事物具有什么样的特征
3. 方法;这类事物具有什么样的行为
- 类对象 和 实例对象都可以保存属性(方法)
- 如果这个属性(方法)是所有实例共享的,则应该将其保存到类对象中
- 如果这个属性(方法)是某个实例独有的,则应该将其保存到实例对象中
- 一般情况下:
1. 属性保存在实例对象中
2. 方法保存在类对象中
3.2、创建对象基本流程
- 创建对象的流程
- p = Person() 的运行流程
1. 创建一个变量
2. 在内存中创建一个对象
3. 执行init方法
4. 将对象id赋值给变量
- 魔术方法使用:
-什么时候调用?
init方法在对象创建以后立刻执行
-作用是什么?
init可以通过self对象向新建的对象中初始化属性
4、类封装
* 封装是面向对象编程(OOP)的三大特性之一:
- 隐藏对象中一些不希望被外部访问到的属性或方法
# 使用封装,确实增加了类定义的复杂程度,但是他也确保了数据的安全性
* 隐藏了属性名,调用者无法随意修改对象中的属性
* 增加getter、setter方法,很好的控制属性是否是只读的。如果属性只读,去掉setter方法,只写是同理的操作
* 使用setter方法可以进行数据的正确性验证等其他方面的操作
* 使用封装的主要好处是,有助于将对象的内部状态与外部接口隔离开来,增强代码的安全性、可维护性和灵活性。在实际开发中,善于利用封装可以显著提高代码质量。
- 定义一个类,简单演示类的封装特性
class MyClass:
name = '程序员' # 公共属性
msg = 'I Love Python'
__score = 0 # 私有属性,类外部无法直接进行访问
def __init__(self,name,msg,score): # 构造函数
self.name = name
self.msg = msg
self.__score = score
def get_name(self): # 公共方法,获取name属性
return self.__name
def set_name(self, name): # 公共方法,设置name属性
self.__name = name
def get_score(self): # 公共方法,获取私有属性
return self.__score
def set_score(self, score): # 公共方法,设置私有属性
if 0 < score < 120:
self.__score = score
obj = MyClass("全栈工程师修炼指南","人生苦短,及时Python",256) #实例化
print("姓名:",obj.get_name()) # 获取name属性
print("分数:",obj.get_score()) # 获取score属性
obj.set_score(120) # 设置score属性值
print("设置后的分数:",obj.get_score()) # 获取修改后的score属性
############ 执行结果 ##############
姓名: 全栈工程师修炼指南
分数: 256
设置后的分数: 64
4.1、属性权限(私有、公有)
* 公有属性:在类中定义,可以在类中和类外调用,可以通过析构函数进行初始化
* 私有属性调用方法:用公有方法返回
# _开头:类似 protected
# __开头:类似 private
- python将双下划线开头的属性或方法进行重命名:_类名__属性,_类名__方法(), 所以在类外部无法通过 对象.__属性/__方法()进行访问调用。
- 内置属性--由系统在定义类的时候默认添加的由前后双下划线构成,如__dic__,__module__
- 私有变量:
1、表面私有:_单个下划线开头,声明该属性是私有的,受保护的。虽然可以直接在外部访问,但是约定俗成,不建议在类的外部调用。
2、实际私有:__两个下划线开头,声明该属性是私有的。不能在类的外部直接被调用。可以通过instance._classname__attribute方式访问 t._Test__security_key
- 私有方法:
1、表面私有:_单个下划线开头,声明该方法是私有的,受保护的。虽然可以直接在外部访问,但是约定俗称,不建议在类的外部调用。
2、实际私有:__两个下划线开头,声明该方法是私有的。不能在类的外部直接被调用。可以通过instance._classname__menthod()调用 t._Test__private_func()
PRIVATE_PASSWD = "xyzsqwedasadas"
class Test:
count = 0 # 静态属性
_age = 19 # 单下划线表示受保护的,虽然可以在外部访问,但是约定俗成,不建议在外部直接访问
__private_passwd = "123456" # 私有属性
public_name = "zkc" # 公共属性
def __init__(self):
Test.count += 1 # 访问静态属性
self.__security_key = "1111"
self.__number = 0
def __private_func(self, passw):
"""私有方法"""
print("this is private func....", self)
str1 = base64.b64encode(passw.encode('utf-8')).decode('utf-8')
return str1
def set_number(self, number):
self.__number = number
def get_number(self):
return self.__number
def public_func(self):
print("1、this is publish func...")
username = self.public_name # 在内部调用公共属性
print("2、内部调用类公共属性: {}".format(username))
passwd = self.__private_passwd # 在内部直接调用私有属性
print("3、内部调用类私有属性: %s" % passwd)
print("4、内部调用类私有方法", self.__class__)
self.__private_func(PRIVATE_PASSWD) # 在内部直接调用私有方法
secret = self.__security_key # 在内部调用私有实例变量
print("5、内部调用实例私有属性: %s" % secret)
if __name__ == '__main__':
t = Test()
t.public_func()
print(t._Test__number)
t.set_number(10)
a = t.get_number() # 通过公有方法访问私有变量
print(a)
print("6、外部调用私有实例变量", t._Test__security_key) # 在类的外部调用私有实例变量
print("7、外部调用私有类变量", t._Test__private_passwd) # 在类的外部调用私有类变量
print("8、外部调用私有类方法")
aa = t._Test__private_func('ZKCLASDHIWIQEQW') # 在类的外部调用私有方法
print(aa)
class Person:
def __init__(self, name):
self._name = name
self.__age = 19
def __greet(self):
print(f"hello,{self._name}, nice to meet you")
class Student(Person):
def __init__(self, name):
super().__init__(name)
s = Student("emma")
s.__greet() # 会报错,因为greet()方法是私有的,子类不能继承
print(s.__age) # 会报错,因为age属性是私有的,子类不能继承
4.2、property 装饰器
* 将方法转换成相同名称的只读属性。
* 可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。既可以保护类的封装特性,又能让开发者可以使用 “对象.属性” 的方式操作操作类属性
'''
将一个方法方法转换为只读属性,提供一种更直观的方法来访问和修改属性
'''
class Mytest:
def __init__(self):
self._value = 0
# 将方法定义成只读属性
@property
def value(self):
return self._value
# 修改属性时调用的方法
@value.setter
def value(self, num):
if num < 0:
pass
else:
self._value = num
# 删除属性时调用的方法
@value.deleter
def value(self):
del._value
class People:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def age(self):
return self._age
@age.deleter
def age(self, age):
del._age
@age.setter
def age(self, age):
if age < self._age:
raise ValueError("new age is not less than old age.")
self._age = age
5、类继承
* 面向对象的三大特性之一:继承
# 继承:
子类继承父类"非私有的属性和方法",还可以重写父类的属性和方法。还可以扩展子类的属性和方法。 适用于 "是一个" 的情形。
父类:要更加抽象方法和抽象类
* 调用类中的成员时,遵循:
- 优先在自己所在类中找,没有的话则去父类中找。
- 如果类存在多继承(多个父类),则先找左边再找右边。
# 继承的好处:
1. 通过继承,子类可以直接获取到父类的属性或方法,避免编写重复性的代码,符合OCP原则
2. 通常使用继承来扩展一个类
# 特别注意:
* 利用继承和组合机制来进行扩展时候,在类名/属性名(用名词)/方法名(用动词)的命名建议要规范化
* 私有属性就是在属性或方法名字前边加上双下划线,从外部是无法直接访问到并会显示AttributeError错误
* 当你把类定义完的时候,类定义就变成类对象,可以直接通过“类名.属性”或者“类名.方法名()”引用或使用相关的属性或方法。
* 类中属性名与方法名一定不要一致,否则属性会覆盖方法,导致BUG的发生;
class People:
def __init__(self, name, age):
self._name = name
self._age = age
class Teacher(People):
def __init__(self, name, age, title):
# 下面三种都是调用父类的初始化方法
super().__init__(name, age)
super(Teacher, self).__init__(name, age)
People.__init__(self, name, age) # 不常用
self._title = title
5.1、单继承
- 单继承:一个类只继承一个父类,简单且常见。
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
# 父类
class Person:
name = '' # 基本属性
__UID = 0 # 私有属性
# 构造函数
def __init__(self, *args):
self.name = args[0]
self.__UID = args[1]
# 类方法
def get_person_info(self):
print("人员信息: %s , %d" %(self.name,self.__UID))
# 子类
class Student(Person):
grade = ''
def __init__(self, *args):
# 调用父类的构造函数
Person.__init__(self,args[0],args[1])
self.grade = args[2]
def get_student_info(self):
print("姓名: %s , 年级: %s ,身份证号:%d " %(self.name,self.grade,self._Person__UID))
# 实例化类,生成对象
xs = Student('经天纬地',512301198010284307,'2018级')
# 调用父类的方法
xs.get_person_info()
# 调用自身类方法
xs.get_student_info()
# 执行结果:
人员信息: 经天纬地 , 512301198010284307
姓名: 经天纬地 , 年级: 2018级 ,身份证号:512301198010284307
5.2、多继承
- 多重继承:一个类可以继承多个父类,可以组合多个父类中的功能,但继承关系复杂。
# 多重继承使用不当会导致重复调用(也叫钻石继承、菱形继承)的问题,导致代码执行效率低
super():这是一个内置函数,用于调用父类的方法。它返回一个临时对象,该对象允许你调用父类的方法。
MRO:Python使用C3线性化算法来确定类的MRO,这决定了在多重继承的情况下方法的查找顺序。C3 算法确保同一个类只会被搜寻一次
方法解析顺序(Method Resolution Order,MRO)
'''
例如,类A,B分别表示不同的功能单元,C为A,B功能的组合,这样类C就拥有了类A, B的功能
'''
class A:
nameA = "a"
def __init__(self,*args):
self.nameA = args[0]
print("Class A", args[0])
def get_a(self):
print(self.nameA," -> ",self.__class__)
class B:
nameB = "b"
def __init__(self,*args):
self.nameB = args[0]
print("Class B", args[0])
def get_b(self):
print(self.nameB," -> ",self.__class__)
# 例子中,C 类同时继承了 A 和 B 类,并可以使用这两个父类中的方法。
class C(A, B):
name = ""
def __init__(self,*args):
# 调用 A 类构造函数
A.__init__(self,args[0])
# 调用 B 类构造函数
B.__init__(self,args[1])
self.name = args[2]
print("Class C", args[2])
def get_c(self):
print(self.name," -> ",self.__class__)
# 实例化
c = C("父类 A","父类 B","类 C")
# 调用多重继承的方法
c.get_a()
c.get_b()
c.get_c()
############### 执行结果 ##############
Class A 父类 A
Class B 父类 B
Class C 类 C
父类 A -> <class '__main__.C'>
父类 B -> <class '__main__.C'>
类 C -> <class '__main__.C'>
class Father(object):
"""
多重继承的顺序不同:
1、调用所有父类都有的方法时,只调用第一个父类的方法。如调用了Father中的eat方法。如果把Father与Musician交换位置,则调用Musician中的eat
2、调用所有父类都有的方法时,只调用第一个父类的方法。与调用的父类初始化方法的顺序无关,与子类继承的父类参数顺序有关
3、调用各个父类中不同的方法时,按照调用的方法名称进行依次调用
"""
def __init__(self, name):
self._name = name
def eat(self):
print("%s 在大吃大喝" % self._name)
def gamer(self):
print("%s 正在玩游戏" % self._name)
class Mather(object):
def __init__(self, name):
self._name = name
def eat(self):
print("%s 在吃水果" % self._name)
def song(self):
print("%s 正在唱歌" % self._name)
class Musician(object):
def __init__(self, name):
self._name = name
def eat(self):
print("%s 在细嚼慢咽" % self._name)
def piano(self):
print("%s 正在弹钢琴" % self._name)
class Son(Father, Mather, Musician):
def __init__(self, name):
super().__init__(name) # super()函数调用父类初始化方法时,只调用第一个父类的初始化方法
def eat(self):
print("%s 正在吃东西" % self._name)
super().eat()
son = Son("小明")
son.eat()
print(Son.__mro__)
class A(object):
def __init__(self):
print("进入A…")
print("离开A…")
def foo(self):
print("foo of A=======", self)
class B(A):
def __init__(self):
print("进入B…")
super().__init__() # 超类
print("离开B…")
def foo(self):
print("foo of B=======", self)
class C(A):
def __init__(self):
print("进入C…")
super().__init__() # 超类
print("离开C…")
def foo(self):
print("foo of C=======", self)
class D(B, C):
def __init__(self):
print("进入D…")
super().__init__() # 超类
print("离开D…")
pass
class E(D):
def __init__(self):
print("进入E…")
super().__init__() # 根据MRO依次调用父类的初始化方法
print("离开E…")
def foo(self):
print("foo of E=======", self)
super().foo() # 调用父类 foo(),但是父类是 D,它没有 foo(), 所以会继续向上找, 找到 B 的 foo()
super(B, self).foo() # super()中的参数是子类和self,用于确定从哪里开始查找。根据MRO, 会从B的下一个类开始找,即C的foo(),没有则继续从C的下一个类寻找
super(C, self).foo() # super()中的参数是子类和self,用于确定从哪里开始查找。根据MRO, 会从C的下一个类开始找,即A的foo(),没有则继续从A的父类开始寻找
if __name__ == '__main__':
print("================== 菱形继承 ====================")
d = D()
d.foo()
e = E()
e.foo()
print(E.__mro__)
# 输出:
================== 菱形继承 ====================
进入D…
进入B…
进入C…
进入A…
离开A…
离开C…
离开B…
离开D…
foo of B======= <__main__.D object at 0x000001EBFDB6FD60>
进入E…
进入D…
进入B…
进入C…
进入A…
离开A…
离开C…
离开B…
离开D…
离开E…
foo of E======= <__main__.E object at 0x000001EBFDB6FD00>
foo of B======= <__main__.E object at 0x000001EBFDB6FD00>
foo of C======= <__main__.E object at 0x000001EBFDB6FD00>
foo of A======= <__main__.E object at 0x000001EBFDB6FD00>
(<class '__main__.E'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
5.3、钻石继承/菱形继承
# 问题:
如何避免钻石继承(菱形继承)问题,在多重继承中,如果不正确使用super(),可能会导致某些父类的方法没有被调用?
# 原因:
Python的MRO决定了方法的调用顺序,如果super()没有按照MRO正确调用,就会遗漏某些父类的初始化。
# 解决方法:
始终使用super()来调用父类的方法,并确保理解当前类的MRO。
方法解析顺序(Method Resolution Order,MRO)
可以使用类名.__mro__来查看MRO顺序。
print(C.__mro__) # 输出类的MRO顺序,帮助你理解方法调用的流程
print(mro(C)) # 输出类的MRO顺序,帮助你理解方法调用的流程
class A(object):
def __init__(self):
print("进入A…")
print("离开A…")
def foo(self):
print("foo of A=======", self)
class B(A):
def __init__(self):
print("进入B…")
super().__init__() # 超类
print("离开B…")
def foo(self):
print("foo of B=======", self)
class C(A):
def __init__(self):
print("进入C…")
super().__init__() # 超类
print("离开C…")
def foo(self):
print("foo of C=======", self)
class D(B, C):
def __init__(self):
print("进入D…")
super().__init__() # 超类
print("离开D…")
pass
class E(D):
def __init__(self):
print("进入E…")
super().__init__() # 超类
print("离开E…")
def foo(self):
print("foo of E=======", self)
super().foo()
super(B, self).foo()
super(C, self).foo()
if __name__ == '__main__':
print("================== 菱形继承 ====================")
d = D()
d.foo()
e = E()
e.foo()
print(E.__mro__)
class A:
def __init__(self):
self.n = 2
def add(self, m):
# 第四步
# 来自 D.add 中的super
# self==d, self.n==d, n==5
print('self is {0} @A.add'.format(self))
self.n += m
# d.n==7
class B(A):
def __init__(self):
self.n = 3
def add(self, m):
# 第二步
# 来自 D.add 中的 super
# self==d, self.n==d, n==5
print('self is {0} @B.add'.format(self))
# 等价于 super(B, self).add(m)
# self 的 MRO 是[D, B, C, A, object]
# 从 B 之后的[C, A, object]中查找 add 方法
super().add(m)
# 第六步
# d.n=11
self.n += 3
# d.n=14
class C(A):
def __init__(self):
self.n = 4
def add(self, m):
# 第三步
# 来自 B.add 中的 super
# self==d, self.n==d, n==5
print('self is {0} @C.add'.format(self))
# 等价于 super(C, self).add(m)
# self 的 MRO 是[D, B, C, A, object]
# 从 C 之后的[A, object] 中查找 add 方法
super().add(m)
# 第五步
# d.n=7
self.n += 4
# d.n=11
class D(B, C):
def __init__(self):
self.n = 5
def add(self, m):
# 第一步
print('self is {0} @D.add'.format(self))
# 等价 super(D, self).add(m)
# self 的 MRO 是[D, B, C, A, object]
# 从 D 之后的[B, C, A, object]中查找 add 方法
super().add(m)
# 第七步
# d.n=14
self.n += 5
# self.n=19
d = D()
d.add(2)
print(d.n)
print(D.mro())
# 输出
self is <__main__.D object at 0x0000020A2C48A400> @D.add
self is <__main__.D object at 0x0000020A2C48A400> @B.add
self is <__main__.D object at 0x0000020A2C48A400> @C.add
self is <__main__.D object at 0x0000020A2C48A400> @A.add
19
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
- 调用过程图如下:

6、组合
* Python 继承机制很有用,但容易把代码复杂化以及依赖隐含继承。因此经常的时候,我们可以使用组合来代替。
* 通过将一个类的实例作为另一个类的属性来实现代码重用,在类定义中把需要的类放进去实例化
* 简单的说: 组合用于 "有一个" 的场景中,继承用于 "是一个" 的场景中
#乌龟类
class Turtle:
def __init__(self, x):
self.num = x
# 鱼类
class Fish:
def __init__(self, x):
self.num = x
# 水池类
class Pool:
def __init__(self, x, y):
self.turtle = Turtle(x) # 组合乌龟类进来 (关键点)
self.fish = Fish(y) # 组合鱼类进来 (关键点)
def print_num(self):
print("水池里总共有乌龟 %d 只,小鱼 %d 条!" % (self.turtle.num, self.fish.num))
# 实例化类
pool = Pool(1, 10)
# 调用方法
pool.print_num() # 结果: 水池里总共有乌龟 1 只,小鱼 10 条!
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self, brand):
self.brand = brand
self.engine = Engine() # 组合
def start(self):
return f"{self.brand} car: {self.engine.start()}"
car = Car("Toyota")
print(car.start()) # 输出: Toyota car: Engine started
7、super()方法
# 下面三种都是调用父类的初始化方法
* super(子类,self).__init__(参数1,参数2, ....)
* 父类名称.__init__(self, 参数1, 参数2, ...) # 这种不常用
* super().__init__(参数1, 参数2, ....)
注意:
* 子类不重写 __init__,实例化子类时,会自动调用父类定义的 __init__。
* 重写了__init__ 时,实例化子类,就不会调用父类已经定义的 __init__
* 如果重写了__init__ 时,要继承父类的构造方法,可以使用 super 关键字
* super(子类,self).__init__(参数1,参数2,....)
* 父类名称.__init__(self,参数1,参数2,...)
* super().__init__(参数1,参数2,....)
1. 因为正常我们通过"类名.方法名"这种硬编码这种方式调用父类中的方法,一旦父类的名字发生改变,那么我们所有引用到地方全部要进行修改,代码维护性较差。而且通过父类名+方法名这种方法,在调用其方法时,传参的时候需要再传入一个self参数。
2. 通过super().方法名 这种软编码的方法引用父类,如果父类名称发生变化,我们后面的代码不需要进行更新,因为super()会自动解析父类的信息。
3. super()在复杂的继承关系中,不是调用父类中方法,而是按照mro算法来进行调用的, 想在实例方法中调用父类的方法
#解释 super(子类, self).方法()
super()中的参数是子类和self,查找顺序是从super参数中子类的下一个类(父类)开始寻找,找到则调用执行,找不到则继续从父类的下一个类寻找,以此类推,直到找不到后报错
在多继承中,self的使用非常重要,self切记是跟调用方法的对象绑定的,谁调用方法,谁就是self对象,并且会随着super(子类名,self)传递到父类的方法中
8、类多态
# 面向对象编程三大特性之一:多态
- 多态(Polymorphism) 是指一种对象能够以多种形式出现的能力。具体来说,多态允许不同类的对象可以通过相同的接口调用方法,从而实现相同的操作,提高了代码的灵活性和可扩展性。
- 所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态。可能大家不太好理解,就是定义的时候我不知道调用谁,只有运行的时候才知道调用谁
- 允许不同类的对象可以通过相同的接口调用方法,从而实现相同的操作。例如,在 Dog类和Cat类中eat()方法,输出可以是不同的信息
# len()
# 之所以一个对象能通过len()来获取对象长度,是因为对象中有一个特殊方法__len__
# 也就是说,只要对象中具有__len__特殊方法,就可以通过len()来获取他的长度
# 与对象的类型无关,只要有__len__这个魔术方法即可
my_list = [1, 2, 3, 4] # 列表对象中有__len__()
my_str = "hello" # 字符串对象中有__len__()
len(my_list)
len(my_str)
# 自创对象验证 多态
class Person(object):
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
self._name = new_name
# 自定义__len__()方法
def __len__(self):
print("这是我自定义的__len__()")
return 10
p = Person('zkc')
print(len(p))
# 输出
10
这是我自定义的__len__()
8.1、 方法重写
在 Python 继承机制中, 子类可以重写父类的属性或方法, 来达到当父类方法功能不足时可自定义扩展的目的。
# 多态通常通过以下几种方式实现:
* 方法重写(Method Overriding):子类重写父类的方法,并在运行时根据对象的实际类型调用相应的方法。
* 父类方法的功能不能满足你的需求,可以在子类重写你父类的方法
* 派生类对基类的方法重写,重写后的基类方法叫做非捆绑方法,不能直接调用,需要使用super函数。
import random as r
class Animal:
def __init__(self):
self.x = r.randint(0,10)
self.y = r.randint(0,10)
def eat(self):
print("前往 Fishc 位置坐标({},{}), Shark 动物开始进食".format(self.x,self.y))
class Fish:
def __init__(self):
self.x = r.randint(0,10)
self.y = r.randint(0,10)
def move(self):
self.x -= 1
print("Fish 位置坐标是:",self.x,self.y)
class Shark(Animal,Fish):
def __init__(self):
super().__init__() # 方法1:设置super() 指代 父类
#Fish.__init__(self) # 方法2: 调用父类构造方法
self.hungry = True
# 覆盖(重写) Animal 父类的 eat() 方法
def eat(self):
if self.hungry:
print("饿了,要吃饭@!")
self.hungry = False
else:
print("太撑了,吃不下了")
# 实例化类
obj = Shark()
# 调用父类方法
obj.move()
obj.eat()
# 使用 super 函数,用子类对象调用父类已被覆盖的方法。
super(Shark,obj).eat()
obj.eat()
obj.move()
######### 执行结果 ########
Fish 位置坐标是: 8 5
饿了,要吃饭@!
前往 Fishc 位置坐标(8,5), Shark 动物开始进食
太撑了,吃不下了
Fish 位置坐标是: 7 5
class Animal:
def sound(self):
return "Some sound"
class Dog(Animal):
def sound(self):
return "Bark"
class Cat(Animal):
def sound(self):
return "Meow"
def make_sound(animal):
print(animal.sound())
dog = Dog()
cat = Cat()
make_sound(dog) # 输出: Bark
make_sound(cat) # 输出: Meow
8.2、鸭子类型
* 鸭子类型(Duck Typing):如果一个对象“看起来像鸭子,叫起来像鸭子”,那么它就可以被视为鸭子,即"无需显式继承",只要实现了所需的方法即可。
"""
Dog、Cat 和 Bird 类并没有继承同一个父类,但由于它们都实现了 sound 方法,因此它们可以被传入 make_sound 函数。
这就是鸭子类型的一个例子:如果一个对象实现了某个方法,它就可以被当作该方法的类型处理。
"""
class Dog:
def sound(self):
return "Bark"
class Cat:
def sound(self):
return "Meow"
class Bird:
def sound(self):
return "Chirp"
def make_sound(animal):
print(animal.sound())
dog = Dog()
cat = Cat()
bird = Bird()
make_sound(dog) # 输出: Bark
make_sound(cat) # 输出: Meow
make_sound(bird) # 输出: Chirp
8.3、猴子补丁
猴子补丁:
一、在内存中发挥作用,不会修改源码,因此只对当前运行的程序实例有效
二、添加的功能函数,如果要用实例调用,则函数第一个参数为self。如果用类名调用,则不需要self参数
主要作用:
1、在运行时替换方法、属性
2、在不修改源码的情况下,对程序本身添加之前没有的功能
3、在运行时的对象中添加补丁,而不是在磁盘的源代码上
4、新增的函数参数可以带self.具体已经是否需要调用源码类中的属性和方法
class Bird:
count = 1000
def fly(self, name):
print(f"a {name} bird is flying...")
def eat(self, food_name):
if self.number > 0:
print(f"about {self.number} birds is eating {food_name}...")
else:
print("no eat")
def sum_num(a, b):
return a+b
class MonkeyJson:
@staticmethod
def json_test():
""" 猴子补丁实测 """
data = """{"name": "kid","age": 18}"""
text = json.loads(data)
print(data)
context = json.dumps(text)
print(context)
print("=========================================== 调用猴子补丁 ========================================")
Bird.number = 4 # 相当于给Bird类增加了一个属性,二调用方式和类的内部定义的属性和方法一样
Bird.sum_num = sum_num(1, 2)
Bird.eat = eat # 相当于给Bird类增加了一个eat方法,而调用方式和类的内部定义的属性和方法一样
b = Bird()
b.fly('kid')
b.eat('eagle')
print(b.sum_num)
import os
import json
import ujson
from duck_monkey import Bird, MonkeyJson
from practice2_extend import Turtle
from practice6_private import Test
print("====================================== 猴子补丁新增函数 ================================================")
def plus_func(self) -> str:
self.name = "zkc"
print("猴子补丁,新增的一个方法", self.__class__)
print("猴子补丁,新增的一个方法,在新方法中获取源码中的属性", self.count)
return "添加成功"
def count_age(self) -> int:
age = self._Turtle__turtle_age
if age > 1000:
print("海归的年龄大于1000岁")
else:
age += 500
print("海归的年龄小于1000岁")
return age
def plus_func_test(self):
passwd = self._Test__private_passwd
return self._Test__private_func(passwd)
def plus_func_1(self):
print("aaaa")
def plus_func_2():
print("bbb")
def monkey_patch_json():
json.__name__ = "ujson"
json.loads = ujson.loads
json.dumps = ujson.dumps
if __name__ == '__main__':
print("============== 猴子补丁新增函数测试1 ==============")
Bird.plus_func = plus_func # 给Bird 类新增了一个功能函数,self为原始类的实例化对象
c = Bird()
c.fly("kkkkk")
c.plus_func() # 调用新增的函数
print(c.count)
c.count = 2000
print(c.count)
print(c.name)
print("============== 猴子补丁新增函数测试2 ==============")
Turtle.count_age = count_age
t = Turtle(1)
age = t.count_age()
print(age)
print("============== 猴子补丁新增函数测试3 ==============")
Test.plus_func_test = plus_func_test
test = Test()
secret_pass = test.plus_func_test()
secret_key = test._Test__security_key
print(f"秘钥是: {secret_key}, 密码是: {secret_pass}")
print(test._age)
print("============== 猴子补丁新增函数测试4 ==============")
Test.plus_func_1 = plus_func_1
test = Test()
test.plus_func_1()
print("============== 猴子补丁新增函数测试5 ==============")
Test.plus_func_2 = plus_func_2
Test.plus_func_2()
print("============== 猴子补丁新增函数测试6 ==============")
MonkeyJson.monkey_patch_json = monkey_patch_json
MonkeyJson.monkey_patch_json()
MonkeyJson.json_test() # 使用ujson进行json序列化和反序列化
9、类属性和类方法
- 类的方法与普通的函数只有一个特别的区别:
1. 在类的内部,使用 def 关键字来定义一个方法
2. 与一般函数定义不同,类方法必须包含参数self, 且为第一个参数,self 代表的是类的实例对象本身
- 注意:
方法调用时,第一个参数由解析器自动传递,所以定义方法时,至少要定义一个形参(self)
* self: 表示当前正在调用该方法的对象,与实例对象进行绑定,不需要主动传参,调用实例方法时,解析器会自动将self传递给方法参数
- 类中定义的属性和方法都是公共的,任何该类实例都能访问
- 属性和方法查找的流程:
- 当我们调用一个对象属性时,解析器会先在当前对象中寻找是否有该属性
- 如果有,则返回当前对象的属性值
- 如果没有,则去当前对象的类对象中寻找,如果找到则返回类对象的属性值,如果没有则报错
* 变量访问顺序:
- 实例对象可以访问实例属性和类属性,从内-外都可以访问
- 当实例对象中没有要访问的属性时,会去类对象中查找,若有则返回,若没有,则报错
- 类对象只能访问类属性
* 类变量:属于类,可以被所有对象共享,一般用于给对象提供公共数据(类似于全局变量)。
- 访问类变量:类.变量名 和 实例对象.变量名
- 修改类变量:类.变量名=XXX
* 实例变量:在__init__()方法中定义的变量
- 访问实例变量:实例对象.变量名,不能使用类.变量名来访问
- 修改实例变量:实例对象.变量名=XXX
* 类方法:
- 使用场景:大多数情况下用来 创建对象,初始化实例属性,实例化对象。一般用来做实例化对象
"""
1、如果方法内部涉及到实例对象属性的操作,建议用普通方法;
2、如果方法内部没有操作实例属性的操作,仅仅包含一些工具性的操作,建议使用静态方法;
3、如果需要对类属性,即静态变量进行限制性操作,则建议使用类方法
三种函数说明:
【普通方法(实例方法)】:
第一个参数self参数,代表实例对象本身,可使用self直接引用定义的实例属性和普通方法。如果需要调用 静态方法 和 类方法,使用 类名.方法名() 调用
【静态方法】:
使用 类名.静态变量 来引用静态变量,使用 类名.方法名() 来引用静态方法和其他类方法。如要引用普通方法,需要先实例化一个对象,再去调用普通方法
【类方法】:
第一个参数cls参数,代表类本身(等价)。通过 cls.静态变量 或 类名.静态变量 引用静态变量。利用 cls.方法名() 或者 类名.方法名() 来引用静态方法和类方法,若要调用普通方法,则需要先实例化一个对象,再去调用普通方法
建议:
1、静态函数 和 类函数 一般建议使用 类名.方法名()。当然也可以使用 实例对象名.方法名()调用。但是用类名调用的话,不需要实例化对象,降低代码运行和维护的开销
2、不需要访问实例成员, 但是 需要访问类成员时,建议使用 @classmethod 将方法定义为类方法
3、不需要访问实例成员,也不需要访问类成员时,建议使用 @staticmethod 将方法定义为静态方法
"""
class Person(object):
country = "中国"
def __init__(self, name, age):
self.name = name
self.age = age
print(Person.country) # 中国
p1 = Person("小峰",20)
print(p1.name) # 小峰
print(p1.age) # 20
print(p1.country) # 中国
p1.name = "root" # 在对象p1中将name重置为root
p1.num = 19 # 在对象p1中新增实例变量 num=19
p1.country = "china" # 在对象p1中新增实例变量 country="china"
print(p1.country) # china,优先访问实例中的属性,没有再去类对象中寻找
print(Person.country) # 中国
Person.country="china" # 修改类对象的属性值
print(Person.country) #china
data = {
"name": "Alice",
"age": 30,
"hobbies": ["reading", "coding"],
"is_student": False
}
class Person:
def __init__(self, name):
self.name = name
def greet(self):
print(f"hello,{self.name}, nice to meet you")
@classmethod
def from_dict(cls, content):
name = content['name']
return cls(name) # 实例化并返回实例对象,用的最多
@classmethod
def from_dict1(cls, content):
name = content['name']
x = cls(name)
x.age = 19 # 创建实例对象后,还可以进行实例属性的初始化操作
return x
p = Person("Bob")
p.greet() # 输出: hello, Bob, nice to meet you
p_1 = p.from_dict(data) # 调用类方法返回实例化对象
print(p_1)
p_1.greet()
p_2 = Person.from_dict(data) # 调用类方法返回实例化对象
print(p_2)
p_2.greet()
class A(object):
'''
类属性:
1、可以通过类或者类的实例访问
2、只能通过类对象修改,实例对象无法修改
'''
count = 0
def __init__(self, name):
'''
实例属性:
1、通过实例对象添加的属性,如 a.sex = 'man'
2、通过self添加的属性
3、只能实例对象访问,类对象无法访问
'''
self._name = name
self._age = 19
def test(self):
'''
实例方法:
1. 在类中定义,第一个参数为self
2. 实例方法可以通过实例对象和类对象调用
2.1 通过实例调用对象时,Python会自动将实例对象传入给实例方法的第一个参数self,不用显式传递self参数
2.2 通过类对象调用时,不会自动传递self,必须手动传递self
'''
print("这是实例方法...")
@classmethod
def test2(cls):
'''
类方法:
1. 使用 @classmethod 装饰器修饰的方法
2. 在类中定义,第一个参数为cls,会被自动传递,就是当前类对象
3. 类方法可以通过实例对象和类对象调用,建议一般使用类对象调用,可以省略实例化过程
'''
print('这是类方法...')
@staticmethod
def test3():
'''
静态方法:
1. @staticmethod 修饰,不需要指定任何参数
2. 静态方法可以通过实例对象和类对象调用,建议一般使用类对象调用,可以省略实例化过程
3. 静态方法是一个和当前类无关的方法,只是保存到当前类的函数
4. 静态方法一般是一些工具方法
'''
print("这是静态方法...")
a = A('zkc')
a.sex = 'man' # 通过实例对象添加的属性
a.test() # 通过实例对象调用实例方法, py自动将a对象传递给test()的self参数
A.test(a) # 通过类对象调用实例方法,需要显式传递 实例对象,给实例方法的第一个参数self
a.test2()
A.test2()
a.test3()
A.test3()
class Home(object):
# 房间中人数
_number = 0
@classmethod
def add_person_number(cls):
cls._number += 1
@classmethod
def get_person_number(cls):
return cls._number
@staticmethod
def is_file():
#代码逻辑,不需要访问类成员,也不需要访问实例成员
'''
1. __new__是 Python 中创建实例的特殊方法,它在__init__之前执行,负责创建并返回实例对象
2. super()在这里会查找Home类的父类,即object类
3. object.__new__(self)是创建实例的基础方法,负责在内存中分配空间并返回实例对象
4. __new__是类方法(虽然这里用了self作为参数名,但实际接收的是类),通常规范的参数名应该是cls
5. new() 方法必须返回一个类的实例。如果 new() 不返回任何内容(即返回 None),那么 init() 方法将不会被调用
6. 继承:在子类中使用 new() 时,通常要调用父类的 new() 方法来确保对象被正确创建
'''
def __new__(cls):
Home.add_person_number()
return super().__new__(cls) # 创建并返回Home实例,在子类中使用 new() 时,通常要调用父类的 new() 方法来确保对象被正确创建
class Person(Home):
def __init__(self):
self._name = 'zkc'
# 创建人员对象时,会调用父类Home的__new__()方法
tom = Person()
bob = Person()
test = Person()
print(Home.get_person_number())
10、垃圾回收
# 程序在运行过程中会产生垃圾,会影响到程序的运行性能,所以必须被清理
1. 没有被引用的对象是垃圾
2. 程序执行结束,也会调用 __del__()方法进行垃圾回收
# 垃圾回收:将没被引用的对象从内存中删除
# Python 有自动垃圾回收机制,会自动将这些没有被引用的对象删除,不需要手动处理
class A:
def __init__(self):
self.name = 'A类'
# 特殊方法,在对象被垃圾回收前调用
def __del__(self):
print('A()对象被删除了~~~',self)
a = A()
print(a.name)
# 不加 a = None, 不会执行__del__方法。加上之后会执行
# 将a设置为None,此时A()对象没有被任何变量引用,就变成了垃圾
a = None
input("按回车键退出~~~~")
# 不加的输出结果(按完回车,程序执行结束,也会调用 __del__()方法进行垃圾回收)
A类
按回车键退出~~~~
A()对象被删除了~~~ <__main__.A object at 0x0000019BD2D462F0>
#加的输出结果
A类
A()对象被删除了~~~ <__main__.A object at 0x0000019A2A2E62F0>
按回车键退出~~~~
11、类的魔术方法
Python 中的类中的魔术方法(magic methods),也称为特殊方法或双下方法(dunder methods),是一组特殊的方法,它们以双下划线(__)开头和结尾。这些方法允许我们定制类的行为,包括对象的创建、表示、运算符重载等。
* __init__(self, ...):初始化方法,在创建对象时自动调用。
* __del__(self):析构方法,在对象被垃圾回收时自动调用。
* __new__(cls, ...):创建对象时自动调用,用于自定义对象的创建过程。
* __str__(self):定义当使用 str() 或 print() 函数输出对象时的字符串表示。
* __repr__(self):定义对象的官方字符串表示,通常用于调试。
* __call__(self, ...):定义当对象被调用时自动执行。
* __add__(self, other):定义加法运算符 + 的行为。
* __sub__(self, other):定义减法运算符 - 的行为。
* __mul__(self, other):定义乘法运算符 * 的行为。
* __truediv__(self, other):定义真除法运算符 / 的行为。
* __mod__(self, other):定义取模(求余数)运算符 % 的行为。
* __pow__(self, other):定义幂运算符 ** 的行为。
* __cmp__(self, other):定义比较运算符 <、> 和 == 的行为。
* __getattr__(self, name):在访问不存在的属性时调用。
* __setattr__(self, name, value):在设置属性值时调用。
* __delattr__(self, name):在删除属性时调用。
* __len__(self):定义对象的长度,使用 len() 函数时调用。
* __getitem__(self, key):定义按键获取项目的行为,使用 obj[key] 时调用。
* __setitem__(self, key, value):定义按键设置项目的行为,使用 obj[key] = value 时调用。
* __delitem__(self, key):定义按键删除项目的行为,使用 del obj[key] 时调用。
11.1、对象的创建与销毁
* __new__()函数用来控制对象的生成过程,在对象上生成之前调用。
* __init__()函数用来对对象进行完善,在对象生成之后调用。
* 若__new__()函数不返回对象,就不会调用__init__()函数
new(cls,…):Python 中创建实例的特殊方法,它在__init__之前执行,负责创建并返回实例对象
1. super()在这里会查找Home类的父类,即object类
2. object.__new__(self)是创建实例的基础方法,负责在内存中分配空间并返回实例对象
3. __new__是类方法(虽然这里用了self作为参数名,但实际接收的是类),通常规范的参数名应该是cls
4. new() 方法必须返回一个类的实例。如果 new() 不返回任何内容(即返回 None),那么 init() 方法将不会被调用
5. 继承:在子类中使用 new() 时,通常要调用父类的 new() 方法来确保对象被正确创建
class Home(object):
def __new__(cls):
return super().__new__(cls) # 创建并返回Home实例,在子类中使用 new() 时,通常要调用父类的 new() 方法来确保对象被正确创建
def __new__(cls):
return object.__new__(cls) #分配内存,创建出一个对象。调用父类的__new__()方法确保对象正确创建
init(self, …):初始化方法,在创建对象时自动调用,用于初始化对象的属性。
class MyClass:
def __init__(self, value):
self.value = value
obj = MyClass(10)
del(self):析构方法,在对象被垃圾回收时自动调用,用于清理资源。
class MyClass:
def __del__(self):
print("Object is being deleted")
11.2、对象的表示
'''
__str__(self):
1. 当使用 str() 或 print() 函数输出对象时的字符串表示。
2. 当我们打印一个对象时,实际上打印的是对象中的特殊方法 __str__() 的返回值
3. 在尝试将对象转换为字符串时调用
4. 作用:指定对象转换为字符串的结果(print函数里打印对象时)
__repr__(self):
1. 对象的官方字符串表示,通常用于调试,在对当前对象使用 repr() 函数时调用
2. 作用:指定对象在 "交互模式" 中直接输出的结果
__str__与__repr__的区别:
__str__魔术方法,需要print()输出.
__repr__魔术方法,直接对象输出
'''
class MyClass:
def __init__(self, name, value):
self.name = name
self.value = value
def __str__(self):
return "MyClass [name=%s, value=%d]" % (self.name, self.value)
def __repr__(self):
return f"MyClass({self.value})"
obj = MyClass(10)
print(obj) # 输出: MyClass [name=zkc, value=10]
print(str(obj)) # 输出: MyClass [name=zkc, value=10]
print(repr(obj)) # 输出: MyClass(10)
11.3、比较运算符
'''
__lt__(self, other) 小于
__le__(self, other) 小于等于
__eq__(self, other) 等于
__ne__(self, other) 不等于
__gt__(self, other) 大于
__ge__(self, other) 大于等于
# 在对象之间作比较的时候调用,该方法的返回值将会作为比较的结果
# 第一个参数是self表示当前对象, 第二个参数other表示和当前对象比较的对象
'''
class Person:
def __init__(self, name, value):
self.name = name
self.value = value
def __gt__(self, other):
# 如果写死成 return True/return False ,这样的话总是self>other/self<other
# 所以用具体的数值来做比较
return self.value > other.value
def __lt__(self, other):
return self.value < other.value
p1 = Person('zkc1', 10)
p2 = Person('zkc2', 18)
print(p1 > p2) # False
print(p2 > p1) # True
print(p1 < p2) # True
print(p2 < p1) # False
11.4、运算符重载
add(self, other):定义加法运算符 + 的行为。
sub(self, other):定义减法运算符 - 的行为。
mul(self, other):定义乘法运算符 * 的行为。
truediv(self, other):定义真除法运算符 / 的行为
class MyClass:
def __init__(self, value):
self.value = value
def __add__(self, other):
return MyClass(self.value + other.value)
def __sub__(self, other):
return MyClass(self.value - other.value)
def __mul__(self, other):
return MyClass(self.value * other.value)
def __truediv__(self, other):
return MyClass(self.value / other.value)
obj1 = MyClass(10)
obj2 = MyClass(20)
result = obj1 + obj2 # result 是 执行__add__(self, other)返回的 MyClass(30) 实例化的对象
print(result) # <__main__.MyClass object at 0x0000013FB5027E20>
print(result.value) # 输出: 30
result2 = obj1 - obj2 # result2 是 执行__sub__(self, other)返回的 MyClass(-10) 实例化的对象
print(result2.value) # 输出: -10
result3 = obj1 * obj2 # result3 是 执行__mul__(self, other)返回的 MyClass(200) 实例化的对象
print(result3.value) # 输出: 200
result4 = obj1 / obj2 # result4 是 执行__truediv__(self, other)返回的 MyClass(0.5) 实例化的对象
print(result4.value) # 输出: 0.5
11.5、属性访问
getattr(self, name):在访问不存在的属性时调用。
setattr(self, name, value):在设置属性值时调用。
delattr(self, name):在删除属性时调用。
class MyClass:
def __getattr__(self, name):
return f"{name} attribute not found"
def __setattr__(self, name, value):
print(f"Setting {name} to {value}")
self.__dict__[name] = value
def __delattr__(self, name):
print(f"Deleting {name}")
del self.__dict__[name]
obj = MyClass()
print(obj.some_attribute) # 输出: some_attribute attribute not found
obj.attr = 10 # 输出: Setting attr to 10
del obj.attr # 输出: Deleting attr
11.6、容器类型实现
len(self):定义对象的长度,使用 len() 函数时调用。
getitem(self, key):定义按键获取项目的行为,使用 obj[key] 时调用。
setitem(self, key, value):定义按键设置项目的行为,使用 obj[key] = value 时调用。
delitem(self, key):定义按键删除项目的行为,使用 del obj[key] 时调用。
class MyClass:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
def __getitem__(self, key):
return self.items[key]
def __setitem__(self, key, value):
self.items[key] = value
def __delitem__(self, key):
del self.items[key]
obj = MyClass([1, 2, 3])
print(len(obj)) # 输出: 3
print(obj[1]) # 输出: 2
obj[1] = 10
print(obj.items) # 输出: [1, 10, 3]
del obj[1]
print(obj.items) # 输出: [1, 3]
11.7、上下文管理
enter(self):进入上下文时调用
exit(self, exc_type, exc_val, exc_tb)
class Foo(object):
def __enter__(self):
print("进入了")
return 666
def __exit__(self, exc_type, exc_val, exc_tb):
print("出去了")
obj = Foo()
with obj as data:
print(data)
11.8、函数/方法调用
class Foo(object):
def __call__(self, *args, **kwargs):
print("执行call方法")
obj = Foo()
obj() # 当遇见 对象名() 的时候就会自动执行call方法
四、Python模块
# 模块(module)
Python中,一个py文件就是一个模块,创建模块就是创建一个py文件
# 模块化:
- 将一个完整的程序,分解为多个的小模块,通过将模块组合,搭建出一个完整的程序
# 模块化优点:
1. 方便开发和维护
2. 模块可以复用
# 模块引入
1. import 模块名(py文件的名字,去掉.py)
2. import 模块名 as 别名
- 同一模块可以导入多次,但模块的实例之创建一个
- import可以在程序的任何位置使用,但一般写在程序的开头
- 每个模块内部 都有一个 "__name__"属性,可以显示引用进来的模块的名字
- __name__的属性值为__main__的模块是主模块,一个程序只有一个主模块。用python执行哪个py,哪个就是主模块
- 私有变量/方法不会被引入,如"_name" ,只能在模块内部访问
3. from 模块名 import 变量1, 变量2,...
4. from 模块名 import *
# 举例
1、test.py
print("我是test模块")
print(__name__)
2、excute.py
import test as t
print(t.__name__) # test
print(__name__) # __main__
# 执行谁,谁就是主模块。每个程序只有一个主模块,主入口。
python excute.py
1、time 模块
import time
* time.sleep(n): 让程序暂停执行 n 秒
* time.time(): 获取当前时间的时间戳(从1970年1月1日0点0分0秒开始到现在经过了多少秒)
# 计算时间差
def func()
for i in range(100000):
print(i)
start = time.time()
func()
end = time.time()
print('函数一共执行了%s秒' % (end-start))
2、datetime 模块
from datetime import datetime, date, time
'''
datetime: 年月日,时分秒
date:年月日
time:时分秒
'''
* datetime.now(): 获取当前系统时间
* datetime(2025, 9, 10, 15, 39, 40): 创建指定时间
* datetime.now().strftime('%Y-%m-%d %H:%M:%S'): 将当前时间格式化成字符串
* datetime.strftime(datetime.now(),'%Y-%m-%d %H:%M:%S'): 将时间对象格式化成字符串,第一个参数是时间对象,第二个参数是格式化模板
* datetime.strptime('时间字符串', '%Y-%m-%d %H:%M:%S'): 将字符串格式化成时间对象,第一个参数是时间字符串,第二个参数是格式化模板
* date.today(): 获取当前日期
* date.today(2025, 9, 10): 获取指定日期
* date.today().strftime('%Y年%m月%d日'): 将当前日期格式化成字符串
* date.strftime(today(), '%Y年%m月%d日'): 将当前日期格式化成字符串,第一个参数是日期对象,第二个参数是格式化模板
* date其他功能与datetime类似...
curr_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
curr_date1 = datetime.now().strftime('%Y年%m月%d日 %H时%M分%S秒')
curr_date2 = datetime.strftime(datetime.now(),'%Y%m%d%H%M%S')
curr_date3 = datetime.strftime(datetime(2025, 9, 10, 15, 39, 40),'%Y-%m-%d %H:%M:%S')
print(curr_date) # 2025-09-10 16:55:30
print(curr_date1) # 2025年09月10日 16时55分30秒
print(curr_date2) # 20250910165530
print(curr_date3) # 2025-09-10 15:39:40
# 求时间差,只有时间对象类型可以相减,字符串不能相减
curr_date4 = datetime.strptime('2025-09-10 16:04:35', '%Y-%m-%d %H:%M:%S')
curr_date5 = datetime.strptime('2025-09-11 15:04:35', '%Y-%m-%d %H:%M:%S')
print(curr_date5-curr_date4) # 23:00:00
print(date.today().strftime('%Y年%m月%d日')) # 2025年09月10日
print(date.strftime(today(), '%Y年%m月%d日')) # 2025年09月10日
3、random 模块
import random
* random.random(): 生成(0,1)范围内的随机小数,不包含边界
* random.uniform(m,n): 生成指定范围的随机小数,不包含边界
* random.randint(m,n): 生成指定范围的随机整数,包含边界
* random.choice(list): 从容器中随机选择一个元素
* random.sample(list, n): 从列表中随机抽取n个元素
print(random.random())
print(random.uniform(1, 10))
print(random.randint(1, 10))
print(random.randrange(1, 10))
lst = [1, 2, 3]
print(random.choice(lst))
print(random.sample(lst,2))
print(random.sample(lst,3))
# 随机验证码
def rand_num():
return str(random.randint(0, 9)) # 0-9的随机整数
def rand_upper():
return chr(random.randint(65, 90)) # 随机大写字母
def rand_lower():
return chr(random.randint(97, 122)) # 随机小写字母
def get_verify_code(n=4):
lst = []
for i in range(n):
swich = random.randint(1, 3)
if swich == 1:
s = rand_int()
elif swich == 2:
s = rand_upper()
elif swich == 3:
s = rand_lower()
lst.append(s)
return ''.join(lst)
print(get_verify_code(8))
4、os 模块
os模块主要封装了关于操作系统、文件系统的相关操作,比如创建文件夹、删除文件夹等
__file__ 是 Python 中的一个内置变量,它返回当前脚本的绝对路径(文件的路径)
# os
* os.makedirs('/dirname1/dirname2'): 创建多层递归目录。若文件夹已存在,则报错FileExistError
* os.removedirs('/dirname1/dirname2'): 递归删除空目录。若目录为空,则删除,并递归到上一级目录,如若也为空,则删除。若目录不为空,则报错OSError目录不为空
* os.mkdir('dirname'): 创建单级目录
* os.rmdir('dirname'): 删除单级空目录,若目录不为空,则无法删除
* os.remove('filename'): 删除一个文件
* os.rename('srcname', 'dstname'): 将文件/目录重命名
* os.listdir('dirname'): 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表形式返回文件夹下文件的相对路径
* os.getcwd(): 获取当前工作目录,即当前python脚本工作的目录路径
* os.chdir('dirname'): 改变当前脚本工作目录
# os.path
# 下面函数除过判断的函数,其它函数可使用绝对路径,也可使用相对路径
* os.path.isfile(path): 需要包含文件名的文件的绝对路径,只要路径包含文件名,并且文件要存在,则返回True
* os.path.isdir(path): 需要文件的绝对路径,不包含文件名,并且路径要存在, 则返回True
* os.path.exists(path): 包含文件名的文件的绝对路径/不包含文件名的绝对路径,都返回True
* os.path.isabs(path): 如果path是绝对路径,则返回True
* os.path.splitext(path): 获取文件扩展名,返回元组,元组第一个元素是文件路径,第二个元素是文件扩展名
* os.path.split(path): 获取文件名,返回元组,元组第一个元素是文件路径,第二个元素是文件名
* os.path.dirname(path):返回指定目录或文件的父目录。每调用一次 os.path.dirname() 都会返回路径的上一级目录
* os.path.basename(path): 返回文件名,即:os.path.split(path)[-1]
* os.path.abspath(path): 返回文件的绝对路径
* os.path.join(path1, path2,...): 将多个路径进行拼接后返回
* os.path.getsize(path): 获取文件大小
# 如果 path 是 /home/user/myproject/manage.py,则 os.path.dirname(path) 返回 /home/user/myproject
import os
curr_path = os.path.dirname(os.path.abspath(__file__)) # 获取当前文件所在目录 E:\zkc\file\code\python-workspace\shell
for file in os.listdir(curr_path):
abs_file_path = os.path.join(curr_path, file) # 拼接得到绝对路径
# 判断是否是文件,并且文件后缀是.sh结尾
if os.path.isfile(abs_file_path) and os.path.splitext(abs_file_path)[-1] == '.sh':
file_name = os.path.split(abs_file_path)[-1] # 获取文件名
file_name1 = os.path.basename(abs_file_path) # 获取文件名
if not os.path.isdir(abs_file_path): # 判断路径是否是目录
continue
- 递归查找所有文件
# 递归查找所有文件
# 获取当前文件所在目录 E:\zkc\file\code\python-workspace\shell
curr_path = os.path.dirname(os.path.abspath(__file__))
shell_path = os.path.join(os.path.dirname(curr_path), 'zkc')
def find_files(path, ceng):
"""
定义一个函数,用于查找指定路径下的所有文件
"""
for file_name in os.listdir(path):
new_path = os.path.join(path, file_name)
if os.path.isdir(new_path):
print("|---- " * ceng, file_name, sep="")
find_files(new_path, ceng + 1)
else:
print("|---- " * ceng, file_name, sep="")
find_files(shell_path, 1)
- 创建文件的正常流程
# 创建文件的正常流程
def create_file(path, is_cover: bool):
file_path = os.path.dirname(path)
# 判断路径是否存在
if not os.path.exists(file_path):
os.makedirs(file_path)
# 判断文件是否存在
if os.path.exists(path):
# 是否需要覆盖
if is_cover:
open(path, 'w').close()
else:
return
else:
open(path, 'w').close()
create_file('/opt/zkc/aa.txt', True)
5、sys 模块
import sys
* sys.argv: 接收命令行参数,返回一个list,第一个参数是文件名,后面跟着脚本参数
* sys.exit(n): 退出程序,正常退出时sys.exit(0),异常退出时sys.exit(1),n可以根据自定义配置
* sys.version: 获取python解释程序的版本信息
* sys.path: 返回模块的搜索路径,import模块时,解释器会根据这些路径搜索模块
* sys.platform: 返回操作系统平台名称
# 获取脚本参数
input_data = sys.argv
file_anme = input_data[0] # 文件名
user_name = input_data[1] # 第一个参数,以此类推
6、pickle 和 json模块
pickle 是 Python 内置的序列化模块,用于将 Python 对象(如字典、列表、类实例等)转换为字节流(序列化),或从字节流恢复为原始对象(反序列化)
# 处理字节
import pickle
# 序列化(对象-字节流)
* pickle.dump(obj, file):将对象序列化后写入文件对象
* pickle.dumps(obj):将对象序列化为字节
# 反序列化(字符流-对象)
* pickle.load(file):将文件对象中读取字节流并反序列化为对象
* pickle.loads(bytes):将字节中反序列化为对象
# 处理json字符串
import json
* json.loads(): 是将字符串形式的JSON数据转换为Python数据类型(字典dict,列表)
* json.dumps(): 将Python对象转换成json字符串,是将一个Python数据类型进行json格式的编码(将字典转化为JOSN字符串)
* json.load(): 是从已打开的json文件对象中读取json数据,并转换为python数据类型
* json.dump(): 将Python对象写入已打开的json文件,以json格式存储
data = {
"name": "Alice",
"age": 30,
"hobbies": ["reading", "coding"],
"is_student": False
}
# 方法1:序列化到文件
with open("data.pkl", "wb") as f: # 注意用二进制模式"wb"
pickle.dump(data, f)
# 方法2:从文件反序列化
with open("data.pkl", "rb") as f: # 注意用二进制模式"rb"
loaded_data = pickle.load(f)
print(loaded_data) # 与原始data完全一致
# 方法1:从字节字符串反序列化
data_bytes = pickle.dumps(data) # 假设已有序列化的字节串
restored_data = pickle.loads(data_bytes)
print(restored_data == data) # True
# 方法2:序列化为字节字符串
data_bytes = pickle.dumps(data)
print(type(data_bytes)) # <class 'bytes'>
# json.loads():将json 字符串 转换为 Python 字典
json_str = '{"name": "Alice", "age": 30}'
data = json.loads(json_str)
# json.dumps():将python对象 转换为 JSON 字符串
data = {"name": "Bob", "age": 25}
json_str = json.dumps(data)
# indent=4指定了每个层级的缩进为4个空格
# sort_keys=True确保字典的键按照字母顺序排序的
# ensure_ascii=False不使用ASCII码(有中文时使用)
json.dumps(data, indent=4, sort_keys=True, ensure_ascii=False)
# json.load():从文件读取并解析 JSON
with open("data.json", "r") as f:
data = json.load(f)
# json.dump():将 Python 对象写入文件
data = {"name": "Charlie", "age": 35}
with open("output.json", "w") as f:
json.dump(data, f, ensure_ascii=False)
7、logging模块
'''
logging.CRITICAL: 50
logging.ERROR: 40
logging.WARNING: 30
logging.INFO: 20
logging.DEBUG: 10
'''
# 基础配置
# 将日志存储到单个文件
import logging
logging.basicConfig(filename='x1.txt',
format='%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
level=30) # level表示>=配置分数的日志会被写入文件
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
- 项目级别的日志文件存储
# 将日志根据级别分开存储
import logging
formatter = logging.Formatter(fmt='[%(asctime)s] %(filename)s %(funcName)s line:%(lineno)d [%(levelname)s] %(message)s')
# 创建一个操作日志的对象logger(依赖FileHandler)
file_handler1 = logging.FileHandler('error.log', 'a', encoding='utf-8')
file_handler1.setFormatter(formatter)
logger1 = logging.Logger('s1', level=logging.ERROR)
logger1.addHandler(file_handler1)
logger1.error('我是error日志')
file_handler2 = logger.FileHandler('critical.log', 'a', encoding='utf-8')
file_handler2.setFormatter(formatter)
logger2 = logging.Logger('s2', level=logging.CRITICAL)
logger2.addHandler(file_handler2)
logger2.critical('我是critical日志')
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import datetime
import logging
import os
import tarfile
import time
formatter = logging.Formatter('[%(asctime)s] %(filename)s->%(funcName)s line:%(lineno)d [%(levelname)s] %(message)s')
error_loggers = []
class LoggerWriter(object):
__instance = None
# 重写new方法实现单例模式
def __new__(cls, *args, **kwargs):
if not cls.__instance:
obj = object.__new__(cls)
cls.__instance = obj
return cls.__instance
def __init__(self, logger=None):
# 如果之前没创建过 就新建一个logger对象,后面相同的实例共用一个logger对象
if 'logger' not in self.__dict__:
# 创建一个logger
self.logger = logging.getLogger(logger)
self.logger.setLevel(logging.INFO)
# 创建一个handler,用于写入日志文件
self.log_time = time.strftime("%Y_%m_%d_")
self.log_path = log_path
self.log_name = self.log_path + 'runtime.log'
self.crt_log_name = self.log_path + 'critical.log'
fh = logging.FileHandler(self.log_name, 'a', encoding='utf-8') # 这个是python3的
fh.setLevel(logging.INFO)
ch = logging.FileHandler(self.crt_log_name, 'a')
ch.setLevel(logging.CRITICAL)
# 定义handler的输出格式
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给logger添加handler
self.logger.addHandler(fh)
self.logger.addHandler(ch)
# 关闭打开的文件
fh.close()
# ch.close()
log_size = os.path.getsize(self.log_name) / (1024 * 1024)
dst_file = log_path + 'runtime_{}.tar.gz'.format(datetime.datetime.now().strftime('%Y%m%d%H%M%S'))
# runtime.log日志打印10M进行绕接
if log_size > 10:
with tarfile.open(dst_file, 'w:gz') as tar:
# 将源文件添加到压缩文件中
tar.add(self.log_name)
os.remove(self.log_name)
def get_log(self):
return self.logger
def log_command_output(self, command, message):
is_success = True
self.logger.info(message)
for d in error_loggers:
for kw in d['keywords'].keys():
error_count = message.count(kw)
if error_count > 0:
d['logger'].warn("Output of %s contains %s:\n%s", command, d['name'], message)
if error_count > d['keywords'][kw]:
is_success = False
d['logger'].error("Result of %s is marked as failed due to %s count %s > %s",
command, d['name'], error_count, d['keywords'][kw])
return is_success
# 单独创建单个的日志对象
def create_logger(name, error_keywords=None):
logger = logging.getLogger(name)
mode = 'w'
if isinstance(error_keywords, dict):
error_loggers.append({"name": name, "logger": logger, "keywords": error_keywords})
mode = 'a'
fh = logging.FileHandler(log_path + name + '.log', mode)
fh.setFormatter(formatter)
logger.addHandler(fh)
fh.close()
return logger
sql_error_logger = create_logger('sql_error', {"GS-": 150})
tool_error_logger = create_logger('tool_error', {"Exception": 0})
progress_logger = create_logger('progress')
8、正则表达式 & re 模块
8.1、正则表达式
在线正则表达式测试: 正则表达式测试
| 符号 | 解释 | 示例 | 说明 |
|---|---|---|---|
| ^ | 匹配字符串的开始 | ^The | 可以匹配The开头的字符串 |
| $ | 匹配字符串的结束 | .exe$ | 可以匹配.exe结尾的字符串 |
| . | 匹配任意字符 | b.t | 可以匹配bat / but / b#t / b1t等 |
| * | 匹配0次或多次 | \w* | {0, } |
| + | 匹配1次或多次 | \w+ | {1,} |
| ? | 匹配0次或1次 | \w? | {0,1} |
| | | 分支 | foo|bar | 可以匹配foo或者bar |
| [] | 匹配来自字符集的任意单一字符 | [abcde] | 可以匹配括号中任一字母字符,括号里面的字符都是普通字符 |
| [^] | 匹配不在字符集中的任意单一字符 | [^abcde] | 可以匹配除括号中任一字母字符 |
| {N} | 固定匹配N次 | \w{3} | |
| {M,} | 匹配至少M次 | \w{3,} | |
| {M,N} | 匹配至少M次至多N次 | \w{3,6} | |
| \w | 匹配字母/数字/下划线 | b\wt | 可以匹配bat、b1t 、b_t 等,但不能匹配b#t等其他特殊字符 |
| \W | 匹配非字母/数字/下划线 | b\Wt | 可以匹配b#t / b@t等但不能匹配but 、b1t 、 b_t等 |
| \s | 匹配空白字符(包括\r、\n、\t等) | love\syou | 可以匹配love you |
| \S | 匹配非空白字符 | love\Syou | 可以匹配love#you等,但不能匹配love you |
| \d | 匹配数字 | \d\d | 可以匹配01 / 23 / 99等 |
| \D | 匹配非数字 | \d\D | 可以匹配9a / 3# / 0F等 |
| \b | 匹配单词的边界 | \bThe\b | |
| \B | 匹配非单词的边界 | \Bio\B | |
| *? | 重复任意次 | a.*b|a.*?b | 将正则表达式应用于aabab,前者会匹配整个字符串aabab,后者会匹配aab和ab两个字符串 |
| +? | 重复1次或多次 | ||
| ?? | 重复0次或1次 | ||
| {M,N}? | 重复M到N次 | ||
| {M,}? | 重复M次以上 | ||
| (?#) | 注释 | ||
| (exp) | 匹配exp并捕获到自动命名的组中 | ||
| (?P<name>exp) | 将匹配exp的内容捕获到名为name的组中 | (?P.*?) | 将匹配到的内容,放到名为game的分组中,使用group(“组名”)来获取 |
| (?:exp) | 匹配exp但是不捕获匹配的文本 | ||
| (?=exp) | 匹配exp前面的位置 | \b\w+(?=ing) | 可以匹配I’m dancing中的danc |
| (?<=exp) | 匹配exp后面的位置 | (?<=\bdanc)\w+\b | 可以匹配I love dancing and reading中的第一个ing |
| (?!exp) | 匹配后面不是exp的位置 | ||
| (?<!exp) | 匹配前面不是exp的位置 |
# 贪婪匹配 和 惰性匹配
.*: 表示提取最长的结果,尽可能多的获取结果,叫贪婪匹配
.*?: 表示提取最短的结果,尽可能少的获取结果,叫惰性匹配
* 普通字符和元字符冲突时,需要进行 "\" 转义
#例如:
我喜欢打游戏,最喜欢dnf游戏,以及lol游戏,以及王者荣耀游戏
# 贪婪匹配:
我.*游戏 # 匹配到:我喜欢打游戏,最喜欢dnf游戏,以及lol游戏,以及王者荣耀游戏
# 惰性匹配:
我.*?游戏 # 匹配到:我喜欢打游戏
8.2、re模块
| 函数 | 说明 |
|---|---|
| compile(pattern, flags=0) | 编译正则表达式返回正则表达式对象 |
| match(pattern, string, flags=0) | 用正则表达式从头开始匹配字符串(相当于正则前面加了^),成功则返回匹配到的match对象 否则返回None |
| search(pattern, string, flags=0) | 搜索字符串中第一次出现正则表达式的模式,成功则返回匹配到的match对象 否则返回None |
| split(pattern, string, maxsplit=0, flags=0) | 用正则表达式指定的模式分隔符拆分字符串,返回列表 |
| sub(pattern, repl, string, count=0, flags=0) | 用指定的repl字符串替换原字符串string中与正则表达式匹配的模式,可以用count指定替换的次数 |
| fullmatch(pattern, string, flags=0) | match函数的完全匹配(从字符串开头到结尾)版本 |
| findall(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回字符串的列表 |
| finditer(pattern, string, flags=0) | 查找字符串所有与正则表达式匹配的模式 返回一个迭代器。从迭代器中提取到的内容是match对象,从match对象中提取数据使用group()方法 |
| purge() | 清除隐式编译的正则表达式的缓存 |
| re.I / re.IGNORECASE | 忽略大小写匹配标记 |
| re.M / re.MULTILINE | 多行匹配标记 |
| re.S | 让 . 匹配所有内容,包括换行 |
* 在使用re模块时,正则表达式前面加'r',这样反斜线不会被当作转义字符
* 如果有长度限制的正则表达式,需要在表达式开头和结尾分别加上"^"和"$"
- 如果没有添加锚点(^ 表示开头,$ 表示结尾),正则不会检查整个字符串的长度
* 如果需要匹配的字符是正则表达式中的特殊字符,那么可以使用\进行转义处理
- 想匹配小数点可以写成\.就可以了,因为直接写.会匹配任意字符
- 想匹配圆括号必须写成\(\),否则圆括号被视为正则表达式中的分组
* 使用group()从match对象中提取匹配的文本
* 正则表达式中的"flags"参数,代表正则表达式的匹配标记
- 可通过该标记来指定匹配时是否忽略大小写、是否进行多行匹配、是否显示调试信息等。
- 如需要为flags参数指定多个值,可以使用按位或运算符进行叠加,如:flags=re.I | re.M
* re模块中的函数,在实际开发中也可以用正则表达式对象的方法替代对这些函数的使用
- 如果一个正则表达式需要重复使用,通过compile函数预编译正则表达式并创建出正则表达式对象, 再调用对象中的方法
- 简单示例
s = '今天是2025年9月16日,我今天赚了11块钱'
res = re.findall(r'\d+', s)
print(res) # ['2025', '9', '16', '11']
res1 = re.finditer(r'\d+', s)
print(res1) # <callable_iterator object at 0x00000144E6DFC070>
for i in res1:
print(i) # <re.Match object; span=(3, 7), match='2025'> <re.Match object; span=(8, 9), match='9'> <re.Match object; span=(10, 12), match='16'> <re.Match object; span=(19, 21), match='11'>
print(i.group()) # 使用group从match对象中提取匹配的文本
res2 = re.search(r'\d+', s)
print(res4.group())
# 预编译
obj = re.compile(r'\d+')
res5 = obj.findall("我今天7点起床,跑了100米")
print(res5)
'''
# 惰性匹配
# .*?\d+: 会匹配到"XXX+数字"最短的字符串
# .*?(\d+).*?: 会匹配到"XXX+数字+XXX" 最短的字符串。匹配数字之前和之后最少的内容,第一个数字前有内容,所以全部匹配到,第一个数字后就可以取最短,所以取0个字符
.*?\d+ 可能出现的结果:
1、今天是2025
2、今天是2025年9
3、今天是2025年9月16
4、今天是2025年9月16日,我今天赚了11
'''
res = re.match(r'.*?(\d+)', s)
print(res) # 今天是2025
res1 = re.match(r'.*?(\d+).*?', s)
print(res1) # 今天是2025
'''
# 贪婪匹配:.*,会匹配到"XXX+数字"最长的字符串
.*\d+:
1、今天是2025年9月16日,我今天赚了11
match: 从头开始匹配,匹配到第一个数字就返回
'''
res2 = re.match(r'.*\d+', s)
print(res2) # 今天是2025年9月16日,我今天赚了11
res3 = re.match(r'.*(\d+).*', s)
print(res3) # 今天是2025年9月16日,我今天赚了11块钱
s = """
<body>
<div>lol</div>
<div>dnf</div>
<div>王者荣耀</div>
</body>
"""
# 惰性匹配,匹配到最近的</div>
obj = re.compile(r'<div>.*?</div>') # 此时将div也输出在匹配到的内容里
obj = re.compile(r'<div>(.*?)</div>') # 将匹配结果放到分组里
res = obj.finditer(s)
for it in res:
print(it.group(1))
# 多个匹配对象,进行分组,分别将匹配到的内容放到不同的组里
s = """
<body>
<div class="10086">lol</div>
<div class="10010">dnf</div>
<div class="110">王者荣耀</div>
</body>
"""
obj = re.compile(r'<div class="(.*?)">(.*?)</div>')
res9 = obj.finditer(s)
for it in res9:
print(it.group(1))
print(it.group(2))
# 对分组进行取名
obj = re.compile(r'<div class="(?P<class>.*?)">(?P<game>.*?)</div>')
res10 = obj.finditer(s)
for it in res10:
print(it.group("class"))
print(it.group("game"))
# 匹配换行符
s = """
<body>
<div class="10086">lo
l</div>
<div class="10010">dn
f</div>
<div class="110">王
者荣耀</div>
</body>
"""
obj = re.compile(r'<div class="(?P<class>.*?)">(?P<game>.*?)</div>', re.S)
res11 = obj.finditer(s)
for it in res11:
print(it.group("class"))
print(it.group("game"))
8.3、简单案例
8.3.1、验证输入用户名和QQ号是否有效
'''
验证输入用户名和QQ号是否有效并给出对应的提示信息
要求:用户名必须由字母、数字或下划线构成且长度在6~20个字符之间,QQ号是5~12的数字且首位不能为0
如果没有添加锚点(^ 表示开头,$ 表示结尾),正则不会检查整个字符串的长度
加上^和$,正则不会检查整个字符串的长度,确保从头到尾都符合规则
'''
def main():
user_name = input("请输入用户名:\n")
qq_number = input("请输入QQ号:\n")
'''
这两种写法在非unicode编码下,写法完全一致。
在unicode编码下,如 Python 中使用re.UNICODE标志,\w可能会匹配比如中文或者其他语言的字符和一些特殊符号
'''
m = re.match(r'^\w{6,20}$', user_name)
m1 = re.match(r'^[0-9a-zA-Z_]{6,20}$', user_name)
if not m1:
print("请输入有效的用户名")
m2 = re.match(r'^[1,9]\d{4,11}$', qq_number)
if not m2:
print("请输入有效的QQ号")
if m1 and m2:
print("输入的用户名和QQ号合法")
if __name__ == "__main__":
main()
8.3.2、从一段文字中提取出国内手机号码
'''
电信号段: 133/153/180/181/189/177;
联通号段: 130/131/132/155/156/185/186/145/176;
移动号段:134/135/136/137/138/139/150/151/152/157/158/159/182/183/184/187/188/147/178
'''
def phone_check():
sentence = '''
重要的事情说8130123456789遍,我的手机号是13512346789这个靓号,
不是15600998765,也是110或119,王大锤的手机号才是15600998765。
'''
# 创建正则表达式对象
# 使用了前瞻和回顾来保证手机号前后不应该出现数字,或者可以用惰性匹配
pattern = re.compile(r'(?<=\D)1[34578]\d{9}.*?')
pattern = re.compile(r'(?<=\D)1[34578]\d{9}(?=\D)')
pattern = re.compile(r'(?<=\D)(1[38]\d{9}|15[0-35-9]\d{8}|14[57]\d{8}|17[678]\d{8})(?=\D)')
num_list = pattern.findall(sentence)
print(num_list)
print('--------华丽的分隔线--------')
result = pattern.finditer(sentence)
for r in result:
print(r.group())
print('--------华丽的分隔线--------')
search_result = pattern.search(sentence)
while search_result:
print(search_result.group())
search_result = pattern.search(sentence, search_result.end())
if __name__ == "__main__":
phone_check()
8.3.3、替换字符串中的不良内容
def words_replace():
content = "这是一段包含敏感信息的字符串,例如:我的手机号是15600998765,而不是:13512346789, 也不是 15600998764"
pattern = r'(\d{11})'
pattern = r'(?<=\D)(\d+)(?=\d)'
result = re.findall(pattern, content)
print(result)
obj = re.sub(pattern, "*", content, count=len(result))
print(obj)
8.3.4、拆分长字符串
def str_split():
poem = '窗前明月光,疑是地上霜。举头望明月,低头思故乡。'
sentence_list = re.split(r'[,。, .]', poem)
print(sentence_list)
while '' in sentence_list:
sentence_list.remove('')
print(sentence_list) # ['窗前明月光', '疑是地上霜', '举头望明月', '低头思故乡']
五、Python标准库
# pprint
pprint.pprint() 将大文本的输出内容进行自动换行美化输出
# sys
1. sys.argv 获取命令行中包含的参数, 以列表形式保存所有参数
2. sys.modules 获取当前程序中引入的所有模块, 以字典形式保存所有参数
# os
1. os.environ 获取系统的环境变量
2. os.system() 执行操作系统的命令
# 举例
python3 test.py 'zkc' 18 'math'
print(sys.argv) # ['test,py', 'zkc', 18, 'math']
pprint.pprint(os.environ['path'])
os.system('dir')
os.system('pwd')
六、异常
- 程序运行过程中,一旦出现异常将会导致程序运行立即终止,异常代码之后的所有代码将不会再执行
# 处理异常
- 程序运行时出现异常,目的并不是让我们的程序直接终止
- python希望在出现异常时,我们可以编写代码对异常进行处理,避免因为一个异常,导致整个程序的终止
- try语句的代码,如果没有错误,则正常执行。如果有错误,则执行except子句中的代码
- 程序不仅会处理在 try 子句中立刻发生的异常,还会处理在 try 子句 中调用(包括间接调用)的函数
- 如果 try 语句时遇到 break,、continue 或 return 语句,则 finally 子句在执行 break、continue 或 return 语句之前执行
- 如果 finally 子句中包含 break、continue 或 return 等语句,异常将不会被重新引发
- 如果 finally 子句中包含 return 语句,则返回值来自 finally 子句的某个 return 语句的返回值,而不是来自 try 子句的 return 语句的返回值(☆☆☆☆☆特别注意)
# 捕获异常
try:
代码块(可能出现错误的语句)
except [异常类型] as [异常名,e/其他]:
代码块(出现错误以后的处理方式)
except [异常类型] as [异常名,e/其他]:
代码块(出现错误以后的处理方式)
else:
代码块(try中没出错时要执行的代码)
finally:
代码块(无论是否出现异常,该子句都会执行)
# finally 子句中包含 break、continue 或 return 等语句,异常将不会被重新引发
def fn():
try:
try:
print(a)
except NameError:
raise NameError('name is not found')
finally:
print("调用前")
# return
except NameError as e:
print("调用后")
fn()
# 加上return
调用前
# 不加return
调用前
调用后
6.1、异常的传播(抛异常)
- 当在函数中出现异常,如果在函数中对异常进行了处理,则异常不会再继续传播
- 如果函数中没有对异常进行处理,则异常会继续向函数调用处传播。如果处理,则不再传播
- 如果函数调用处没有处理异常,则继续向调用处传播,以此类推
- 直到传递到全局作用域(主模块),如果没有处理,则程序终止,并且显示异常信息
1 def fn():
2 print('hello fn')
3 print(10/0)
4 def fun2():
5 print('hello fn2')
6 fn()
7 def fun3():
8 print('hello fn3')
9 fn2()
10 fn3()
# 输出结果
Traceback (most recent call last):
File "E:\zkc\file\code\python-workspace\shell\execute.py", line 10, in <module>
fn3()
File "E:\zkc\file\code\python-workspace\shell\execute.py", line 9, in fn3
fn2()
File "E:\zkc\file\code\python-workspace\shell\execute.py", line 6, in fn2
fn()
File "E:\zkc\file\code\python-workspace\shell\execute.py", line 3, in fn
print(10/0)
ZeroDivisionError: division by zero
hello fn3
hello fn2
hello fn
6.2、异常对象
try:
代码块(可能出现错误的语句)
except [异常类型] [as 异常名,e/其他]:
代码块(出现错误以后的处理方式)
except [异常类型] [as 异常名,e/其他]:
代码块(出现错误以后的处理方式)
else:
代码块(try中没出错时要执行的代码)
finally:
代码块(无论是否出现异常,该子句都会执行)
- except后不跟任何类型或者跟Exception,则会捕获所有的异常
- Exception是所有异常的父类,也会捕获所有异常
- except后跟具体异常类型,则只会捕获该类型的异常
- 使用 as e,将异常对象赋值给e,我们可以看到具体的异常信息
6.3、raise 抛出异常
- 触发异常(也称异常抛出): 使用 raise 语句,支持强制触发指定的异常,其参数必须是异常实例或异常类(派生自 BaseException 类,例如 Exception 或其子类)
- 异常链: 若未处理的异常发生在 except 部分内,它将会有被处理的异常附加到它上面,并包括在错误信息中,为了表明一个异常是另一个异常的直接后果, raise 语句允许一个可选的 from 子句,在转换异常时,这种方式很有用,并且它还允许使用 from None 表达禁用自动异常链。
# 启动异常链
def func():
print("open database.sqlite")
raise ConnectionError
try:
func()
except ConnectionError as exc:
raise RuntimeError('Failed to open database') from exec
# 执行结果:
# open database.sqlite
# ----------------------------------------------------------------------------------
# Traceback (most recent call last):
# ConnectionError
# During handling of the above exception, another exception occurred:
# Traceback (most recent call last):
# TypeError: exception causes must derive from BaseException
# 禁用自动异常链
try:
open('database.sqlite')
except OSError as exc:
raise RuntimeError("Failed to open database") from None
# 执行结果:
# open database.sqlite
# Traceback (most recent call last):
# RuntimeError: Failed to open database
- 捕获错误目的只是记录一下,便于后续追踪。
- 但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。
- raise 语句如果不带参数,就会把当前错误原样抛出
def foo(s):
n = int(s)
if n==0:
raise ValueError('invalid value: %s' % s)
return 10 / n
def bar():
try:
foo('0')
except ValueError as e:
print('ValueError!')
raise
bar()
6.4、自定义异常
# 创建一个类,继承Exception
class MyException(Exception):
pass
def fn(a, b):
if a < 0 or b < 0:
raise MyException('The param is not Zero!.')
return a + b
七、文件操作
"""
open操作文件的模式
r : 只读模式,文件不存在会报错
r+ : 可读可写,覆盖写,文件不存在会报错
w : 只写模式,覆盖写,文件不存在则新建
w+ : 可读可写,覆盖写,文件不存在则新建
a : 只写模式,追加写,文件不存在则创建
a+ : 可读可写,追加写,文件不存在则创建
b : 读取二进制文件(读取非文本文件,不能省略), 如'rb'
t : 读取文本文件(默认值, 可省略)。如'w'='wt', 'r'='rt', 'a'='at', 'r+'='rt+'
x : 新建文件,文件不存在则创建,存在则报错
"""
7.1、open
file_name = 'demo1.txt'
curr_path = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(curr_path, file_name)
file = None
try:
file = open(file_path, 'r', encoding='utf-8')
context = file.read()
print(context)
except FileNotFoundError:
raise FileNotFoundError('file is not found') from None
finally:
if file is not None:
file.close()
7.2、with…as语句
- read(): 读取整个文本
- write(): 将文本写入文件
- readline(): 逐行读取文本
- readlines(): 逐行读取文本,一次性将读取到的内容放到一个列表
- writelines(): 一次性写入多行文本,参数为一个列表
''' 文本文件读取'''
file_name = 'demo.txt'
curr_path = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(curr_path, file_name)
try:
with open(file_path, 'r', encoding='utf-8') as file:
context = file.read()
print(context)
except FileNotFoundError:
raise FileNotFoundError('file is not found')
''' 文本文件写入'''
try:
with open(file_path, 'w', encoding='utf-8') as file:
file.write('i love Python')
except FileNotFoundError:
raise FileNotFoundError('file is not found')
''' 二进制文件读取'''
binary_name = 'demo.flac'
curr_path = os.path.dirname(os.path.abspath(__file__))
file_path = os.path.join(curr_path, binary_name)
with open(file_path, 'rb') as file:
new_file = 'aa.flac'
with open(file_path, 'wb') as new_file:
# 定义每次读取的大小
chunk = 1024 * 100
while True:
#从旧文件中读取数据,每次100字节
context = file.read(chunk)
if not context:
break
new_file.write(context)
7.3、tell() 和 seek()
- tell(): 用来查看当前读取到的位置
- seek(): 用来修改当前读取到的位置
- 有两个参数:
1. 第一个参数:要切换的位置
2. 计算位置方式: 文本模式不支持从当前/末尾位置偏移,二进制模式支持从当前/末尾位置偏移
(1) 0: 从头计算,默认值可省略
(2) 1: 从当前位置计算,往后/往后偏移,再从偏移后的位置开始往后读
(3) 2: 从末尾位置计算,往后/往后偏移,再从偏移后的位置开始往后读
with open(file_path, 'rt', encoding='utf-8') as file:
context = file.read(100)
context1 = file.read(10)
file.seek(20) # 将指针指向第20个字节处
print('当前读取到了:', file.tell())
with open(file_path, 'rb', encoding='utf-8') as file:
# 文本模式和二进制模式都可以从文件偏移量为0处读取
file.seek(0, 0) # 将指针切换到文本开头
file.seek(70, 0) # 将指针从开头切换到第70个字节,指针指向第70个字节位置处,从第70个字节开始往后读
# 只有二进制模式才能从当前位置位移或者末尾位置位移
file.seek(50, 1) # 将指针从当前70个字节位置开始,再往后累加50个字节,指针指向第120个字节位置处,从第120个字节开始往后读
file.seek(-40, 1) # 将指针从当前120个字节处,往前位移40个字节,此时指针指向第80个字节处,从第80个字节处往后读
file.seek(-100, 2) # 将指针从末尾位置开始,往前累加100个字节,指针指向倒数第100个字节处,从倒数第100个字节处往后读
7.4、os库
# 参数 path 为相对或者绝对路径,一般写绝对路径
- os.listdir(path): 返回的是文件夹下的文件(夹)列表(相对路径)
- os.path.isfile(path): 需要包含带文件名的绝对路径,只要路径包含文件名,并且文件要存在,则返回True
- os.path.isdir(path): 需要文件的绝对路径,不包含文件名,并且路径要存在, 则返回True
- os.path.exists(path): 包含文件名的文件的绝对路径/不包含文件名的绝对路径,都返回True
- os.getcwd(): 获取当前所在目录
- os.mkdir(path): 创建目录
- os.rmdir(path): 删除目录
- os.remove(path): 删除文件
- os.rename(old_path, new_path): 对文件重命名或者将文件从源路径移动到目标路径并且重命名
os.rename('aa.txt', 'demo.txt') # 当前同一目录下文件重命名
os.rename('/opt/zkc/aa.txt', '/opt/demo.txt') # 从原路径移动到目标路径并且重命名
os.rename('/opt/zkc/aa.txt', 'demo.txt') # 移动到当前位置并且重命名
os.rename('aa.txt', '/opt/demo.txt') # 从当前位置移动到目标路径并且重命名
69万+

被折叠的 条评论
为什么被折叠?



