Skip to content

Python

python常用的网站:
Python中的内置函数: https://docs.python.org/zh-cn/3.13/library/functions.html
Python中的模块: https://docs.python.org/zh-cn/3.13/py-modindex.html

1. pycharm常用快捷键

alt text

2. 字面量

字面量就是写在代码中的具体的值

例如下面的值

python
"张三"
18
65.2

"李四"
55
74.6

"王五"
25
80

写在pythOn 文件头部的字符串,会被自动识别成docstring,也就是文档字符串。文档字符串必须使用三个字符串包裹。

3. 变量

在python中,变量其实就是一个代号,用来和某个值建立绑定关系

之所以交变量,是因为他和某个值的绑定关系,可以随时改变

语法: 变量名 = 值

python
name = '张三'

4. 标识符的命名规则

1. 只能包含: 数字、字母、下划线,且不能以数字开头,不饿能包含空格
2. 标识符区分大小写
2. 标识符不能使用关键字
3. 标识符尽量不要与内置函数同名
4. 标识符虽然没有长度限制,但应追求: 简洁清晰,具有描述性

5. 常量

一旦被赋值,不希望被修改的变量,称之为常量

常量命名规则:

  1. 常量名通常使用大写字母,多个单词之间用下划线连接
  2. 常量一旦赋值,就不能被修改

Python中没有强制的常量机制,Python中所谓的变量,其本质其实还是变量,只不过我们约定好不去修改

例如

AGE = 18

6. 注释

在python中多行注释的本质就是三个字符串包裹的一段文字

单行注释: #
多行注释: ''' ''' 或 """ """
文档编码注释: 写在python文件头部,用于指定当前文件的字符编码

单行注释不能换行,只有多行注释才可以换行

字符编码

存数据时采用的编码格式,决定了数据读取的编码格式。两者必须一致

7. 数据类型

  1. 字符串(str)

  2. 整形(int)

  3. 浮点型(float)

  4. 布尔值(bool)

在python中,可以使用type()函数,查看某个值的数据类型

在python中,变量是没有类型的,值是有类型的

8. 整型

python
# 所谓整型,就是没有小数点的数字,可以是正数,也可以是负数,也可以是0
import sys

age = 18
temp = -15
score = 0

# 当数很大时,我们可以使用下划线将数字进行分组,来让数字变得更易读

salary = 300_000
house_price = 3_200_000

# python中整数的上限值,取决于执行代码的计算机的内存和处理能力

a = 9 ** 9999
sys.set_int_max_str_digits(0)
b = a + 100
print(b)

9. 浮点型

python
# 浮点型就是带有小数点的数字
weight = 65.2
balance = 1425.58
out_temp = -25.2
price = 120.0

print(type(weight))
print(type(balance))
print(type(out_temp))
print(type(price))


# 浮点型的科学计数法表示.

speed_of_sound = 3.4e+2 #3.4乘以10的2次方
world_population = 7.8e9 #7.8乘以10的9次方
distance_sun_earth = 1.496E8 #1.496 乘以10的8次方

print(speed_of_sound)
print(world_population)
print(distance_sun_earth)

one_ml = 1e-3  # 1乘以10的-3次方

10. 字符串的四种定义方式

python
# 单引号和双引号是不能直接换行的
# 单引号和双引号的写法是等价的,二者都不能直接换行(要用圆括号才能换行),单引号用的多
message1 = '尚硅谷,让天下没有难学的技术!'
message2 = "尚硅谷,让天下没有难学的技术!"
# 三个单引号的写法,可以直接换行,并且可以作为多行注释使用
message3 = '''尚硅谷,让天下没有难学的技术!'''
# 三个双引号的写法,可以直接换行,也可以作为多行注释使用,还能作为文档字符串使用
message4 = """尚硅谷,让天下没有难学的技术!"""

print(message1)
print(message2)
print(message3)
print(message4)

11. 格式化输出

python
name = "张三"
gender = "男"
weight = 35.2
age = 12

# 写法一: 直接用加号进行拼接,写起来很麻烦且代码很乱,而且只能是字符串之间拼接
info1 ='我叫' + name + '我是,' + gender + '生'
print(info1)

# 写法二: 使用占位符

# %s占位字符串,%f 展位浮点数, %i占位整数,%d占位十进制的整数,%s是万能的
info2 ='我叫%s,我是%s生,我的体重是%f,年龄是%d' % (name,gender,weight,age)
print(info2)

# 写法三: 使用f-string (最推荐的方式)

info3 = f'我叫{name},我是{gender}生,我体重是{weight},年龄是{age}'
print(info3)

12. 字符串占位符精度控制

字符串占位
%m.ns
m:

  1. 字符串的最小宽度,位数不够会自动使用空格补齐,位数小于字符串长度则不起作用
  2. 正数是右对齐,负数是左对齐

n:

  1. 精度控制,最多输出n个字符(若n大于实际字符串长度,则不起作用)

字符串占位

%m.nf

m:

  1. 控制整体宽度(整体宽度 = 整数宽度 + 小数点 + 小数宽度)
  2. 位数不够空格来补,位数小于整体宽度,则自动失效
  3. 正数是右对齐,负数是左对齐

n:

  1. 精度控制,保留n位小数,默认值是6,不够6位是会使用0补位,如果需要截断,是需要四舍五入的

整数占位

%m.nd

m:

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

n:

  1. 精度控制,含义: 最少用n位显示数字
  2. 位数不够用0来补,位数小于整数位,则自动失效
python

# 字符串占位符精度控制

"""
字符串的占位
%m,n
m是字符串的宽度
n是字符串的精度

m是正数,向右对齐
m是复数,向左对齐

浮点数的占位
精度控制,保留n位小数,默认值是6,不够6位是会使用0补位,如果需要截断,是需要四舍五入的

整数的占位

m过小自动失效
m过大使用空格补齐

n过小,自动失效
n过大,使用0补齐
"""

name = "张三"
gender = "男"
weight = 35.2
age = 12

info ='我叫%4.4s,我是%s生,我的体重是%f,年龄是%d' % (name,gender,weight,age)
print(info)

13. 转义字符

python
# 转移字符

# 写法一: 使用双引号
print("在python中,可以使用'包裹一个字符串")
# 写法二:使用转移字符
print('在python中,可以使用\'包裹一个字符串')
print('在python中,可以使用\"包裹一个字符串')

# 使用\n进行换行
print("注册会员需要以下信息: \n姓名\n年龄\n手机号")

# 使用\\输入\
print("\\")

# 使用\b 删除前一个字符
print("helloo\b")

# 使用 \r 使光标回到本行开头,覆盖输出
print('67%\r68%')

# 使用 \t: 水平制表符(让光标跳转到下一个制表位)
print('ab\tcd')


# 表明在什么环境下,制表位都是4个空格
print('ab\tcd'.expandtabs(4))
print('abc\td'.expandtabs(4))

14. 数据类型转换

把指定数据转为字符串,任何类型都可以转成字符串类型

python
result1 = str(18)
result2 = str(75.6)

print(result1)
print(result2)

使用str()将指定数据转换为字符串

python
result1 = str(18)
result2 = str(75.6)
result3 = str(1.8e3)
result4 = str(12_000)

print(type(result1), result1)
print(type(result2), result2)
print(type(result3), result3)
print(type(result4), result4)

把指定数据转为整型: int()

python
result5 = int(15.6)
result6 = int("79")
result7 = int("   79    ")
result8 = int(48)

print(type(result5), result5)
print(type(result6), result6)
print(type(result7), result7)
print(type(result8), result8)

把指定数据转为浮点型: float()

python
result9 = float(18)
result10 = float("15.6")
result11 = float("   5.7    ")
result12 = float(14.8)
result13 = float("48")

print(type(result9), result9)
print(type(result10), result10)
print(type(result11), result11)
print(type(result12), result12)
print(type(result13), result13)

把指定数据转为布尔类型: bool()

Python中除0以外的任何数,转为布尔值后都为True,只关心是不是0,0为False,非0为True

python
result9 = float(18)
result10 = float("15.6")
result11 = float("   5.7    ")
result12 = float(14.8)
result13 = float("48")

print(type(result9), result9)
print(type(result10), result10)
print(type(result11), result11)
print(type(result12), result12)
print(type(result13), result13)

Python中除空字符串以外的任何数,转为布尔值后都为True

15. 算数运算符

python
# 算数运算符

"""
+ 加
- 减
* 乘
/ 除
// 取整
% 取模或取余
** 指数
"""

16. 赋值运算符

python
age = 18    #赋值运算符

age = age + 1 等价于 age += 1    # 加法 复合赋值运算符
age = age - 1 等价于 age -= 1    # 减法 复合赋值运算符
age = age / 2 等价于 age /= 1    # 除法 复合赋值运算符
age = age // 2 等价于 age //= 1    # 取整 复合赋值运算符
age = age % 2 等价于 age %= 1    # 取模 复合赋值运算符
age = age % 2 等价于 age %= 1    # 取模 复合赋值运算符
age = age ** 2 等价于 age **= 1    # 指数 复合赋值运算符

17. 比较运算符

python
==      判断左右两侧是否相等
!=      判断左右两侧是否不相等
>       判断左侧是否大于右侧
>=      判断左侧是否大于等于右侧
<       判断左侧是否小于右侧
<=      判断左侧是否小于等于右侧

18. 布尔类型

python
# 布尔类型
a = True
b = False

c = 7 > 3
d = 7 < 2

print(type(a),a)
print(type(b),b)
print(type(c),c)
print(type(d),d)


# 布尔类型是int类型的子类型,底层的本质是用1表示True, 用0表示False

print(4 + True)  # 5
print(8 - False) # 8
print(True + True) #2
print(False - False) #0


# 使用bool将指定内容转换为布尔类型
print(bool(1))
print(bool(0))

19. 逻辑运算符

python
# and 用于判断其两侧的值,是否都为True
print(True and True)
print(True and False)
print(False and True)
print(False and False)

# and具备逻辑短路的能力

print(False and 3/0)  # False

# and返回的不一定是布尔值,它返回的是某个参与计算的值本身
# 规则: and会先看左边,如果左边是"假",就直接返回左边,否则返回右边
# 备注: 若参与and的值不是布尔值,那Python会自动转换为布尔值,然后再进行逻辑操作


# or 用于判断其两侧,是否至少有一个为True(只要有一个是True,那就返回True)

print(True or True)
print(True or False)
print(False or True)
print(False or False)

# or具备逻辑短路的能力
print(True or 3/0)
print(9 > 3 or 3/0)


# or返回的也不一定是布尔值,它返回的是参与计算的值本身
# 规则: or会先看左边,如果左边是"真",就直接返回左边,否则返回右边
# 备注: 若参与or的值不是布尔值,那Python会自动转换为布尔值,然后再进行逻辑操作



# not 用于取反
# 备注: 若参与not的值不是布尔值,那Python会自动转换为布尔值,然后再进行逻辑操作


print(not True)
print(not False)
print(not 3 > 2)
print(not 3 < 2)

#not 返回的值,一定是布尔值

20. 进制

10进制转其他进制字符串

bin(10) # 十进制转二进制字符串
oct(10) # 十进制转八进制字符串
hex(10) # 十进制转十六进制字符串

其他进制转为十进制数字

使用int()将指定进制的数,转为十进制数字
int('0b11001', 2) # 转为二进制
int('0o1034', 8) # 转为八进制
int('0x1cf', 16) # 转为十六进制

21. 输入语句

python
name = input("请输入你的姓名: \n")
age = input("请输入你的年龄: \n")
age = int(age)
print(f'{name},你今年的年龄是{age}')
print(f'{name},你明年的年龄是{age + 1}')

22. 流程控制语句

单分支(基于判断实现)

python
age = input("请输入您的年龄: \n")
age = int(age)
if age >= 18:
    print('你是成年人')
    print('成年人的世界,虽不容易,但很精彩!')

print("================================")

双分支

python
age = input("请输入您的年龄: \n")
age = int(age)
if age >= 18:
    print('你是成年人')
    print('成年人的世界,虽不容易,但很精彩!')
else:
    print("你是未成年人")
    print("好好加油,努力学习,未来可期!")

print("欢迎你来学习python")

多分支

python
age = input("请输入您的年龄: \n")
age = int(age)
if age <= 10:
    print('你是幼儿')
elif age <= 18:
    print("你是青少年")
elif age <= 30:
    print("你是青年")
elif age <= 50:
    print("你是中年")
elif age <= 60:
    print("你是中老年")
else:
    print("您是老年")

嵌套分支

python
age = input("请输入您的年龄: \n")
has_report = input("您是否提交了体检报告?(是/否)")
level = int(input('请输入您的会员等级(1/2/3)'))
age = int(age)

print("*****程序的识别结果如下******")
if  18 <= age <= 45:
    print("您的年龄符合比赛要求!")
    if has_report == '是':
        print("您已提交体检报告!")
        print("您可以参加比赛")

        if level == 1:
            print(f'尊敬的{level}级会员,比赛结束后,您可以领取纪念T恤一键')
        elif level == 2:
            print(f'尊敬的{level}级会员,比赛结束后,您可以领取纪念T恤一键')
        elif level == 3:
            print(f'尊敬的{level}级会员,比赛结束后,您可以领取纪念T恤一键')
        else:
            print("您的会员等级不符")
    elif has_report == '否':
        print("您未提交体检报告,不能参加比赛")
    else:
        print("您输入的体检报告有误!")
else:
    print("抱歉,您的年龄不符合比赛要求!")

23.while循环

python
n = 1
while n <= 10:
    print(f'你好啊====={n}')
    n += 1

案例

python
print("您现在身处密室,需要正确回答问题之后,才能逃出密室!")
riddle = '你是什么人?'
answer = '你的心上人'
guess = ''

while guess != answer:
    print(f'问题: {riddle}')
    guess = input('请输入答案:\n')
    if guess == answer:
        print('答案正确,逃脱成功!')
    else:
        print('回答错误,请再想想!')

24.for循环

for 当前取出的值 in 可迭代对象: 要执行的循环操作1 要执行的循环操作2

python
for n in range(10):
    print('你好啊',n)

25. while循环与for循环的区别

  1. while循环是先判断条件,再执行循环体
  2. for循环是先取出可迭代对象中的值,然后再执行循环体
  3. 一般情况下,for循环的性能要优于while循环
  4. 当不确定循环次数时,使用while循环;当确定循环次数时,使用for循环

26. for循环实现99乘法表

python
for i in range(1, 10):  # 外层循环控制行数
    for j in range(1, i + 1):  # 内层循环控制列数,从1到当前i的值
        print(f'{j}x{i}={i*j}', end='\t')  # 打印乘法表的一项并使用制表符分隔
    print()  # 每完成一行后换行

27. contine 和 break

  1. continue 和 break 在for循环和wile循环中都可以使用
  2. continue 用于结束本次循环,但不退出整个循环
  3. break 用于结束整个循环
  4. coontinue 和 break 都只在当前循环有效

28. 流程控制综合案例

python
# 流程控制综合案例
print('欢迎来到: 答题闯关挑战赛(输入q可随时退出)')

# 题目与答案
ques1,ans1 = 'Python中用于输入的函数是?','print'
ques2,ans2 = 'Python中用于表示逻辑“并且”的关键字是?','and'
ques3,ans3 = 'Python属于编译型还是解释型?','解释型'

# 最多可尝试次数
max_tries = 3

# 总关卡数
total_levels = 3

# 是否处于可游戏状态
is_playing = True

for level in range(1,total_levels + 1):
    print(f'*********第{level}关*********')
    if level == 1:
        question,answer = ques1,ans1
    elif level == 2:
        question, answer = ques2, ans2
    else:
        question, answer = ques3, ans3
    # 若已经尝试的次数,小于等于最大尝试次数,则进入循环
    tries = 1
    while tries <= max_tries:
        user_input = input(f'{question} \n')
        if user_input == answer:
            print('回答正确!')
            break
        elif user_input == '':
            print('您的输入为空,请重新作答!')
            continue
        elif user_input == 'q':
            print('您已退出游戏!')
            is_playing = False
            break
        else:
            # 计算剩余次数
            leave = max_tries - tries
            if leave > 0:
                print(f'回答错误,您还剩{leave}次机会!')
                tries += 1
                continue
            else:
                print(f'挑战失败,本题的正确答案是: {answer},游戏结束!')
                is_playing = False
            continue

    if not is_playing:
        break

    if is_playing:
        print('恭喜您,全部通关')

29. 函数

函数是组织好的、可重复使用的、用于执行特定任务的代码块

Python中函数的分类

  1. 内置函数
    无需要任何操作,直接就能使用的函数
  2. 模块提供的函数
    需要导入指定的模块后,才能使用。
  3. 自定义函数
    程序员自己定义的函数(先定义再使用)

30. 函数的基本使用

python
# 函数
def welcome():
    print('欢迎来到尚硅谷课堂!')
    print('尚硅谷,让天下没有难学的技术!')
welcome()

31. 函数参数的使用

python
# 函数参数的使用
def order(num, dish):
    print(f'您点的是: {num}{dish}')


order(1, '辣椒炒肉')

32. 函数_位置参数

python
# 位置参数
# 位置参数: 调用函数时,根据参数在函数定义中出现的顺序,把实参的值一次传递给对应的形参
def greet(name,gender,age,height):
    print(f'我叫{name},性别{gender},年龄{age}岁,身高是{height}cm')

greet('张三','男',18,172)

33. 函数_关键字参数

使用关键词参数时,位置参数必须在关键字参数之前

python
# 关键字参数: 在调用函数时,根据形参的名字来传递实参的值

def greet(name,gender,age,height):
    print(f'我叫{name},性别{gender},年龄{age}岁,身高是{height}cm')

greet(name='张三', age=18, gender='男',  height=172)

34. 限制传参方式

具体规则:

  1. /前边只能用位置参数,*后面只能用关键字参数
  2. / 和 * 同时使用时,/必须在*前面
python
# 限制传参方式
#具体规则: /前边只能用位置参数,*后面只能用关键字参数
# / 和 * 同时使用时,/必须在*前面
def greet(name,/,gender,*,age,height):
    print(f'我叫{name},性别{gender},年龄{age}岁,身高是{height}cm')

# greet('张三','男',18,172)
greet('张三', age=18, gender='男',  height=172)

35. 默认参数

定义函数时,通过形参名=值的形式,为参数指定一个默认值

默认参数必须要放在必选参数的后面,或者说: 某个形参,一旦设置了默认值,那么该参数就必须放在非必选参数的后面

如果没传默认参数,那么该参数使用默认值,如果传了默认参数,那么该参数使用传入的值

python
def greet(name,gender='男',age=18,height=172):
    print(f'我叫{name},性别{gender},年龄{age}岁,身高是{height}cm')

greet('张三')

36. 可变参数

位置参数: 定义函数时,在形参名前加*,表示该参数可以接收任意数量的位置参数,并打包成一个元组
关键字参数:定义函数时,在形参名前加**,表示该参数可以接收任意数量的关键字参数,并打包成一个字典

可变位置参数、可变关键字参数,可以同时使用,但必须先写可变位置参数
可变位置参数,可变关键字参数,也能与其他类型的参数一起使用

python
# 定义函数(使用*args)去接收: 可变位置参数
# 定义函数(可变位置参数)
def test1(*args):
    print(args)

test1('张三','男',18,172)
# 定义函数(可变关键字参数)
def test2(**kwargs):
    print(kwargs)

test2(name='张三',gender='男',age=18,height=172)

# 定义函数(同时使用,可变位置参数,可变关键字参数)
def test3(*args,**kwargs):
    print(args)
    print(kwargs)

test3('张三','男',age=18,height=172)

37. 特殊的字面量_None

python
# None
#NoneType类型: None

# 1.None是一个特殊的字面量,它表示: 空值/无值/无意义
msg = None
# 2.None的类型是NoneType
print(type(msg))
# 3.None转为布尔值是False
print(bool(msg))
# 4.None不能参与数学运算,也不能与字符串拼接
# result1 = msg + 1
# 5.不给函数设置返回值,函数会默认返回None

38. 函数的返回值

函数返回值: 函数执行完毕后,会把执行结果交给调用者,这个执行结果就是返回值

return关键字: retrun会结束函数执行,并把return后的值,作为函数的返回值

python
def add(n1,n2):
    print(f'我收到了: {n1}{n2},二者相加是: {n1 + n2}')
    return n1 + n2
result = add(100,200)

39. 全局作用域 vs 局部作用域 以及global关键字的使用

什么是作用域? ---- 变量能起作用的范围(变量在哪里能用,在哪里不能用)。

  1. 全局作用域: 整个.py文件最外层的范围,就是全局作用域
  2. 局部作用域: 在函数内部,就是局部作用域,只能在当前函数中使用

局部作用域和局部作用域,会在函数调用时创建,在函数执行后销毁

python
# 全局作用域和局部作用域
a=100
b=200
def test():
    c='尚硅谷'
    d='你好啊'
    a = 300
    print(a) # 300
    print(b)
    print(c)
    print(d)

test()

print(a) # 100
print(b)
# print(c)   报错
# print(d)   报错


def test():
    c='尚硅谷'
    d='你好啊'
    global a # 声明n是全局变量
    a = 300
    print(a) # 300
    print(b)
    print(c)
    print(d)

test()

print(a) # 300
print(b)
# print(c)   报错
# print(d)   报错

40. 函数的嵌套调用

在一个函数的执行过程中,去调用了另外一个函数

python

41. 函数的递归调用

递归调用: 是指函数自己调用自己的一种操作。

python
# 递归调用
def welcome(n):
    print(f'你好啊!----------{n}')
    if n > 1:
        welcome(n - 1)
welcome(5)

# 输出 
# 你好啊!----------5
# 你好啊!----------4
# 你好啊!----------3
# 你好啊!----------2
# 你好啊!----------1


def welcome(n):
    if n > 1:
        welcome(n - 1)
    print(f'你好啊!----------{n}')
welcome(5)

# 输出 
# 你好啊!----------1
# 你好啊!----------2
# 你好啊!----------3
# 你好啊!----------4
# 你好啊!----------5

原理是: 待执行函数的调用,会形成一个栈结构,当函数执行完毕后,就会从栈中弹出。
这两段代码在递归时,其实都需要逐层压栈,二者调用深度完全相同,只是打印的时机不同。

42. 递归的应用

python
# 递归的应用
#使用递归求一个数的阶乘
# 某个数的阶乘就是: 所有小于及等于

def factorial(num):
    if num == 0:
        return 1
    else:
        return num * factorial(num - 1)

result = factorial(5)

print(result)

43. 函数的说明文档

说明文档: 写在函数里的文字说明,用来描述: 函数的功能、需要哪些承诺书、返回什么结果。

python
# 函数的说明文档
# 接收两个数字: n1、n2,返回二者相加的结果

def add(n1, n2):
    """
    计算两个数相加的结果
    :param n1: 第一个数
    :param n2:  第二个数
    :return:  二者相加的结果
    """
    return n1 + n2


result = add(100,500)


print(result,'result========')

44. 函数_综合案例

python
# 函数综合案例

def calc_total(*nums):
    """
    计算总运动量
    :param nums: 每一天的运动量(可变参数)
    :return: 总运动量(个)
    """
    return sum(nums)


def calc_avg(total, days=3):
    """
    计算平均值
    :param total: 总运动量
    :param days: 天数(默认值是7)
    :return: 平均值
    """
    return total / days


def check_success(total, goal=120):
    """
    判断本次挑战是否成功
    :param total: 总运动量
    :param goal: 目标运动量
    :return: 成功或失败的具体信息
    """
    print(total)
    print(goal)
    if total >= goal:
        return '恭喜!挑战成功!'
    else:
        return '抱歉!挑战失败!'


def main(title, duration,goal):
    """
    主函数,用于开始一场挑战赛
    :param title: 比赛标题
    :param duration: 比赛持续天数
    :param goal: 目标运动量
    :return: None
    """
    print(f'【{title}】【{duration}】天挑战赛(请输入每天的数量)')
    num1 = int(input('第1天: '))
    num2 = int(input('第2天: '))
    num3 = int(input('第3天: '))
    # 计算总数
    total = calc_total(num1, num2, num3)
    # 计算平均值
    avg = calc_avg(total,duration)
    # 判断挑战是否成功
    result = check_success(total,goal)
    print(f'【{title}】【{duration}】天健身总结')
    # print('总数: %d,平均数: %.1f'% (total, avg))
    print(f'总数: {total},平均数: {avg:.1f}')
    print(result)


main('仰卧起坐', 3,30)

45.数据容器

1. 何为数据容器

数据容器: 一种能存放多个数据的数据类型

数据容器可以更高效的管理成批的数据,且便于存储、访问

容器中的每一个数据,又称: 每一个元素 Python中有很多中数据容器

2. 列表

1. 定义列表

python
# 列表
list1 = [34,56,21,56,11]
list2 = ['北京','尚硅谷','你好啊']
list3 = [23,'尚硅谷',True,None]
list4 = [23,'尚硅谷',True,None,[100,200,300]]


# 定义空列表(列表中的数据,后期会通过特定写法填充)
list5 = []
list6 = list()
print(list1,type(list1))
print(list2,type(list2))
print(list3,type(list3))
print(list4,type(list4))
print(list5,type(list5))
print(list6,type(list6))

2. 列表_ 下标

下标(索引值): 列表中元素的位置编号

正索引: 从左往右数,起始元素是0,随后是1,依次类推 负索引: 从右往左数,起始元素是-1,随后是-2,依次类推

python
#定义一个列表
nums = [10,20,30,40,50]

# 正索引
print(nums[0])
print(nums[1])
print(nums[2])
print(nums[3])
print(nums[4])

# 负索引
print(nums[-1])
print(nums[-2])
print(nums[-3])
print(nums[-4])
print(nums[-5])


# 定义一个嵌套列表
nums2 = [10,20,['你好啊','尚硅谷'],40,50]
# 取出"尚硅谷"
print(nums2[2][1])

3. 列表_增删改查

新增操作:

append() 添加元素到列表的末尾
insert() 在列表的指定位置插入一个元素
extend() 将可迭代对象中的内容一次取出,追加到列表的尾部

删除操作:

pop() 删除指定位置的元素,并返回被删除的元素 语法: 列表.pop(下标) 默认删除最后一个元素,返回被删除的元素值
remove() 删除列表中第一次出现的指定元素 语法: 列表.remove(元素值)
clear() 清空列表中的所有元素 语法: 列表.clear()
del 列表下标 删除指定位置的元素 语法: del 列表[下标]

python
# 定义一个空列表

nums = [10,20,30,40]
# 向列表尾部追加一个元素
nums.append(1)
# 语法: 列表.append(元素)
print(nums)

# 在列表指定下标处添加一个元素
nums.insert(2,666)
print(nums)
# 语法: 列表.insert(下标,元素)


# 将可迭代对象中的内容一次取出,追加到列表的尾部
nums.extend([70,80,90])
print(nums)
# 语法: 列表.extend(下标,元素)

修改操作:

通过下标修改指定位置的元素 语法: 列表[下标] = 新值

查询操作:

通过下标查询指定位置的元素 语法: 列表[下标]

4. 列表_常用方法

  1. 列表.index(值) 查找指定元素在列表中第一次出现的下标 返回值: 元素下标 只在第一层列表查找
python
fruits = ['香蕉','苹果','橙子','香蕉']
result = fruits.index('香蕉')
print(result)
  1. 列表.count(值) 统计某个元素在列表中出现的次数 返回值: 元素出现的次数
python
nums = [10,20,10,30,10,40,[10,10,10]]
result = nums.count(10)
print(result)
  1. 列表.reverse() 反转列表(会改变原列表) 返回值: 无
python
nums = [10,20,10,30,10,40,[10,10,10]]
nums.reverse()
print(nums)
  1. 列表.sort(reverse=布尔值) 对列表进行排序(从小到大,会改变原列表) reverse 用于控制排序方式 返回值: 无
python
nums = [10,20,10,30,10,40,[10,10,10]]
nums.sort(reverse=True)
print(nums)

5. 常用的内置容器

1. 使用内置的sorted函数,返回一个排序后的新容器(不改变原容器,默认顺序: 从小到大)

python
nums = [23,11,32,30,17]
result = sorted(nums)
print(result)

2. 使用内置的len函数,获取容器中元素的总数量,返回值是: 元素总数量

python
nums = [10,20,10,30,10,40,[50,60,70]]
result = len(nums)
print(result)

3. 使用内置的max函数,获取容器中的最大值,返回值是: 最大值

python
nums = [23,11,32,30,17]
result = max(nums)
print(nums)
print(result)

4. 使用内置的min函数,获取容器中的最小值,返回值是: 最小值

python
nums = [23,11,32,30,17]
result = min(nums)
print(nums)
print(result)

5. 使用内置的sum函数,对容器中的数据进行求和,元素只能是数值

python
nums = [23,11,32,30,17]
result = sum(nums)
print(nums)
print(result)

5. 列表·循环遍历

把容器中的元素依次取出,并执行后续操作的过程,称为: 循环遍历

1. 使用while循环遍历列表

python
# 定义一个成绩列表
score_list = [62,50,60,48,80,20,95]
index = 0
while index < len(score_list):
    print(score_list[index],end=',')
    index += 1

2. 使用for循环遍历列表

写法1
python
# 定义一个成绩列表
## for循环遍历
for item in score_list:
    print(item,end=',')
写法2
python
# 定义一个成绩列表
## for循环遍历
for index in range(len(score_list)):
    print(score_list[index])
写法3

可以同时获取index和item

python
# 定义一个成绩列表
## for循环遍历
for index,item in enumerate(score_list):
    print(index,item)

enumerate可以传入第二个参数,表示从指定值开始计数

python
# 定义一个成绩列表
## for循环遍历
for index,item in enumerate(score_list,5):
    print(index,item)

6. 列表_特点总结

  1. 可存放不同类型的元素
  2. 元素是有序存储的(正索引、负索引)
  3. 列表中的元素允许重复
  4. 元素是允许修改的(增删改查其他操作)
  5. 长度不固定,可以随着操作自动调整大小

列表是最常用的数据容器,当遇到要"存储一批数据"的场景时,首选列表

7. 元组

元组是一种和列表类似的数据容器,它和列表的区别是: 元素中的元素不可修改。

python
# 定义一个元组
t1 = (1,5,6,7,8,9)
print(t1,type(t1))

# 元组中如果存放了可变类型(列表),那可变类型中的内容仍可修改

t3 = (28,67,21,67,11,[100,200,300,('你好','尚硅谷')])

t3[5][2] = 400

print(t3)

1. 元组常用的方法

python
# 获取指定元素在元组中第一次出现的下标
result = t3.index(67)
print(result)

# 统计元组中
result = t3.count(67)
print(result)

2. 元组中常用的内置函数

python
res = max(t3)
print(res)
res = min(t3)
print(res)
res = len(t3)
print(res)
res = tuple(sorted(t3))
print(res)

3. 元组的循环遍历

python
t4 = (28,67,21,67,11)
index = 0
while index < len(t4):
    print(t4[index])
    index += 1

4. 函数调用时,使用*对列表或元组进行解包后,再传递参数

python
def test(*args):
    print(f'我是test函数,我收到的参数是: {args}, 参数类型是: {type(args)}')

list1 = [100,200,300,400]
tuple1 = ('你好','北京','尚硅谷')

test(*list1)
test(*tuple1)

5. 元组(tuple)_特点

  1. 可存放不同类型的元素
  2. 元素是有序存储的(正索引、负索引)
  3. 元组中的元素允许重复
  4. 元组中的元素不允许修改
  5. 元组的长度固定,一旦创建就不能修改大小

元组是一种只读的数据容器,想保存一批"不会变的数据"时,首选元组。

8. 字符串

字符串就是存放多个字符的容器

  1. 字符串是不可变的
  2. 字符串不能嵌套

1. 字符串常用的方法

index方法: 获取指定字符在字符串中第一次出现的下标

python
msg = 'welcome to atguigu'
result = msg.index('t')
print(result)

split方法: 将字符串按照指定字符进行分隔,并返回一个列表

python
msg = '尚硅谷@atguigu@你好'
result = msg.split('@')
print(result)

replace方法: 将字符串中的某个字符串片段,替换成目标字符串

python
msg = 'welcome to atguigu'
result = msg.replace('atguigu','尚硅谷')
print(result)

count方法: 统计字符串中某个字符出现的次数

python
msg = 'welcome to atguigu'
result = msg.count('t')
print(result)

strip方法: 从某个字符串中删除指定字符串中的任意字符串
规则: 从字符串两端开始删除,直到遇到第一个不再指定字符串中的字符就停下
默认会去掉字符串两边的空格

python
msg = '666尚6硅6谷666'
result = msg.strip('6')
print(msg)
print(result)#尚6硅6谷


msg = '1234尚12硅34谷4321'
result = msg.strip('1324')
print(result) #尚12硅34谷

2. 常用的内置函数

len函数: 统计字符串中,字符的个数(字符串长度)
max函数:返回字符串中Unicode编码值最大的字符,不是下标
min函数:返回字符串中Unicode编码值最小的字符,不是下标
sorted函数:将字符串按Unicode编码值进行排序,返回一个新的列表

9. 序列

序列: 能联系存放元素的数据容器,元素有先后顺序,且可以通过下标访问

切片: 从序列中按照指定范围,取出一部分元素,形成一个新的序列的操作

语法: 序列[起始索引:结束索引:步长]

python
list1 = [10,20,30,40,50,60,70,80,90,100]

list2 = list1[0:10:1]
# 等价于
list3 = list1[::]

list4 = list1[:999:]

print(list2)
print(list3)
print(list4)


list5 = list1[3::]
print(list5)

list6 = list1[:5:]
print(list6)

list7 = list1[::4]
print(list7)

当起始索引大于结束索引时,步长必须为负数,否则结果是空列表

python
list8 = list1[7:2:-1]
print(list8)

特殊情况: 当同时省略起始索引和结束索引时,如果步长为负数,那Python会自动对调起始索引和结束索引

python
list9 = list1[::-1]
print(list9)

对元组进行切片

python
tuple1 = (10,20,30,40,50)
tuple2 = tuple1[0:5:1]
print(tuple2)

对字符串进行切片

python
msg1 = 'welcome to atguigu'
msg2 = msg1[0:10:1]
print(msg2)

序列的其他操作

相加

心血列 = 序列1 + 序列2
两个同类型的序列才能相加(字符串+字符串、列表+列表、元组+元组)
列表相加

python
list1 = [10,20,30,40]
list2 = [50,60,70,80]
list3 = list1 + list2
print(list3)

元组相加

python
tuple1 = (10,20,30,40)
tuple2 = (50,60,70,80)
tuple3 = tuple1 + tuple2
print(tuple3)

字符串相加

python
msg1 = 'hello'
msg2 = 'world'
msg3 = msg1 + msg2
print(msg3)
相乘(重复)

新序列 = 序列 * n

注意: n必须是整数,不能是浮点数

python
list1 = [10,20,30,40]
list2 = list1 * 3

print(list2)

元组相乘

python
tuple1 = (10,20,30,40)
tuple2 = tuple1 * 3

print(tuple2)

字符串相乘

python
msg1 = 'hello'
msg2 = msg1 * 3

print(msg2)

10. 集合

集合有两个set/frozenset两种类型,二者区别是:frozenset是不可修改的集合

可变集合: 创建后可以增删元素
不可变集合: 创建后不可以增删元素

集合的特点: 内部的元素无序(不保证顺序),不能通过下标访问元素,会自动去除重复元素

1. 集合的定义

可变集合
python
s1 = {10,20,30,40,50,60,60,70,80,90}
print(type(s1), s1)

s2 = {'你好','你好','hello world'}
print(s2)
print(type(s2), s2)
s3 = {10,'你好',True,1,12.4}
print(type(s3), s3)
不可变集合
python
s1 = frozenset({10,20,30,40,50,60,60,70,80,90})
print(type(s1), s1)

s2 = frozenset({'你好','你好','hello world'})
print(s2)
print(type(s2), s2)
s3 = frozenset({10,'你好',True,1,12.4})
print(type(s3), s3)

# frozenset 接收的参数,可以是任意可迭代对象,但最终返回的一定是[不可变集合]

s4 = frozenset('hello')
print(type(s4),s4)

#集合中不能嵌套【可变集合】,但可以嵌套【不可变集合】
#原因: 集合不支持下标,但底层依然需要给其中的每个元素,分配一个编号,这个编号可以用来:快速定位元素,并且这个编号是哈希值

# 定义空集合(可变集合)
s1 = set()
print(type(s1),s1)

2. 集合增删改查

python
# add方法: 向集合中添加元素
s1 = {10,20,30,40,50}
s1.add(60)
print(s1)

# update方法: 向集合中添加元素(必须传递可迭代对象,例如: 列表、元组、集合等)
s1 = {10,20,30,40,50}
s1.update([70,80])
print(s1)
python
# 增
# add方法: 向集合中添加元素
s1 = {10,20,30,40,50}
s1.add(60)
print(s1)

# update方法: 向集合中添加元素(必须传递可迭代对象,例如: 列表、元组、集合等)
s1 = {10,20,30,40,50}
s1.update([70,80])
print(s1)
# 删
# remove(元素):从集合中一处指定元素(若元素不存在,报错)
s1 = {10,20,30,40,50}
s1.remove(20)

# s1.remove(70)   报错
print(s1)
#discard(元素): 从集合中移除指定元素(若元素不存在,不报错)
s1 = {10,20,30,40,50}
s1.discard(70)
print(s1)
#pop():从集合中移除一个任意元素,返回值是移除的那个元素
s1 = {10,20,30,40,50}
s1.pop()
print(s1)
#clear(): 清空集合
s1 = {10,20,30,40,50}
s1.clear()
print(s1)

集合没有下标,也不支持replace方法,所以集合没有专门用于"改"的方法。
可以用: remove + add的组合,来达到"修改"的效果

python
s1 = {10,20,30,40,50}
s1.remove(30)
s1.update([60])
s1.add(74)
print(s1)

由于集合没有下标,也不支持切片操作,所以集合不具备按位置访问的能力
通过成员运算符可以判断: 某个元素是否在集合中

3. 集合的常用方法

  1. 集合A.difference(集合B): 返回一个新集合,包含A中有但B中没有的元素
python
s1 = {10,20,30,40,50}
s2 = {30,40,50,60,470}

result = s1.difference(s2)
print(result)
print(s1)
print(s2)
  1. 集合A.difference_update(集合B): 让A去掉与B相同的元素
python
s1 = {10,20,30,40,50}
s2 = {30,40,50,60,470}

result = s1.difference_update(s2)
print(result)
print(s1)
print(s2)
  1. 集合A.union(集合B): 返回一个新集合,包含A和B中所有的元素(求并集)
python
s1 = {10,20,30,40,50}
s2 = {30,40,50,60,470}

result = s1.union(s2)
print(result)
print(s1)
print(s2)
  1. 集合A.insubset(集合B): 判断A是否是B的子集
python
s1 = {10,20,30,40,50}
s2 = {10,20}
result = s2.insubset(s1)
print(result)
  1. 集合A.issuperset(集合B): 判断A是否是B的子集
python
s1 = {10,20,30,40,50}
s2 = {10,20}
result = s1.issuperset(s2)
print(result)
  1. 集合A.isdisjoint(集合B): 判断A和B是否没有交集
python
s1 = {10,20,30,40,50}
s2 = {10,20,50}
result = s1.isdisjoint(s2)
print(result)

4. 集合的数学运算

并集(|)
python
s1 = {10,20,30,40,50,60}
s2 = {40,50,60,70,80,90}

# 并集
result = s1 | s2
print(result)
交集(&)
python
s1 = {10,20,30,40,50,60}
s2 = {40,50,60,70,80,90}

# 交集
result = s1 & s2
print(result)
差集(-)
python
s1 = {10,20,30,40,50,60}
s2 = {40,50,60,70,80,90}

# 差集
result = s1 - s2
print(result)
对称差集(^)
python
s1 = {10,20,30,40,50,60}
s2 = {40,50,60,70,80,90}
# 对称差集
result = s1 ^ s2
print(result)

5. 集合的循环遍历

集合不能使用while循环遍历,可以使用for循环遍历

python
#集合不能使用while循环遍历,可以使用for循环遍历
s1 = {10,20,30,40,50,60}

for item in s1:
    print(item)

6. 集合的特点

  1. 无序: 集合中的元素没有固定顺序,无法通过下标访问
  2. 不重复: 自动去除重复元素
  3. 分为两种: 可变集合(set)和不可变集合(frozenset)
  4. 集合中的元素必须是不可变类型(如: 数字、字符串、元组)
  5. 集合支持: 并集、交集、差集、对称差集等数学运算

集合是可以去重的数据容器,当只关心元素是否存在,而不在乎顺序时,首选集合

11.字典

1. 字典的定义

字典中的key必须是不可变类型,value可以是任意数据类型

python
# 字典
d1 = {
    '张三': 72,
    '李四': 60,
    '王五': 85
}

print(type(d1),d1)

d2 = dict()
print(type(d2),d2)

2. 字典的增删改查

python
# 查询
d1 = {'张三': 72,'李四': 60,'王五': 85}
# 直接取值,若键(key)不存在,会报错
# result = d1['张三']

# 安全取值,若键(key)不存在,会返回默认值(若没有设置默认值,则会返回None)
result = d1['张三']
result2 = d1.get('奥特曼','抱歉,key不存在')
print(result)
print(result2)

# 新增

d1 = {'张三': 72,'李四': 60,'王五': 85}
d1['赵六'] = 100
print(d1)

# 批量修改
d1.update({'李四': 50,'王五':100})
print(d1)

# 删除
d1 = {'张三': 72,'李四': 60,'王五': 85}
print(d1)
del d1['张三']
print(d1)

# 删除指定key所对应的那组键值对,并返回这个key所对应的值
# pop方法可以设置默认值
# 默认值可以保证: 当要删除的key不存在的情况下,程序不会报错,并且返回这个默认值
result = d1.pop('李四')
print(d1)
print(result)


# 清空字典
d1.clear()
print(d1)

3. 字典的常用方法

python
# keys方法: 用于获取字典中所有的键
d1 = {'张三': 72,'李四': 60, '王五': 85}

# keys方法的返回值不是list,而是一种叫做dict_keys的类型

result = d1.keys()
print(result)
print(type(result))

# dict_keys和列表类似,可以被遍历,但要注意的是: 他不能通过下标访问元素

for item in result:
    print(item)
#print(result[0])

# 借助内置的list函数,可以将dict_keys转换成list
l1 = list(result)
print(l1)
print(type(l1))

# values方法: 获取字典中所有的值

d1 = {'张三': 72,'李四': 60,'王五': 85}

# values方法的返回值类型是: dict_values,它的特点和dict_keys一样

result = d1.values()
print(result)
print(type(result))


# items方法: 获取字典中所有的键值对(每组键值对以元组的形式呈现)
d1 = {'张三': 72,'李四': 60,'王五': 85}
result = d1.items()

print(result)
print(type(result))

4. 字典的循环遍历

python
# 字典不能使用while循环遍历,但可以使用for循环遍历
d1 = {'张三': 72,'李四': 60,'王五': 85}

for key in d1:
    print(f'{key}的成绩是{d1[key]}')

for key in d1.keys():
    print(f'{key}的成绩是{d1[key]}')

12. 数据容器的通用操作

1. list函数: 1. 定义空列表。2.将【可迭代对象】转为列表

python
res1 = list(range(8))
res2 = list('欢迎来到尚硅谷')
res3 = list({10,20,30,40,50})
res4 = list({'张三': 75,'李四':80})

print(type(res1),res1)
print(type(res2),res2)
print(type(res3),res3)
print(type(res4),res4)

2. tuple函数: 1. 定义空元组。2.将【可迭代对象】转为元组

python
res1 = tuple(range(8))
res2 = tuple('欢迎来到尚硅谷')
res3 = tuple({10,20,30,40,50})
res4 = tuple({'张三': 75,'李四':80})

print(type(res1),res1)
print(type(res2),res2)
print(type(res3),res3)
print(type(res4),res4)

3. set函数: 1. 定义空集合。2.将【可迭代对象】转为集合

python
res1 = set(range(8))
res2 = set('欢迎来到尚硅谷')
res3 = set({10,20,30,40,50})
res4 = set({'张三': 75,'李四':80})

print(type(res1),res1)
print(type(res2),res2)
print(type(res3),res3)
print(type(res4),res4)

4. str函数: 1. 定义空字符串。2.将【任意类型】转为字符串

python
res1 = str(range(8))
res2 = str('欢迎来到尚硅谷')
res3 = str({10,20,30,40,50})
res4 = str({'张三': 75,'李四':80})

print(type(res1),res1)
print(type(res2),res2)
print(type(res3),res3)
print(type(res4),res4)

5. str函数: 1. 定义空字典。2.将【可迭代对象】转为字典

备注: 交给dict函数的内容必须是键值对才可以,否则就会报错
python
res1 = dict({'张三': 72,'李四': 60,'王五': 85})
res2 = dict([('张三', 72), ('李四', 60), ('王五', 85)])
res3 = dict((('张三', 72), ('李四', 60), ('王五', 85)))

print(type(res1),res1)
print(type(res2),res2)
print(type(res3),res3)

所有的数据容器,都支持【成员运算符】 in / not in 作用: 判断某个元素是否存在于容器中

python
hobby = ['抽烟','喝酒','烫头']
nums = (10,20,30,40,50)
message = 'hello,atguigu'
citys = {'北京','上海','广州'}
score = {'张三': 72,'李四': 60,'王五': 85}


print('喝酒' in hobby)
print(20 in nums)
print('atguigu' in message)
print('上海' in citys)
print('李华' in score)

小练习

python
# 练习一: 水果清单
fruits = {
    '苹果': 4.5,
    '香蕉': 3.2,
    '橙子': 5.8,
    '草莓': 12.0,
    '哈密瓜': 8.8
}

# 需求1:打印所有的水果
for key in fruits:
    print(f'{key}:{fruits[key]}')
# 需求2: 找到最贵的水果

key = max(fruits, key=fruits.get)

print(f'最贵的水果是{key},价格是{fruits[key]}')

# 练习二: 学生成绩表

students = [
    {
        'name': '张三',
        'scores': {'语文': 88,'数学': 92,'英语': 95}
    },
{
        'name': '李四',
        'scores': {'语文': 75,'数学': 83,'英语': 80}
    },
{
        'name': '王五',
        'scores': {'语文': 92,'数学': 95,'英语': 88}
    },
]

# 需求1: 计算每位学生的平均分
for stu in students:
    # 获取当前学生的成绩列表
    score_list = stu['scores'].values()
    # 计算平均值
    avg = sum(score_list) / len(score_list)
    print(f"{stu['name']}的平均成绩是: {avg:.1f}")

# 需求2: 找到总分最高的学生

def find_best():
    # 记录分数最高的学生
    best_students = []
    # 记录最高分
    best_score = 0
    # 循环遍历
    for stu in students:
        # 获取当前学生的总分
        total = sum(stu['scores'].values())
        print(total,type(total))
        # 当前学生的成绩如果大于best_score,就会更新数据
        if total > best_score:
            best_students = [stu['name']]
            best_score = total
        # 当前学生的成绩与最高分相同,就加入列表
        elif total == best_score:
            best_students.append(stu['name'])
    print(f'最高分为{best_score},取得最高分的学生有: {best_students}')
find_best()


# 需求3: 评论内容

comment = '这家奶茶真好喝,环境也不错,就是价格有点贵,好喝好喝好喝!强烈推荐!'

# 需求1: 统计’好喝‘出现次数
print(comment.count('好喝'))
# 需求2: 将字符串中的’贵‘替换为’略高‘
comment2 = comment.replace('贵','略高')
print(comment2)
# 需求3: 是否包含’推荐两个字‘
print('推荐' in comment)

46.数据容器总结

alt text

47.类

1. 类的定义

python
# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    # 说明: 当一个函数被定义在了类中时,那这个函数就被称为: 方法。
    # __init__方法: 初始化方法,主要作用: 给当前正在创建的实例对象添加属性
    # __init__方法收到的参数: 当前正在创建的实例对象(self)、其他自定义参数
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

2. 创建实例

python
# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    # 说明: 当一个函数被定义在了类中时,那这个函数就被称为: 方法。
    # __init__方法: 初始化方法,主要作用: 给当前正在创建的实例对象添加属性
    # __init__方法收到的参数: 当前正在创建的实例对象(self)、其他自定义参数
    # 当我们以后编写代码去创建Person类实例的时候,Python会自动调用__init__
    def __init__(self,name,age,gender):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name
        self.age = age
        self.gender = gender

p1 = Person('张三',18,'男')
p2 = Person('李四',22,'女')

# 如果直接打印一个实例的话,我们是看不到实例身上的属性的
# print(p1,'p1')
# print(p2,'p2')

# 通过点语法可以访问或修改实例身上的属性
print(p1.name)
print(p1.age)
print(p1.gender)
print('='*20)
print(p2.name)
print(p2.age)
print(p2.gender)

p1.name = '阿三'
print(p1.name)

# 通过实例.__dict__ 可以查看实例身上的所有属性

print(p1.__dict__)
print(p2.__dict__)

# 实例创建完毕后,依然可以通过 实例.属性名 = 值 去给实例追加属性
p1.address = '北京昌平宏福科技园'
print(p1.__dict__)

# 通过type函数,可以查看某个实例对象,是由哪个类创建出来的

print(type(p1))
print(type(p2))

3. 自定义方法

python
# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    def __init__(self,name,age,gender):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name
        self.age = age
        self.gender = gender
    # 初始化方法(给实例添加行为)
    # speak方法收到的参数是: 调用speak方法的实例对象(self)、其他参数
    # speak方法只有一份,保存在Person类身上的,所有Person类的实例对象,都可以调用到speak方法
    def speak(self,msg):
        print(f'我叫{self.name},年龄是{self.age},性别是{self.gender},我想说: {msg}')

# 验证以下: speak方法是存在Person类身上的

# print(Person.__dict__)

# 创建person类的实例对象

p1 = Person('张三',18,'男')
p2 = Person('李四',22,'女')

# 验证以下Person的实例对象身上是没有speak方法的
print(p1.__dict__)
print(p2.__dict__)

# 所有Person类的实例对象,都可以调用到speak方法
# 当执行p1.speak()的时候,查找speak方法的过程: 1.实例对象自身(p1) => 2.实例的“缔造者”的身上
p1.speak('好好学习')
p2.speak('天天向上')

# 验证一下上述的查找过程

def speak():
    print('巴拉巴拉巴拉')
p1.speak = speak

print(Person.__dict__)
print(p1.__dict__)
print(p2.__dict__)

p1.speak()

4. 实例属性

python
# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    def __init__(self,name,age,gender):
        # 通过【实例.属性名 = 值】给实例添加的属性,就叫实例属性
        # 实例属性只能通过实例访问,不能通过类访问
        # 每个实例都有自己【独一份的】实例属性,各个实例之间是互不干扰的
        self.name = name
        self.age = age
        self.gender = gender

# 创建person类的实例对象

p1 = Person('张三',18,'男')
p2 = Person('李四',22,'女')

# 实例属性只能通过实例访问,不能通过类访问
# print(Person.name)
# print(p1.name)

5. 类属性

python
# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    # max_age、planet 他们都是类属性,类属性是保存在类身上的
    # 类属性可以通过类访问,也可以通过实例访问
    # 类属性通常用于保存:公共数据
    max_age = 120
    planet = '地球'
    # 初始化方法
    def __init__(self,name,age,gender):
        # 给实例添加属性
        self.name = name
        self.age = age
        self.gender = gender
# 验证一下: 类属性是保存在类身上的
print(Person.__dict__)

# 创建person类的实例对象

p1 = Person('张三',18,'男')
p2 = Person('李四',22,'女')

# 验证一下: 实例身上是没有类属性的
print(p1.__dict__)
print(p2.__dict__)

# 验证一下: 类属性可以通过类访问,也可以通过实例访问
print(Person.max_age)
print(p1.max_age)
print(p2.max_age)

6. 实例方法

python
# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    def __init__(self,name,age,gender):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name
        self.age = age
        self.gender = gender

    # 下面的speak方法、run方法,都保存在类身上,但他们主要是供实例调用,所以他们都叫:实例方法
    # 自定义方法(给实例添加行为)
    def speak(self,msg):
        print(f'我叫{self.name},年龄是{self.age},性别是{self.gender},我想说: {msg}')

    # 自定义方法(给实例添加行为)
    def run(self, distance):
        print(f'我叫{self.name}疯狂的奔跑了{distance}米')

# 通过实例调用实例方法
p1 = Person('张三',18,'男')
p2 = Person('李四',22,'女')

p1.speak('你好')
p2.run(300)

# 通过类调用实例方法
Person.run(p1,100)

7. 类方法

python
from datetime import datetime

# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    # 类属性
    max_age = 120

    planet = '地球'

    def __init__(self,name,age,gender):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name
        self.age = age
        self.gender = gender

    # 下面的speak方法、run方法,都保存在类身上,但他们主要是供实例调用,所以他们都叫:实例方法
    # 自定义方法(给实例添加行为)
    def speak(self,msg):
        print(f'我叫{self.name},年龄是{self.age},性别是{self.gender},我想说: {msg}')

    # 自定义方法(给实例添加行为)
    def run(self, distance):
        print(f'我叫{self.name}疯狂的奔跑了{distance}米')

    # 使用 @classmethod 装饰过去的方法,就叫:类方法,类方法保存在类身上
    # 类方法收到的参数: 当前类本身(cls)、自定义的参数
    # 因为收到了cls参数,所以类方法中是可以访问类属性的
    # 类方法通常用于实现: 与类相关的逻辑,例如: 操作类级别的信息、一些工厂方法
    @classmethod
    def change_planet(cls,value):
        cls.planet = value

    @classmethod
    def create(cls,info_str):
        # 从info——str中获取到有效信息
        name,year,gender = info_str.split('-')
        # 获取当前年份
        current_year = datetime.now().year
        # 计算年龄
        age = current_year - int(year)
        # 创建Person类的实例对象
        return cls(name,age,gender)

# 验证一下: 类方法保存在类身上的
print(Person.__dict__)

# 类方法需要通过类调用
# Person.test1(900)
# Person.test2(900)

Person.change_planet('月球')

# 通过实例调用实例方法
p1 = Person('张三',18,'男')
p2 = Person('李四',22,'女')

# 验证一下: 类方法身上的属性已经被修改了

print(p1.planet)
print(p2.planet)

# 测试一下类方法 -- create

8. 静态方法

python
from datetime import datetime

# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:

    def __init__(self,name,age,gender):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name
        self.age = age
        self.gender = gender

    # 静态方法
    # 使用@staticmethod 装饰过的方法,就叫:静态方法,静态方法也是保存在类身上的
    # 静态方法只是单纯的定义在类中,它不会收到:self、cls参数,它收到的参数都是自定义参数
    # 由于静态方法没有收到:self、cls参数,所以其内部不会访问任何类和实例相关的内容
    # 静态方法通常用于定义: 与类相关的工具方法
    @staticmethod
    def is_adult(year):
        # 获取当前的年份
        current_year = datetime.now().year
        # 计算年龄
        age= current_year - year

        return  age >= 18

    @staticmethod
    def mask_idcard(idcard):
        return idcard[:6] + '*********' + idcard[-4:]

# 验证一下: 静态方法也是保存在类身上的
# print(Person.__dict__)

# 静态方法需要通过类去调用
# result = Person.is_adult(20)
# print(result)


# result2 = Person.mask_idcard('1402252665896485275687')
# print(result2)

9. 继承

python
# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    # 类属性
    max_age = 120

    planet = '地球'

    def __init__(self,name,age,gender):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name
        self.age = age
        self.gender = gender

    def speak(self,msg):
        print(f'我叫{self.name},年龄是{self.age},性别是{self.gender},我想说: {msg}')

# 定义一个Student类(字类、派生类),继承自Person类(父类,超类、基类)
class Student(Person):
    def __init__(self, name, age, gender, stu_id, grade):
        # 方式一
        # super().__init__(name, age, gender)

        # 方式二
        Person.__init__(self,name,age,gender)

        # 子类独有的属性,需要自己手动完成初始化
        self.stu_id = stu_id
        self.grade = grade

    def study(self):
        print(f'我叫{self.name},我在努力的学习,争取做到{self.grade}年级的第一名')

# 创建student类的实例对象
s1 = Student('李华',16,'男','2025001','初二')

print(s1.__dict__)
print(type(s1))

# def speak(data):
#     print("hello")
# s1.speak = speak

# 查找speak方法的过程: 1.实例自身(s1) => 2.Student类 => 3.Person类
s1.speak('你好')

s1.study()

10. 方法重写

python
# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    # 类属性
    max_age = 120

    planet = '地球'

    def __init__(self,name,age,gender):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name
        self.age = age
        self.gender = gender

    def speak(self,msg):
        print(f'我叫{self.name},年龄是{self.age},性别是{self.gender},我想说: {msg}')

# 定义一个Student类(字类、派生类),继承自Person类(父类,超类、基类)
class Student(Person):
    def __init__(self,name,age,gender,stu_id,grade):
        super().__init__(name,age,gender)
        self.stu_id = stu_id
        self.grade = grade

    # 方法重写: 当子类中定义了一个与父类中相同的方法,那么子类中的方法就会"覆盖"父类的方法
    def speak(self, msg):
        super().speak(msg)
        print(f'我是学生,我的学号是{self.stu_id},我正在读{self.grade},我想说: {msg}')

s1 = Student('李华',12,'男','2025001','初二')

s1.speak('好好学习')

11. 两个常用的方法

python
# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    # 类属性
    max_age = 120

    planet = '地球'

    def __init__(self,name,age,gender):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name
        self.age = age
        self.gender = gender


# 定义一个Student类(字类、派生类),继承自Person类(父类,超类、基类)
class Student(Person):
    def __init__(self,name,age,gender,stu_id,grade):
        super().__init__(name,age,gender)
        self.stu_id = stu_id
        self.grade = grade

p1 = Person('张三',18,'男')
s1 = Student('李华',12,'男','2025001','初二')

# 方法1: isinstance(instance,Class), 作用: 判断某个对象是否为指定类或其子类的实例
print(isinstance(s1,Student))
print(isinstance(p1,Person))
print(isinstance(s1,Person))
print(isinstance(p1,Student))
# 方法2: issubclass(Class1,Class2),作用: 判断某个类是否是另一个类的子类
print(issubclass(Student,Person))
print(issubclass(Person,Student))

12. 多重继承

python
# 概念: 多重继承指一个类同时继承多个父类,从而拥有多个父类的属性和方法
# 举例: 就像孩子不仅继承爸爸的长相,也能继承妈妈的性格
# 定义一个Person类(类名通常使用: 大驼峰写法)
class Person:
    def __init__(self,name,age,gender):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name
        self.age = age
        self.gender = gender

    def speak(self):
        print(f'我叫{self.name},年龄是{self.age},性别是{self.gender}')

class Worker:
    def __init__(self,company):
        self.company = company
    def do_work(self):
        print(f'我在{self.company}做兼职')

class Student(Person, Worker):
    def __init__(self,name,age,gender,company,stu_id,grade):
        # super().__init__(name,age,gender)
        # super().__init__(company)
        Person.__init__(self,name,age,gender)
        Worker.__init__(self,company)
        self.stu_id = stu_id
        self.grade = grade
    def study(self):
        print(f'我在努力的学习,争取做{self.grade}年级的第一名')

s1 = Student('张三',18,'男','麦当劳','2025001','初二')

print(s1.__dict__)
s1.speak()
s1.do_work()
s1.study()

# 类的__mro__属性: 用于记录属性和方法的查找顺序
# 通过实例去查找属性或方法时,会先在实例身上去查找,如果没有,就按找__mro__记录的顺序去查找
print(Student.__mro__)

13. 三种权限

python
class Person:
    def __init__(self,name,age,idcard):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name        # 公有属性: 当前类中、子类中、类外部,都可以访问
        self._age = age         # 受保护的属性:当前类中、子类中,都可以访问
        self.__idcard = idcard  # 私有属性: 仅在当前类中访问

    def speak(self):
        print(f'我叫: {self.name},年龄: {self._age},身份证: {self.__idcard}')

class Student(Person):
    def hello(self):
        print(f'我是学生({self.name}-{self._age})')

s1 = Student('张三',18,'110101199001011234')
s1.hello()

p1 = Person('张三',18,'110101199001011234')
# print(p1.name)
# 在类的外部,如果强制访问【受保护的属性】也能访问到,但十分不推荐!
print(p1._age)
# 在类的外部,如果强制访问【私有属性】不能访问到,而且会报错!
# print(p1.__idcard)

# Python底层是通过重命名的方式,实现私有属性的
print(p1.__dict__)

14. getter和setter

python
class Person:
    def __init__(self,name,age,idcard):
        # 给实例添加属性(语法为: self.属性名=属性值)
        self.name = name        # 公有属性: 当前类中、子类中、类外部,都可以访问
        self._age = age         # 受保护的属性:当前类中、子类中,都可以访问
        self.__idcard = idcard  # 私有属性: 仅在当前类中访问
    # 注册age属性的getter方法,当访问Person实例的age属性时,下面的age方法就会被自动调用
    @property
    def age(self):
        return self._age

    # 注册age属性的setter方法,当访问Person实例的age属性时,下面的age方法就会被自动调用
    @age.setter
    def age(self,value):
        self._age = value

    @property
    def idcard(self):
        return self.__idcard[:6] + '*********' + self.__idcard[-4:]
    @idcard.setter
    def idcard(self,value):
        self.__idcard = value


p1 = Person('张三',18,'11010119901011246')
print(p1.name)


p1.age = 99

print(p1.age)

print(p1.idcard)

15. 魔法方法

python
# 概念: 以 __xx__ 命名的特殊方法(双下划线开头和结尾)
# 特点: 不需要我们手动调,我们只要 准备好这些方法,Python会在特定场景下,去自动调用。

class Person:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender

    # 当执行print(Person的实例对象) 或 str(Person的实例对象) 时调用
    def __str__(self):
        return f'{self.name}-{self.age}-{self.gender}'

    # 当执行len(Person的实例对象)或str(Person的实例对象)时调用
    def __len__(self):
        return len(p1.__dict__)

    # 当执行 Person实例对象1 < Person实例对象2 时调用
    def __lt__(self,other):
        return self.age < other.age

    # 当执行 Person实例对象1 > Person实例对象2 时调用
    def __gt__(self, other):
        return self.age > other.age
    # 当执行 Person实例对象1 = Person实例对象2 时调用
    def __eq__(self, other):
        return len(self.__dict__) == len(other.__dict__)
    # 当访问Person实例对象身上不存在的属性时调用
    def __getattr__(self, item):
        return f"您访问的{item}属性不存在"
p1 = Person('张三',22,'男')
# p2 = Person('张三',22,'男')
p2 = Person('李四',19,'女')

# print(p1)
# print(p2)

# res = len(p1)
# print(res)

# print(p1 < p2)
# print(p1 > p2)
# print(p1 == p2)


print(p1.address)

16. object类

python
# Python中,所有的类都继承了 object类,即: object类是所有类的顶层父亲。

class Person:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
# 验证一下,所有的类继承了object类
# print(issubclass(Person,object))
# print(issubclass(int,object))
# print(issubclass(str,object))
# print(issubclass(list,object))
# print(issubclass(bool,object))
# print(issubclass(tuple,object))

# 因为 object 是所有类的父亲,所以 Python 中的所有对象,都间接是 object 类的实例。

# p1 = Person('张三',18,'男')
# print(isinstance(p1,object))
# print(isinstance(100,object))
# print(isinstance('hello',object))
# print(isinstance(True,object))
# print(isinstance(None,object))
# print(isinstance([10,20,30],object))
# print(isinstance({'吃饭','睡觉'},object))

# 所有对象都继承了object类所提供的: 各种属性和方法,从而保证了每个对象都具备统一的基本能力

# for key in object.__dict__:
#     print(key)

p1 = Person('张三',18,'男')
print(p1.__dict__) # 对象身上自己的东西
print(dir(p1)) # 对象可以访问到的东西

print(p1.__str__())

17. 标准多态

python
# 多态的概念: 同一个方法名,在不同的对象上调用时,能呈现出不同的行为。
# Python中支持: 标准多态、鸭子多态
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) # 该行代码在其他语言中就已经报错了,在Python中不推荐

18. 鸭子多态

python
# 核心理念: 如果一个东西看起来像鸭子,叫起来也像鸭子,那它就是鸭子。

# 鸭子类型是一种编程风格,它不检查对象的类型,只关注对象能否"做某件事"(是否有对应的方法)

# 鸭子多态
class Dog:
    def speak(self):
        print('汪汪汪')

class Cat:
    def speak(self):
        print('喵喵喵!')

class Pig:
    def speak(self):
        print('哼哼哼!')
class Fish:
    def speak(self):
        print('咕噜噜!')

class Computer:
    def speak(self):
        print('滋滋滋!')

def make_sound(animal):
    animal.speak()

# 创建实例对象
d1 = Dog()
c1 = Cat()
p1 = Pig()
f1 = Fish()
com = Computer()

print(make_sound(d1))
print(make_sound(c1))
print(make_sound(p1))
print(make_sound(f1))
print(make_sound(com))

19. 抽象类

【抽象类】是一种不能直接实例化的类,它通常作为"规范",让子类去继承,并实现其中定义的【抽象方法】

python
from abc import ABC,abstractmethod

# 【抽象类】是一种不能直接实例化的类,它通常作为"规范",让子类去继承,并实现其中定义的【抽象方法】
# MustRun类一旦继承了ABC类,那么MustRun类就是抽象类
class MustRun(ABC):
    @abstractmethod
    def run(self):
        pass

class Person(MustRun):
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
    def run(self):
        print(f'我叫{self.name},我在努力的奔跑')


p1 = Person('张三',18,'男')
p1.run()

20. 小练习

python
from datetime import datetime
class Person:
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
class Student(Person):
    count = 0
    def __init__(self, name, age, gender):
        super().__init__(name,age,gender)
        Student.count += 1
        self.stu_id = f'{datetime.now().year}{Student.count:03d}'
        self.scores = {}

    # 给当前学生添加成绩
    def add_score(self,subject,score):
        self.scores[subject] = score

    # 计算平均分
    def calcu_avg(self):
        if self.scores:
            return sum(self.scores.values()) / len(self.scores)
        else:
            return 0
    # 魔法方法
    def __str__(self):
        return f'{self.name}({self.age}-{self.gender}),成绩: {self.scores},平均分: {self.calcu_avg():.1f}'

# s1 = Student('张三',18,'男')
# s1.add_score('数学',90)
# s1.add_score('语文',80)
# s1.add_score('英语',70)
# print(s1.calcu_avg())
# # print(s1.__dict__)
# print(s1)
class Manager:
    def __init__(self):
        self.stu_list = []
    # 添加学生
    def add_student(self):
        name = input('请输入姓名:')
        age = int(input('请输入年龄:'))
        gender = input('请输入性别:')
        stu = Student(name,age,gender)
        self.stu_list.append(stu)
        print(f"添加成功!学号是: {stu.stu_id}")
    # 删除学生
    def del_student(self):
        sid = input('请输入学号:')
        target = None
        for stu in self.stu_list:
            if stu.stu_id == sid:
                target = stu

        if target:
            self.stu_list.remove(target)
            print("删除成功")
        else:
            print("学号有误,删除失败!")
    # 展示所有学生
    def show_all_student(self):
        if self.stu_list:
            for stu in self.stu_list:
                print(stu)
        else:
            print('暂无学生!')
    # 给指定学生设置成绩
    def set_score(self):
        sid = input('请输入学号:')
        for stu in self.stu_list:
            if stu.stu_id == sid:
                score_str = input('请输入成绩(学科-分数,学科-分数)')
                score_list = score_str.replace(',',',').split(',')
                for item in score_list:
                    subject,score = item.split('-')
                    subject = subject.strip()
                    score = float(score.strip())
                    stu.add_score(subject,score)
                print('添加成功!')
                return
        print('学号有误!')
    # 提供主菜单
    def run(self):
        while True:
            print('********学生管理********')
            print('1.添加学生')
            print('2.删除学生')
            print('3.查看所有学生')
            print('4.录入成绩')
            print('5.退出')

            choice = input('请输入您需要进行的操作: ')
            if choice == '1' :
                self.add_student()
            elif choice == '2':
                self.del_student()
            elif choice == '3':
                self.show_all_student()
            elif choice == '4':
                self.set_score()
            elif choice == '5':
                print('再见!')
                break
            else:
                print('输入有误!')


m1 = Manager()
m1.run()

48. 重新认识函数

1. 函数也是对象

python
# 1. 函数也是对象
a1 = 100        #int类的实例对象
a2 = 'hello'    #str类的实例对象
a3 = [10,20,30] #list类的实例对象

print(type(a1))
print(type(a2))
print(type(a3))

def welcome():      #function类的实例对象
    print('你好啊')

print(type(welcome))

2.函数可以动态添加属性

python
# 2.函数可以动态添加属性
def welcome():
    print('你好啊')

welcome.desc = '这是一个打招呼的函数'
welcome.version = '1.0'
print(welcome.desc)
print(welcome.version)

3. 函数可以赋值给变量

python
# 3. 函数可以赋值给变量
def welcome():
    print('你好啊')

welcome.desc = '这是一个打招呼的函数'
welcome.version = '1.0'

say_hello = welcome

say_hello()
print(say_hello.desc)
print(say_hello.version)

4. 可变参数 vs 不可变参数

python
# 4. 可变参数 vs 不可变参数
# 不可变参数
a = 666
def welcome(data):
    print('data修改前',data,id(data))
    data = 888
    print('data修改后',data,id(data))
    print(data)
print('data修改前', a, id(a))
welcome(a)
print('data修改后', a, id(a))
print(a)

# 可变参数
a = [10,20,30]
def welcome(data):
    print('data修改前',data,id(data))
    data[2] = 99
    print('data修改后',data,id(data))
print('函数调用前',a,id(a))
welcome(a)
print('函数调用后',a,id(a))

5. 函数也可以作为参数

python
def welcome():
    print('你好啊')

def caller(f):
    print('caller函数调用了')
    f()

caller(welcome)

6. 函数也可以作为返回值

python
def welcome():
    print('你好啊')
    def show_msg(msg):
        print(msg)
    return show_msg

result = welcome()

result('尚硅谷')

49. 多返回值_参数的打包与解包

python
# 一、函数的多返回值
def calculate(x,y):
    res1 = x + y
    res2 = x - y
    return res1,res2

# result = calculate(30,10)
# print(result)

r1,r2 = calculate(30,10)
print(r1)
print(r2)


# 二、参数的打包与解包

# 1. 打包结束参数:
# *args:   打包所有的位置参数(会形成一个元组)
# *kwargs: 打包所有的关键字参数(会形成一个字典)

def show_info(*args,**kwargs):
    print(args)
    print(kwargs)

show_info(10,20,30,name='张三',age=18,gender='男')

# 2. 解包传递参数
# *变量名: 将元组拆解成一个一个独立的位置参数
# **变量名: 将字典拆解一个一个 key=value形式的关键字参数

def show_info(num1,num2,num3,name,age,gender):
    print(num1,num2,num3)
    print(name,age,gender)

nums = (10,20,30)
person = {'name': '张三','age': 18,'gender':'男'}

# show_info(10,20,30,'张三',18,'男')
show_info(*nums,**person)

# 3. 打包接收参数 和 解包传递参数 一起使用
def show_info(*args,**kwargs):
    print(args)
    print(kwargs)

nums = (10,20,30)
person = {'name': '张三','age': 18,'gender':'男'}

show_info(*nums,**person)

50. 高阶函数

高阶函数: 当一个函数的【参数是函数】或者【返回值是函数】那该函数就是【高阶函数】

高阶函数的意义:

  1. 代码复用性高: 可以把行为"独立出去",传入不同函数实现不同逻辑
  2. 能让函数更灵活,更通用
  3. 高阶函数是: 装饰器、闭包的基础
python
# 高阶函数: 当一个函数的【参数是函数】或者【返回值是函数】那该函数就是【高阶函数】

# 高阶函数的意义:
# 1. 代码复用性高: 可以把行为"独立出去",传入不同函数实现不同逻辑
# 2. 能让函数更灵活,更通用
# 3. 高阶函数是: 装饰器、闭包的基础

def info(msg):
    return '[提示]:' + msg

def warn(msg):
    return '[警告]:' + msg

def error(msg):
    return '[错误]:' + msg


def log(func,text):
    print(func(text))

log(info,'文件保存成功!')
log(warn,'磁盘空间不足!')
log(error,'该用户不存在!')

51. 条件表达式

表达式: 执行后能得到值的代码,就是表达式(表达式最终会形成一个值,可以写在任何需要值得地方)
python
# 表达式: 执行后能得到值的代码,就是表达式(表达式最终会形成一个值,可以写在任何需要值得地方)
# a1 = 3 + 5
# a2 = 'abc' * 3
# a3 = 5 > 3
# a4 = 'y' in 'python'
# a5 = len('hello')
# 条件表达式: 根据条件得真假,在两个结果中二选一的表达式(又称: 三元表达式、三目运算符)
age = 21

# 传统的if-else去写:
# if age >= 18:
#     text = '成年'
# else:
#     text = '未成年'
#
# print(text)

# 条件表达式去写: 值1 if条件 else 值2

text = '成年' if age >=18 else '未成年'

print(text)

# 条件表达式的使用场景: 简单的二选一场景
rain = True
eat = '外卖' if rain else '出去吃'

print(eat)


is_vip = False
disscount = 0.8 if is_vip else 1.0

print(disscount)


is_login = False
msg = '欢迎回来!' if is_login else print('哈哈哈')

print(msg)

52. 匿名函数

概念: 所谓【匿名函数】,就是没有名字的函数,它无需使用def关键字去定义。

语法: Python中使用lambda关键字来定义【匿名函数】,格式为: lambda参数: 表达式

使用场景: 当一个函数只用一次、只做一点点小事,使用匿名函数会更简洁

注意点:

  1. 只能写一行,不能写多行代码。
  2. 不能写代码块(if、for、while)
  3. 冒号右边必须是表达式,且只能写一个表达式。
  4. 表达式结果自动作为返回值。
python
# 概念: 所谓【匿名函数】,就是没有名字的函数,它无需使用def关键字去定义。
# 语法: Python中使用lambda关键字来定义【匿名函数】,格式为: lambda参数: 表达式
# 使用场景: 当一个函数只用一次、只做一点点小事,使用匿名函数会更简洁

# 使用普通函数实现计算效果
def add(x,y):
    return x + y

def sub(x,y):
    return  x - y

def calculate(func,a,b):
    print(f'计算结果为: {func(a,b)}')

calculate(add,30,10)
calculate(sub,30,10)

# 匿名函数

add1 = lambda x,y: x + y
sub = lambda x,y: x - y
add2 = lambda x: x + x
add3 = lambda : '我是add3函数'
result = add1(30,10)
result2 = sub(30,10)
result3 = add2(30)
result4 = add3()
print(result)
print(result2)
print(result3)
print(result4)


# 使用匿名函数实现计算效果

def calculate(func,a,b):
    print(f'计算结果为: {func(a,b)}')


calculate(lambda x,y: x + y,30,10)
calculate(lambda x,y: x - y,30,10)

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

53. 几个数据处理函数

map函数

map函数: 对一组数据中的每一个元素,统一执行某种操作(加功),并生成一组新数据。
语法格式: map(操作函数,可迭代对象)

注意点:

  1. 延迟执行: map 不会立刻计算,只有在"需要结果"时才执行计算。
  2. 返回的是迭代器对象,且一旦遍历完成,就会被“耗尽”。
  3. map不会影响元素数量
python
# map函数: 对一组数据中的每一个元素,统一执行某种操作(加功),并生成一组新数据。
# 语法格式: map(操作函数,可迭代对象)

# 统一数据处理
nums = [10,20,30,40]
def double(x):
    return  x * 2

# map函数的返回值是一个迭代器对象,需要我们自己去手动遍历,或者手动类型转换
result = map(lambda x: x * 2,nums)
print(list(result))
print(nums)


# 字符串转换
names = ('python','java','js')
result = map(lambda x: x.upper(),names)
print(tuple(result))
print(names)
# str1 = 'hello'
# result = str1.upper()
# print(result)

# 类型转换
str_number = {'1','2','3'}
result = map(int,str_number)
print(set(result))
print(str_number)


# 注意点:
# 1. 延迟执行: map 不会立刻计算,只有在"需要结果"时才执行计算。
# 2. 返回的是迭代器对象,且一旦遍历完成,就会被“耗尽”。
# 3. map不会影响元素数量
filter函数

filter函数: 从一组数据中,筛选出符合条件的元素(过滤),并组成一组新数据。
语法格式: filter(过滤函数,可迭代对象)

注意点:

  1. 延迟执行: filter不会立刻筛选,只有在“需要结果”时才执行。
  2. 返回的是迭代器对象,且一旦遍历完成就会被“耗尽”
  3. filter可能会影响元素数量
python
# filter函数: 从一组数据中,筛选出符合条件的元素(过滤),并组成一组新数据。
# 语法格式: filter(过滤函数,可迭代对象)

# 筛选数值
nums = [10,20,30,40,50]
result = filter(lambda n: n > 30, nums)
print(list(result))
sorted函数

sorted函数: 对一组数据进行排序,返回一组新数据
语法格式: sorted(可迭代对象,key=xxx,reverse=xxx)

python
# sorted函数: 对一组数据进行排序,返回一组新数据
# 语法格式: sorted(可迭代对象,key=xxx,reverse=xxx)

# 数字排序
nums = [30,40,20,10]

result = sorted(nums,reverse=True)
print(result)

# 按照字符串的长度去排序
names = ['python','sql','java']
result = sorted(names,key=len,reverse=True)
print(result)

# 根据字典中的某个字段进行排序
persons = [
    {'name': '张三','age':15,'gender': '男'},
    {'name': '李四','age':17,'gender': '男'},
    {'name': '王五','age':19,'gender': '男'},
    {'name': '李华','age':20,'gender': '男'},
    {'name': '赵六','age':18,'gender': '男'},
    {'name': '孙七','age':16,'gender': '男'},
]

result = sorted(persons,key=lambda n: n['age'])
print(result)

# 我们之前将的max函数、min函数,也可以传递key参数,用于设置筛选依据
result1 = max(persons,key=lambda n: n['age'])
result2 = min(persons,key=lambda n: n['age'])
print(result1)
print(result2)
reduce函数

reduce函数: 将一组数据不断'合并',最终归并成一个结果。
语法格式: reduce(合并函数,可迭代对象,初始值)
备注: reduce函数需要从functools模块中引入才能使用

python
# reduce函数: 将一组数据不断'合并',最终归并成一个结果。
# 语法格式: reduce(合并函数,可迭代对象,初始值)
# 备注: reduce函数需要从functools模块中引入才能使用

# 从functools模块中引入reduce
from functools import reduce

# 数值统计
nums = [1,2,3,4,5]

def count(a,b):
    return a + b

result = reduce(count,nums,0)
print(result)

54. 列表推导式

python
# 列表推导式: 用一条简洁语句,从可迭代对象中,生成新列表的语法结构
# 备注: 列表推导式本质上是对for循环 + append()的一种简写形式。
# 语法格式: [表达式 for 变量 in 可迭代对象]

# 需求:让列表中每个元素,都变为原来的2倍,得到是一个新的列表

# 方式一: 用map函数
nums = [10,20,30,40]
result = list(map(lambda n: n * 2,nums))
print(result)

# 方式二: 用map函数
nums = [10,20,30,40]
result = []
for item in nums:
    result.append(item * 2)
print(result)

# 方式三: 用列表推导式
nums = [10,20,30,40]
result = [n*2 for n in nums]
print(result)


# 带条件的列表推导式
nums = [10,20,30,40]

result = [n * 2 for n in nums if n > 20]

print(result)

# 字典推导式
names = ['张三','李四','王五']
scores = [60,70,80]

result = { names[i]:scores[i] for i in range(len(names)) }
print(result)

# 集合推导式
names = ['张三','李四','王五']

result = {n + '!' for n in names}
print(result)

# Python中没有元组推导式,下面这种写法叫生成器
names = ['张三','李四','王五']
result = (n + '!' for n in names)
print(result)

55. 深拷贝与浅拷贝

浅拷贝

创建一个新的外层容器,但内部元素仍然引用原来的对象。
浅拷贝存在的问题:
嵌套数据仍然是共享的,修改嵌套数据会相互影响

深拷贝

创建一个新的外层容器,并对其内部所有的可变对象进行递归复制
备注:
1.深拷贝可以彻底消除数据之间的相互影响
2.深拷贝遇到【不可变对象】不会复制,会直接引用

56.四种作用域

局部作用域

在函数内部定义的变量,只能在函数内部使用

全局作用域

在函数外部定义的变量,可以在所有地方使用

内置作用域

Python内置的名称空间,可以直接调用。例如: print()、len()等

闭包作用域

嵌套函数中,内层函数可以访问外层函数的参数和变量,但反过来不行

python
# 作用域: 指的是变量的有效范围

# 局部作用域
def test():
    x = 100
    print(x)
test()

# 全局作用域
x = 200
def test():
    print(x)
test()

# 内置作用域
import builtins
print(dir(builtins))

# 闭包作用域
def outer_func():
    a = 100
    b = 200
    def inner_func():
        print(a,b)
    return inner_func

result = outer_func()
result()

57. 闭包

什么是闭包? --- 闭包 = 内层函数 + 被内层函数所引用的外层变量
闭包产生的条件:
  1. 要有函数嵌套
  2. 在【内层函数】中,要访问【外层函数】的变量
  3. 并且【外层函数】要返回【内层函数】。---只有返回了内层函数,闭包才能“活下来”
闭包的优点:
  1. 可以记住状态: 不用全局变量,也不用写类,就能在多次调用之间保存数据。
  2. 可以做"配置过的函数": 先传一部分参数,把环境固定住,得到一个定制版函数。
  3. 可以实现简单的"数据隐藏": 外层变量对外不可见,只能通过内层函数访问
  4. 是装饰器(decorator)等高级用法的基础
闭包的缺点:
  1. 理解成本较高: 对初学者不太友好,滥用会让代码难读。
  2. 如果闭包里引用了很大的对象,又长期不释放,可能会增加内存占用。
  3. 很多场景下,其实用【类+实例属性】会更清晰,闭包不一定是最优解
python
# 前置知识一:
# 1. 每次调用函数时,Python都会为函数创建一个新的局部作用域
# 2. 函数执行完毕后,这个局部作用域会被销毁,其中的局部变量也会随之被释放

def outer():
    num = 10
    num += 1
    print(num)

outer()
outer()
outer()

# 前置知识二:
# 1. 在Python中,【内置函数】可以访问其【外层函数】作用域中的变量
# 2. 访问外层函数变量无需使用nonlocall但修改外层变量时要使用nonlocal

def outer():
    num = 10

    def inner():
        nonlocal num
        num = 12
        print('局部作用域中的num:',num)

    inner()

    print('全局作用域中的num:',num)

outer()


# 什么是闭包? --- 闭包 = 内层函数 + 被内层函数所引用的外层变量
# 闭包产生的条件:
#   1.要有函数嵌套
#   2.在【内层函数】中,要访问【外层函数】的变量
#   3.并且【外层函数】要返回【内层函数】。---只有返回了内层函数,闭包才能“活下来”

def outer():
    num = 10
    print(hex(id(num)))
    def inner():
        nonlocal num
        num += 1
        print(num)
    print(inner.__closure__)

    return inner

f = outer()
f()

# 结论:
# 1. outer函数中,被inner所使用到的那些变量,会被封存到【闭包单元(cell)】中
# 2. 这些cell会组成一个 __closure__元组,最终放在了inner函数身上。


# 打印__closure__元组
# print(f.__closure__) # 闭包单元

# 打印 __closure__元组中的某一项
# print(f.__closure__[0])

# 打印 __closure__元组中的某一项的具体值
# print(f.__closure__[0].cell_contents)


# 注意点
# 1. 调用n次外层函数,就会得到n个不用的闭包,并且这些闭包之间互补影响

def outer():
    num = 10
    print(hex(id(num)))
    def inner():
        nonlocal num
        num += 1
        print(num)

    return inner

f1 = outer()
f1()
f1()
f1()
print('*********')
f2 = outer()
f2()


# 2. 内层函数中用到的外层变量是可变对象,多个闭包之间依然互不影响
def outer():
    nums = []

    def inner(value):
        nonlocal nums
        nums.append(value)
        print(nums)

    return inner

f1 = outer()
f1(10)
f1(20)
f1(30)
print('************')
f2 = outer()
f2(666)

# 闭包的优点:
# 1. 可以记住状态: 不用全局变量,也不用写类,就能在多次调用之间保存数据。
# 2. 可以做"配置过的函数": 先传一部分参数,把环境固定住,得到一个定制版函数。
# 3. 可以实现简单的"数据隐藏": 外层变量对外不可见,只能通过内层函数访问
# 4. 是装饰器(decorator)等高级用法的基础

def beauty(char,n):
    def show_msg(msg):
        print(char * n + msg + char * n)

    return show_msg

show1 = beauty('*',3)
show1("你好啊")
show1("尚硅谷")

show2 = beauty('@',5)
show2('你好啊')
show2("尚硅谷")

# 闭包的缺点:
# 1. 理解成本较高: 对初学者不太友好,滥用会让代码难读。
# 2. 如果闭包里引用了很大的对象,又长期不释放,可能会增加内存占用。
# 3. 很多场景下,其实用【类+实例属性】会更清晰,闭包不一定是最优解

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)

b1 = Beauty('*',3)
b1.show_msg('你好啊')
b1.show_msg('尚硅谷')

58. 函数装饰器

装饰器:

  1. 装饰器是一种【可调用对象】(通常是函数),它能接收一个函数作为参数,并且会返回一个新函数。
  2. 装饰器可以在不修改原函数代码的前提下,增强或改变原函数的功能。 实际应用: 在不改变原函数的前提下,给函数统一加上: 日志、计时、校验、缓存 等功能

关键点:

  1. 接收被装饰的函数、同时返回新函数(wrapper)
  2. 装饰器"吐出来"的是wrapper函数,以后别人调用的也是wrapper 函数。
  3. 为了保证参数的兼容性,wrapper 函数要通过*args 和 **kwargs 接收参数
  4. wrapper 函数中主要做的是: 调用原函数(被装饰的函数)、执行其他逻辑,但要记得将原函数的返回值 return出去。
python
# 装饰器:
# 1. 装饰器是一种【可调用对象】(通常是函数),它能接收一个函数作为参数,并且会返回一个新函数。
# 2. 装饰器可以在不修改原函数代码的前提下,增强或改变原函数的功能。


# 实际应用: 在不改变原函数的前提下,给函数统一加上: 日志、计时、校验、缓存 等功能

# 关键点:
# 1.接收被装饰的函数、同时返回新函数(wrapper)
# 2.装饰器"吐出来"的是wrapper函数,以后别人调用的也是wrapper 函数。
# 3.为了保证参数的兼容性,wrapper 函数要通过*args 和 **kwargs 接收参数
# 4. wrapper 函数中主要做的是: 调用原函数(被装饰的函数)、执行其他逻辑,但要记得将原函数的返回值 return出去。

 def say_hello(func):
     def wrapper(*args,**kwargs):
         # ***********
         print('****你好啊****')
         return func(*args,**kwargs)
     return wrapper

@say_hello
def add(x,y,z):
    res = x + y + z
    print(f'{x}{y}{z}相加的结果是{res}')
    return res
# 正常调用add函数
 result = add(10,20)
 print(result)

# 需求: 在不修改add函数的前提下,给add函数增加一些额外的功能,例如: 计算前先打印一句欢迎语
# 实现方案: 使用装饰器
 add = say_hello(add)
 result = add(10,20)
 print(result)

# 进阶: 带参数的装饰器

def say_hello(msg):
    def outer(func):
        def wrapper(*args,**kwargs):
            # ***********
            print(f'****你好,我要开始{msg}计算了****')
            return func(*args,**kwargs)
        return wrapper
    return outer
#
 @say_hello('加法')
 def add(x,y,z):
     res = x + y + z
     print(f'{x}{y}{z}相加的结果是{res}')
     return res

 @say_hello('减法')
 def sub(x,y):
     res = x - y
     print(f'{x}{y}相减的结果是{res}')
     return res

# # 正常调用add函数
result1 = add(10,20,30)
print(result1)

result2 = sub(20,10)
print(result2)


# 进阶: 多个装饰器一起使用

def test1(func):
    print('我是test1装饰器')
    def wrapper(*args,**kwargs):
        print('我是test1追加的逻辑')
        return func(*args,**kwargs)
    return wrapper

def test2(func):
    print('我是test2装饰器')
    def wrapper(*args,**kwargs):
        print('我是test2追加的逻辑')
        return func(*args,**kwargs)
    return wrapper

@test1
@test2
def add(x,y):
    res = x + y
    print(f'{x}{y}相加的结果是{res}')
    return res

result = add(10,20)

59. 类装饰器

类装饰器:

  1. 包含__call__方法的类,就是类装饰器
  2. 像调用函数一样,去调用类装饰器的实例对象,就会触发 __call__方法的调用。
  3. __call__方法通常接受一个函数作为参数,并且会返回一个新函数。
python
# 类装饰器:
# 1.包含__call__方法的类,就是类装饰器
# 2. 像调用函数一样,去调用类装饰器的实例对象,就会触发 __call__方法的调用。
# 3. __call__方法通常接受一个函数作为参数,并且会返回一个新函数。

class SayHello:
    def __call__(self,func):
        def wrapper(*args,**kwargs):
            print('你好,我要开始计算了')
            return func(*args,**kwargs)

        return wrapper

@SayHello()
def add(x,y):
    res = x + y
    print(f'{x}{y}相加的结果是{res}')
    return res

# 正常调用add函数
result = add(10,20)
print(result)

# 使用 SayHello去装饰 add函数(手动装饰)
# say = SayHello()
#
# add = say(add)
#
# result = add(10,20)
# print(result)


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(x,y):
    res = x + y
    print(f'{x}{y}相加的结果是{res}')
    return res
result = add(10,20)
print(result)


# 多个类装饰器的使用
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(x, y):
    res = x + y
    print(f'{x}{y}相加的结果是{res}')
    return res

result = add(10,20)
print(result)

60. 变量类型注解

变量类型注解: 给变量加上类型说明,可增强代码的可读性、让IDE的提示更友好

python
# 变量类型注解: 给变量加上类型说明,可增强代码的可读性、让IDE的提示更友好
num:int = 100
price: float = 12.5
message: str = '你好啊'
is_vip:bool = False
result: None = None # 语法上没有问题,但这么些没有意义

# 注意: 可以先写变量的类型注解,以后再赋值
school: str
print('*******')
school = '尚硅谷'

# hobby 是列表,并且列表中的所有元素必须是 str 类型
hobby:list[str] = ['抽烟','喝酒','烫头']
hobby.append('学习')
print(hobby)

# hobby 是列表,并且列表中的元素,可以是: str 或 int 类型
hobby:list[str | int] = ['抽烟','喝酒','烫头']
hobby.append('学习')
hobby.append(100)

# 上面这行代码的就写法如下:
from typing import Union
hobby:list[Union[str,int]] = ['抽烟','喝酒','烫头']

# cities 是集合,并且集合中所有元素必须是 str 类型
cities:set[str] = {'北京','上海','深圳'}

# citys 是集合,并且集合中所有元素可以是: str 或 float 或 bool 类型
citys:set[str | float | bool] = {'北京','上海','深圳'}

# persons 是字典,键是 str 类型,值是int类型
persons: dict[str,int] = {'张三':18,'李四':19,'王五': 20}

# persons 是字典,键是 str 或 int 类型,值是int类型
persons: dict[str | int,int] = {'张三':18,'李四':19,'王五': 20}

# 元组的类型声明有点特殊,各位要留意一下:
# scores 是元组,并且元组中仅包含一个int类型的元素
scores: tuple[int] = (60,)

# scores 是元组,并且元组中包含三个int类型的元素
scores: tuple[int,int,int] = (60,70,80)

# scores 是元组,并且元组中包含任意个数的元素,但每个元素的类型必须是int
scores: tuple[int,...] = (60,70,80)

# scores 是元组,并且元组中包含任意个数的元素,但每个元素的类型是int 或 str

scores: tuple[int | str,...] = (60,"70",80)

# Python 会根据初始赋值推导变量的类型:
# 1. 对于普通变量: 后续如果改变类型,不会警告。
# 2. 对于容器变量: 要求内部元素类型必须与推导出来的一致,否则就会警告。
x = 100
x = '尚硅谷'

y:list[int] = [10,20,30]
# y.append('40')
# y = 100

61. 函数的类型注解

python
# 函数类型注解: 给函数的【参数】和【返回值】添加类型说明。
# 语法格式: 函数名(参数1:类型,参数2:类型) -> 返回值的类型。

# 示例1: 设置参数类型注解、设置返回值类型注解
def add(x:int,y:int) -> int:
    return x + y

result = add(10,20)
print(result)
# 示例2: 参数有默认值(Python可以推导出参数的类型)、设置返回值类型

def add(x=1,y=1) -> int:
    return x + y

result = add(10,20)
print(result)

# 示例3: 设置多个返回值的类型注解
def show_nums_info(nums:list[int]) -> tuple[int,int,float]:
    max_value = max(nums)
    min_value = min(nums)
    result = max_value / min_value
    return max_value,min_value,result

res = show_nums_info([10,20,30,40])
print(res)

# 示例4: 设置*args的类型注解,要求 args中的每个参数都必须是int类型
def add(*args:int) -> int:
    return sum(args)

res = add(10,20,30,40)
print(res)

# 示例5: 设置**kwargs的类型注解,要求 kwargs中的每组参数的值,必须是str或int类型
def show_info(**kwargs: str | int):
    print(kwargs)

show_info(name='张三',age=18,gender='男')

# 获取函数的注解信息
print(add.__annotations__)

62. 错误与异常

错误: 代码本身有语法错误,解释器无法执行代码。---无法通过异常处理机制解决

python
age = 18
if age >= 18
    print('成年人')

异常: 代码在语法上没问题,但执行过程中出现了问题。---可以通过异常处理机制解决

一些开发中常见的异常:

  1. ZeroDivisionError: 当除数为0时触发。
python
# 1. ZeroDivisionError: 当除数为0时触发。
num1 = 100
num2 = 0
result = num1 / num2

2.Typeerror: 当操作的数据类型不正确或不兼容时触发。

python
result = '10' + 5

3.AttributeError: 当对象没有指定的属性或方法时触发。

python
# 演示1
class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age
p1 = Person('张三',18)
print(p1.name)
print(p1.age)
print(p1.gender)

# 演示2
nums = [10,20,30]
nums.add(40)
  1. IndexError: 当索引超出范围(索引越界)时触发
python
nums = [10,20,30,40]
print(nums[5])
  1. NameError: 当使用了不存在的变量时触发
python
print(school)
  1. KeyError: 当访问字典中不存在的key时触发
python
person = {name: '张三',age:18}
print(person.gender)
  1. ValueError: 当值不合法,但类型正确时触发。
python
int('hello')

63. 异常处理

为什么要进行异常处理?

  1. 程序运行过程中出现的异常,如果得不到处理,那程序就会立刻崩溃,导致后续代码无法执行。
  2. 异常处理不是让异常消失,而是将异常捕获到,随后根据异常的具体情况,来执行指定的逻辑。
python
# 为什么要进行异常处理?
# 程序运行过程中出现的异常,如果得不到处理,那程序就会立刻崩溃,导致后续代码无法执行。
# 异常处理不是让异常消失,而是将异常捕获到,随后根据异常的具体情况,来执行指定的逻辑。
print('欢迎使用本程序')
a = int(input('请输入第一个数:'))
b = int(input('请输入第二个数:'))

result = a / b
print(f'{a}除以{b}的结果是: {result}')
print('******我是后续的其他逻辑1******')
print('******我是后续的其他逻辑2******')

# 2.异常处理(初级):
# 1. 将可能出现异常的代码放在 try中,出现异常后的处理代码写在 except中
# 2. 如果try中的代码出现异常,那try中的后续代码将不会执行,并自动跳转到except中处理异常
# 3. 如果 try 中的代码没有异常,那 except 中的代码就不会执行
# 4. 无论是否发生异常,try-except 后面的代码都会继续执行
# 5. 直接写except 会捕获到 Python 中所有的异常 --- 实际开发中不推荐这样做。

print('欢迎使用本程序')
try:
    a = int(input('请输入第一个数:'))
    b = int(input('请输入第二个数:'))
    result = a / b
    print(f'{a}除以{b}的结果是: {result}')
except:
    print('抱歉,程序出现了异常!')
print('******我是后续的其他逻辑1******')
print('******我是后续的其他逻辑2******')

# 3.异常处理(捕获指定的类型的异常):
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******')

# 4.验证一下异常类之间的继承关系
print(issubclass(ZeroDivisionError,ArithmeticError))
print(issubclass(ZeroDivisionError,Exception))
print(issubclass(ValueError,Exception))
print(issubclass(KeyboardInterrupt,Exception))

# 5.多个 except 从上往下匹配,匹配成功后不再向下匹配。
print('欢迎使用本程序')
try:
    a = int(input('请输入第一个数:'))
    b = int(input('请输入第二个数:'))
    print(x)
    result = a / b
    print(f'{a}除以{b}的结果是: {result}')
except ZeroDivisionError:
    print('程序异常: 0不能作为除数!')
except ValueError as e:
    print('程序异常: 您输入的必须是数字')
except Exception 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******')

# 6. 一个except,也可以捕获不同的异常
print('欢迎使用本程序')
try:
    a = int(input('请输入第一个数:'))
    b = int(input('请输入第二个数:'))
    print(x)
    result = a / b
    print(f'{a}除以{b}的结果是: {result}')
except (ZeroDivisionError,ValueError,Exception) as e :
    if isinstance(e,ZeroDivisionError):
        print('程序异常: 0不能作为除数!')
    elif isinstance(e,ValueError):
        print('程序异常: 您输入的必须是数字')
    else:
        print(f'程序异常:{e}')

# except ZeroDivisionError:
#     print('程序异常: 0不能作为除数!')
# except ValueError as e:
#     print('程序异常: 您输入的必须是数字')
# except Exception as e:
#     print(f'程序异常:{e}')

print('******我是后续的其他逻辑1******')
print('******我是后续的其他逻辑2******')

# 7. 异常处理的完整写法:
# 1. try:  尝试去做可能会出现异常的事情
# 1. except:  出现异常时的处理(出现异常时怎么补救)
# 3.else: 如果一切顺利(没有异常出现)要做的事
# 4.finally: finally: 无论有没有异常,都要做的事


print('欢迎使用本程序')
try:
    a = int(input('请输入第一个数:'))
    b = int(input('请输入第二个数:'))
    # print(x)
    result = a / b
    print(f'{a}除以{b}的结果是: {result}')
except (ZeroDivisionError,ValueError,Exception) as e :
    if isinstance(e,ZeroDivisionError):
        print('程序异常: 0不能作为除数!')
    elif isinstance(e,ValueError):
        print('程序异常: 您输入的必须是数字')
    else:
        print(f'程序异常:{e}')

else:
    print('挺好的,try中的代码没有任何异常!')
finally:
    print('无论有没有异常,我的计算都结束了!')

# except ZeroDivisionError:
#     print('程序异常: 0不能作为除数!')
# except ValueError as e:
#     print('程序异常: 您输入的必须是数字')
# except Exception as e:
#     print(f'程序异常:{e}')

print('******我是后续的其他逻辑1******')
print('******我是后续的其他逻辑2******')

64. 手动抛出异常

当程序遇到不符合预期情况时,可以使用 raise 语句手动触发(抛出)异常。

python
print('欢迎使用年龄判断系统')
try:
    age = int(input('请输入你的年龄: '))
    if 18 <= age <= 120:
        print('成年')
    elif 0 <= age < 18:
        print('未成年')
    else:
        # print('输入的年龄有误!(年龄应该为0~120的整数)')
        raise ValueError('年龄应该为0~120的整数')
except Exception as e:
    print(f'程序异常: {e}')

65. 异常的传递机制

  1. 如果异常没有被当前代码块所捕获处理,那该异常就会沿着调用链,逐层传递给其调用者。
  2. 如果所有调用者,都没有捕获该异常,那最终程序将因【未处理异常】而意外终止
python
# 异常的传递机制:
# 1. 如果异常没有被当前代码块所捕获处理,那该异常就会沿着调用链,逐层传递给其调用者。
# 2. 如果所有调用者,都没有捕获该异常,那最终程序将因【未处理异常】而意外终止
def test1():
    print('******test1开始******')
    try:
        result = '100' + 100
    except Exception as e:
        print(f'程序异常: {e}')
    print('******test1结束******')

def test2():
    print('******test2开始******')
    test1()
    print('******test2结束******')

def test3():
    print('******test3开始******')
    test2()
    print('******test3结束******')

test3()

66. 自定义异常

  1. 由开发人员自己定义一个异常类,用来表示代码中"更具体、更有业务含义"的异常。
  2. 具体规则: 定义一个类(类名通常以Error结尾),继承 Exception 类或它的子类。
python
# 自定义异常类:
# 1. 由开发人员自己定义一个异常类,用来表示代码中"更具体、更有业务含义"的异常。
# 2. 具体规则: 定义一个类(类名通常以Error结尾),继承 Exception 类或它的子类。

class SchoolNameError(Exception):
    def __init__(self,msg):
        super().__init__('【校名异常】' + msg)

def check_school_name(name):
    if len(name) > 10:
        raise SchoolNameError('学校名过长')
    else:
        print('学校名是合法的')

try:
    check_school_name('atguiguuuuuuuuuuuuuu')
except SchoolNameError as e:
    print(f'程序异常: {e}')

67. 模块

模块概述:

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

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

模块命名注意点:

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

常见的模块导入方式

  1. import 模块名
  2. import 模块名 as 别名
  3. from 模块名 import 具体内容1,具体内容2,.。。。
  4. from 模块名 import 具体内容1 as 别名1,具体内容2 as 别名2,。。。。。。
  5. from 模块命 import *(不推荐,容易造成命名污染)

关于__all__:

  1. 在Python模块中,可通过__all__来控制【from 模块 import *】能导入哪些内容
  2. __all__的值可以是: 列表、元组。

关于__name__:

  1. __name__是每个Python模块(.py文件) 都拥有的一个内置变量。
  2. 它的具体值取决于模块的运行方式:
  3. 作为主程序直接运行,name__的值是__main
  4. 作为模块被导入到其他程序中运行,__name__的值是模块的文件名(不带.py)

68.标准库模块

  1. Python中的模块分为三类,分别是: 标准库模块、自定义模块、第三方模块
  2. 标准库模块:随着 Python 一起安装在我们电脑上的哪些模块
  3. 有一些标准库模块是用c语言实现的,这些用c语言实现的模块,又称: 【内置模块】。

69.包

包概述:

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

包与模块的关系:

  1. 一个模块就是一个.py文件,包是用来"管理模块"的目录(文件夹)
  2. 一个包中可以有多个模块,也可以有多个子包。

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

包命名注意点:

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

包的多种导入方式

  1. import 包名.模块名
python
import trade.order
import trade.pay
trade.order.create_order()
trade.pay.wechat_pay()
  1. import 包名.模块名 as 别名
python
import trade.order as dd
import trade.pay as zf
dd.create_order()
zf.wechat_pay()
  1. from 包名.模块名 import 具体内容1,具体内容2,。。。。。
python
from trade.order import max_order_amount,create_order
from trade.pay import timeout,wechat_pay

print(max_order_amount)
print(timeout)
create_order()
wechat_pay()
  1. from 包名.模块名 import 具体内容1 as 别名1,具体内容2 as 别名2,。。。。。
python
from trade.order import max_order_amount,create_order
from trade.pay import timeout,wechat_pay as w_pay

print(max_order_amount)
print(timeout)
create_order()
w_pay()
  1. from 包名.模块名 import *
python
from trade.order import *
from trade.pay import *

print(max_order_amount)
create_order()
cancel_order()
show_info()

print(timeout)
wechat_pay()
ali_pay()
show_info()
  1. from 包名 import 模块名
python
from trade import order,pay

order.create_order()
order.cancel_order()
pay.wechat_pay()
pay.ali_pay()
  1. from 包名 import 模块名 as 别名
python
from trade import order as dd,pay as p

dd.create_order()
dd.cancel_order()
p.wechat_pay()
p.ali_pay()

# 关于 __init__.py文件:
# 1. __init__.py是包的初始化文件,在包被导入时,__init__.py会被自动调用
# 2. __init__.py是可以编写一些包的初始化逻辑
# 3. __init__.py中所定义的内容,会被【from 包名 import *】形式全部引入
# 4. __init__.py中也可以使用__all__ 来控制包中的哪些模块可以被【from 包名 import *】 引入
  1. from 包名 import *
python
from trade import *

print(a)
print(b)
print(order.max_order_amount)
  1. import 包名
python
import trade

print(trade.a)
print(trade.b)

70.常用的pip命令

alt text

71.迭代器

知识点1: 能被 for 循环遍历的对象,被称为: 可迭代对象(iterable)

python
names = ['张三','李四','王五']
citys = ('北京','上海','深圳')
msg = 'hello'
age = 10

def test():
    pass

for item in citys:
    print(item)

知识点2: 可迭代对象(iterable)能调用到 __iter__方法。

python
names = ['张三','李四','王五']
citys = ('北京','上海','深圳')
msg = 'hello'
age = 10
def test():
    pass
names.__iter__()
citys.__iter__()
msg.__iter__()
print(hasattr(names,'__iter__'))
print(hasattr(citys,'__iter__'))
print(hasattr(msg,'__iter__'))
print(hasattr(age,'__iter__'))
print(hasattr(test,'__iter__'))

知识点3: 调用__iter__方法会得到: 迭代器(iterator)

备注1: __iter__是一个魔法方法,当调用iter函数时,__iter__会自动调用。
备注2: 可迭代对象.iter() 等价于 iter(可迭代对象)。
备注3: 如果 iter(obj)能得到一个迭代器(iterator),那obj就是可迭代对象。

python
names = ['张三','李四','王五']
citys = ('北京','上海','深圳')
msg = 'hello'

print(names.__iter__())
print(citys.__iter__())
print(msg.__iter__())

print(iter(names))
print(iter(citys))
print(iter(msg))

知识点4: 迭代器(iterator)拥有__next__方法,每次调用都会根据当前的状态,返回下一个元素

备注1: 迭代器.next() 等价于 next(迭代器)
备注2: 当所有元素全部取出后,若继续调用 next 方法,Python会抛出 StopIteration 异常。

python

names = ['张三','李四','王五']
it = iter(names)
print(it.__next__())
print(it.__next__())
print(it.__next__())

print(next(it))
print(next(it))
print(next(it))

#for 循环背后的工作逻辑
names = ['张三','李四','王五']
for item in names:
    print(item)


#调用可迭代对象的__iter__方法 获取到一个迭代器
it = iter(names)
#开启一个无限循环
while True:
    try:
        # 调用__next__方法,获取下一个元素
        item = next(it)
        print(item)
    except StopIteration:
        # 捕获 StopIteration异常,随后结束循环
        break

知识点5: 迭代器(iterator)也拥有 __iter__方法,并且其返回值是迭代器自身。
这么设计的原因如下: 让for循环也能遍历迭代器(即: 为了让iter(迭代器)不出错)

python
names = ['张三','李四','王五']

it = iter(names)
print(it)
print(iter(it))
print(it == iter(it))
print(iter(iter(it)))

for item in it:
    print(item)

知识点6: 迭代器协议

  1. 能被iter()接受
  2. 能被next()一步一步取值

手写迭代器

python
# 迭代器是一次性的,状态只会向前推进,且不会自动重置(迭代器在遍历的过程中会被消耗)
# region
# names = ['张三','李四','王五']
# it1 = iter(names)
# it2 = iter(names)
#
# print(next(it1))
# print(next(it1))
# print(next(it1))
# print(next(it2))
# print(next(it2))
# print(next(it2))

# for item in it1:
#     print(item)
#
# for item in it2:
#     print(item)
# endregion

# 需求: 让for循环可以遍历Person的实例对象
# 实现方式一:
# region
# class Person:
#     def __init__(self,name,age,gender,address):
#         self.name = name
#         self.age = age
#         self.gender = gender
#         self.address = address
#
#     def __iter__(self):
#         print(self)
#         return PersonIterator(self)
#
# class PersonIterator:
#     def __init__(self,p):
#         # 将外部传进来的数据保存好
#         self.p = p
#         # 设置迭代器的初始化状态(指针的位置)
#         self.index = 0
#         # 配置好要遍历的内容
#         self.attrs = [p.name,p.age,p.gender,p.address]
#     def __iter__(self):
#         return self
#     # 每次调用__next__方法,会根据当前的状态,返回下一个元素
#     def __next__(self):
#         # 如果指针的位置超出范围,那就抛出StopIteration异常
#         if self.index >= len(self.attrs):
#             raise StopIteration
#         # 获取要返回的内容
#         value = self.attrs[self.index]
#         # 更新迭代器的状态
#         self.index += 1
#         return value
# #
# # # 目标:
# p1 = Person('张三',18,'男','北京昌平')
# it = iter(p1)
# for item in it:
#     print(item)
#
# for item in it:
#     print(item)

# endregion
# 实现方式二:
# region
# class Person:
#     def __init__(self,name,age,gender,address):
#         self.name = name
#         self.age = age
#         self.gender = gender
#         self.address = address
#         # 设置迭代器的初始化状态(指针位置)
#         self.__index = 0
#         # 配置好要遍历的内容
#         self.__attrs = [name,age,gender,address]
#
#
#     def __iter__(self):
#         # self.__index = 0
#         return self
#
#     def __next__(self):
#         if self.__index >= len(self.__attrs):
#             raise StopIteration
#         value = self.__attrs[self.__index]
#         self.__index += 1
#         return value
#
# # 目标
# # 下面的p1即是可迭代对象,又是迭代器
# p1 = Person('张三',18,'男','北京昌平')
# for item in p1:
#     print(item)
# endregion



# 进阶: 迭代器玩的就是__next__
from cn2an import an2cn
class Person:
    def __init__(self,name,age,gender,address):
        self.name = name
        self.age = age
        self.gender = gender
        self.address = address
        # 设置迭代器的初始化状态(指针位置)
        self.__index = 0
        # 配置好要遍历的内容
        self.__attrs = [name,age,gender,address]


    def __iter__(self):
        # self.__index = 0
        return self

    def __next__(self):
        if self.__index >= len(self.__attrs):
            raise StopIteration
        value = self.__attrs[self.__index]
        # 将字符串转为大写
        if isinstance(value,str):
            value = value.upper()
        # 将数字转为汉语
        if isinstance(value,int):
            value = an2cn(value)
        self.__index += 1
        return value
#
# # 目标
# # 下面的p1即是可迭代对象,又是迭代器
p1 = Person('zhangsan',18,'男','北京昌平')
for item in p1:
    print(item)

迭代器的优势

python
import tracemalloc
import sys


# 使用迭代器实现(你的版本)
class Fibo:
    def __init__(self, total):
        self.total = total
        self.index = 0
        self.pre = 1
        self.cur = 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.pre + self.cur
            self.pre = self.cur
            self.cur = value
        self.index += 1
        return value


# 不用迭代器实现
def fibo_list(total):
    if total <= 0:
        return []
    if total == 1:
        return [1]
    nums = [1, 1]
    for i in range(2, total):
        nums.append(nums[-1] + nums[-2])
    return nums


# 测试迭代器版本的内存
print("=== 测试迭代器版本 ===")
tracemalloc.start()
f_iter = Fibo(10000)  # 只创建对象,内存占用很小
# 真正遍历生成所有数据
for item in f_iter:
    pass
m1 = tracemalloc.get_traced_memory()
print(f'迭代器峰值内存: {m1[1] / 1024 / 1024} MB')
tracemalloc.stop()

# 测试列表版本的内存
print("\n=== 测试列表版本 ===")
tracemalloc.start()
f_list = fibo_list(10000)  # 一次性生成所有数据并存入列表
m2 = tracemalloc.get_traced_memory()
print(f'列表峰值内存: {m2[1] / 1024 / 1024:.2f} MB')
tracemalloc.stop()

72.生成器

生成器:

  1. 生成器函数: 函数体中如果出现了 yield 关键字, 那该函数是【生成器函数】。
  2. 生成器对象: 调用【生成器函数】时,其函数体不会立刻执行,而是返回一个【生成器对象】。
    备注: 不管能否执行到 yield 所在的位置,只要函数中有yield关键字,那该函数,就是【生成器函数】。
python
def demo():
    print('demo函数开始执行了')
    print(100)
    yield
    a = 200
    print(a)


a = demo()

print(a)

下载【生成器函数】中的代码,需要通过【生成器对象】来执行:

  1. 调用【生成器对象】的 next 方法,会让【生成器函数】中的代码开始执行。
  2. 当【生成器函数】中的代码开始执行后,遇到yield 会"暂停"执行,并且其内部会记录"暂停"的位置。
  3. 后续调用 next 方法,都会从上一次暂停的位置,继续运行,直到再次遇到yield
  4. 遇到 return 会抛出 StopIteration 异常,并将 return 后面的表达式,作为异常信息
  5. yield 后面所写的表达式,会作为本次 next 方法的返回值。
python
def demo():
    print('demo函数开始执行了')
    print(100)
    yield '我是第一个yield所返回的数据'
    a = 200
    print(a)
    yield '我是第二个yield所返回的数据'
    b = 300
    print(b)
    return '尚硅谷'

d = demo()

result = next(d)
print(result)
result2 = next(d)
print(result2)

try:
    next(d)
except StopIteration as e:
    print(e)
print(d)
next(d)
next(d)
next(d)

生成器对象是一种特殊的迭代器(本质是通过 yield 自动实现了迭代器协议)

python
def demo():
    print('demo函数开始执行了')
    print(100)
    yield '我是第一个yield所返回的数据'
    a = 200
    print(a)
    yield '我是第二个yield所返回的数据'
    b = 300
    print(b)
    return '尚硅谷'

d = demo()

result = next(d)
print(result)
result2 = next(d)
print(result2)

try:
    next(d)
except StopIteration as e:
    print(e)
print(d)
next(d)
next(d)
next(d)

endregion
3.生成器对象是一种特殊的迭代器(本质是通过 yield 自动实现了迭代器协议)
region
def demo():
    print('demo函数开始执行了')
    print(100)
    yield '我是第一个yield所返回的数据'
    a = 200
    print(a)
    yield '我是第二个yield所返回的数据'
    b = 300
    print(b)
    return '尚硅谷'

d = demo()

#验证生成器对象d,和迭代器一样,也拥有: __iter__ 和 __next__ 方法
print(hasattr(d,"__iter__"))
print(hasattr(d,"__next__"))

#验证生成器对象的__iter__方法,和迭代器一样,返回的也是自身
result = iter(d)
print(result == d)

#for循环遍历
for item in d:
    print(item)

#for循环背后的逻辑
gen = iter(d)
while True:
    try:
        value = next(gen)
        print(value)
    except StopIteration:
        break

yield 也能写在循环里

python
def create_car(total):
    for index in range(total):
        yield f'我是第{index}台车'

cars = create_car(5)

# 调用一次cars的__next__方法,就会得到一台车
c1 = next(cars)
print(c1)
c2 = next(cars)
print(c2)
c3 = next(cars)
print(c3)
c4 = next(cars)
print(c4)
c5 = next(cars)
print(c5)

for car in cars:
    print(car)

yield from 能把一个【可迭代对象】里的东西依次 yield 出去。(替代: for + yield)

python
def demo():
    nums = [10,20,30,40]
    # for n in nums:
    #     yield n
    yield from nums

d = demo()
r1 = next(d)
print(r1)
r2 = next(d)
print(r2)
r3 = next(d)
print(r3)
r4 = next(d)
print(r4)

for item in d:
    print(item)

使用: 生成器.send(值)可以让生成器继续执行的同时,给上一次 yield 传值

备注1: next只能取值,send 既能取值,也能送值
备注2: 第一次启动生成器,不能传值!

python
def demo():
    print('demo函数开始执行了')
    print(100)
    a = yield '我是第一个yield所返回的数据'
    print(a)
    b = yield '我是第二个yield所返回的数据'
    print(b)
    return '尚硅谷'
d = demo()
r1 = d.send(None)
print(r1)
r2 = d.send(666)
print(r2)
try:
    d.send(888)
except StopIteration as e:
    print(e)

用生成器遍历Person类的实例对象

python
class Person:
    def __init__(self,name,age,gender,address):
        self.name = name
        self.age = age
        self.gender = gender
        self.address = address
        self.__atter = [name,age,gender,address]
    def __iter__(self):
        # yield self.name
        # yield self.age
        # yield self.gender
        # yield self.address
        yield from self.__atter
p1 = Person('张三',18,'男','北京昌平')
# 目标:
for attr in p1:
    print(attr)

用生成器实现斐波那契数列

python
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)
# 无论是迭代器,还是生成器对象,都可以用list、tuple、set等直接拿到其里面的所有内容(注意: 容易挤爆内存)

生成器表达式: 一种类似列表推导式的语法,快速创建生成器对象的方法。
语法格式: (表达式 for 变量 in 可迭代对象)
什么时候适合用生成器表达式? --- 当“每个结果”,只依赖当前这一个元素时。

python
nums = [10,20,30,40]

# 列表推导式
result = [n * 2 for n in nums]
print(result)

# 生成器表达式
result2 = (n * 2 for n in nums)
print(result2)
# for item in result2:
#     print(item)

73.文件相关操作

1. 前序知识

文件的分类: 1.纯文本文件、2.二进制文件

纯文本文件:

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

二进制文件

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

绝对路径 vs 相对路径:

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

2. 读取文件

Python操作文件的标准流程:

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

文件操作的核心 --- open函数: 它可以打开或创建文件,且支持多种操作模式,返回值时【文件对象】。
open 函数最常用的三个参数如下:

  1. file: 要操作的文件路径
  2. mode: 文件的打开模式
    r: 读取(默认值)
    w: 写入,并先截断文件
    x: 排他性创建,如果文件已存在,则创建失败
    a: 打开文件用于写入,如果文件存在,则在文件末尾追加内容
    b: 二进制模式
    t: 文本模式(默认值)
    +: 打开用于更新(读取与写入)
  3. encoding: 字符编码
读取操作1: 使用【文件对象】的read 方法,读取文件中的内容。

#read 方法说明:

  1. read(size) 中的size是可选参数。
    若不传递 size 参数,表示: 读取文件中所有的内容(注意内存占用)
    若传递了 size 参数,表示: 读取文件中指定个数的字符,或指定大小的字节。
  2. read 会从上一次 read 的位置继续读取(指针思想), 若到达文件末尾后继续读取,将返回空字符串。
python
#read 方法说明:
#    1.read(size) 中的size是可选参数。
#        若不传递 size 参数,表示: 读取文件中所有的内容(注意内存占用)
#        若传递了 size 参数,表示: 读取文件中指定个数的字符,或指定大小的字节。
#    2.read 会从上一次 read 的位置继续读取(指针思想), 若到达文件末尾后继续读取,将返回空字符串。
# 第一步.创建【文件对象】
file = open(file='a.txt',mode='rt',encoding='utf-8')
file = open('a.txt','rt',encoding='utf-8')
file = open('a.txt','rt',encoding='utf-8')
# 第二步.操作文件(读取)
# 多次调用read去逐步读取文件
result = file.read(2)
result2 = file.read(2)
result3 = file.read(2)
print(repr(result))
print(repr(result2))
print(result)
print(result2)
print(result3)

# 用循环配合多次read(对内存友好)
while True:
    result = file.read(2)
    if(result == ''):
        break
    print(result,end='')


# 第三步.关闭文件
file.close()
读取操作2: 使用【文件对象】的read 方法,读取文件中的内容。

readline 方法说明:

  1. readline(size) 中的size是可选参数。
    若不传递 size 参数,表示: 读取文当前这一行所有的内容
    若传递了 size 参数,表示: 读取当前行时,最多能读取的字符数,或字节数(size不是行数)。
  2. readline 会从上一次 readline 的位置继续读取(指针思想), 若到达文件末尾后继续读取,将返回空字符串。
python
# 第一步.创建【文件对象】
file = open('a.txt','rt',encoding='utf-8')

# 第二步.操作文件(读取)
r1 = file.readline()
r2 = file.readline()
r3 = file.readline()
r4 = file.readline()
print(r1.strip())
print(r2.strip())
print(r3.strip())
print(r4.strip())
# 第三步.关闭文件
file.close()
读取操作3: 使用 for 循环直接遍历文件对象
python
# 第一步.创建【文件对象】
file = open(file='a.txt',mode='rt',encoding='utf-8')
# 第二步.操作文件(读取)
for line in file:
    print(line,end='')
# 第三步.关闭文件
file.close()
读取操作4: 使用文件对象的 readlines 方法,一次性按行读完,返回一个列表

readlines 方法说明:

  1. readlines(hint) 中的hint是可选参数。
    若不传递 hint 参数,表示: 读取当前文件的所有行
    若传递了 hint 参数,表示: 期望读取的【字符个数 或 字节数的上限】(hint不是行数)
  2. 注意: readlines 是一次性读取文件的所有内容,所以不适合读取体积较大的文件。
python
# 第一步.创建【文件对象】
file = open(file='a.txt',mode='rt',encoding='utf-8')
# 第二步.操作文件(读取)
result = file.readlines()
print(result)
# # 第三步.关闭文件
file.close()
最佳实践: 使用with 上下文管理器,结合for循环遍历,逐行读取文件。
python
with open('a.txt','rt',encoding='utf-8') as file:
    for line in file:
        print(line,end='')

3. 关于with

  1. Python 中的 with 主要用于管理程序中"需要成对出现的操作",例如:
    上锁 / 解锁
    打开 / 关闭
    借用 / 归还

  2. 最终目标: 编码者只管做具体的事,"进入"和"离开"的事,让Python自动处理

  3. 语法格式如下:
    with 能得到一个上下文管理器的表达式 as 变量:
    具体的事1
    具体的事2
    具体的事3

  4. 上下文管理器协议:
    (1). __enter__方法: with 中的代码执行【之前】调用,其返回值会赋值给 as 后的变量
    (2). __exit__方法: with 中的代码执行【结束后】调用(无论是with中否出现异常都会调用)

python
# 定义一个 Person 类,让其实例对象遵循: 上下文管理器协议
class Person:
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def speak(self):
        print(f'我叫{self.name},年龄是{self.age}')

    def __enter__(self):
        print('-------我是进入的逻辑-------')
        return self

    # 当 with 中的代码发生异常时,__exit__方法的返回值规则如下:
    #       返回真: 表示异常【已经】被处理,异常【不会】被继续抛出。
    #       返回假: 表示异常【没有】被处理,异常【会】被继续抛出。

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('-------我是离开的逻辑-------')
        #   exc_type 异常类型
        #   exc_val  异常对象
        #   exc_tb   异常追踪信息
        if exc_type:
            print(f'异常类型:{exc_type}')
            print(f'异常对象:{exc_val}')
            print(f'异常追踪信息:{exc_tb}')
        return True

# p1 = Person('张三',18)
# p1.speak()

# 1.计算with后面的表达式,得到一个【上下文管理器】
# 2.调用【上下文管理器】的 __enter__方法,并将其返回值赋给 as 后面的变量
# 3. 执行with 所管理的代码
# 4.无论with中的代码,是正常结束,还是发生异常,都会自动调用【上下文管理器】的__exit__方法


with Person('张三',18) as p1:
    p1.speak()
    # p1.study()
    print(666)

4. 文件写入

在 Python 中文件写入时,并不是每写一次就立刻落盘,而是: 先写到“缓冲区”里。

seek(offset,whence)方法: 用于改变文件对象指针的位置,参数说明如下:
offset: 偏移量,要移动多少距离
whence: 参考点,从哪里开始计算偏移,有三种取值:
0: 从文件开头计算(默认值)
1: 从当前位置计算
2: 从文件末尾计算
注意: 在文本模式下,不要随意去定义中文字符位置,否则可能破坏文件编码。

python
# open 函数最常用的三个参数如下:
#   1.file: 要操作的文件路径
#   2.mode: 文件的打开模式
#       r: 读取(默认值)
#       w: 写入,并先截断文件
#       x: 排他性创建,如果文件已存在,则创建失败
#       a: 打开文件用于写入,如果文件存在,则在文件末尾追加内容
#       b: 二进制模式
#       t: 文本模式(默认值)
#       +: 打开用于更新(读取与写入)

# 测试 w 模式
with open('b.txt','wt',encoding='utf-8') as file:
    file.write('你好')

# 测试 x 模式
with open('demo.txt', 'xt', encoding='utf-8') as file:
    file.write('你好')

# 测试 a 模式
with open('demo.txt', 'at', encoding='utf-8') as file:
    file.write('你好')


# 在 Python 中文件写入时,并不是每写一次就立刻落盘,而是: 先写到“缓冲区”里。
with open('demo.txt','rt+',encoding='utf-8') as file:
    file.seek(0,2)
    file.write('你好1')

# 测试rt+
with open('demo.txt','rt+',encoding='utf-8') as file:
    # seek(offset,whence)方法: 用于改变文件对象指针的位置,参数说明如下:
    #   offset: 偏移量,要移动多少距离
    #   whence: 参考点,从哪里开始计算偏移,有三种取值:
    #       0: 从文件开头计算(默认值)
    #       1: 从当前位置计算
    #       2: 从文件末尾计算
    #   注意: 在文本模式下,不要随意去定义中文字符位置,否则可能破坏文件编码。


    file.seek(0,2)
    file.write('你好1')

# 测试wt+
with open('demo.txt','wt+',encoding='utf-8') as file:
    file.write('Python,你好啊')
    file.seek(0)
    result = file.read()
    print(result)

# 测试xt+
with open('demo.txt','xt+',encoding='utf-8') as file:
    file.write('Python,你好啊')
    file.seek(0)
    result = file.read()
    print(result)

# 测试at+
with open('demo.txt','at+',encoding='utf-8') as file:
    file.write('Python,你好啊')
    file.seek(0)
    result = file.read()
    print(result)

5. 目录操作

  1. os.mkdir(path): 创建"单级"目录(如果目录已经存在,则会排除异常)
python
import os
import shutil

os.mkdir('demo/a')
  1. os.makedirs(path): 创建多级目录(如果路径中的所有目录都已经存在,则会抛出异常)
python
os.makedirs("demo/aa/bb")
  1. os.rmdir(path): 删除空目录(如果目录不存在,或目录非空,都会抛出异常)
python
os.rmdir('demo')
  1. os.removedirs(path): 递归删除空目录,在成功删除末尾一级目录后,会"向上"尝试把父级目录也删除(直到父目录不是空目录)
python
os.removedirs("demo")
  1. os.path.exists(path): 判断路径是否存在(文件/目录都算)
python

result = os.path.exists('demo/aa/bb')
print(result)
  1. os.path.isdir(path): 用于判断路径,具体规则如下:
    1.路径不存在 ========> 返回False
    2.路径存在,但指向的是文件 ========> 返回False
    3.路径存在,并且是目录 ========> 返回True
python
result = os.path.isdir('demo/aa/bb')
print(result)
  1. os.path.isfile(path): 判断是否为文件:
python
result = os.path.isfile('demo/aa/bb')
print(result)
  1. os.scandir(path): 扫描指定目录:
python
result = os.scandir('demo/aa')

for item in result:
    print(item)
    print(item.name)
    print('目录' if item.is_dir() else '文件',item.name)
  1. os.walk(path): 按层级,递归地遍历指定目录下,所有的子目录和文件
python
result = os.walk('demo/aa')
for item in result:
    print(item)

【危险操作】: 删除有内容的目录

python
shutil.rmtree('demo')

6. 小练习

练习1: 将一个二进制文件复制到指定位置

python
# 源文件
import os.path
import time

# source = 'demo.txt'
# # 目标目录
# target = 'D:/media'
#
# # 如果目标目录不存在,那就去创建
# if not os.path.isdir(target):
#     os.makedirs(target)
#
# with open(source,'rt',encoding='utf-8') as f1,open(target + '/' + 'demo.txt','wt',encoding='utf-8') as f2:
#     while True:
#         # 每次只读取1KB
#         data = f1.read(1024)
#         # 如果文件读取完毕了,就跳出循环
#         if not data:
#             break
#         # 向目标文件中写入数据
#         f2.write(data)

练习2: 日志记录

  1. 用户输入用户名和密码后,程序进行校验:
  2. 用户名不存在,提示"用户名未注册",并记录日志
  3. 用户名存在,但密码错误,提示“密码错误”,并记录日志
  4. 用户名和密码均正确,提示"登录成功",并记录日志。
python
users = {
    '张三': '123456',
    '李四': '888888',
    '王五': 'abc123',
}

# 提示输入信息

username =  input('请输入用户名:')
password =  input('请输入密码:')

# 获取当前的时间
now = time.strftime('%Y-%m-%d %H:%M:%S')

if username not in users:
    print('用户名未注册')
    with open('log.txt','at',encoding='utf-8') as file:
        file.write(f'{now}  {username}  登录失败(用户未注册)\n')
elif users[username] != password:
    print('密码不正确!')
    with open('log.txt','at',encoding='utf-8') as file:
        file.write(f'{now}  {username}  密码错误\n')

else:
    print('登录成功')
    with open('log.txt','at',encoding='utf-8') as file:
        file.write(f'{now}  {username}  登录成功\n')

74.并发_并行_同步_异步_进程_线程

1. 一些概念

一、并发 vs 并行:
1.并发:
概念: 在一段时间内,当CPU面对多个任务时,会将每个任务交替着执行一段时间。
特点:
(1).对于某个瞬间,CPU实际上只在执行一个任务
(2).CPU通过高频切换不同的任务,让每个任务都能得到推进,仿佛多个任务在"同时执行"
2.并行:
概念: 并行依赖于多个CPU,或多核心的CPU,在同一时刻,每个核心都在执行不同的任务
特点:
(1).对于某个瞬间,每个CPU(或每个核心)都在执行不同的任务
(2).通过多个CPU(或多个核心)同时工作的方式,让多个任务真的在同时执行。

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

python
import os
print(os.cpu_count())

二、同步 vs 异步
1.同步(sync):
概念: 发起一个任务之后,需要等该任务完成后,才能继续执行后续任务。
表现: 当前执行流会被【阻塞】
2.异步(async):
概念: 发起一个任务之后,不必等该任务完成,就可以继续执行其他任务。
备注: 虽然不必等待任务完成,但任务完成后,仍然可以通过特定方式获取结果。
表现: 当前执行流不会被【阻塞】

三、注意区分概念:
并发 / 并行: 描述的是任务如何被执行,即: 多个任务在执行时,CPU要如何处理
同步 / 异步: 描述的是任务如何被组织和等待,即: 要不要等当前发起的任务执行完,再进行下一个任务。
注意点: CPU 的核心数和执行速度,不会改变任务之间的逻辑依赖关系!例如:
一旦任务1、任务2、任务3之间被设计为同步关系,那么: 即便 CPU 切换任务的速度再快,核心数量再多,也不会在【任务1】没完成的情况下去启动【任务2】

四、进程与线程
1.进程:
(1).一个正在运行的程序或软件,背后就对应着一个或多个进程。
(2).进程是操作系统进行资源分配的基本单位。
(1).每个进程都有自己独立的一块内存空间。

2.线程:  
   (1).线程是进程内部的执行单元(一个进程中可以有多个线程)  
   (2).线程是操作系统进行CPU调度的基本单位  
   (3).同一进程内的线程共享进程资源。

2. 使用Process创建进程

python
# 定义一个 speak 函数,功能是: 每隔一秒说话一次(一共说话10次)
import os
import time
from multiprocessing import Process

def speak():
    for index in range(1,11):
        print(f'我在说话{index},进程pid是:{os.getpid()}')
        time.sleep(1)

# 定义一个 study 函数,功能是: 每隔一秒学习一次(一共学习15次)
def study():
    for index in range(1,16):
        print(f'我在学习{index},进程pid是:{os.getpid()}')
        time.sleep(1)


# speak()
# study()

# 注意: 一定要写 if __name__ == '__main__' 这个判断,原因如下:
#   1.当创建子进程时,Python 并不会把父进程内存里的speak函数直接交给子进程
#   2.Python会启动一个全新的 Python解释器进程,重新执行当前的 .py 文件(作为模块)
#   3.在执行过程中,重新定义出一个 speak 函数,交给子进程。


if __name__ == '__main__':
# 创建两个 Process 类的实例对象(进程对象),分别是p1和p2
# 注意点1: p1 和 p2 就对应着以后的两个子进程,在创建它们的时候,就要指定好他们要执行的任务。
# 注意点2: 此时的 p1 和 p2 只是代码层面的两个进程对象,操作系统还没有真的创建 p1 和 p2 两个进程。

    p1 = Process(target=speak)
    p2 = Process(target=study)

    # 调用进程对象的 start 方法,会立刻向操作系统申请一个进程,并且会将该进程交由操作系统进行调度。
    p1.start()
    p2.start()