前言
最近ai势头正猛,公司也是要求学习ai了!自己先从Python入手,整理了份笔记,记录一下python语法。其语法和其他语言是相通的,只是写法不同。文档基于禹神的python教程,推荐禹神的视频教程。
一、字面量
- 所谓字面量,就是直接写在代码中的具体值
- Python的字符串中可以包含任意字符,且必须使用引号包起来。
- 字符串的引号,可以是:单引号、双引号、三个单引号、三个双引号。
- 写在Python文件头部的字符串,会被自动识别成docstring(文档字符串)
- 文档字符串的主要作用是:对当前Python文件进行一些说明,且文档字符串必须用三个双引号
"""python"""
"python"
'python'
'''python'''
二、变量
语法:变量名 = 值
name = '李四'
age = 18
height = '188'
print('你好!我叫' + name, '今年', age, '岁,身高' + height + 'cm。')
三、标识符命名规则
- 只能包含:数字、字母、下划线,且不能以数字开头,不能包含空格
- 标识符区分大小写,例如 Name和name是两个不同的标识符。
- 标识符不能使用关键字,
- 标识符尽量不要与内置函数同名。
- 标识符虽然没有长度限制,但应追求:简洁清晰,具有描述性。
| Python中的关键字 | ||||
|---|---|---|---|---|
| False | assert | continue | except | if |
| nonlocal | return | None | async | def |
| finally | import | not | try | True |
| await | del | for | in | or |
| while | and | break | elif | from |
| is | pass | with | as | class |
| else | global | lambda | raise | yield |
四、常量
一般约定全大写变量名来表示常量,涉及多个单词则以下划线分割。
python中没有强制的常量机制,其本质还是变量,只是约定好不要去修改!
# ctrl+shift+u 转换大小写
AGE = 13
NAME = 'abc'
五、数据类型
| 类型名称 | 英文名 | 举例 | 说明 |
|---|---|---|---|
| 字符串 | string | '李四' | 用引号包裹 |
| 整型 | int | 18 | 没有小数的数字 |
| 浮点型 | float | 20.26 | 有小数的数字 |
| ... |
type() 用来判断类型
print(type('李四')) # <class 'str'>
print(type(18)) # <class 'int'>
print(type(23.12)) # <class 'float'>
整型 int
# 整型:没有小数点的整数,可以是正数,负数,0
age = 18
age2 = 23.12
age3 = 0
# 当数很大时,可以使用下划线_,使数字易读
num = 10000
price = 100_000_999
# print(num, price)
# Python中整数值的上限,取决于执行代码的计算机的内存和处理能力
max_limit = 999 ** 9999
print(max_limit)
浮点型 float
# 浮点型就是带有小数的数字
height = 182.5
wight = 80.0
unit = -9.0
# 浮点型的科学计数法表示
speed = 3.4e+2 # 3.4乘以10的2次方 340.00
pi = 3.14e3 # 3.14乘以10的3次方 3140.
num_a = 5E8 # 5乘以10的8次方 500000000.0
num_b = 6E+6 # 6乘以10的6次方 6000000.0
num_c = 1E-2 # 1乘以10-2次方 0.01
num_d = 1e-3 # 1乘以10-3次方 0.001
字符串 string
字符串拼接
year = 2020
day = 6.20
school = '德州学院'
# 写法1:字符串之间以加号相连,但代码较乱且不能拼接数字
decr = '我毕业于' + school + ',毕业时间'
# 写法2:占位符 %s字符串 %i整数 %f浮点数 %d占位十进制整数
#decr2 = '我毕业于%s,毕业时间%i年的%f号'%(school,year,day) # 我毕业于德州学院,毕业时间2020年的6.200000号
decr2 = '我毕业于%s,毕业时间%i年的%d号'%(school,year,day) # 我毕业于德州学院,毕业时间2020年的6号
# 写法3:f-string
decr3 = f'我毕业于{school},毕业时间{year}年的{day}号' # 我毕业于德州学院,毕业时间2020年的6.2号
占位符精度控制 %m.nf
m
- 最小宽度,位数不够会使用空格补全,位数小于整数位数,则自动失效
- 正数是右对齐,负数是左对齐
n
- 精度控制,含义:最少用n位显示数字。
- 位数不够用0来补,位数小于整数位,则自动失效。
name = '李四'
age = 18
height = 188.45
info = '我是%4s,今年%-3i,身高%2.2f'%(name,age,height) # 我是 李四,今年18 ,身高188.45
# 写法
info2 = f'{height:.2f}'
转义字符
# 使用 \
print('输出一个'字符')
print('输出一个"字符')
# 使用\n换行
print('你好\n你也好')
# 使用\ 输出 \
print('D:\file')
# 使用\b删除前一个字符
print('helloo\b')
# \r 使光标回到本行开头,覆盖输出
print('10%\r20%')
# \t 表示水平制表符,让光标跳转到下一个制表位
print('1234123412341234')
print('ab\tcd')
print('abc\td')
print('abcd\ta')
print('显示\t中文')
"""
1234123412341234
ab cd
abc d
abcd a
显示 中文
"""
布尔类型 boolean
只用两个值 True 和 False
a = True
b = False
c = 1 > 2
print(type(a), a) # <class 'bool'>
print(type(b), b) # <class 'bool'> False
print(type(c), c) # <class 'bool'> False
boolean值本质是int的子类型,底层是用1表示True,用0表示False
print(int(True), int(False)) # 1 0
数据类型转换
str() 把数据转为字符串
result = str(10)
result2 = str(10.11)
result3 = str(1.1e3)
result4 = str(10_101)
print(type(result), result) # <class 'str'> 10
print(type(result2), result2) # <class 'str'> 10.11
print(type(result3), result3) # <class 'str'> 1100.0
print(type(result4), result4) # <class 'str'> 10101
int() 把数据转为整型
result = int(10)
result2 = int('10')
result3 = int(10.99)
result4 = int(' 10 ')
print(type(result), result) # <class 'int'> 10
print(type(result2), result2) # <class 'int'> 10
print(type(result3), result3) # <class 'int'> 10
print(type(result4), result4) # <class 'int'> 10
无效:
- int(' 1 0 ') 中间有空格
- int('中文') 中文
- int('10天')
- int('1.2')
float() 把数据转为浮点型
result = float(10)
result2 = float('20.2')
result3 = float(' 30.3 ')
result4 = float(40.4)
result5 = float('40')
print(type(result), result) # <class 'float'> 10.0
print(type(result2), result2) # <class 'float'> 20.2
print(type(result3), result3) # <class 'float'> 30.3
print(type(result4), result4) # <class 'float'> 40.4
print(type(result5), result5) # <class 'float'> 40.0
无效:
- int(' 1. 0 ') 中间有空格
- int('中文') 中文
- int('10天')
- int('1.2.3')
bool()将数据转为布尔类型
# 使用
print(bool(1), bool(0), bool('1'), bool(''))
# True False True False
六、运算符
1、算术运算符
| 运算符 | 说明 | 示例 | 结果 |
|---|---|---|---|
| + | 加 | 1 + 2 | 3 |
| - | 减 | 2 - 1 | 1 |
| * | 乘 | 1 * 2 | 2 |
| / | 除 | 3 / 2 | 1.5 |
| // | 取整 | 3 / 2 | 1 |
| % | 取余 | 8 / 5 | 3 |
| ** | 指数 | 2 ** 3 | 8 |
2、赋值与复合赋值运算符
| 运算符 | 说明 | 示例 | |
|---|---|---|---|
| = | 赋值运算 | name = '李四' | |
| += | 加法赋值运算 | num += 2 | num = num + 2 |
| -= | 减法赋值运算 | num -= 2 | num = num - 2 |
| *= | 乘法赋值运算 | num *= 2 | num = num * 2 |
| /= | 除法赋值运算 | num /= 2 | num = num / 2 |
| //= | 取整赋值运算 | num //= 2 | num = num // 2 |
| %= | 取余赋值运算 | num %= 2 | num = num % 2 |
| **= | 指数赋值运算 | num **= 2 | num = num ** 2 |
3、比较运算符
| 运算符 | 作用 | |
|---|---|---|
| == | 比较左右两侧是否相等 | |
| != | 比较左右两侧是否不相等 | |
| > | 比较左侧是否大于右侧 | 同类型比较 |
| < | 比较左侧是否小于右侧 | |
| >= | 比较左侧是否大于等于右侧 | |
| <= | 比较左侧是否小于等于右侧 |
注:
- 比较结果True or False,首字母是大写的
- 字符串进行比较时,是依次比较每个字符的Unicode编码
- 使用 ord() 查看字符Unicode编码,使用 chr() 将Unicode码转为字符
4、逻辑运算符
| 运算符 | 名称 | 说明 |
|---|---|---|
| and | 逻辑与 | 判断两侧的值,是否都为True |
| or | 逻辑或 | 判断两侧的值,是否至少有一个为True |
| not | 逻辑非 | 对值取反 |
and
- and 返回的不一定是布尔值,而是某个参与计算的值本身
- and 会先看左边,若左边是“假”,则直接返回左边,否则返回右边
- 若参与and运算的值不是布尔值,会自动转为布尔值,再进行逻辑与判断
print(True and True) # True
print(True and False) # False
print(False and True) # False
print(False and False) # False
print(2 > 1 and 2 > 0)
# # print(3/2 and 3 / 0)
print(1 - 1 and True) # 0
print('' and True) #
print(True and 2 - 1) # 1
print(1 + 2 and 2 * 1) # 2
or
- or 返回的不一定是布尔值,而是参与计算的值本身
- or 会先看左边,若左边是“真”,则直接返回左边,否则返回右边
- 若参与or运算的值不是布尔值,会自动转为布尔值,再进行逻辑判断
print(True or True) # True
print(True or False) # True
print(False or True) # True
print(False or False)# False
print(2 - 1 or False) # 1
print('你好' or '您好') # 你好
print(False or 2 / 1) # 2.0
print(1 + 2 or 2 * 1) # 3
not
- 若参与not运算的值不是布尔值,会转为布尔值,再进行逻辑判断
- not返回的值一定是布尔值
print(not True) # False
print(not False) # True
print(not 2 > 1) # False
print(not 2 < 1) # True
print(not 0) # True
七、进制
- 0b开头表示二进制
- 0o开头表示八进制
- 0x开头表示十六进制
# 0b开头表示二进制
num = 0b11001
# 0o开头表示八进制
num2 = 0o1034
# 0x开头表示十六进制
num3 = 0x1cf
Python中所有的非十进制数字,只是代码层面的编写方式,计算时会自动将其转为十进制
# Python中所有的非十进制数字,只是代码层面的编写方式,计算时会自动将其转为十进制
print(num, num2, num3) # 25 540 463
print(num + 1) # 26
print(num2 > 1) # True
print(str(num3)) # 463
Python中的进制转换
| 方法 | 说明 | 示例 |
|---|---|---|
| bin() | 十进制转二进制字符串 | bin(25) => '0b11001' |
| oct() | 十进制转八进制字符串 | bin(540) => '0o1034' |
| hex() | 十进制转十六进制字符串 | hex(463) => '0x1cf' |
| int() | 其他进制转十进制 | int('0b11001', 2) => 25 |
| int('0o1034', 8) => 540 | ||
| int('0x1cf', 16) => 463 |
八、输入语句
input() 用于获取用户的输入
input()获取到的内容全是字符串类型
name = input('请输入你的名字:')
age = input('请输入你的年龄:')
# 将age转为整型
age = int(age)
print(f'你的名字是{name},今年{age + 1}岁。')
九、流程控制语句
单分支 if
num = int(input('请输入数字:'))
if num >= 18:
print('输入的数字大于等于18')
双分支 if else
if num > 18:
print('输入的数字大')
else:
print('输入的数字小')
多分支 if elif if
if num > 18:
print('输入的数字大')
elif num < 18:
print('输入的数字小')
else:
print('相等')
嵌套分支
if xxx:
xxx
if xxx:
xxx
elif xxx:
xxx
else:
xxx
else:
xxx
while 循环
n = 1
while n <= 10:
print(f'第{n}次')
n += 1
练习
print('请输入正确密码,才能登录!')
text = '请输入密码!'
password = '123'
inputPassword = ''
while inputPassword != password:
print(f'{text}')
inputPassword = input('密码:')
if inputPassword == password:
print('密码正确')
else:
print('密码错误')
for 循环
遍历range()范围内的数字
# range(10)的范围即[0, 10),为左闭右开
for n in range(10):
print(n) # 0 1 2 3 4 5 6 7 8 9
for n in range(1, 10):
print(n) # 1 2 3 4 5 6 7 8 9
遍历字符串
for n in 'abcdef':
print(n) # a b c d e f
练习:加解密
# 练习1:
text = input('请输入要加密的文字:')
secret = ''
for char in text:
secret += chr(ord(char) + 1)
print(f'最终加密后:{secret}')
# 练习2:
secret = input('请输入要解密的文字:')
text = ''
for char in secret:
text += chr(ord(char) - 1)
print(f'最终解密后:{text}')
练习:九九乘法表
while n <= 9:
m = 1
while m <= n:
print(f'{m} * {n} = {n * m}', end='\t')
if m == n:
print('')
m += 1
n += 1
print('', end ='') 可使输出不换行
continue与break
- continue 跳出本次循环剩余语句,进入下一次循环
- break 终止循环,不再执行剩余循环
- 两者在while和for循环中均可使用,作用一样
- 两者在嵌套循环中,只能作用在其所在的循环
for m in range(1, 11):
print(f'第{m}次')
continue
print('你好')
for m in range(1, 11):
print(f'第{m}次')
if m == 2:
continue
print('你好')
for m in range(1, 11):
print(f'第{m}次')
break
print('你好')
for m in range(1, 11):
print(f'第{m}次')
if m == 2:
break
print('你好')
十、函数
内置函数:docs.python.org/zh-cn/3.13/…
内置模块:docs.python.org/zh-cn/3.13/…
语法:
# 定义
def 函数名():
函数体
# 调用
函数名()
def say():
print('你好')
say()
参数:
位置参数:
def 函数名(参数1, 参数2...)
def say(name, content):
print(f'{name}说{content}')
say('李四', 'nice to meet you')
say('张三','你好')
关键字参数:
调用时 函数名(形参名=值):
def say(name, age, gender, height):
print(f'我是{name},年龄{age},性别{gender},身高{height}')
say(name='王五', age=18, gender='男', height=188)
# 位置参数需在关键字参数之前使用
say('赵六', 20, gender='女', height=190)
- 位置参数需在关键字参数之前使用
限制传参方式:
- / 前面只能用位置参数,* 后面只能用关键字参数
- / 和 * 同时使用时, / 只能在 * 前面
def say(name, /, age, *, gender, height):
print(f'我是{name},年龄{age},性别{gender},身高{height}')
say('tom', 18, gender='男', height=190)
参数默认值
- 语法
def func(参数名=值) - 形参使用了默认值,该形参后面的参数也要有默认值
def say(name, age, gender='男', height=180):
print(f'我是{name},年龄{age},性别{gender},身高{height}')
say('tom', 18)
可变参数
定义函数时,在形参前加 * ,可接受任意数量的位置参数,并打包成一个元组
def func(*args):
print(args)
func(1, 2, 3) # (1, 2, 3)
定义函数时,在形参前加 ** ,可接受任意数量的关键字参数,并打包成一个字典
def func(**kwargs):
print(kwargs)
func(name='李四', age=19, height=76, weight=90)
# {'name': '李四', 'age': 19, 'height': 76, 'weight': 90}
NoneType 类型:None
- None是一个特殊的字面量,它表示:空值/无值/无意义
- None的类型是NoneType。
- None转为布尔值是False。
- None不能参与数学运算,也不能与字符串拼接。
- 不给函数设置返回值,函数会默认返回None。
name = None
print(type(name)) # <class 'NoneType'>
nameC = bool(name)
print(nameC) # False
返回值
使用关键字 return,得到函数返回值
def add(n, m):
return n+m
res = add(1,2)
print(res)
作用域
全局和局部
在局部作用域中可使用global声明变量是全局变量
n = 1
def add():
global n
n = 100
m = n + 1
print(m)
add()
add()
print(n)
十一、列表
数据容器:一种能存放多个数据的数据类型
定义列表
定义一个列表[元素1, 元素2, 元素3, 元素4...]
list1 = [1, 2, 3, 4, 5]
list2 = ['a', 'b', 'c', 'd', 'e']
list3 = [1, True, 'a']
list4 = [1,False,'b', [2,4,6]]
# 定义空列表
list5 = []
list6 = list()
print(list1, type(list1)) # [1, 2, 3, 4, 5] <class 'list'>
print(list2, type(list2))
print(list3, type(list3))
print(list4, type(list4))
print(list5, type(list5)) # [] <class 'list'>
print(list6, type(list6))
列表添加元素的方法
| 方法 | 说明 |
|---|---|
| append() | 向列表尾部追加一个元素 |
| insert() | 在指定下标处,插入一个元素 |
| extend() | 将可迭代对象中的内容依次取出,追加至列表尾部 |
list1 = [1,2,3]
# append() 尾部追加一个元素
list1.append(6)
print(list1)
# insert() 在指定下标处,插入一个元素
list2 = [1, 2, 3]
list2.insert(2,4)
print(list2)
# extend() 将可迭代对象中的内容依次取出,追加至列表尾部
list1.extend('星期五')
list1.extend(range(1,4))
print(list1)
列表删除元素的方法
| 方法 | 说明 |
|---|---|
| pop() | 删除指定位置元素,并返回该元素 |
| remove() | 移除首次出现的元素 |
| clear() | 清空所有元素 |
| del | 删除指定元素 |
# pop() 删除指定位置元素,并返回该元素
list1 = [1,2,3]
res = list1.pop(1)
print(res, list1)
# remove() 移除首次出现的元素
list2 = [1,2,3,1]
list2.remove(1)
print(list2)
# clear() 清空所有元素
list2.clear()
print(list2)
# del 删除指定元素
list3 = [1,2,3,4]
del list3[1]
print(list3)
列表修改元素的方法 列表名[下标] = 值
列表查询元素的方法 列表名[下标]
常用方法
| 方法 | 说明 | |
|---|---|---|
| index() | 返回元素首次出现的下标 | |
| count() | 返回元素出现的次数 | |
| reverse() | 反转列表 | 无返回值 |
| sort(reverse=True/False) | 排序列表 | 无返回值 |
list1 = ['a', 'b', 'c', 'a']
# index() 返回元素首次出现的下标
res1 = list1.index('a')
print(res1)
# count() 返回元素出现的次数
res2 = list1.count('a')
print(res2)
# reverse() 反转列表元素,无返回值
list1.reverse()
print(list1)
# sort() 排序列表,无返回值
list1.sort(reverse=False) # reverse=True/False 可控制是否排序
print(list1)
常用内置函数
| 方法 | 说明 | |
|---|---|---|
| sorted(容器, reverse=True/False) | 排序 | 返回排序后的容器 |
| max() | 找出最大值 | 返回最大值 |
| min() | 找出最小值 | 返回最小值 |
| len() | 获取容器中元素总数 | 返回元素总数 |
| sum() | 对容器元素求和<字符串不能使用sum> | 返回元素总和 |
list1 = [4, 5, 7, 1, 2, 3, 8, 6]
# sorted() 对容器进行排序
res1 = sorted(list1)
print(res1)
print(list1)
# max() 找出最大值
res2 = max(list1)
print(res2)
# min() 找出最小值
res3 = min(list1)
print(res3)
# len() 获取元素总数
res4 = len(list1)
print(res4)
# sum() 对容器元素求和<字符串不能使用sum>
res5 = sum(list1)
print(res5)
循环遍历
- 通过
for item in list1:获取每项元素item for index in range(len(list1)):获取到元素下标indexfor index, item in enumerate(list1, start=5):获取到元素下标index和元素item,参数start可让计数从指定值开始
十二、元组
- 元组是和列表类似的数据容器,区别是元组中的元素不可修改
- 元组中存在可变类型(列表),那可变类型中的元素可修改
定义一个元组(元素1, 元素2, 元素3, 元素4...)
定义空元组
t1 = ()
t2 = tuple()
定义只有一个元素的元组:元素后须跟逗号
t1 = ('a',)
t2 = (1,)
# 定义元组
t1 = (1, 2, 3, 4, 5)
t2= ('a', 'b', 'c', 'd', 'e')
t3 = (1,True,'哈哈', (30,230,239))
# 元组下标
print(t1[1])
print(t2[-1])
print(t3[2])
print(t3[3][0])
# 元组中存在可变类型(列表),那可变类型中的元素可修改
t4 = (1,2,3,4, [5,6,7,8, ('你好', 'hh')])
# t4[0] = 10 X
# t4[4] = 100 X
t4[4][1] = 55
# t4[4][1][0] = 22 X
print(t4)
常用方法
| 方法 | 说明 | |
|---|---|---|
| index() | 返回元素首次出现的下标 | |
| count() | 返回元素出现的次数 |
t1 = (1,2,2,4,5,2)
print(t1.index(2)) # 1
print(t1.count(2)) # 3
常用内置函数
| 方法 | 说明 | |
|---|---|---|
| sorted(容器, reverse=True/False) | 排序 | 返回排序后的容器 |
| max() | 找出最大值 | 返回最大值 |
| min() | 找出最小值 | 返回最小值 |
| len() | 获取元组中元素总数 | 返回元组长度 |
| sum() | 对元组元素求和<字符串不能使用sum> | 返回元素总和 |
t1 = (1, 2, 2, 4, 5, 2)
print(max(t1)) # 5
print(min(t1)) # 1
print(len(t1)) # 6
print(sorted(t1)) # [1, 2, 2, 2, 4, 5]
print(sum(t1)) # 16
使用*对解包列表或元组
def test(*args):
print(args)
tt1 = (1,2,3,4,5,6)
tt2 = ('a','b','c','d','e','f')
test(*tt1) # 相当于test(1,2,3,4,5,6)
test(*tt2)
十三、字符串 str
- 字符串同样有下标
- 字符串中的字符不可修改
- 字符串不能嵌套
常用方法
| 方法 | 说明 | |
|---|---|---|
| index() | 返回字符首次出现的下标 | |
| split() | 按照指定字符分割,组成新列表 | |
| replace() | 将某个字符替换成目标字符 | |
| count() | 返回字符出现的次数 | |
| strip() | 删除指定字符串中的任意字符 |
- strip() 从字符两端开始删除,直到遇到第一个不在字符串中的字符就停止
str1 = 'welcome to python'
print(str1[1])
# str1[1] = '2' # X
print(str1.index('e')) # 1
print(str1.split(' ')) # ['welcome', 'to', 'python']
print(str1.replace('e','*')) # w*lcom* to python
print(str1.count('e')) # 2
# strip() 从字符两端开始删除,直到遇到第一个不在字符串中的字符就停止
str3 = '111we1l1come111'
res = str3.strip('1')
print(res) # we1l1come
str4 = '1234py32thon1342'
res2 = str4.strip('1234')
print(res2) # py32thon
str5 = '12534py32thon16342'
res5 = str5.strip('321')
print(res5) # 534py32thon1634
# strip()不传参数,用于去两端空格
str6 = ' python '
res6 = str6.strip()
print(res6) # python
常用内置函数
| 方法 | 说明 |
|---|---|
| len() | 统计字符串中字符的个数 |
| max() | 找出字符串中unicode编码值最大的字符 |
| min() | 找出字符串中unicode编码值最小的字符 |
| sorted() | 按照unicode值排序 |
序列的切片操作
- 序列:能连续存放元素的数据容器,元素有先后顺序,可通过下标访问,列表、元组、字符串都是序列
- 切片:从序列中按照指定范围,取出一部分元素,形成一个新序列的操作
语法:[起始索引:结束索引:步长]
默认起始索引为0,结束索引截取到末尾,步长为1
起始索引大于结束索引时,步长需为负数
list1 = [10, 20, 30, 40, 50, 60, 70, 80, 90]
res1 = list1[1:5:1] # [20, 30, 40, 50]
res2 = list1[2:8:2] # [30, 50, 70]
res3 = list1[::] # [10, 20, 30, 40, 50, 60, 70, 80, 90]
res4 = list1[5:2:-1] # [60, 50, 40]
res = list1[::-1] # [90, 80, 70, 60, 50, 40, 30, 20, 10]
相加:新序列 = 序列1 + 序列2 同类型的才能相加
list2 = [1,2,3]
list3 = [4,5,6]
res23 = list2 + list3 # [1, 2, 3, 4, 5, 6]
t1 = ('a','b','c')
t2 = ('c','b','a')
t12 = t1 + t2 # ('a', 'b', 'c', 'c', 'b', 'a')
str1 = 'hello'
str2 = ' world'
str3 = str1 + str2 # hello world
相乘(重复)新序列 = 序列 * n n必须为整数
list2 = [1,2,3]
t1 = ('a','b','c')
str1 = 'hello'
print(list2*2) # [1, 2, 3, 1, 2, 3]
print(t1*2) # ('a', 'b', 'c', 'a', 'b', 'c')
print(str1*2) # hellohello
十四、集合
集合内部元素无序,不能通过下标访问元素,会自动去重
set()
可变集合:创建后可以增删元素 {元素0, 元素1, 元素3...}
s1 = {10, True, 1, '你好'}
s2 = {1, 2, 3, 1, 3, 4, 5}
s3 = {'a', 'b', 'c', 'a', 'd'}
print(s1, type(s1)) # {True, 10, '你好'} <class 'set'>
print(s2, type(s2)) # {1, 2, 3, 4, 5} <class 'set'>
print(s3, type(s3)) # {'c', 'b', 'a', 'd'} <class 'set'>
定义空集合 s1 = set()。
不可定义s1 = {},因为直接写{}定义的是空字典
frozenset()
不可变集合:创建后不可增删元素 frozenset({元素0, 元素1, 元素3...})
s1 = frozenset({10, True, 1, '你好'})
s2 = frozenset({1, 2, 3, 1, 3, 4, 5})
s3 = frozenset({'a', 'b', 'c', 'a', 'd'})
print(s1, type(s1)) # frozenset({True, 10, '你好'}) <class 'frozenset'>
print(s2, type(s2)) # frozenset({1, 2, 3, 4, 5}) <class 'frozenset'>
print(s3, type(s3)) # frozenset({'d', 'c', 'a', 'b'}) <class 'frozenset'>
frozenset接收的参数,可以是任意可迭代参数,但最终返回的一定是【不可变集合】
s1 = frozenset(['a', 'b', 'c', 'd'])
s2 = frozenset(['a', 'b', 'c', 'd'])
s3 = frozenset('hello')
print(s1, type(s1)) # frozenset({'b', 'c', 'd', 'a'}) <class 'frozenset'>
print(s2, type(s2)) # frozenset({'b', 'c', 'd', 'a'}) <class 'frozenset'>
print(s3, type(s3)) # frozenset({'o', 'e', 'h', 'l'}) <class 'frozenset'>
定义空集合 s1 = frozenset()
集合中不可嵌套【可变集合】,但可以嵌套【不可变集合】
s1 = {1, 2, 3, 4, 5}
s2 = frozenset({10, 20, 30, 40, 50})
l1 = [100, 200, 300, 400, 500]
t1 = (11, 22, 33, 44, 55)
# s3 = {66, 77, 88, s1} # 报错
s3 = {66, 77, 88, s2} # 运行正常
# s4 = {6, 7, 8, l1} # 报错
s4 = {6, 7, 8, t1} # 运行正常
集合的增删改
增:add()、update()
# add() 方法向集合中添加元素
s1.add(4)
s1.add(5)
print(s1)
# update() 方法向集合中添加元素(必须传递可迭代对象,列表、元组、集合等)
s1.update([10, 20, 30])
s1.update((40, 50))
s1.update({60, 70, 80})
s1.update(range(100, 105))
print(s1)
删:remove()、discard()、pop()、clear()
s1 = {1, 2, 3, 4, 5}
# remove() 从集合中移除指定元素,若不存在,会报错,无返回值
# s1.remove(1)
# discard() 从集合中移除指定元素,若不存在,不会报错,无返回值
# s1.discard(5)
# pop() 从集合中移除任意一个元素(随机),返回移除的元素
res = s1.pop()
# clear() 清空集合
s1.clear()
print(res, s1)
集合没有下标,也不支持切片操作
改:
可通过remove和add达到修改操作
s1 = {1, 2, 3, 4, 5}
s1.remove(4)
s1.add(6)
查:
可通过【成员运算符】in或not in去查看集合中是否有某个元素
s1 = {1, 2, 3, 4, 5}
res = 1 in s1
res2 = 1 not in s1
print(res, res2)
常用方法
difference()
a = {1, 2, 3, 5}
b = {4, 5, 6, 1}
# difference找出集合a中不同于集合b的元素,集合a和集合b都不变,返回一个新集合
res = a.difference(b)
print(a) # {1, 2, 3, 5}
print(b) # {1, 4, 5, 6}
print(res) # {2, 3}
difference_update()
a = {1, 2, 3, 5}
b = {4, 5, 6, 1}
# difference_update 从集合a中,删除b中存在的元素,集合a会被修改,b不会,无返回值
a.difference_update(b)
print(a) # {2, 3}
print(b) # {1, 4, 5, 6}
union()
a = {1, 2, 3, 5}
b = {4, 5, 6, 1}
# 合并两个集合,集合a、b都不变,返回一个新集合
res = a.union(b)
print(a) # {1, 2, 3, 5}
print(b) # {1, 4, 5, 6}
print(res) # {1, 2, 3, 4, 5, 6}
issubset()、issuperset()、isdisjoint()
a = {1, 2, 3}
b = {1, 2, 3, 4, 5, 6}
c = {4, 5, 6, 7, 8, 9}
# 判断集合a是否是集合b的子集,返回布尔值
print(a.issubset(b)) # True
print(c.issubset(b)) # False
# 判断集合a是否是集合b的超集,返回布尔值
print(b.issuperset(a)) # True
print(c.issuperset(b)) # False
# 判断集合a和集合b是否没有交集,返回布尔值
print(a.isdisjoint(b)) # False
print(a.isdisjoint(c)) # True
集合中的数学运算
并集 集合a | 集合b
a = {1, 2, 3, 4, 5, 6}
b = {4, 5, 6, 7, 8, 9}
# 并集:合并集合的元素
res = a | b # {1, 2, 3, 4, 5, 6, 7, 8, 9}
交集 集合a & 集合b
# 交集:找出集合中共有的元素
# res = a & b # {4, 5, 6}
差集 集合a - 集合b
# 差集 去除集合a中,集合b中也有的元素
# res = a - b # {1, 2, 3}
对称差集 集合a ^ 集合b
# 对称差集 在并集中去除掉交集
# res = a ^ b # {1, 2, 3, 7, 8, 9}
集合的循环遍历
集合没有下标,所以不能使用while,但可以使用for
b = {4, 5, 6, 7, 8, 9}
# 循环遍历
for item in b:
print(item)
# 集合没有下标 不能使用while
# index = 0
# while index < len(b):
# print(b[index])
十五、字典
定义字典 {key: value}
d1 = {'张三': 11, '李四': 2, '王五': 3, 'tom': 4}
print(d1, type(d1)) # {'张三': 11, '李四': 2, '王五': 3, 'tom': 4} <class 'dict'>
# 定义空字典
d2 = {}
d3 = dict()
字典key不能重复
# 字典的key不能重复,若出现重复,后者会覆盖前者
d1 = {'张三': 11, '李四': 2, '王五': 3, 'tom': 4, '张三': 22}
print(d1)
key必须是不可变类型,但value可为任意类型
# key必须是不可变类型,但value可为任意类型
d1 = {12: 12, '李四': 22}
d2 = {('你好', 'python'): 12, '李四': 22}
d3 = {['你好', 'python']: 23} # 报错
字典可以嵌套
# 字典嵌套
d1 = {
2001: {'张三': 11, '李四': 2, '王五': 3, 'tom': 4},
2002: {'张三': 12, '李四': 22, '王五': 3, 'tom': 4},
2003: {'张三': 13, '李四': 23, '王五': 3, 'tom': 4},
}
字典增删改查
</tbody>
| 操作 | 写法 | 说明 | 返回值 |
|---|---|---|---|
| 新增 | 字典[key] = 值 | 新增一组键值对(若键已存在则变为修改) | 无 |
| 删除 | |||
| del 字典[key] | 删除指定键对应的键值对(键不存在会报错) | 无 | |
| 字典.pop(key, 默认值) | 删除指定键对应的键值对 | 返回被删除键的值或默认值 | |
| 字典.clear() | 删除所有键值对 | 无 | |
| 修改 | |||
| 字典[key] = 值 | 修改指定键对应的键值对(键不存在则为新增) | 无 | |
| 字典.update(其他字典) | 批量修改或合并多个键值对 | 无 | |
| 查询 | |||
| 字典[key] | 根据键取值(键不存在会报错) | 键对应的值 | |
| 字典.get(key, 默认值) | 安全取值,键不存在则返回默认值,未设置默认值返回None | 键对应的值或默认值 |
常用方法
| 方法 | 说明 | 返回值类型 |
|---|---|---|
| 字典.keys() | 获取所有的键 | 'dict_keys' |
| 字典.values() | 获取所有的值 | 'dict_values' |
| 字典.items() | 获取所有的键值对 | 'dict_items' |
dict1 = {'a': 1, 'b': 2, 'c': 3, }
# keys:获取字典中所有的键
res = dict1.keys() # dict_keys(['a', 'b', 'c']) <class 'dict_keys'>
# dict_keys和列表相似,可以遍历,但不能用下标访问
for item in res:
print(item, end='\t')
# print(res[0])
# 借助内置函数list函数,可将dict_keys转为list
res_list = list(res) # ['a', 'b', 'c'] <class 'list'>
# values: 获取字典中所有的值
res = dict1.values() # dict_values([1, 2, 3]) <class 'dict_values'>
# items: 用于获取字典中所有键值对
res = dict1.items() # dict_items([('a', 1), ('b', 2), ('c', 3)]) <class 'dict_items'>
十六、类
定义一个类
类名通常使用大驼峰写法
- 当一个函数被定义在了类中时,那这个函数就被称为:方法
- init 方法:初始化方法,是给当前正在创建的实例对象添加属性
- init 方法接收到的参数:当前正在创建的实例对象(self)、其他的自定义参数
- 当创建Person类实例的时候,Python会自动调用__init__
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 创建实例对象
p1 = Person('张三', 19)
实例属性
- 给实例添加属性(语法为:self.属性名 = 值 )
- 通过实例.属性名 = 值 给实例添加的属性,就是实例属性
- 实例属性只能通过实例访问,不能通过类访问,每个实例间的实例属性都是互不干扰的
通过 实例.dict 可以查看实例上的所有属性
# print(p1.__dict__)
# {'name': '张三', 'age': 18, 'gender': 'male'}
类属性
- 类属性可以通过类访问,也可以通过实例访问
- 类属性通常用于保存 公共数据
class Person:
# 类属性
max_age = 120
min_age = 90
def __init__(self, name, age):
self.name = name
if age > Person.max_age:
self.age = Person.max_age
else:
self.age = age
# 类属性
p1 = Person('张三', 910)
# p1.speak('哈哈哈')
# 类属性是存在类身上的,实例上是没有的
print(p1.__dict__)
# {'name': '张三', 'age': 120}
print(Person.__dict__)
# {'__module__': '__main__', '__firstlineno__': 2, 'max_age': 120, 'min_age': 90, '__init__': <function Person.__init__ at 0x0000021FF0966610>, 'speak': <function Person.speak at 0x0000021FF0966820>, '__static_attributes__': ('age', 'name'), '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
# 类属性可以通过类访问,也可通过实例访问
print(Person.min_age)
print(p1.min_age) # 查找min_age的过程:1、实例自身(p1) => 2、实例的缔造者(Person)
自定义方法
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self, words):
print(f'我是{self.name},年龄{self.age},说了{words}')
p2 = Person('李四', 22)
# speak 方法是存在Person类身上的,Person的实例对象上是没有speak方法的
# p2.speak('侬好!')
print(p2.__dict__)
# {'name': '李四', 'age': 22}
print(Person.__dict__)
# {'__module__': '__main__', '__firstlineno__': 2, '__init__': <function Person.__init__ at 0x0000024F783A6610>, 'speak': <function Person.speak at 0x0000024F783A6820>, '__static_attributes__': ('age', 'name'), '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
# 当执行p2.speak()时,查找speak的过程:1.实例对象自身(p2) => 2.实例的缔造者身上(Person)
def speak():
print('你好~')
p2.speak = speak
p2.speak()
# 你好~
实例方法
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# speak、run方法都存在类身上,主要是供实例使用,即实例方法
def speak(self, msg):
print(f'我是{self.name},年龄{self.age},说了{msg}')
def run(self, km):
print(f'{self.name}距离还有{km}')
# 创建实例
p1 = Person('张三', 88)
# 通过实例调用实例方法
p1.run(100)
# 通过类调用实例方法
Person.run(p1,200)
类方法
- 通过@classmethod装饰过的方法就是类方法,类方法是保存在类身上的
- 类方法收到的参数:当前类本身cls、自定义的参数
- cls:就可访问类属性
- 类方法通常用于实现与类相关的逻辑:操作类级别的信息,一些工厂函数
from datetime import datetime
class Person:
max_age = 120
def __init__(self, name, age):
self.name = name
self.age = age
# 实例方法
def speak(self, msg):
print(f'我是{self.name},年龄{self.age},说了{msg}')
def run(self, km):
print(f'{self.name}距离还有{km}')
# 类方法
@classmethod
def test(cls, data):
print(f'这里是类方法{data}')
@classmethod
def get_age(cls, value):
print(cls, cls.max_age) # <class '__main__.Person'> 120
current_year = datetime.now().year
age = current_year - value
return age
p1 = Person('张三', 88)
# 类方法是存在类身上的
# print(Person.__dict__)
# 类方法要通过类调用
# Person.test(100)
res = Person.get_age(1997)
print(res)
静态方法
- 使用 @staticmethod 装饰过的方法,就叫:静态方法,静态方法也是保存在类身上的
- 静态方法只是单纯的定义在类中,它不会收到:self、cls它收到的参数都是自定义参数
- 由于静态方法没有收到:self、cls参数,所以其内部不会访问任何:类和实例相关的内容
- 静态方法常用于定义:与类相关的工具方法
from datetime import datetime
class Person:
age_limit = 18
def __init__(self, name, age):
self.name = name
self.age = age
@staticmethod
def is_adult(year):
current_year = datetime.now().year
age = current_year - year
return age >= 18
# 静态方法是存在类身上的
print(Person.__dict__)
# {'__module__': '__main__', '__firstlineno__': 3, 'age_limit': 18, '__init__': <function Person.__init__ at 0x000001E1C4D06610>, 'is_adult': <staticmethod(<function Person.is_adult at 0x000001E1C4D06820>)>, '__static_attributes__': ('age', 'name'), '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
# 静态方法要通过类去调用
res = Person.is_adult(2015)
print(res)
继承
定义一个Student类(子类、派生类),继承自Person类(父类、超类、基类) class Student(Person):
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self, words):
print(f'{self.name} is {self.age} years old, said {words}')
# 定义一个Student类(子类、派生类),继承自Person类(父类、超类、基类)
class Student(Person):
pass
# 创建Student类的实例
s1 = Student('李四', 18)
print(s1.name)
print(s1.age)
# 查找speak的过程:1、实例自身(s1) => 2、Student类 => 3、Person类
s1.speak('hello')
在子类中,有两种方式去调用父类的初始化方法,来实现对继承属性name,age的初始化
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
# 定义一个Student类
class Student(Person):
def __init__(self, name, age, stu_id, stu_grade):
# 方式一
super().__init__(name, age)
# 方式二
Person.__init__(self, name, age)
# 子类独有的属性 需自己初始化
self.stu_id = stu_id
self.stu_grade = stu_grade
def study(self ):
print(f'我叫{self.name},现在{self.stu_grade}年级')
# 创建Student类的实例
s1 = Student('李四', 18, 1001, 2)
# 查找study的过程:1、实例自身(s1) => 2、Student类 => 3、Person类
s1.study()
方法重写
当子类中定义一个与父类中相同的方法,那子类中的方法就会“重写”父类中的方法
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self, words):
print(f'{self.name} is {self.age} years old, said {words}')
# 定义一个子类继承父类
class Student(Person):
def __init__(self, name, age, stu_id, stu_grade):
super().__init__(name, age)
self.stu_id = stu_id
self.stu_grade = stu_grade
# 方法重写
def speak(self, words):
# 调用父类
super().speak(words)
print(f'学号是{self.stu_id},今年{self.stu_grade}年级,说了{words}')
# 创建实例
s1 = Student('李四',19, '10021', 3)
s1.speak('你好')
常用方法
- issubclass(Class, Class) 判断某个类是否为另一个类的子类
- isinstance(instance, Class) 判断某个对象是否为指定类或子类的实例
...
p1 = Person('张三', 28)
s1 = Student('李四',19, '10021', 3)
# isinstance
print(isinstance(p1, Person))
print(isinstance(s1, Student))
print(isinstance(s1, Person))
print(isinstance(p1, Student))
# True True True False
# issubclass
print(issubclass(Student, Person))
print(issubclass(Person, Student))
# True False
多重继承
定义一个子类,继承多个父类class Student(Person, Worker):
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self, words):
print(f'{self.name} is {self.age} years old, said {words}')
class Worker:
def __init__(self, work_name):
self.work_name = work_name
def do_work(self):
print(f'{self.work_name} is doing work')
# 定义一个子类,继承多个父类
class Student(Person, Worker):
def __init__(self, name, age, work_name, grade):
# 用类名去初始
Person.__init__(self, name, age)
Worker.__init__(self, work_name)
self.grade = grade
def study(self):
print(f'study hard,{self.grade} grade!')
s1 = Student('张三', 10, 'waiter',5)
print(s1.__dict__)
s1.speak('hello')
s1.do_work()
s1.study()
# 类的__mro__属性,用于记录属性和方法的查找顺序
# 通过实例查询属性或方法时,会现在实例自身找,若没有,再按照__mro__记录的顺序去查找
print(Student.__mro__)
# (<class '__main__.Student'>, <class '__main__.Person'>, <class '__main__.Worker'>, <class 'object'>)
三种访问限制
- 共有属性
属性名当前类、子类、类外部,都能访问 - 受保护的属性
_属性名当前类、子类,都能访问 - 私有属性
__属性名仅能在当前类中访问
class Person:
def __init__(self, name, age, id_number):
self.name = name # 共有属性
self._age = age # 受保护的属性
self.__id_number = id_number # 私有属性
def speak(self):
print(f'名称:{self.name}, 年龄:{self._age}, 身份证:{self.__id_number}')
class Student(Person):
def study(self):
print(f'子类中-名称:{self.name}, 年龄:{self._age}, 身份证:{self.__id_number}')
# 当前类中访问
p1 = Person('李四', 19, '10002')
p1.speak() # 名称:李四, 年龄:19, 身份证:10002
# 子类中访问
s1 = Student('张三', 22, '10003')
# s1.study() # AttributeError: 'Student' object has no attribute '_Student__id_number'
# 在外部访问
print(p1.name) # 李四 共有属性可以正常访问
# print(p1._age) # 19 受保护的属性强制访问,可以访问,但不推荐
# print(p1.__id_number) # 私有属性访问报错 AttributeError: 'Person' object has no attribute '__id_number'
# Python底层是通过重命名实现私有属性的
print(p1.__dict__) # {'name': '李四', '_age': 19, '_Person__id_number': '10002'}
print(p1._Person__id_number) # 10002 不推荐
getter&setter
注册age属性的getter方法
@property
def age(self):
...
注册age属性的setter方法
@age.setter
def age(self,value):
...
class Person:
max_age = 120
def __init__(self, name, age, id_number):
self.name = name
self._age = age
self.__id_number = id_number
# 注册age属性的getter方法,当访问Person实例的age属性时,下面的age方法就会被自动调用
@property
def age(self):
return self._age
# 注册age属性的setter方法,当修改Person实例的age属性时,下面的age方法就会被自动调用
@age.setter
def age(self,value):
if value > self.max_age:
print('年龄超出限制')
else:
self._age = value
# 注册id_number属性的getter方法,当访问Person实例的id_number属性时,下面的id_number方法就会被自动调用
@property
def id_number(self):
return self.__id_number[:6] + '*******'+ self.__id_number[-4:]
# 注册id_number属性的setter方法,当修改Person实例的id_number属性时,下面的id_number方法就会被自动调用
@id_number.setter
def id_number(self,value):
print('身份证号不允许修改!')
p1 = Person('张三', 22, '370831199712345678')
print(p1.age)
p1.age = 130
print(p1.age)
print(p1.id_number)
p1.id_number = '10002'
print(p1.id_number)
魔法方法
以__xxx__双下划线开头和结尾命名的特殊方法
| 方法 | 调用时机 |
|---|---|
__str__ | 当调用print(对象)或str(对象)时 |
__len__ | 当调用len(对象)时 |
__gt__ | 当执行对象1>对象2时 |
__lt__ | 当执行对象1<对象2时 |
__eq__ | 当执行对象1==对象2时 |
__getattr__ | 当访问不存在属性时 |
class Person:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
# 当执行 print(Person的实例对象) 或 str(Person的实例对象) 时调用
def __str__(self):
# 默认返回 <__main__.Person object at 0x0000021B2B7270E0>
# 修改返回 我是张三,今年22
return f'我是{self.name},今年{self.age},性别{self.gender}'
# 当执行 len(Person的实例对象) 时调用
def __len__(self):
return len(self.__dict__)
# 当执行 Person实例对象1 > Person实例对象2 时调用
def __gt__(self, other):
return self.age > other.age
# 当执行 Person实例对象1 < Person对象实例2 时调用
def __lt__(self, other):
return self.age < other.age
# 当执行 Person实例对象1 == Person对象实例2 时调用
def __eq__(self, other):
return self.__dict__ == other.__dict__
# 当访问不存在属性时
def __getattr__(self, item):
return f'属性{item}不存在'
p1 = Person('张三', 22, '男')
p2 = Person('李四', 23, '女')
# print(p1)
# print(str(p1))
# print(len(p1))
# res = p1 > p2
# print(res)
# res = p1 < p2
# print(res)
res = p1 == p2
print(res)
print(p1.no_name)
object类
# Python中,所有的类都继承了 object 类,即object类是所有类的顶层父类
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
# Person类继承了object类
# print(issubclass(Person, object))
# 所有的类继承了object类
# print(issubclass(int, object))
# print(issubclass(str, object))
# print(issubclass(bool, object))
# print(issubclass(list, object))
# print(issubclass(tuple, object))
# object是所有类的父类,所以所有对象,都间接是object的实例
p1 = Person('张三', 12)
# print(isinstance(p1, Person))
# print(isinstance(p1, object))
# print(isinstance(200, object))
# print(isinstance('python', object))
# print(isinstance(True, object))
# print(isinstance(None, object))
# print(isinstance([1, 2, 3], object))
# print(isinstance({'a', 'b'}, object))
# 调用对象都继承了object类所提供的 各种属性和方法,从而保证了每个对象都具备统一的基本能力
for item in object.__dict__:
print(item)
多态
- 多态:同一个方法,在不同的对象上调用时,能呈现出不同的行为
- 多态又分 标准多态、鸭子多态
标准多态
标准多态的三个条件:继承关系、方法重写、类型限制
class Animal:
def speak(self):
print('动物们的叫声')
class Dog(Animal):
def speak(self):
print('汪汪汪')
class Cat(Animal):
def speak(self):
print('喵喵喵')
class Pig:
def speak(self):
print('哼哼哼')
def make_sound(animal: Animal): # 类型注解
# 多态的体现
animal.speak()
# 创建实例
a1 = Animal()
d1 = Dog()
c1 = Cat()
p1 = Pig()
make_sound(a1)
make_sound(d1)
make_sound(c1)
make_sound(p1) # 不报错,但不推荐
鸭子多态
鸭子类型是一种编程风格,不检查对象的类型,只关注对象能否“做某件事”(是否有对应的方法)
# 鸭子多态
class Animal:
def speak(self):
print('动物们的叫声')
class Dog:
def speak(self):
print('汪汪汪')
class Cat:
def speak(self):
print('喵喵喵')
class Pig:
def speak(self):
print('哼哼哼')
def make_sound(animal):
animal.speak()
# 创建实例
a1 = Animal()
d1 = Dog()
c1 = Cat()
p1 = Pig()
make_sound(a1)
make_sound(d1)
make_sound(c1)
make_sound(p1)
抽象类
抽象类是一种不能直接实例化的类,它通常作为“规范”,让子类去继承,并实现其中定义的的抽象方法
from abc import ABC, abstractmethod
# MustRun类一旦继承就ABC类,那么MustRun类就是抽象类了
class MustRun(ABC):
@abstractmethod
def run(self):
pass
# m1 = MustRun() #无法实例化抽象类 'MustRun'
# 类 Person 必须实现所有 abstract 方法
class Person(MustRun):
def __init__(self, name):
self.name = name
def run(self):
print(f'{self.name} is running...')
p1 = Person('P1')
p1.run()
重新认识函数
1、函数也是对象
a1 = 100 # int类的实例对象
a2 = 'hello' # str类的实例对象
a3 = ['a', 'b', 'c'] # list类的实例对象
def welcome(): # welcome函数是function类的实例对象
print('你好')
print(type(a1))
print(type(a2))
print(type(a3))
print(type(welcome))
2、函数可以动态添加属性
def welcome():
print('你好')
welcome.desc = '这里是描述'
welcome.version = 1.1
print(welcome.desc)
print(welcome.version)
3、函数可以赋值给变量
def welcome():
print('你好')
welcome.desc = '这里是描述'
welcome.version = 1.1
say_hello = welcome
say_hello() # 直接调用
print(say_hello.desc)
print(say_hello.version)
4、可变参数 vs 不可变参数
可变参数
a = [1, 2, 3, 4]
def welcome(data):
print('data修改前', data, id(data))
data[2] = 6
print('data修改后', data, id(data))
print('调用函数前a', a, id(a))
welcome(a)
print('调用函数后a', a, id(a))
# 调用函数前a [1, 2, 3, 4] 2649018135552
# data修改前 [1, 2, 3, 4] 2649018135552
# data修改后 [1, 2, 6, 4] 2649018135552
# 调用函数后a [1, 2, 6, 4] 2649018135552
不可变参数
a = 666
def welcome(data):
print('data修改前', data, id(data))
data = 688
print('data修改后', data, id(data))
print('调用函数前a', a, id(a))
welcome(a)
print('调用函数后a', a, id(a))
# 调用函数前a 666 2576383653584
# data修改前 666 2576383653584
# data修改后 688 2576388584464
# 调用函数后a 666 2576383653584
5、函数也可做为参数
def welcome(fn):
print('你好')
fn()
def say_hello():
print('hello')
welcome(say_hello)
6、函数也可作为返回值
def welcome():
print('你好')
def show_msg(msg):
print(msg)
return show_msg
# res = welcome()
# res('say hello')
welcome()('say hello')
函数的多返回值
def more_fun(x, y):
res1 = x + y
res2 = x - y
return res1, res2
res = more_fun(2, 3)
print(res)
r1, r2 = more_fun(2, 3)
print(r1, r2)
参数的打包与解包
打包接收参数
- *args: 打包所有的位置参数,会形成一个元组
- **kwargs: 打包所有的关键字参数,会形成一个字典
def show(*args, **kwargs):
print(args) # ('hello', 'world')
print(kwargs) # {'name': 'zs', 'age': 18
show('hello', 'world', name='zs', age=18)
解包传递函数
- *变量名:将元组拆解成一个一个独立的位置参数
- **变量名:将字典拆解成一个一个 key=value 形式的关键字参数
def show(num1, num2, name, age):
print(num1, num2) # 111 222
print(name, age) # 张三 19
nums = (111, 222)
person = {'name': '张三', 'age': 19}
show(*nums, **person)
打包解包一块使用
def show(*args, **kwargs):
print(args) # (111, 222)
print(kwargs) # {'name': '张三', 'age': 19}
nums = (111, 222)
person = {'name': '张三', 'age': 19}
show(*nums, **person)
高阶函数
高阶函数:当一个函数的『参数是函数』或者『返回值是函数』那该函数就是『高阶函数」
高阶函数的意义:
- 代码复用性高:可以把行为“独立出去”,传入不同函数实现不同逻辑。
- 能让函数更灵活,更通用。
- 高阶函数是:装饰器、闭包的基础。
def info(msg):
return f'【提示】:{msg}'
def warn(msg):
return f'【警告】:{msg}'
def log(fn, msg):
res = fn(msg)
print(res)
log(warn, '禁止🎉')
log(info, '恭喜😘')
表达式
表达式:执行后能得到值的代码,就是表达式(表达式最终会形成一个值,可以写在任何需要值的地方)
a1 = 1 + 2
a2 = 'abc' * 3
print(1 * 3)
int('y' in 'python')
a5 = len('python')
条件表达式:根据条件的真假,在两个结果中二选一的表达式(又称:三元表达式、三目运算符)
语法: 1 if 条件 else 值2
age = 20
res = '成年人' if age >= 18 else '未成年人'
print(res) # 成年人
匿名函数
- 概念:所谓『匿名函数』,就是没有名字的函数,它无需使用 def 关键字去定义。
- 语法:Python 中使用lambda 关键字来定义『匿名函数』,格式为:
lambda参数:表达式 - 使用场景:当一个函数只用一次、只做一点点小事,使用匿名函数会更简洁。
# 普通函数实现计算结果
def add(a, b):
return a + b
def sub(a, b):
return a - b
def calc(func, a, b):
print(f'计算结果:{func(a, b)}')
calc(add, 2, 3)
calc(sub, 4, 5)
# 匿名函数
add1 = lambda x, y: x + y
add2 = lambda x: x * 2
add3 = lambda: '我是add3函数'
res1 = add1(1, 2)
res2 = add2(6)
res3 = add3()
print(res1, res2, res3)
# 用匿名函数实现计算结果
def calc(func, a, b):
print(f'计算结果:{func(a, b)}')
calc(lambda x, y: x + y, 30, 20) # 计算结果:50
calc(lambda x, y: x - y, 30, 20) # 计算结果:10
注意事项:
- 只能写一行,不能写多行代码。
- 不能写代码块(if、for、while)
- 冒号右边必须是表达式,且只能写一个表达式。
- 表达式结果自动作为返回值。
数据处理函数
map函数
- map函数:对一组数据中的每一个元素,统一执行某种操作(加工),并生成一组新数据。
- 语法格式:
map(操作函数,可迭代对象) - 延迟执行:map不会立刻计算,只有在“需要结果”时才执行计算。
- 返回的是迭代器对象,且一旦遍历完成,就会被“耗尽”。
- map不会影响元素数量。
# 统一数据处理
nums = [1, 2, 3, 4, 5]
res = map(lambda x: x * 2, nums)
# map的返回值是一个迭代器对象,需要手动遍历,或手动类型转换
# print(res) # <map object at 0x0000022F82B67F00>
# 遍历处理
for item in res:
print(item)
# 类型转换处理
print(list(res)) # [2, 4, 6, 8, 10]
# 字符转换
ele = ('python', 'vue', 'java')
res = map(lambda x: x.upper(), ele)
print(tuple(res)) # ('PYTHON', 'VUE', 'JAVA')
# 类型转换
str1 = ['1', '2', '3']
res1 = map(int, str1)
print(list(res1)) # [1, 2, 3]
print(list(res1)) # [] # 返回的是迭代器对象,且一旦遍历完成,就会被“耗尽”。
print(str1) # ['1', '2', '3'] # map不会影响元素数量。
filter函数
- filter函数:从一组数据中,筛选出符合条件的元素(过滤),并组成一组新数据。
- 语法格式:
filter(过滤函数,可迭代对象) - 延迟执行:filter不会立刻筛选,只有在“需要结果”时才执行。
- 返回的是迭代器对象,且一旦遍历完成就会被“耗尽”。
- filter可能会影响元素数量
- filter的特殊用法,如果不传过滤函数,会自动过滤“假值”
# 筛选数据
nums = [1, 2, 3, 4, 5]
res = filter(lambda x: x > 2, nums)
print(list(res)) # [3, 4, 5]
print(list(res)) # []
print(nums) # [1, 2, 3, 4, 5]
# 筛选成年人
person = [ {'name': '张三', 'age': 15}, {'name': '李四', 'age': 18}, {'name': '王五', 'age': 19},]
res1 = filter(lambda p: p['age'] >= 18, person)
print(list(res1)) # [{'name': '李四', 'age': 18}, {'name': '王五', 'age': 19}]
# 过滤非法字符
names = ['张三', '', '李四', None]
res2 = filter(lambda n: n, names)
print(list(res2)) # ['张三', '李四']
# filter的特殊用法,如果不传过滤函数,会自动过滤“假值”
data = [1, '2', [], None, '', 666]
res3 = filter(None, data)
print(list(res3)) # [1, '2', 666]
sorted函数
- sorted函数:对一组数据进行排序,返回一组新数据
- 语法格式:
sorted(可迭代对象, key=xxx, reverse=布尔值)
# 数字排序
nums = [1, 4, 6, 3, 2, 8, 5, 7]
res = sorted(nums, reverse=True)
print(res) # [8, 7, 6, 5, 4, 3, 2, 1]
# 按照字符串长度排序
str1 = ['python', 'java', 'javascript', 'vue']
# res1 = sorted(str1, key=lambda x: len(x))
res1 = sorted(str1, key=len)
print(res1) # ['vue', 'java', 'python', 'javascript']
# 根据字典中某个字段排序
persons = [ {'name': '张三', 'age': 20}, {'name': '李四', 'age': 18}, {'name': '王五', 'age': 19}, {'name': '赵六', 'age': 12}, {'name': '小李', 'age': 21},]
res2 = sorted(persons, key=lambda p: p['age'])
print(res2) # [{'name': '赵六', 'age': 12}, {'name': '李四', 'age': 18}, {'name': '王五', 'age': 19}, {'name': '张三', 'age': 20}, {'name': '小李', 'age': 21}]
# max()、min()函数也可设置key,进行筛选数据
res3 = max(persons, key=lambda p: p['age'])
res4 = min(persons, key=lambda p: p['age'])
print(res3) # {'name': '小李', 'age': 21}
print(res4) # {'name': '赵六', 'age': 12}
reduce函数
- reduce函数:将一组数据不断的合并,最终合成一个结果
- 语法:
reduce(合并函数, 可迭代对象, 初始值) - reduce函数需要从 functools 模块中引入才能使用
from functools import reduce
# 数值统计
nums = [1, 2, 3, 4, 5]
res = reduce(lambda a, b: a + b, nums, 6)
print(res) # 21
# 字符拼接
str1 = ['a', 'bc', 'de', 'f']
res1 = reduce(lambda x, y: x + y, str1)
print(res1) # abcdef
列表推导式
- 列表推导式:用一条简洁语句,从可迭代对象中,生成新列表的语法结构。
- 备注:列表推导式本质上是对for循环+append()的一种简写形式。
- 语法格式:
[表达式for变量in 可迭代对象]
# 需求:让列表中每个元素,都变为原来的2倍,得到是一个新的列表
nums = [1, 2, 3, 4, 5]
# 用map实现
res = list(map(lambda x: x * 2, nums))
print(res) # [2, 4, 6, 8, 10]
# 用 for + append 实现
res1 = []
for item in nums:
res1.append(item * 2)
print(res1) # [2, 4, 6, 8, 10]
# 用列表推导式实现
res2 = [n * 2 for n in nums]
print(res2) # [2, 4, 6, 8, 10]
# 带条件的列表推导式
res3 = [n * 2 for n in nums if n > 2]
print(f'res3 {res3}') # [6, 8, 10]
# 字典推导式
names = ['张三', '李四', '王五']
scores = [10, 20, 30]
res4 = {names[i]: scores[i] for i in range(len(names))}
print(f'res4 {res4}') # {'张三': 10, '李四': 20, '王五': 30}
# 集合推导式
res5 = {n + '!' for n in names}
print(f'res5 {res5}') # {'张三!', '王五!', '李四!'}
# python中没有元组推导式
res6 = (n + '!' for n in names)
print(f'res6 {res6}') # <generator object <genexpr> at 0x000002C8FC608EE0>
常用内置函数
1、输出与输入
print输出函数
- print 输出指定内容
- 完整参数:
print(*objects, sep='', end='\n', file=sys.stdout, flush=False) - 参数详解
- objects: 要输出的内容
- sep: 分隔符
- end: 结束符
- file: 输出位置
- flush: 是否立即刷新
# 1、sep, end
print(10, 20, 30, 40, sep='-', end='!') # 10-20-30-40!
# 2、file
f = open('a.txt', 'w', encoding='utf-8')
print(10, 20, 30, 40, sep='-', end='!', file=f) # 在同级目录下a.text文件中
# 3、flush
from time import sleep
# 第一种进度条: 加载中.....完成
print('加载中', end='', flush=True)
for i in range(5):
print('.', end='', flush=True)
sleep(1)
print('完成', end='')
# 第二种进度条:已加载1% 10% 100%
for i in range(1,101):
print(f'\r已加载{i}%', end='', flush=False)
sleep(0.1)
input 输入函数
- input 获取用户输入
2、类型转换
| 函数 | 函数 | 函数 | |||
|---|---|---|---|---|---|
| int() | 转为整数 | float() | 转为浮点数 | str() | 转为字符串 |
| bool() | 转为布尔值 | list() | 转为列表 | tuple() | 转为元组 |
| set() | 转为集合 | dict() | 转为字典 |
3、数学函数
| 函数 | 函数 | 函数 | |||
|---|---|---|---|---|---|
| abs() | 取绝对值 | round() | 四舍五入 | pow() | 次方 |
| divmod() | 商和余数 | max() | 最大值(支持key) | min() | 最小值(支持key) |
| sum() | 求和 | map() | 加工一组数据 | fillter() | 条件过滤(支持key) |
| reduce() | 合并计算(需导入functools) | sorted() | 排序(支持key) |
# abs() 取绝对值
print(abs(-1)) # 1
print(abs(-2.5)) # 2.5
print(abs(2 - 4)) # 2
# round() 四舍五入
# 注:round函数的四舍五入是银行家舍入法:小于5就舍,大于5就入,等于5看奇偶,奇入偶舍
print(round(1.2)) # 1
print(round(2.6)) # 3
print(round(3.5)) # 4
print(round(4.5)) # 4
# pow() 次方
print(pow(2, 3)) # 2的3次方 8
print(pow(2, -1)) # 2的-1次方 0.5
print(pow(2, 0.5)) # 2的开方 1.4142135623730951
print(pow(2, 3, 5)) # 2的3次方再对5取模 3
# divmod() 商和余数
print(divmod(5, 3)) # (1, 2)
4、数据容器相关
| 函数 | 函数 | ||
|---|---|---|---|
| len() | 获取容器中的元素个数 | range() | 生成一个数字序列,可用于循环 |
| enumerate() | 给序列添加索引 | zip() | 将多个序列一一匹配 |
# range() 生成一个数字序列,可用于循环
for i in range(0, 10, 2):
print(i, end='\t')
# zip() 将多个序列一一配对
names = ['张三', '李四', 'tom']
age = [19, 28, 39]
print(list(zip(names, age)))
5、类型判断与对象相关
| 函数 | 函数 | ||
|---|---|---|---|
| type() | 查看类型 | isinstance() | 判断类型 |
| issubclass | 判断两个类型的继承关系 | id() | 查看对象内存地址 |
6、逻辑判断相关
| 函数 | 函数 | ||
|---|---|---|---|
| all() | 全为真,返回True | any() | 有一个为真,返回True |
# all() 全为真 返回True
data = [1, 'a', {1, 2, 3}]
print(all(data))
# any() 有一个为真即返回True
data1 = ['', None, False, 1]
print(any(data1))
7、字符串辅助相关
| 函数 | 函数 | ||
|---|---|---|---|
| ord() | 获取字符的 Unicode 编码值 | chr() | 将 Unicode 编码值转为字符 |
浅拷贝与深拷贝
直接赋值
两个变量指向同一个对象,修改其中一个,就会影响另一个
num1 = [1, 2, 3, 4]
num2 = num1
num2[1] = 6
print(num1, num2) # [1, 6, 3, 4] [1, 6, 3, 4]
print(id(num1)) # 1808580998144
print(id(num2)) # 1808580998144
浅拷贝
创建一个新的外层容器,但内部元素仍然引用原来的对象
import copy
num1 = [1, 2, 3, 4]
num2 = copy.copy(num1)
num2[1] = 6
print(id(num1)) # 1280278459392
print(id(num2)) # 1280278575616
print(num1, num2) # [1, 2, 3, 4] [1, 6, 3, 4]
浅拷贝的问题:嵌套数据仍然是共享的,修改嵌套数据会互相影响
num1 = [1, 2, 3, [4, 5]]
num2 = copy.copy(num1)
num2[3][1] = 6
print(id(num1)) # 1783767218688
print(id(num2)) # 1783768575744
print(num1) # [1, 2, 3, [4, 6]]
print(num2) # [1, 2, 3, [4, 6]]
深拷贝
创建一个新的外层容器,并对其内部所有的【可变子对象】进行递归复制
- 深拷贝可以彻底消除数据之间的相互影响
- 深拷贝遇到【不可变对象】不会复制,会直接引用
num1 = [1, 2, 3, [4, 5]]
num2 = copy.deepcopy(num1)
num2[3][1] = 6
print(id(num1)) # 2932165097984
print(id(num2)) # 2932166455040
print(num1) # [1, 2, 3, [4, 5]]
print(num2) # [1, 2, 3, [4, 6]]
- 深拷贝只复制可变对象,不可变对象会直接引用
- 元组中如果只包含不可变对象,则深拷贝没有效果
# 深拷贝只复制可变对象,不可变对象会直接引用
num1 = 6
num2 = copy.deepcopy(num1)
print(id(num1)) # 140728209462552
print(id(num2)) # 140728209462552
# 元组中如果只包含不可变对象,则深拷贝没有效果
a = (1, 2, 3, (4, 5))
b = copy.deepcopy(a)
print(id(a)) # 1711475171520
print(id(b)) # 1711475171520
作用域
- Python 中有四种作用域,分别是:Local、Enclosing、Global、Built-in
- 当访问一个变量时,Python会按以下顺序查找:Local =>Enclosing =>Global =>Built-in
局部作用域(Local)
定义:函数的内部就是局部作用域,局部作用域中的变量,只能在该函数内部可见。
特点:
- 每次调用函数都会创建一个新的局部作用域。函数运行结束后,局部作用域随之销毁。
- 局部作用域优先级最高(LEGB中的L)。
外层作用域(Enclosing)
定义:如果函数中又定义了函数,那么外层函数的作用域,就是内层函数的Enclosing作用域。
特点:
- 只有当函数“嵌套定义”时才会出现。
- 内层函数可以读取外层函数变量。
- 想修改外层变量必须使用nonlocal
全局作用域(Global)
定义:.py文件就是全局作用域,全局作用域中的变量,在当前.py 文件的任何位置都可以访问。
特点:
- 全局变量只在当前.py 文件中可见。
- 函数内部可以使用global关键字修改全局变量。
内建作用域(Built-in)
定义:Python预先定义好的东西,会放在内建作用域中,所有.py 文件都可以直接使用。
特点:
- 所有.py文件都能直接使用其中的名称。
- 例如:print、len、range、sum、max 查找优先级最低(LEGB的B)
闭包
闭包 = 内层函数 + 被内层函数所引用的外层变量
闭包产生的条件:
- 要有函数嵌套
- 在【内层函数】中,要访问【外层函数】的变量
- 并且在【外层函数】要返回【内层函数】。--只有返回了内层函数,闭包才能“活下来”
def outer():
num = 1
print(hex(id(num)))
def inner():
nonlocal num
# num += 1
print(f'inner-{num}')
return inner
f = outer()
f()
# f()
# f()
结论
- outer函数中,被inner所使用到的那些变量,会被封存到【闭包单元cell】中
- 这些cell会组成一个__closure__元组,最终放在了inner函数上
# 打印__closure__元组 => (<cell at 0x0000019712C19FC0: int object at 0x00007FFBFE095478>,)
print(f.__closure__)
# 打印__closure__元组中的某一项 => <cell at 0x0000019712C19FC0: int object at 0x00007FFBFE095478>
print(f.__closure__[0])
# 打印__closure__元组中的某一项的具体值 => 1
print(f.__closure__[0].cell_contents)
注意点
- 调用n次外层函数,就会得到n个不同的闭包,且这些闭包之间互不影响
- 内层函数中用到的外层变量是可变对象,多个闭包之间依然互不影响
# 1、调用n次外层函数,就会得到n个不同的闭包,且这些闭包之间互不影响
def outer():
num = 10
def inner():
nonlocal num
num += 1
print(num)
return inner
f = outer()
f() # 11
f() # 12
f() # 13
f2 = outer()
f2() # 11
# 2、内层函数中用到的外层变量是可变对象,多个闭包之间依然互不影响
def outer():
nums = []
def inner(val):
nums.append(val)
print(nums)
return inner
f = outer()
f(1) # [1]
f(2) # [1, 2]
f(3) # [1, 2, 3]
print('------------')
f2 = outer()
f2(666) # [666]
闭包的优点
- 可以“记住”状态:不用全局变量,也不用写类,就能在多次调用之间保存数据。
- 可以做“配置过的函数”:先传一部分参数,把环境固定住,得到一个定制版函数。
- 可以实现简单的“数据隐藏”:外层变量对外不可见,只能通过内层函数访问。
- 是装饰器(decorator)等高级用法的基础。
def beauty(char, n):
def show_msg(msg):
print(char * n + msg + char * n)
return show_msg
show1 = beauty('*', 3)
show1('你好啊') # ***你好啊***
show2 = beauty('@', 5)
show2('hello') # @@@@@hello@@@@@
闭包的缺点
- 理解成本较高:对初学者不太友好,滥用会让代码难读。
- 如果闭包里引用了很大的对象,又长期不释放,可能会增加内存占用。
- 很多场景下,其实用【类+实例属性】会更清晰,闭包不一定是最优解。
class Beauty:
def __init__(self, char, n):
self.char = char
self.n = n
def show_msg(self, msg):
print(self.char * self.n + msg + self.char * self.n)
print('-------------')
b1 = Beauty('*', 3)
b1.show_msg('今天周二') # ***今天周二***
b1.show_msg('明天周三')
print('-------------')
b2 = Beauty('@', 5)
b2.show_msg('python') # @@@@@python@@@@@
b2.show_msg('java')
装饰器
函数装饰器
- 装饰器是一种【可调用对象】(通常是函数),它能接收一个函数作为参数,并且会返回一个新函数
- 装饰器可以在不修 改原函数代码的情况下,增强或改变原函数的功能
关键点
- 接收被装饰的函数、同时返回新函数(wrapper)
- 装饰器“吐出来”的是wrapper函数,以后别人调用的也是wrapper函数。
- 为了保证参数的兼容性,wrapper函数要通过 *args 和 **kwargs 接收参数。
- wrapper函数中主要做的是:调用原函数(被装饰的函数)、执行其它逻辑,但要记得将原函数的返回值出去return出去
实际应用:在不改变原函数的前提下,给函数统一加上:日志、计时、校验、缓存
def say_hello(func):
print('欢迎~现在开始计算!')
# def wrapper(a, b):
# return func(a, b)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# 第二种、使用@装饰名
@say_hello
def add(a, b):
res = a + b
print(f'{a}+{b}={res}')
return res
# 正常调用函数
# result = add(1, 2)
# print(f'结果:{result}')
# 在不修改add函数的前提下,给add函数增加一些额外的功能:计算器先打印一句欢迎语
# 第一种、手动装饰
# add_pro = say_hello(add)
# result = add_pro(1, 2)
# print(f'结果:{result}')
# 第二种、@装饰名
result = add(11, 22)
print(f'结果:{result}')
带参数的装饰器
(三层嵌套:外层接收配置、中间层接收函数、内层接收具体参数)
# 进阶:带参数的装饰器(三层嵌套:外层接收配置、中间层接收函数、内层接收具体参数)
def say_hello(msg):
def outer(func):
def inner(*args, **kwargs):
print(f'欢迎,现在开始{msg}计算了!')
return func(*args, **kwargs)
return inner
return outer
@say_hello('加法')
def add(a, b):
result = a + b
print(f'{a}+{b}={result}')
return result
@say_hello('减法')
def sub(a, b):
result = a - b
print(f'{a}-{b}={result}')
return result
# res1 = add(66, 88)
#
# res2 = sub(88, 66)
多个装饰器
def test1(func):
print('test1装饰器')
def inner(*args, **kwargs):
print('这是test1追加的逻辑')
return func(*args, **kwargs)
return inner
def test2(func):
print('test2装饰器')
def inner(*args, **kwargs):
print('这是test2追加的逻辑')
return func(*args, **kwargs)
return inner
@test1
@test2
def multip(a, b):
res = a * b
print(f'{a}*{b}={res}')
return res
res_mul = multip(10, 20)
类装饰器
- 包含__call__ 方法的类,就是类装饰器。
- 像调用函数一样,去调用类装饰器的实例对象,就会触发__call__ 方法的调用。
- call 方法通常接收一个函数作为参数,并且会返回一个新函数。
class SayHello:
def __call__(self, func):
def wrapper(*args, **kwargs):
print('欢迎,开始计算了')
return func(*args, **kwargs)
return wrapper
# 使用类装饰器
@SayHello()
def add(a, b):
res = a + b
print(f'{a}+{b}={res}')
return res
# 正常调用add函数
result = add(1, 2)
# 手动装饰:用 SayHello 去装饰add函数
say = SayHello()
add_pro = say(add)
result = add_pro(10, 20)
带参数的类装饰器
class SayHello:
def __init__(self, msg):
self.msg = msg
def __call__(self, func):
def wrapper(*args, **kwargs):
print(f'欢迎,开始{self.msg}计算了')
return func(*args, **kwargs)
return wrapper
@SayHello('加法')
def add(a, b):
res = a + b
print(f'{a}+{b}={res}')
return res
result = add(11, 22)
多个类装饰器
class Test1:
def __call__(self, func):
def wrapper(*args, **kwargs):
print('Test1类装饰器')
return func(*args, **kwargs)
return wrapper
class Test2:
def __call__(self, func):
def wrapper(*args, **kwargs):
print('Test2类装饰器')
return func(*args, **kwargs)
return wrapper
@Test1()
@Test2()
def add(a, b):
res = a + b
print(f'{a}+{b}={res}')
return res
result = add(20, 30)
print(result)
类型注解
变量类型注解
变量类型注解:给类型加上类型说明,增加代码可读性,让IDE提示更友好
num: int = 10
price: float = 20.9
name: str = '李四'
is_vip: bool = False
res: None = None
# 注:可以先写变量的注解,再赋值
age: int
print('----------------')
age = 20
练习1:列表类型注解
hobby 是列表,并且列表中的所有元素必须是 str 类型
hobby: list[str] = ['a', 'b', 'c']
hobby.append('d')
hobby 是列表,并且列表中的元素可以是 str 或 int 类型
hobby: list[str | int] = ['a', 'b', 1, 2]
低版本3.10以下旧写法
from typing import Union
hobby: list[Union[str | int]] = ['a', 'b', 1, 2]
hobby.append('c')
hobby.append(3)
练习2:集合类型注解
cities 是集合,并且集合中的所有元素必须是 str 类型
cities: set[str] = {'北京', '上海', '广州'}
cities.add('深圳')
cities 是集合,并且集合中的元素可以是 str 或 int 或 bool 类型
cities: set[str | int | bool] = {'python', 666, True}
cities.add('9.99')
练习3:字典类型注解
persons 是字典,键是str类型,值是int类型
persons: dict[str, int] = {'张三': 18, '李四': 20}
persons['小米'] = 21
persons 是字典,键是str或int类型,值是int类型
persons: dict[str | int, int] = {'张三': 18, '李四': 20, 11: 100}
persons['小米'] = 21
persons[100] = 666
练习4:元组类型注解
scores 是元组,且元组中只包含一个int类型的元素
scores: tuple[int] = (666,)
scores 是元组,且元组中包含3个的int类型的元素
scores: tuple[int, int, int] = (666, 777, 888)
scores 是元组,且元组中包含任意个数的int类型的元素
scores: tuple[int, ...] = (666, 777, 888, 999)
scores 是元组,且元组中包含任意个数的int类型的元素
scores: tuple[int | str, ...] = (666, '777', '888', 999)
函数类型注解
函数类型注解:给函数的【参数】和【返回值】添加类型说明
语法:函数名(参数1: 类型, 参数2: 类型) -> 返回值类型:
示例1:设置参数类型注解、设置返回值类型注解
def add(x: int, y: int) -> int:
return x + y
result = add(10, 20)
获取函数的类型注解信息 函数名.__annotations__
# 获取函数的类型注解信息
print(add.__annotations__)
# {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
示例2参数有默认值(python可以推导出参数的类型)、设置返回值类型
def add(x=1, y=2) -> int:
return x + y
result = add(10, 20)
示例3:设置多个返回值的类型注解
def get_nums(nums: list[int]) -> tuple[int, int, float]:
max_val = max(nums)
min_val = min(nums)
avg = sum(nums) / len(nums)
return max_val, min_val, avg
result = get_nums([10, 20, 30, 40])
print(result)
示例4:设置 *args 的类型注解,要求 args 中的每个参数都必须是 int 类型
def add(*args: int) -> int:
return sum(args)
result = add(10, 20, 30, 40)
print(result)
示例5:设置 **kwargs 的类型注解,要求 kwargs 中的每组参数的值,必须是 str 或 int 类型
def show_info(**kwargs):
print(kwargs)
show_info(name='张三', age=19, gender='男')
异常处理
- 将可能出现异常的代码放在try 中,出现异常后的处理代码写在 except 中。
- 如果try 中的代码出现异常,那try 中的后续代码将不会执行,并自动跳转到except 中处理异常。
- 如果try 中的代码没有异常,那 except 中的代码就不会执行。
- 无论是否发生异常,try-except后面的代码都会继续执行。
- 直接写 except 会捕获到Python 中所有的异常- 实际开发中不推荐这样做。
具体异常:官方文档
print('欢迎~')
try:
a = int(input('请输入第一个数:'))
b = int(input('请输入第二个数:'))
result = a / b
print(f'计算:{a}/{b}={result}')
except:
print('程序出现异常')
print('后续代码1')
print('后续代码2')
异常处理:捕获指定类型的异常
print('欢迎~')
try:
a = int(input('请输入第一个数:'))
b = int(input('请输入第二个数:'))
result = a / b
print(f'计算:{a}/{b}={result}')
except ZeroDivisionError:
print('除数不能为0!')
except ValueError:
print('必须输入数字!')
print('后续代码1')
print('后续代码2')
获取异常的具体信息
print('欢迎~')
try:
a = int(input('请输入第一个数:'))
b = int(input('请输入第二个数:'))
print(x)
result = a / b
print(f'计算:{a}/{b}={result}')
except ZeroDivisionError:
print('除数不能为0!')
except ValueError:
print('必须输入数字!')
except BaseException as e:
print(f'程序异常,异常信息:{e}')
print(f'程序异常,异常类型:{type(e)}')
print(f'程序异常,异常参数:{e.args}')
print(f'程序异常,异常的文件:{e.__traceback__.tb_frame.f_code.co_filename}')
print(f'程序异常,异常的行数:{e.__traceback__.tb_lineno}')
# 通过 traceback 来回溯异常
# import traceback
# print(traceback.format_exc())
print('后续代码1')
print('后续代码2')
一个 except 也可捕获不同的异常
print('欢迎~')
try:
a = int(input('请输入第一个数:'))
b = int(input('请输入第二个数:'))
result = a / b
print(x)
print(f'计算:{a}/{b}={result}')
except (ZeroDivisionError, ValueError, BaseException) as e:
if isinstance(e, ZeroDivisionError):
print('除数不能为0!')
elif isinstance(e, ValueError):
print('必须输入数字!')
else:
print(f'程序异常,异常信息:{e}')
print('后续代码1')
print('后续代码2')
完整写法
- try: 尝试去做可能会出现异常的事情
- except: 出现异常时的处理(出现异常时怎么补救)
- else: 如果一切顺利(没有异常出现)要做的事
- finally: 无论有没有异常,都要做的事
print('欢迎~')
try:
a = int(input('请输入第一个数:'))
b = int(input('请输入第二个数:'))
result = a / b
print(x)
print(f'计算:{a}/{b}={result}')
except (ZeroDivisionError, ValueError, BaseException) as e:
if isinstance(e, ZeroDivisionError):
print('除数不能为0!')
elif isinstance(e, ValueError):
print('必须输入数字!')
else:
print(f'程序异常,异常信息:{e}')
else:
print('代码无异常!')
finally:
print('代码执行完毕!')
print('后续代码1')
print('后续代码2')
手动抛出异常
当程序遇到不符合预期情况时,可使用 raise 语句手动触发(抛出异常)
try:
age = int(input('请输入您的年龄:'))
if 18 <= age <= 120:
print('成年人')
elif 0 <= age < 18:
print('未成年人')
else:
# print('输入年龄有误')
raise ValueError('输入年龄有误')
except Exception as e:
print(f'程序异常:{e}')
自定义异常类
- 由开发自己定义一个异常类,用来表示代码中“更具体、更有业务含义”的异常
- 具体规则:定义一个类(类名通常以 Error 结尾),继承 Exception 类或它的子类
# 实现一个名称异常类
class PersonNameError(Exception):
def __init__(self,msg):
super().__init__(f'【校名异常】{msg}')
def check_name(name):
if len(name) > 10:
raise PersonNameError('姓名过长')
else:
print('正常的姓名')
try:
check_name('张三李四哇哇双方的第三方')
except PersonNameError as e:
print(f'程序异常:{e}')
模块
模块概述
- 在 Python 中,一个.py文件就是一个模块(Module)。
- 模块中可以包含:变量、函数、类、等很多内容。
- 通常把能够实现某一特定功能的代码,集中放在一个模块中(模块就类似于一个工具箱).
- 模块可以提高代码的可维护性与可复用性,还可以避免命名冲突。
模块的分类:
Python 中的模块分为三类,分别是:标准库模块、自定义模块、第三方模块。
自定义模块
模块命名注意点:
- 要符合标识符命名规则
- 模块名(.py文件名)区分大小写
- 不要与标准库模块同名(一旦同名,Python会优先引入标准库模块)
常用的模块引入方式
1、import 模块名
import order
import pay
order.creat_order()
order.close_order()
order.show_info()
pay.wechat_pay()
pay.ali_pay()
pay.show_info()
2、import 模块名 as 别名
import order as dd
import pay as zf
dd.creat_order()
dd.close_order()
dd.show_info()
print('*' * 10)
zf.wechat_pay()
zf.ali_pay()
zf.show_info()
3、from 模块名 import 具体内容1, 具体内容2,...
from order import creat_order, show_info
from pay import wechat_pay, ali_pay
creat_order()
show_info()
print('*' * 10)
wechat_pay()
ali_pay()
4、from 模块名 import 具体内容1 as 别名1, 具体内容2 as 别名2,...
from order import show_info as order_info
from pay import show_info as pay_info
order_info()
pay_info()
5、 from 模块名 import *
from order import *
creat_order()
show_info()
__all__
在python模块中,可通过__all__来控制【from 模块 import *】能导入哪些内容__all__的值可以是:列表、元组
pay.py
def wechat_pay():
print('微信支付~')
def ali_pay():
print('支付宝支付~')
def show_info():
print('[支付信息!]')
__all__ = ('ali_pay', 'show_info')
main.py
from pay import *
ali_pay()
show_info()
# wechat_pay() # 未解析的引用 'wechat_pay'
__name__
__name__是每个python模块(.py文件)都有的一个内置变量
它的具体值取决于模块的运行方式:
- 作为主程序直接运行,
__name__的值是__main__ - 作为模块被导入到其他程序中运行,
__name__的值是模块的文件名(不带.py)
order.py
max_order_amount = 999999
def creat_order():
print('创建订单~')
print(f'order模块:{__name__}')
# 直接运行时 order模块:__main__
# main中引入时 order模块:order
标准库模块
标准库模块:随着 Python 一起安装在我们电脑上的那些模块,Python标准库官方文档
有一些标准库模块是用c语言实现的,这些用c语言实现的模块,又称内置模块
一些常用标准库模块
import copy # 拷贝对象(浅拷贝、深拷贝)
import os # 操作系统相关操作(文件、文件夹、路径系统层面的操作)
import random # 随机数相关(生成随机数、随机选择、洗牌)
import time # 时间相关操作(获取时间、延时、格式化时间等。)
import math # 数学相关(开平方、去绝对值等等)
import sys # Python 解释器相关操作
# 创建一个文件夹
os.mkdir('demo')
# 随机选择一个人
names = ['张三', '李四', '小米', '小王']
print(random.choice(names))
# 洗牌
random.shuffle(names)
print(names)
# 休眠
time.sleep(2)
print('sleep')
# 获取当前时间
print(time.strftime('%H:%M:%S')) # 24小时制
print(time.strftime('%I:%M:%S')) # 12小时制
print(time.strftime('%p %I:%M:%S')) # PM 02:07:29
# 开平方
print(math.sqrt(9))
# 取绝对值
print(math.fabs(-11))
# 获取python解释器的版本
print(sys.version)
包
包概述
- 在 Python 中,包含__init__.py 的文件夹就是一个包(Package)
- 我们通常会把某个特定功能相关的所有模块放入一个包中。
- 使用包可以进一步提升代码的:可维护性、可复用性,便于管理大型项目。 包与模块的关系:
- 一个模块就是一个.py文件,包是用来“管理模块”的目录(文件夹)。
- 一个包中可以有多个模块,也可以有多个子包。
包的分类:
Python 中的包分为三类,分别是:标准库包、自定义包、第三方包。
包命名注意点:
- 要符合标识符命名规范。
- 包名区分大小写(建议全部使用小写字母)
- 不要与标准库包同名。
包引入
1、import 包名.模块名
import trade.order
import trade.pay
trade.order.creat_order()
trade.pay.show_info()
2、import 包名.模块名 as 别名
import trade.order as dd
import trade.pay as zf
dd.creat_order()
zf.wechat_pay()
3、from 包名.模块名 import 具体内容1,具体内容2,...
from trade.order import creat_order, close_order
from trade.pay import wechat_pay, show_info, ali_pay
close_order()
ali_pay()
4、from 包名.模块名 import 具体内容1 as 别名1,具体内容2 as 别名2,...
from trade.order import creat_order as dd_create, close_order as dd_close
from trade.pay import wechat_pay as wx_zf , ali_pay as al_zf
dd_create()
wx_zf()
5、from 包名.模块名 import *
from trade.order import *
from trade.pay import *
creat_order()
print(max_order_amount)
ali_pay()
6、from 包名 import 模块名
from trade import order, pay
order.creat_order()
pay.show_info()
7、from 包名 import 模块名
from trade import order as dd, pay as zf
dd.show_info()
zf.show_info()
8、from 包名 import *
关于__init__.py
__init__.py是包的初始化文件,在包被导入时,__init__.py会被自动调用__init__.py中可以编写一些包的初始化逻辑__init__.py中所定义的内容,会被from 包名 import *形式全部引入__init__.py中也可以使用__all__来控制包中的哪些模块可以被from 包名 import *引入
9、import 包名
第三方包
| 镜像源 | |
|---|---|
| 清华大学 | pypi.tuna.tsinghua.edu.cn/simple |
| 阿里云 | mirrors.aliyun.com/pypi/simple |
| 中国科技大学 | pypi.mirrors.ustc.edu.cn/simple |
| pip命令 | |
|---|---|
| pip install 包名 | 安装指定包 |
| pip install -i 镜像地址 包名 | 临时使用镜像地址安装指定包 |
| pip config set global.index-url 地址 | 设置pip所使用的镜像地址 |
| pip config list | 查看当前环境的pip配置 |
| pip config unset global.index-url | 恢复官方默认镜像地址 |
| pip list | 查看当前环境中已安装的所有第三方包 |
| pip uninstall 包名 | 卸载指定的第三方包 |
全局环境与虚拟环境
虚拟环境
- 执行命令
python -m venv .venv创建虚拟环境 - 再在命令面板(
Ctrl+Shift+P)中搜索Python: Select Interpreter选择项目虚拟环境
迭代器
- 知识点1:能被for循环遍历的对象,被称为:可迭代对象(iterable)
- 知识点2:可迭代对象(iterable)能调用到
__iter__方法。
names = ['张三', '李四', '小李', '小丽']
msg = 'hello'
num = 10
names.__iter__()
msg.__iter__()
# num.__iter__() # AttributeError: 'int' object has no attribute '__iter__'. Did you mean: '__str__'?
print(hasattr(names, '__iter__')) # True
print(hasattr(num, '__iter__')) # False
-
知识点3:调用
__iter__方法会得到:迭代器(iterator)备注1:
__iter__是一个魔法方法,当调用iter函数时,__iter__会自动调用。备注2:
可迭代对象.__iter__()等价于iter(可迭代对象)。备注3:如果iter(obj)能得到一个迭代器(iterator),那 obj就是可迭代对象。
print(names.__iter__()) # <list_iterator object at 0x000001E687A105E0>
print(msg.__iter__()) # <str_ascii_iterator object at 0x000001E6A809A710>
print(iter(names)) # <list_iterator object at 0x000001E687A105E0>
-
知识点4:迭代器(iterator)拥有
__next__方法,每次调用都会根据当前的状态,返回下一个元素。备注1:
迭代器.__next__()等价于next(迭代器)。备注2:当所有元素全都取出后,若继续调用_next_ 方法,Python会抛出 StopIteration 异常。
it = names.__iter__()
print(it.__next__()) # 张三
print(it.__next__()) # 李四
print(it.__next__()) # 小李
print(it.__next__()) # 小丽
print(it.__next__()) # 抛出异常:StopIteration
知识点5:迭代器(iterator)也拥有__iter__方法,并且其返回值是迭代器自身
it = iter(names)
print(it) # <list_iterator object at 0x000001F3DBF005E0>
result = iter(it)
print(result) # <list_iterator object at 0x000001F3DBF005E0>
x = iter(result)
print(x) # <list_iterator object at 0x000001F3DBF005E0>
知识点6:迭代器协议
- 能被 iter() 接受
- 能被 next() 一步一步取值
优点
- 迭代器是惰性计算,不会一次性生成所有结果,所以能显著降低内存占用。
- 当数据量很大,不确定要用多少结果时,推荐使用迭代器。
# 实现费波纳茨数列
# 迭代器实现
class Fibonacci:
def __init__(self, total):
# 需要的总数
self.total = total
# 指针
self.index = 0
# 前两个数
self.prev, self.curr = 1, 1
def __iter__(self):
return self
def __next__(self):
if self.index >= self.total:
raise StopIteration
if self.index < 2:
value = 1
else:
value = self.prev + self.curr
self.prev, self.curr = self.curr, value
self.index += 1
return value
# f1 = Fibonacci(10)
import tracemalloc
tracemalloc.start()
f1 = Fibonacci(1000)
m = tracemalloc.get_traced_memory()[1]
print(f'内存占用是{m/1024/1024}MB') # 内存占用是0.00069427490234375MB
# 需遍历才能拿到数据
# for item in f1:
# print(item)
# 函数实现
def fibo(total):
if total <=0:
return []
if total == 1:
return [1]
num = [1, 1]
for i in range(2, total):
num.append(num[-1] + num[-2])
return num
# import tracemalloc
# tracemalloc.start()
# f2 = fibo(1000)
# m = tracemalloc.get_traced_memory()[1]
# print(f'内存占用是{m/1024/1024}MB') # 内存占用是0.08054733276367188MB
生成器
一、生成器
- 生成器函数:函数体中如果出现了 yield 关键字,那该函数就是生成器函数
- 生成器对象:调用生成器函数时,其函数体不会立即执行,而是返回一个生成器对象
- 不管能否执行到 yield 所在的位置,只要函数中有 yield 关键字,那该函数就是生成器函数
二、写在生成器函数中的代码,需要通过生成器对象来执行
- 调用生成器对象的 __next__方法,会让生成器函数中的代码开始执行
- 当生成器函数中的代码开始执行后,遇到yield会“暂停”执行,且内部会记录“暂停”的位置
- 后续调用__next__方法时,都会从上一次“暂停”的位置,继续运行,直到再次遇到yield
- 遇到return会抛出StopIteration异常,并将return后面的表达式,作为异常信息
- yield后所写的表达式,会作为本次__next__方法的返回值
def demo():
print('demo函数开始执行了')
print(666)
yield '第一个yield'
a = 777
print(a)
yield '第二个yield'
b = 888
print(b)
return 999
d= demo()
# print(d) # <generator object demo at 0x000001C59B9D9700>
# next(d) # 666
# next(d) # 777
# next(d) # 888
# next(d) # StopIteration: 999
d1 = next(d)
print(d1) # 第一个yield
d2 = next(d)
print(d2) # 第二个yield
# d3 = next(d)
# print(d3) # StopIteration: 999
try:
next(d)
except StopIteration as e:
print(e) # 999
三、生成器对象是一种特殊的迭代器(本质是通过yield自动实现了迭代器协议)
def demo():
print('demo函数开始执行了')
print(666)
yield '第一个yield'
a = 777
print(a)
yield '第二个yield'
b = 888
print(b)
return 999
d = demo()
# 验证:生成器对象d,和迭代器一样,也拥有:__iter__ 和 __next__ 方法
print(hasattr(d, '__iter__')) # True
print(hasattr(d, '__next__')) # True
# 验证:生成器对象的__iter__方法,和迭代器一样,返回的也是自身
result = iter(d)
print(result == d) # True
for循环遍历生成器
for item in d:
print(item)
for循环背后的逻辑
gen = iter(d)
while True:
try:
value = next(gen)
print(value)
except StopIteration:
break
四、yield也能写在循环里
def create_car(total):
for index in range(1, total + 1):
yield f'这是第{index}辆车'
# cars 是生成器对象
cars = create_car(5)
# print(cars)
for car in cars:
print(car)
五、yield from 能把一个可迭代对象里的东西依次 yield 出去,替代 for + yield
def demo():
nums = [10,20,30,40,50]
yield from nums
d = demo()
d1 = next(d)
print(d1)
d2 = next(d)
print(d2)
六、使用:生成器.send(值) 可以让生成器继续执行的同时,给上一次yield传值
def demo():
print('demo函数开始执行了')
a = yield '第一个yield'
print(a)
b = yield '第二个yield'
print(b)
return 999
d = demo()
d1 = next(d)
print(d1)
d2 = d.send(888)
print(d2)
用生成器实现遍历Person类的实例对象
class Person:
def __init__(self, name, age, gender, address):
self.name = name
self.age = age
self.gender = gender
self.address = address
self.attrs = [name, age, gender, address]
def __iter__(self):
# yield self.name
# yield self.age
# yield self.gender
# yield self.address
yield from self.attrs
p1 = Person('张三', 12, '女', '北京')
for item in p1:
print(item)
def fibo(total):
pre = 1
cur = 1
for index in range(total):
if index < 2:
yield 1
else:
value = pre + cur
pre = cur
cur = value
yield value
f1 = fibo(10)
for item in f1:
print(item, end = '\t')
无论是迭代器还是生成器对象,都可以用list, turple, set等直接拿到其中的所有内容(注意:容易挤爆内存)
生成器表达式:一种用类似列表推导式的语法,快速创建生成器对象的方式
语法:(表达式 for 变量 in 可迭代对象)
当每个结果,只依赖当前这一个元素时,适合用生成器表达式
nums = [10,20,30,40]
# 列表推导式
n1 = [n * 2 for n in nums]
print(n1)
# 生成器表达式
n2 = (n * 2 for n in nums)
for item in n2:
print(item)
文件操作
文件的分类:1.纯文本文件、2.二进制文件
纯文本文件:
- 读取和存储时,要遵循某种『字符编码』规范(如:UTF-8等)进行编码和解码,最终以『二进制』的形式存储。
- 『纯文本文件』最终会呈现为:可以直接阅读的文本信息。
- 常见的『纯文本文件』有:.txt.py.md.html等。
二进制文件:
- 读取和存储时,不涉及字符编码,会按照某种『文件格式规范』把内容转为『二进制』数据进行存储。
- 二进制文件需要由『能够识别其格式的软件』进行解析,最终的呈现形式多种多样(音频、视频、图像、幻灯片等)
- 常见的二进制文件有:.mp3.mp4.doc.ppt.jpg.png等。
绝对路径vs 相对路径:
- 绝对路径:从文件系统的『根目录』开始,完整描述文件或目录所在的位置
- 相对路径:以当前『工作目录』为参照,描述目标文件或目录相对于它的位置。
文件操作的核心
open 函数:它可以打开或创建文件,且支持多种操作模式,返回值是文件对象
open函数最常用的三个参数如下:
1、file: 要操作的文件路径
2、mode: 文件的打开方式
- r: 读取(默认值)
- w: 写入,并先截断文件
- x: 排它性创建,若文件已存在,则创建失败
- a: 打开文件用于写入,若文件存在,则在文件末尾追加内容
- b: 二进制模式
- t: 文本模式(默认值)
- +: 打开用于更新(读取与写入)
3、encoding: 字符编码
读取文件
python中操作文件的标准流程
- 创建文件对象
- 操作文件(读取、写入 等)
- 关闭文件
读取操作1:使用文件对象的read方法,读取文件中的内容
# 第一步:创建文件对象
file = open(file='04_基础/a.txt', mode='rt', encoding='utf-8')
# file = open('D:/Download/1774527988203.jpg', 'rb')
# 第二步:操作文件
result = file.read()
print(result)
# 第三步:关闭文件
file.close()
read方法说明:
1.read(size)中的size是可选参数
- 未传size参数表示:读取文件中所有的内容(注意内存占用)
- 传了size参数表示:读取文件中指定个数的字符,或指定大小的字节
2.read会从上一次read的位置继续读取(指针思想),若到达文件末尾继续读取,将返回空字符串
# 第一步:创建文件对象
file = open('04_基础/a.txt', 'rt', encoding='utf-8')
# 第二步:操作文件
# 多次调用read方法逐步读取文件
# r1 = file.read(2)
# r2 = file.read(3)
# r3 = file.read(4)
# r4 = file.read()
# print(r1, end='')
# print(r2, end='')
# print(r3, end='')
# print(r4)
# 循环读取read
while True:
r = file.read(10)
if r == '':
break
print(r, end='')
# 第三步:关闭文件
file.close()
读取操作2:使用文件对象的 readline 方法,读取文件中的一行
readline方法说明:
1.readline(size)中的size是可选参数
- 未传size参数表示:读取当前这一行所有内容
- 传了size参数表示:读取读取当前行时,最多读取的字符数,或字节数(size不是行数)
2.readline会从上一次位置继续读取,若到达文件末尾继续读取,将返回空字符串
# 第一步:创建文件对象
file = open('04_基础/a.txt', 'rt', encoding='utf-8')
# 第二步:操作文件
# 依次调用readline逐行读取
# r1 = file.readline(2)
# r2 = file.readline(3)
# r3 = file.readline()
# # print(repr(r1)) # repr() 可以查看原生形式 => '一二三四五,\n'
# print(r1.strip())
# print(r2.strip())
# print(r3.strip())
# 循环读取readline
while True:
r = file.readline()
if r == '':
break
print(r.strip())
# 第三步:关闭文件
file.close()
读取操作3:使用 for 直接遍历文件对象
# 第一步:创建文件对象
file = open('04_基础/a.txt', 'rt', encoding='utf-8')
# 第二步:操作文件
for line in file:
print(line.strip())
# 第三步:关闭文件
file.close()
读取操作4:使用文件对象的readlines方法,一次性按行读完,返回一个列表
readlines 方法说明:
1.readlines(hint)中的hint 是可选参数。
- 若不传递hint 参数,表示:读取当前文件的所有行。
- 若传递了hint 参数,表示:期望读取的【字符个数或字节数的上限】(hint不是行数)
2.注意:由于readlines 是一次性读取文件的所有内容,所以不适合读取体积较大的文件。
# 第一步:创建文件对象
file = open('04_基础/a.txt', 'rt', encoding='utf-8')
# 第二步:操作文件
r = file.readlines()
print(r)
# 第三步:关闭文件
file.close()
最佳实践:使用 with 上下文管理器,结合 for 循环遍历,逐行读取文件
with open('04_基础/a.txt', 'rt', encoding='utf-8') as file:
for line in file:
print(line.strip())
写入文件
测试 w 模式
with open('04_基础/b.txt', 'w', encoding='utf-8') as f:
f.write('hello world')
测试 x 模式
with open('04_基础/c.txt', 'xt', encoding='utf-8') as f:
f.write('hello world')
测试 a 模式
with open('04_基础/a.txt', 'at', encoding='utf-8') as f:
f.write('hello world')
写入文件时,并不是每写一次就立刻落盘,而是先写到“缓冲区” 文件对象的flush方法:把缓冲区的数据立刻写入盘中
import time
with open('04_基础/demo.txt', 'at', encoding='utf-8') as f:
f.write('写入内容1\n')
f.write('写入内容2\n')
f.flush()
time.sleep(10)
f.write('写入内容3\n')
测试 rt+ 模式
seek(offset,whence)方法:用于改变文件对象指针的位置,参数说明如下:
- offset:偏移量,要移动多少距离(字节)
- whence:参考点,从哪里开始计算偏移,有三种取值:
- 0:从文件开头计算(默认值)
- 1:从当前位置计算
- 2:从文件末尾计算 注意:在文本模式下,不要随意去定位中文字符位置,否则可能破坏文件编码。
with open('04_基础/demo.txt', 'rt+', encoding='utf-8') as f:
f.seek(0, 2)
f.write('你好')
测试 wt+ 模式
with open('04_基础/demo.txt', 'wt+', encoding='utf-8') as f:
f.write('你好')
f.seek(0) # 重置文件指针到文件开头
result = f.read()
print(result)
测试 xt+ 模式
with open('04_基础/demo1.txt', 'xt+', encoding='utf-8') as f:
f.write('你好')
f.seek(0)
result = f.read()
print(result)
测试 at+ 模式
with open('04_基础/demo.txt', 'at+', encoding='utf-8') as f:
f.write('你好')
f.seek(0)
result = f.read()
print(result)
目录操作
1、os.mkdir(path): 创建单级目录,如果目录已经存在,会抛出异常
import os
os.mkdir('./test')
os.mkdir('./test/sub')
os.mkdir('E:/test')
2、os.makedirs(path): 创建多级目录,如果路径中的所有目录都已存在,则会抛出异常
os.makedirs('./test/sub/aaa')
3、os.rmdir(path): 删除空目录,如果目录不存在,或非空目录,则会抛出异常
os.rmdir('./test/sub')
4、os.removedirs(path): 递归删除空目录,在成功删除末尾一级目录后,会向上尝试把父级目录也删除,直到父目录不是空目录
os.removedirs('./test/sub/aaa')
5、os.path.exists(path): 判断路径是否存在,文件/目录都算
result = os.path.exists('./test/test.txt')
print(result)
6、os.path.isdir(path): 判断路径, ①路径不存在,返回False,②路径存在,但指向文件,返回False,③路径存在,且指向目录,返回True
result = os.path.isdir('./test/sub')
print(result)
7、os.path.isfile(path): 判断是否为文件
result = os.path.isfile('./test/test.txt')
print(result)
8、os.scandir(path): 扫描指定目录下的所有文件和目录,返回一个迭代器
result = os.scandir('./test')
for item in result:
# is_dir() => 判断是否为目录
# print(item.is_dir(),item.name)
print('目录' if item.is_dir() else '文件', item.name)
9、os.walk(path): 按层级,递归的遍历指定目录下,所有的子目录和文件
result = os.walk('./test')
for item in result:
print(item)
shutil.rmtree(path)删除有内容的目录
import shutil
# ⚠️危险操作,删除有内容的目录
shutil.rmtree('./test')
多线程 多进程
概念
一、并发vs并行
1.并发:
概念:在一段时间内,当CPU面对多个任务时,会将每个任务交替着执行一段时间。
特点:
- 对于某个瞬间,CPU实际上只在执行一个任务。
- CPU通过高频切换不同的任务,让每个任务都能得到推进,仿佛多个任务在“同时执行”
2.并行:
概念:并行依赖于多个cPu,或多核心的cPu,在同一时刻,每个核心都在执行不同的任务。
特点:
- 对于某个瞬间,每个CPu(或每个核心)都在执行不同的任务。
- 通过多个cPu(或多个核心)同时工作的方式,让多个任务真的在同时执行。
说明:在现代操作系统中,并发与并行通常都是同时存在的。
二、同步vs异步:
1.同步(sync):
概念:发起一个任务之后,需要等该任务完成后,才能继续执行后续任务。
表现:当前执行流会被『阻塞』
2.异步(async):
概念:发起一个任务之后,不必等该任务完成,就可以继续执行其他任务。
- 备注:虽然不必等待任务完成,但任务完成后,仍然可以通过特定方式获取结果。
表现:当前执行流不会被『阻塞』
三、注意区分概念:
并发/并行:描述的是任务如何被执行,即:多个任务在执行时,CPU要如何处理。
同步/异步:描述的是任务如何被组织和等待,即:要不要等当前发起的任务执行完,再进行下一个任务。
注意点:CPU的核心数和执行速度,不会改变任务之间的逻辑依赖关系!
例如: 一旦任务1、任务2、任务3之间被设计为同步关系,那么:即便CPu切换任务的速度再快,核心数量再多,也不会在【任务1】没完成的情况下去启动【任务2】
四、进程与线程
1.进程:
- 一个正在运行的程序或软件,背后就对应着一个或多个进程。
- 进程是操作系统进行资源分配的基本单位。
- 每个进程都有自己独立的一块内存空间。
2.线程:
- 线程是进程内部的执行单元(一个进程中可以有多个线程)。
- 线程是操作系统进行CPu调度的基本单位。
- 同一进程内的线程共享进程资源。
创建进程
p1 = Process(target=函数对象) p1.start()
import os
import time
from multiprocessing import Process
print(100, __name__) # 作为主进程执行时为100 __main__,作为子进程执行时为100 __mp_main__(p1,p2各执行一次)
def speak():
for item in range(10):
# 说话0;当前进程pid:42704;当前进程ppid:23112
print(f'说话{item};当前进程pid:{os.getpid()};当前进程ppid:{os.getppid()}')
time.sleep(1)
def study():
for item in range(15):
# 学习0;当前进程pid:15700;当前进程ppid:23112
print(f'学习{item};当前进程pid:{os.getpid()};当前进程ppid:{os.getppid()}')
time.sleep(1)
# speak()
# study()
# 注意:一定要写 if __name__ == '__main__', 原因如下:
# 1、当创建子进程时,python并不会把父进程内存中的speak函数直接交给子进程
# 2、python会启动一个全新的python解释器进程,重新执行当前的.py文件(作为模块)
# 3、在执行过程中,重新定义出一个speak函数,交给子进程
if __name__ == '__main__':
print(200, os.getpid()) # 200 23112
print('我是第一行')
# 创建两个Process类的实例对象(进程对象),分别是 p1和 p2
# 1、此时p1和p2就对应着以后的两个子进程,在创建它们时,就要制定好它们要执行的任务
# 2、此时的p1和p2只是代码层面的两个进程对象,操作系统还没有真的创建p1和p2两个进程
p1 = Process(target=speak)
p2 = Process(target=study)
p1.start()
p2.start()
print('我是最后一行')
Process的参数
- group: 默认组,默认值为None(应当始终为None)
- target: 子进程要执行的可调用对象,默认值为 None
- name: 进程名称,默认值为 None,若设置为None,会自动分配名称
- args: 给target传的位置参数(元组)
- kwargs: 给target传的关键字参数(字典)
- daemon: 标记进程是否为守护进程,取值为布尔值,默认为None,表示从创建方进程继承
from multiprocessing import Process
def speak(a, b, name, age):
# 说话,位置参数1、2,关键字参数张三、10
print(f'说话,位置参数{a}、{b},关键字参数{name}、{age}')
if __name__ == '__main__':
p1 = Process(target=speak, name='进程名称1', args=(1,2), kwargs={'name': '张三', 'age': 10})
# 进程名称1
print(p1.name)
p1.start()
Lock进程锁
from multiprocessing import Process, Lock, RLock
import time
def speak(lock):
for item in range(10):
# 上锁:如果锁是空闲的,立刻上锁,继续往下执行;如果锁被别人拿着,当前进程会阻塞等待(原地等)
lock.acquire()
lock.acquire()
print(1, end='')
print(2, end='')
print(3, end='')
print(4, end='')
print(5)
# 释放锁:acquire和release必须成对出现,否则会永远卡住(死等)
lock.release()
time.sleep(1)
def study(lock):
for index in range(15):
# with lock: 会自动完成两件事
# 1、进入前:自动执行lock.acquire()上锁
# 2、离开后:自动执行lock.release()释放锁
# 好处:即便代码块里发生异常,也能保证释放锁,避免卡死
# RLock 可以多次上锁
with lock:
print('a', end='')
print('b', end='')
print('c', end='')
print('d', end='')
print('e')
time.sleep(1)
if __name__ == '__main__':
# lock = Lock()
lock = RLock()
p1 = Process(target=speak, args=(lock, ))
p2 = Process(target=study, args=(lock, ))
p1.start()
p2.start()
join方法
join方法的作用:阻塞当前进程,等join前面的进程执行完,再继续往下执行
join(timeout),其中timeout是可选参数,表示等多久,单位是秒
注:
- p.join()不是让进程p等,而是让执行这行join代码的那个进程去等,等的是p这个进程
- 当达到了timeout所指定的时间后,进程并不会终止,只是不再等了
- join必须在start之后
import os
import time
from multiprocessing import Process
def speak():
for index in range(5):
print(f'说话{index},当前进程pid{os.getpid()},父进程pid{os.getppid()}')
time.sleep(1)
def study():
for index in range(10):
print(f'学习{index},当前进程pid{os.getpid()},父进程pid{os.getppid()}')
time.sleep(1)
if __name__ == '__main__':
print('这是第一行')
p1 = Process(target=speak)
p2 = Process(target=study)
p1.start()
p1.join(3)
p2.start()
# p1.join() # 让主进程等p1进程执行完后,主进程再继续执行
# p2.join() # 让主进程等p2进程执行完后,主进程再继续执行
print('这是最后一行')
terminate方法
#
import os
import time
from multiprocessing import Process
def speak():
for index in range(5):
print(f'说话{index},当前进程pid{os.getpid()},父进程pid{os.getppid()}')
time.sleep(1)
def study():
for index in range(10):
print(f'学习{index},当前进程pid{os.getpid()},父进程pid{os.getppid()}')
time.sleep(1)
if __name__ == '__main__':
print('这是第一行')
p1 = Process(target=speak)
p2 = Process(target=study)
p1.start()
p2.start()
time.sleep(3)
print('我是主进程,我准备强制终止p1进程')
# 向操作系统申请强制终止p1进程
p1.terminate()
# 等操作系统彻底终止调了p1进程
p1.join()
# 查看p1进程是否活着
print(p1.is_alive())
print('这是最后一行')
守护进程
Process(target=子进程, daemon=True)
- 一种“依附于主进程存在的子进程”,一旦主进程结束,它就会被自动终止。
- 简言之:主进程一死,守护进程必跟着死。
守护进程的使用场景:
- 后台监控类任务
- 日志/统计/采样类任务
- 辅助型“陪跑任务”
注意点:
- 守护进程必须是 子进程。
- 主进程结束,守护进程也会随之结束。
- 守护进程中,不允许再创建新的子进程。
- 必须在 start 之前,start()之后,不能再设置 daemon
import os
import time
from multiprocessing import Process
def monitor():
while True:
try:
with open('05_基础/log.txt', 'r', encoding='utf-8') as file:
lines = sum(1 for _ in file)
except FileNotFoundError:
lines = 0
print(f'我是守护进程{os.getpid()},log.txt文件有{lines}行')
time.sleep(1)
def test():
for index in range(10):
print(f'我是测试进程{os.getpid()},index={index}')
time.sleep(1)
if __name__ == '__main__':
print(f'我是主进程{os.getpid()}中的第一行代码!')
p1 = Process(target=monitor, daemon=True)
p2 = Process(target=test)
p1.start()
p2.start()
# 向文件中写入数据
with open('05_基础/log.txt', 'a', encoding='utf-8') as file:
for index in range(5):
file.write(f'python-{index}\n')
file.flush()
time.sleep(1)
print(f'我是主进程{os.getpid()}中的最后一行代码!')
进程之间不共享变量
import os
from multiprocessing import Process
num = 100
names = ['张三']
def test1():
global num, names
num += 10
names.append('李四')
print(f'我是测试进程1:{os.getpid()},num={num},names={names}')
# 我是测试进程1:28996,num=110,names=['张三', '李四']
def test2():
global num, names
num -= 10
names.append('王五')
print(f'我是测试进程2:{os.getpid()},num={num},names={names}')
# 我是测试进程2:34792,num=90,names=['张三', '王五']
if __name__ == '__main__':
print(f'我是主进程{os.getpid()}第一行代码,num={num},names={names}')
# 我是主进程35708第一行代码,num=100,names=['张三']
p1 = Process(target=test1)
p2 = Process(target=test2)
p1.start()
p2.start()
p1.join()
p2.join()
print(f'我是主进程{os.getpid()}最后一行代码,num={num},names={names}')
# 我是主进程35708最后一行代码,num=100,names=['张三','李四','王五']
队列
我们之前学过 list、tuple、dict,它们的特点是:数据想怎么拿数据,就怎么拿。
队列(Queue)是:一种“先进先出”的数据结构(先放进去的数据,一定会先取出来)
创建队列
from multiprocessing import Queue, Process
# 创建一个队列 (不限制大小,即不设置容量上限)
q1 = Queue()
# 创建一个队列 (最多能保存3个元素)
q2 = Queue(3)
put方法:向队列中放数据(入队)
q1.put(10)
q1.put(20)
q1.put(30)
get方法:从队列中取数据(出队)
value1 = q1.get()
value2 = q1.get()
value3 = q1.get()
print(value1, value2, value3)
empty方法:判断队列是否为空
print(q1.empty()) # False
print(q2.empty()) # True
full方法:判断队列是否已满
print(q1.full()) # False
q2.put(100)
q2.put(200)
q2.put(300)
print(q2.full()) # True
qsize方法:获取队列长度
q1.put(40)
print(q1.qsize()) # 4
print(q2.qsize()) # 0
队列具有等待模式
q2.put(100)
q2.put(200)
q2.put(300)
# (1)当队列已满,继续 put,就会进入等待模式,等别人调用get方法,取走一个元素
q2.put(400)
print('放入完毕')
# (2)当队列已满,执行:put(元素,timeout=秒数),就会等待指定秒数。
q2.put(400, timeout=3) # 等待三秒,抛出异常queue.Full
print('放入完毕')
# (3)put_nowait方法,会直接向队列中添加元素,不会进入等待模式,若队列已满,会抛出异常。
备注:put_nowait等价于 put(元素,block=False),block的默认值为True
q2.put_nowait(400) # 直接抛出异常queue.Full
q2.put(400, block=False)
# get读多了,也会进入等待模式
q2.get()
q2.get()
q2.get()
# (1)当队列已空,继续get,就会进入等待模式
q2.get()
# (2)当队列已空,执行:get(元素,timeout=秒数),就会等待指定秒数。
q2.get(timeout=3) # 等待三秒,抛出异常queue.Empty
# (3)get_nowait方法,会直接从队列中取出元素,不会进入等待模式,若队列为空,会抛出异常。
备注:get_nowait等价于 get(元素,block=False)
q2.get_nowait() # 直接抛出异常queue.Empty
q2.get(block=False)
通过多进程,演示一下:当队列满了以后,再次put会等待,当有人从队列中取出元素后,put会继续。
import time
def test(q):
time.sleep(3)
result = q.get()
print(f'我从队列中取出了一个元素:{result}')
if __name__ == '__main__':
q = Queue(2)
q.put('python')
q.put('java')
print(f'队列是否已满:{q.full()}')
# 创建子进程
p1 = Process(target=test, args=(q,))
# 启动子进程,等待3秒后,从队列中取出一个元素
p1.start()
print('即将向已满的队列中放一个元素....')
q.put('newnewnew')
print('目前队列中的元素有:', q.get(), q.get())
未整理完接下一篇:Python基础知识(二)