Python基础知识(一)

140 阅读1小时+

前言

最近ai势头正猛,公司也是要求学习ai了!自己先从Python入手,整理了份笔记,记录一下python语法。其语法和其他语言是相通的,只是写法不同。文档基于禹神的python教程,推荐禹神的视频教程。

一、字面量

  1. 所谓字面量,就是直接写在代码中的具体值
  2. Python的字符串中可以包含任意字符,且必须使用引号包起来。
  3. 字符串的引号,可以是:单引号、双引号、三个单引号、三个双引号。
  4. 写在Python文件头部的字符串,会被自动识别成docstring(文档字符串)
  5. 文档字符串的主要作用是:对当前Python文件进行一些说明,且文档字符串必须用三个双引号
"""python"""
"python"
'python'
'''python'''

二、变量

语法:变量名 = 值

name = '李四'
age = 18
height = '188'
print('你好!我叫' + name, '今年', age, '岁,身高' + height + 'cm。')

三、标识符命名规则

  1. 只能包含:数字、字母、下划线,且不能以数字开头,不能包含空格
  2. 标识符区分大小写,例如 Name和name是两个不同的标识符。
  3. 标识符不能使用关键字,
  4. 标识符尽量不要与内置函数同名。
  5. 标识符虽然没有长度限制,但应追求:简洁清晰,具有描述性。
Python中的关键字
Falseassertcontinueexceptif
nonlocalreturnNoneasyncdef
finallyimportnottryTrue
awaitdelforinor
whileandbreakeliffrom
ispasswithasclass
elsegloballambdaraiseyield

四、常量

一般约定全大写变量名来表示常量,涉及多个单词则以下划线分割。

python中没有强制的常量机制,其本质还是变量,只是约定好不要去修改!

# ctrl+shift+u 转换大小写
AGE = 13
NAME = 'abc'

五、数据类型

类型名称英文名举例说明
字符串string'李四'用引号包裹
整型int18没有小数的数字
浮点型float20.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

  1. 最小宽度,位数不够会使用空格补全,位数小于整数位数,则自动失效
  2. 正数是右对齐,负数是左对齐

n

  1. 精度控制,含义:最少用n位显示数字。
  2. 位数不够用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 + 23
-2 - 11
*1 * 22
/3 / 21.5
//取整3 / 21
%取余8 / 53
**指数2 ** 38

2、赋值与复合赋值运算符

运算符说明示例
=赋值运算name = '李四'
+=加法赋值运算num += 2num = num + 2
-=减法赋值运算num -= 2num = num - 2
*=乘法赋值运算num *= 2num = num * 2
/=除法赋值运算num /= 2num = num / 2
//=取整赋值运算num //= 2num = num // 2
%=取余赋值运算num %= 2num = num % 2
**=指数赋值运算num **= 2num = num ** 2

3、比较运算符

运算符 作用
== 比较左右两侧是否相等
!= 比较左右两侧是否不相等
> 比较左侧是否大于右侧 同类型比较
< 比较左侧是否小于右侧
>= 比较左侧是否大于等于右侧
<= 比较左侧是否小于等于右侧

注:

  1. 比较结果True or False,首字母是大写的
  2. 字符串进行比较时,是依次比较每个字符的Unicode编码
  3. 使用 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

  1. None是一个特殊的字面量,它表示:空值/无值/无意义
  2. None的类型是NoneType。
  3. None转为布尔值是False。
  4. None不能参与数学运算,也不能与字符串拼接。
  5. 不给函数设置返回值,函数会默认返回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)

循环遍历

  1. 通过 for item in list1: 获取每项元素item
  2. for index in range(len(list1)): 获取到元素下标index
  3. for 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)

查:

可通过【成员运算符】innot 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('你好')

常用方法

  1. issubclass(Class, Class) 判断某个类是否为另一个类的子类
  2. 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'>)

三种访问限制

  1. 共有属性 属性名 当前类、子类、类外部,都能访问
  2. 受保护的属性 _属性名 当前类、子类,都能访问
  3. 私有属性 __属性名 仅能在当前类中访问
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)

高阶函数

高阶函数:当一个函数的『参数是函数』或者『返回值是函数』那该函数就是『高阶函数」

高阶函数的意义:

  1. 代码复用性高:可以把行为“独立出去”,传入不同函数实现不同逻辑。
  2. 能让函数更灵活,更通用。
  3. 高阶函数是:装饰器、闭包的基础。
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

注意事项:

  1. 只能写一行,不能写多行代码。
  2. 不能写代码块(if、for、while)
  3. 冒号右边必须是表达式,且只能写一个表达式。
  4. 表达式结果自动作为返回值。

数据处理函数

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)
  • 参数详解
    1. objects: 要输出的内容
    2. sep: 分隔符
    3. end: 结束符
    4. file: 输出位置
    5. 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()全为真,返回Trueany()有一个为真,返回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

image.png

局部作用域(Local)

定义:函数的内部就是局部作用域,局部作用域中的变量,只能在该函数内部可见。

特点:

  • 每次调用函数都会创建一个新的局部作用域。函数运行结束后,局部作用域随之销毁。
  • 局部作用域优先级最高(LEGB中的L)。

外层作用域(Enclosing)

定义:如果函数中又定义了函数,那么外层函数的作用域,就是内层函数的Enclosing作用域。

特点:

  • 只有当函数“嵌套定义”时才会出现。
  • 内层函数可以读取外层函数变量。
  • 想修改外层变量必须使用nonlocal

全局作用域(Global)

定义:.py文件就是全局作用域,全局作用域中的变量,在当前.py 文件的任何位置都可以访问。

特点:

  • 全局变量只在当前.py 文件中可见。
  • 函数内部可以使用global关键字修改全局变量。

内建作用域(Built-in)

定义:Python预先定义好的东西,会放在内建作用域中,所有.py 文件都可以直接使用。

特点:

  • 所有.py文件都能直接使用其中的名称。
  • 例如:print、len、range、sum、max 查找优先级最低(LEGB的B)

闭包

闭包 = 内层函数 + 被内层函数所引用的外层变量

闭包产生的条件:

  1. 要有函数嵌套
  2. 在【内层函数】中,要访问【外层函数】的变量
  3. 并且在【外层函数】要返回【内层函数】。--只有返回了内层函数,闭包才能“活下来”
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 是列表,并且列表中的元素可以是 strint 类型
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 是集合,并且集合中的元素可以是 strintbool 类型
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 是字典,键是strint类型,值是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')

完整写法

  1. try: 尝试去做可能会出现异常的事情
  2. except: 出现异常时的处理(出现异常时怎么补救)
  3. else: 如果一切顺利(没有异常出现)要做的事
  4. 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}')

模块

模块概述

  1. 在 Python 中,一个.py文件就是一个模块(Module)。
  2. 模块中可以包含:变量、函数、类、等很多内容。
  3. 通常把能够实现某一特定功能的代码,集中放在一个模块中(模块就类似于一个工具箱).
  4. 模块可以提高代码的可维护性与可复用性,还可以避免命名冲突。

模块的分类:

Python 中的模块分为三类,分别是:标准库模块、自定义模块、第三方模块。

自定义模块

模块命名注意点:

  1. 要符合标识符命名规则
  2. 模块名(.py文件名)区分大小写
  3. 不要与标准库模块同名(一旦同名,Python会优先引入标准库模块)

常用的模块引入方式

image.png

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)

包概述

  1. 在 Python 中,包含__init__.py 的文件夹就是一个包(Package)
  2. 我们通常会把某个特定功能相关的所有模块放入一个包中。
  3. 使用包可以进一步提升代码的:可维护性、可复用性,便于管理大型项目。 包与模块的关系:
  4. 一个模块就是一个.py文件,包是用来“管理模块”的目录(文件夹)。
  5. 一个包中可以有多个模块,也可以有多个子包。

包的分类:

Python 中的包分为三类,分别是:标准库包、自定义包、第三方包。

包命名注意点:

  1. 要符合标识符命名规范。
  2. 包名区分大小写(建议全部使用小写字母)
  3. 不要与标准库包同名。

包引入

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 包名卸载指定的第三方包

全局环境与虚拟环境

虚拟环境

  1. 执行命令python -m venv .venv创建虚拟环境
  2. 再在命令面板(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:迭代器协议

  1. 能被 iter() 接受
  2. 能被 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

生成器

一、生成器

  1. 生成器函数:函数体中如果出现了 yield 关键字,那该函数就是生成器函数
  2. 生成器对象:调用生成器函数时,其函数体不会立即执行,而是返回一个生成器对象
  3. 不管能否执行到 yield 所在的位置,只要函数中有 yield 关键字,那该函数就是生成器函数

二、写在生成器函数中的代码,需要通过生成器对象来执行

  1. 调用生成器对象的 __next__方法,会让生成器函数中的代码开始执行
  2. 当生成器函数中的代码开始执行后,遇到yield会“暂停”执行,且内部会记录“暂停”的位置
  3. 后续调用__next__方法时,都会从上一次“暂停”的位置,继续运行,直到再次遇到yield
  4. 遇到return会抛出StopIteration异常,并将return后面的表达式,作为异常信息
  5. 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.二进制文件

纯文本文件:

  1. 读取和存储时,要遵循某种『字符编码』规范(如:UTF-8等)进行编码和解码,最终以『二进制』的形式存储。
  2. 『纯文本文件』最终会呈现为:可以直接阅读的文本信息。
  3. 常见的『纯文本文件』有:.txt.py.md.html等。

二进制文件:

  1. 读取和存储时,不涉及字符编码,会按照某种『文件格式规范』把内容转为『二进制』数据进行存储。
  2. 二进制文件需要由『能够识别其格式的软件』进行解析,最终的呈现形式多种多样(音频、视频、图像、幻灯片等)
  3. 常见的二进制文件有:.mp3.mp4.doc.ppt.jpg.png等。

绝对路径vs 相对路径:

  1. 绝对路径:从文件系统的『根目录』开始,完整描述文件或目录所在的位置
  2. 相对路径:以当前『工作目录』为参照,描述目标文件或目录相对于它的位置。

文件操作的核心

open 函数:它可以打开或创建文件,且支持多种操作模式,返回值是文件对象

open函数最常用的三个参数如下:

1、file: 要操作的文件路径

2、mode: 文件的打开方式

  • r: 读取(默认值)
  • w: 写入,并先截断文件
  • x: 排它性创建,若文件已存在,则创建失败
  • a: 打开文件用于写入,若文件存在,则在文件末尾追加内容
  • b: 二进制模式
  • t: 文本模式(默认值)
  • +: 打开用于更新(读取与写入)

3、encoding: 字符编码

读取文件

python中操作文件的标准流程

  1. 创建文件对象
  2. 操作文件(读取、写入 等)
  3. 关闭文件

读取操作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)方法:用于改变文件对象指针的位置,参数说明如下:

  1. offset:偏移量,要移动多少距离(字节)
  2. 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(或多个核心)同时工作的方式,让多个任务真的在同时执行。

说明:在现代操作系统中,并发与并行通常都是同时存在的。 image.png

二、同步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)

  1. 一种“依附于主进程存在的子进程”,一旦主进程结束,它就会被自动终止。
  2. 简言之:主进程一死,守护进程必跟着死。

守护进程的使用场景:

  1. 后台监控类任务
  2. 日志/统计/采样类任务
  3. 辅助型“陪跑任务”

注意点:

  1. 守护进程必须是 子进程。
  2. 主进程结束,守护进程也会随之结束。
  3. 守护进程中,不允许再创建新的子进程。
  4. 必须在 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基础知识(二)