恋恋风辰的个人博客


  • Home

  • Archives

  • Categories

  • Tags

  • Search

装饰器和闭包

Posted on 2026-06-25 | In python , AI

一、全局变量和局部变量

作用域

作用域就是代码生效的范围

在Python代码中,作用域分为两种情况:全局作用域 与 局部作用域

变量的作用域

全局作用域:在函数外面的作用域就是全局作用域

局部作用域:在函数内的作用域就是局部作用域

在全局作用域定义的变量 => 全局变量

在局部作用域定义的变量 => 局部变量

全局变量与局部变量的访问范围

局部作用域可以访问全局变量,且可通过global修改全局变量

在函数局部作用域中,默认可以读取全局变量;如果需要修改全局变量,需要使用 global 关键字声明。

python中赋值操作可被解读成两种情况
num1 = 100

  1. 被当作赋值,修改原变量的值
  2. 被当作变量定义,定义一个新的变量
    这就是python赋值的二义性
    不像是C++
    int num1 = 200; //这是定义
    num1 = 500; //这是修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
'''
演示变量的作用域
'''

#全局变量num1
num1 = 100

#定义函数
def func_read():
# 访问全局变量
print(f"num1 is {num1}")

'''
python中赋值操作可被解读成两种情况
num1 = 100
1. 被当作赋值,修改原变量的值
2. 被当作变量定义,定义一个新的变量
这就是python赋值的二义性
不像是C++
int num1 = 200; //这是定义
num1 = 500; //这是修改
'''

# 在局部作用域修改全局变量
def func_write():
# 定义一个局部变量,解释器优先查看局部作用域内是否有num1,
# 如果没有则将这个赋值理解为定义
num1 = 200
num2 = 500
print(f'in func_write num1 is {num1}')

# 在局部作用域中修改全局变量
def func_change():
# 声明num1为全局变量
global num1
# 修改num1的值为200
num1 = 200
print(f'in func_change num1 is {num1}')

if __name__ == '__main__':
func_read()
print('😭*20')
func_write()
# 打印全局变量
print(f'outer func num1 is {num1}')
# 在全局作用域中无法访问局部变量num2
# print(f'num2 is {num2}')
print(f'😭'*20)
func_change()
print(f'outer func num1 is {num1}')

全局作用域无法访问局部变量

函数内部定义的局部变量,仅能在函数局部作用域中使用,函数执行结束后会被回收,全局作用域无法读取。

1
2
3
4
5
6
7
8
9
10
11
12
'''
演示全局无法访问局部变量
'''
def func():
# 局部变量,仅函数内部有效
num2 = 200
print(f'函数内部访问局部变量num2 = {num2}')

func()
# 下方代码会报错 NameError: name 'num2' is not defined
# print(num2)

全局无法访问局部变量的底层原因

主要原因在于,在Python的底层存在“垃圾回收机制”,用于自动回收闲置内存空间,提升程序运行效率。函数执行结束后,函数内部定义的所有局部变量都会被系统自动回收释放,因此全局作用域无法访问已销毁的局部变量。

Python变量查找LEGB规则(核心底层)

Python访问变量时,会严格按照固定层级逐层查找,找不到则抛出 NameError,也是闭包、变量作用域的底层原理:

  • L(Local)局部作用域:当前函数内部变量

  • E(Enclosing)嵌套作用域:外层嵌套函数的局部变量(闭包专属层级)

  • G(Global)全局作用域:文件顶层定义的全局变量

  • B(Built-in)内置作用域:Python系统内置变量/函数(print、len等)

global 与 nonlocal 关键字对比

关键字 作用范围 使用场景
global 全局作用域(G层) 函数内部修改全局变量,无法操作嵌套函数变量
nonlocal 嵌套局部作用域(E层) 闭包内部修改外层函数变量,无法操作全局变量

二、闭包

闭包定义

在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,这个引用外层变量的内部函数即为闭包。

闭包的构成条件(三步走)

  1. 有嵌套:存在外层函数和内层函数的嵌套结构

  2. 有引用:内层函数调用外层函数的局部变量/参数

  3. 有返回:外层函数将内层函数作为返回值返回

基础闭包代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
'''
闭包基础演示
条件:
1. 函数嵌套
2. 内部函数使用外部函数的变量
3. 外部函数返回内部函数
'''
# 定义外部函数
def outer_func(num1):
# 定义内部函数
def inner_func(num2):
# 使用外部函数的变量num1
num = num1+num2
print('现在的值:', num)
# 返回内部函数
return inner_func

# 调用外部函数, 返回内层函数的内存地址
# fn 等价于 inner_func,常驻内存
fn = outer_func(10)
# 调用闭包函数,等价于 inner_func(1)
fn(1)

image-20260626105417850

闭包核心作用

正常函数执行结束后,内部局部变量会被垃圾回收机制销毁。而闭包可以常驻外层函数的局部变量,实现全局作用域中间接访问、持续使用已结束函数的局部变量。

闭包注意事项

由于闭包会持续引用外部函数的变量,导致外层变量无法被垃圾回收,会长期占用内存,大量使用闭包可能造成内存资源消耗。(内存逃逸)

闭包中修改外层变量(nonlocal关键字)

变量赋值二义性问题

Python中 = 赋值存在二义性:左侧变量会被优先判定为本地局部变量。如果闭包内层函数直接修改外层变量,会因找不到本地变量报错,此时需要 nonlocal 声明外层变量。

标准修改案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
'''
演示nonlocal关键字的用法
'''

def outer_func():
num1 = 10
def inner_func():
# 赋值优先被当作定义
num1 = 20
print(f'inner func num1 is {num1}')
inner_func()
print(f'outer func num1 is {num1}')
return inner_func

def outer_func2():
num1 = 10
def inner_func():
# 可以声明nonlocal,表示不是本地变量
# 通知解释器去外层查找num1
nonlocal num1
# 修改的是outer_func2内部的局部变量num1
num1 = 20
print(f'inner func num1 is {num1}')
inner_func()
print(f'outer func num1 is {num1}')
return inner_func

def outer_func3():
num1 = 10
def inner_func():
# 使用外层函数的num1
nonlocal num1
num1 = num1 + 20
print(f'inner func num1 is {num1}')

inner_func()
print(f'outer func num1 is {num1}')
return inner_func


if __name__ == '__main__':
outer_func()
print('😀'*20)
outer_func2()
print('😀'*50)
outer_func3()

特殊规则:可变对象无需nonlocal

如果外层变量是列表、字典等可变对象,仅修改容器内部元素、不对变量整体重新赋值时,无需使用 nonlocal 关键字。

1
2
3
4
5
6
7
8
9
10
11
12
def outer():
# 可变对象列表
data = [1, 2, 3]
def inner():
# 仅修改列表内部元素,无整体赋值
data.append(4)
print(data)
return inner

f = outer()
f()

闭包进阶知识点

自由变量

闭包中被内层函数引用的外层变量,称为自由变量。闭包会将自由变量保存在函数内置属性中,阻止垃圾回收。可通过内置属性查看:

1
2
3
4
5
6
7
8
9
10
11
12
def outer():
num = 100
def inner():
print(num)
return inner

fn = outer()
# 查看自由变量名称
print(fn.__code__.co_freevars)
# 查看闭包保存的变量对象
print(fn.__closure__)

闭包经典陷阱:循环延迟绑定

循环批量生成闭包时,所有闭包会共用同一个外层变量,最终取值均为循环最后值,是高频面试坑点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
'''
演示闭包的循环引用问题
'''

# 错误案例
funcs = []
# i是局部变量
for i in range(3):
def inner():
# 因为内部函数要是用变量i,所以触发闭包机制
# 将i打包成cell,放入inner.__clouser__,
# 内部函数访问i其实是获取i的地址中的数据
print(i)
# 将函数存储到列表中
funcs.append(inner)

# 全部输出2,而非0,1,2
funcs[0]()
funcs[1]()
funcs[2]()

# 正确案例
funcs = []
# i是局部变量
for i in range(3):
def inner(num=i):
print(num)
funcs.append(inner)

funcs[0]()
funcs[1]()
funcs[2]()


image-20260626114057223

image-20260626114821655

闭包内存释放

闭包会造成内存常驻,无需使用时可手动解除引用,释放内存:fn = None

闭包综合案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'''
闭包累加案例:持续保留上次计算结果
'''
def func():
# 外层变量,常驻内存
result = 0
def inner_func(num):
# 引用外层变量并修改
nonlocal result
result = result + num
return result
# 返回闭包
return inner_func

# 绑定闭包函数
g = func()
print(g(1)) # 0+1=1
print(g(2)) # 1+2=3
print(g(3)) # 3+3=6

三、装饰器入门与进阶

装饰器定义

装饰器是特殊的闭包函数,核心作用:不修改原有函数源代码、不改变原有函数调用方式,动态为函数新增额外功能。

装饰器必备条件(闭包三条件):函数嵌套、内层引用外层参数(原函数)、外层返回内层函数。

基础装饰器(无语法糖)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
'''
演示装饰器用法
'''

'''
装饰器实现步骤:
1. 函数嵌套
2. 内层函数引用外层函数的参数,这里引用外层函数的形参,也就是原函数
3. 在外层函数内返回内层函数
4. 在内层函数中装饰原函数
'''


# 原函数
def play_game():
print('打一局蛋仔派对...')

# 定义装饰器(外层函数接受原函数)
def check(fn):
# 封装一个内层函数,该内层函数使用外层函数的参数
# 装饰器就是内层函数使用外层函数的形参(fn函数,原函数)
def inner():
# 新增装饰功能
print('登录验证')
# 执行原函数的功能
fn()
return inner

# 等号左侧的play_game相当于inner函数
play_game = check(play_game)
play_game()

image-20260702084516505

语法糖装饰器(推荐写法)

@装饰器名 是Python提供的语法糖,等价于手动赋值装饰,代码更简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'''
演示装饰器语法糖
'''

# 定义装饰器
def check(fn):
def inner():
print('验证登录')
fn()
return inner

# 相当于 comment = check(comment)

@check
def comment():
print('发表评论')

comment()

各类场景装饰器实战

装饰有参数无返回值函数

1
2
3
4
5
6
7
8
9
10
11
12
13
def print_info(func):
def inner(a,b):
print(f'正在努力计算中。。。。')
func(a,b)
return inner

@print_info
def get_sum(x,y):
z = x+y
print(f'两数之和为:{z}')

get_sum(1,2)

装饰有返回值函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def print_info(func):
def inner():
print(f'友好提示,正在努力计算中...')
res = func()
print(f'计算成功...')
# 返回原函数调用结果
return res
return inner



@print_info
def get_sum():
return 20 + 38


print(get_sum())

装饰有参数有返回值函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def print_info(func):
# 内层参数与原函数保持一致
def inner(a,b):
print(f'正在努力计算中...')
res = func(a,b)
print(f'计算成功!!!')
return res
return inner

@print_info
def get_sum(x,y):
z = x + y
return z

print(get_sum(1,2))

装饰可变参数函数(*args、**kwargs)

装饰可变位置参数的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

def decorator1(func):
# 内层函数接受可变位置参数
def inner(*args):
print('努力计算中...')
# args本质上是一个元组,
# *args就是将元组中的数据拆包,拆成1个1个的位置参数
result = func(*args)
print('计算成功')
return result
return inner

@decorator1
def sum(a,b):
return a+b

print(sum(1,2))

@decorator1
def sum2(a,b, c,d):
return a+b+c+d

print(sum2(1,2,3,4))
# 调用失败,inner接受的是可变位置参数
# sum(a = 1, b = 2)

image-20260702091752408

装饰可变关键字参数的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

def decorator(func):
# 内层函数接受可变关键字参数
def inner(**kwargs):
print('努力计算中...')
# kwargs本质上是一个字典
# **kwargs就是将字典中的数据拆包,拆成1个1个的key=value的形式
print(f'kwargs = {kwargs}')
result = func(**kwargs)
print('计算成功')
return result
return inner

@decorator
def sum(a,b):
return a+b
# 因为inner无法接受可变位置参数
# print(sum(1,2))

# inner可以接受可变关键字参数
print(sum(a = 1, b = 2))

image-20260702093200634

通过可变参数适配任意参数数量、任意参数类型的原函数,是通用装饰器核心写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
'''
这是通用装饰器
'''

def decorator(func):
def inner(*args, **kwargs):
print(f'努力计算中...')
print(f'args = {args}')
print(f'kwargs = {kwargs}')
# 元组,字典拆包
# args相当于元组,*args相当于将元组拆解成可变位置参数
# kwargs相当于字典,**kwargs相当于将字典拆解为可变关键字参数
return func(*args, **kwargs)
return inner

@decorator
def sum(*args,**kwargs):
'''
需求,对元组中的所有数据求和,对字典中的value求和
:param args: args是可变位置参数打包后的元组
:param kwargs: kwargs是可变关键字参数打包后的字典
:return:
'''
sum = 0
# 遍历元组求和
for arg in args:
sum += arg

# 遍历字典的value求和
for value in kwargs.values():
sum += value
return sum

print(sum(1,2,3, a = 4, b=5, c = 6))
# 打印sum函数的名字
print(f'sum.__name__ = {sum.__name__}')
# 打印函数的文档
print(f'sum.__doc__ = {sum.__doc__}')

image-20260702094758275

多个装饰器装饰同一个函数

执行规则:从上到下装饰包裹,从下到上执行逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
'''
多装饰器执行:先登录校验、再权限校验,最后执行业务
'''

def check_login(func):
def inner():
print('✔ 检测用户是否登录(SSO验证)')
func()
return inner

def check_permission(func):
def inner():
print('✔ 检查用户是否具备报销权限(财务系统权限)')
func()
return inner

# 多装饰器叠加
@check_login
@check_permission
def submit_reimbursement():
print('📄 提交报销申请(上传发票+填写金额)')

submit_reimbursement()

image-20260707095118051

带参数装饰器(三层函数结构)

带参装饰器为三层函数结构:外层接收自定义参数、中层接收原函数、内层执行业务逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
'''
带参数装饰器:根据操作类型记录不同日志(公司审计系统)
'''

def audit(action_type):
# 中层:接收原函数
def decorator(func):
# 内层:执行业务
def inner(user, amount):
if action_type == 'recharge':
print('--[审计] 用户正在进行充值操作')
elif action_type == 'withdraw':
print('--[审计] 用户正在进行提现操作')
elif action_type == 'transfer':
print('--[审计] 用户正在进行转账操作')

result = func(user, amount)
return result
return inner
return decorator


@audit('recharge')
def wallet(user, amount):
print(f'{user} 账户变动金额:{amount}')
return f'操作成功:{amount}'


print(wallet('Alice', 100))

image-20260707101031179

装饰器高阶进阶

解决装饰器函数元信息丢失

函数被装饰后,默认会丢失原函数的函数名、文档注释等元信息,可通过 functools.wraps 修复,所有正式项目必须使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import functools

def timer(func):
# 保留原函数元信息
@functools.wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner

@timer
def calc():
"""数值计算函数"""
pass

# 正常输出原函数信息
print(calc.__name__)
print(calc.__doc__)

自定义func_wrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 自定义一个带参数的装饰器,模仿functools.wraps这个函数
def func_wrapper(func):
# func是函数接受的参数,原函数
def decorator(fn):
# fn相当于另一个开发者实现的装饰器中的inner函数
# 不是下面的inner函数
def inner(*args, **kwargs):
return fn(*args, **kwargs)
inner.__name__ = func.__name__
inner.__doc__ = func.__doc__
return inner
return decorator


def timer(func):
@func_wrapper(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner

@timer
def calc():
'''数值计算函数'''
pass

# 打印函数名字
print(calc.__name__)
print(calc.__doc__)

image-20260707102533236

image-20260707104123529

常用实战通用装饰器

耗时统计装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time
import functools

def cost_time(func):
@functools.wraps(func)
def inner(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"函数{func.__name__}执行耗时:{end-start:.4f}秒")
return result
return inner

@cost_time
def heavy_task():
s = 0
for i in range(1000000):
s += i
return s

heavy_task()

异常捕获装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import functools

def catch_error(func):
@functools.wraps(func)
def inner(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"函数执行异常:{e}")
return None
return inner

@catch_error
def div(a, b):
return a / b

print(div(10, 0))

类装饰器拓展

通过类实现装饰器,依靠 __call__ 魔法方法让类实例可被调用,适用于复杂装饰场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import functools

class LogDecorator:
def __init__(self, func):
self.func = func
functools.update_wrapper(self, func) # 正确保留函数元信息

def __call__(self, *args, **kwargs):
print("类装饰器:函数开始执行")
result = self.func(*args, **kwargs)
print("类装饰器:函数执行结束")
return result


@LogDecorator
def test():
print("业务代码运行")


test()

四、课后实战练习

需求

实现三层嵌套的带参数装饰器 log_action,接收字符串参数 action_type(取值:查询、录入、移除、更新)。

功能要求:

  1. 函数执行前打印:正在执行【xxx】数据操作

  2. 兼容任意参数、任意返回值的函数(使用 *args、**kwargs)

  3. 保留原函数返回结果,使用 functools.wraps 修复函数元信息

  4. 严格使用三层函数结构:外层收参数、中层收函数、内层执行业务

完整实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import functools

# 带参数通用日志装饰器
def log_action(action_type):
# 第二层:接收被装饰函数
def decorator(func):
@functools.wraps(func)
# 第三层:通用执行逻辑
def inner(*args, **kwargs):
print(f"正在执行【{action_type}】数据操作")
# 执行原函数并接收返回值
ret = func(*args, **kwargs)
# 返还原函数结果
return ret
return inner
return decorator

# 测试1:数据查询功能
@log_action("查询")
def query_data(table_name):
print(f"正在读取数据表:{table_name}")
return "数据读取完成"

# 测试2:数据录入功能
@log_action("录入")
def insert_data(table_name, count):
print(f"向{table_name}插入{count}条记录")
return "录入完成"

# 测试3:数据移除功能
@log_action("移除")
def remove_data(row_id):
print(f"删除行号:{row_id}的数据")
return "删除完成"

# 调用测试
print(query_data("sales_data"))
print("-"*25)
print(insert_data("user_info", 35))
print("-"*25)
print(remove_data(608))

预期输出

1
2
3
4
5
6
7
8
9
10
11
12
正在执行【查询】数据操作
正在读取数据表:sales_data
数据读取完成
-------------------------
正在执行【录入】数据操作
向user_info插入35条记录
录入完成
-------------------------
正在执行【移除】数据操作
删除行号:608的数据
删除完成

魔法方法

Posted on 2026-06-14 | In python , AI

Python 魔术方法是什么

魔术方法就是前后都有双下划线的方法,例如:

1
2
3
__str__
__len__
__getitem__

它们不是让你直接调用的,而是让 Python 在某些语法场景下自动调用。

例如:

1
len(obj)

背后其实会调用:

1
obj.__len__()

1. __new__

作用

__new__ 负责“创建对象”。

对象创建过程分两步:

1
obj = 类名()

实际顺序是:

1
2
__new__   # 先创建对象
__init__ # 再初始化对象

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
'''
演示new方法
'''
class Person:
# cls是类名,name是形参接受实参
# __new__的形参取决于接受的实参的个数
def __new__(cls,name):
print(f'__new__:创建对象')
# 一层一层向上找,直到找到object的__new__方法
# object的__new__方法是创建对象的
obj = super().__new__(cls)
return obj
# self 存储的是对象,这个对象从__new__返回的
def __init__(self,name):
print(f'__init__: 初始化对象')
self.name=name

class A:
def __new__(cls):
print('创建失败')
return None
def __init__(self):
print('初始化')

if __name__ == '__main__':
'''
下面的创建对象的流程,相当于
obj = Person.__new__(Person, 'Tom')
obj = Person.__init__(obj,'Tom')
'''
p = Person('Tom')
print(p.name)
'''
因为A类的new方法返回的是None, 所以不会调用init方法
'''
a = A()

输出:

1
2
__new__:创建对象
__init__:初始化对象

注意

__new__ 必须返回对象实例,否则 __init__ 不会正常执行。

1
2
3
4
5
6
7
8
9
10
11
class A:
def __new__(cls):
print("创建失败")
return None

def __init__(self):
print("初始化")


a = A()
print(a)

输出:

1
2
创建失败
None

示例:单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'''
演示单例模式
'''

'''
单例模式,就是无论同一个类创建多少对象,都是同一个对象
网络编程,TcpServer, DBManager
'''

class Singleton(object):
# 类属性
__instance = None
def __new__(cls, *args, **kwargs):
if not cls.__instance:
cls.__instance = super().__new__(cls)
return cls.__instance

a = Singleton()
b = Singleton()
print(a is b)
print(f'id(a) is {id(a)}')
print(f'id(b) is {id(b)}')

注意:

  • 必须返回实例,否则对象创建失败
  • 比 __init__ 更底层

2. __str__

作用

__str__ 用来定义对象给“用户”看的字符串。

触发方式

1
2
print(obj)
str(obj)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'''
演示new方法
'''
class Person:
def __init__(self,name):
self.name=name
# 打印对象的时候会输出__str__返回的字符串
def __str__(self):
return f'我的名字是: {self.name}'

if __name__ == '__main__':
p = Person('Zack')
print(f'p is {p}')

输出:

1
姓名:Tom,年龄:18

3. __repr__

作用

__repr__ 用来定义对象给“开发者”看的字符串,主要用于调试。

触发方式

1
repr(obj)

或者在交互式环境中直接输入对象:

1
p

示例

1
2
3
4
5
6
7
8
9
10
11
12
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def __repr__(self):
return f"Person(name={self.name!r}, age={self.age!r})"


p = Person("Tom", 18)

print(repr(p))

输出:

1
Person(name='Tom', age=18)

str 和 __repr__的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
'''
演示new方法
'''
class Person:
def __init__(self,name):
self.name=name
# 打印对象的时候会输出__str__返回的字符串
def __str__(self):
return f'我的名字是: {self.name}'

# 用于调试的字符串输出
def __repr__(self):
return f'in __repr__ 我的名字是: {self.name}'

if __name__ == '__main__':
'''
__str__和__repr__都能在打印的时候输出指定的字符串
如果实现了__str__优先调用__str__
否则没有实现__str__再去查找__repr__
'''
p = Person('Zack')
# 默认调用__str__
print(f'p is {p}')
# 显示调用repr
print(repr(p))
# 显示调用str
print(str(p))

输出:

1
2
我是 Tom
Person('Tom')

区别:

方法 面向对象 典型用途
__str__ 普通用户 打印展示
__repr__ 开发者 调试、日志
只写 __repr__ str(obj) 也会用它 常见
只写 __str__ repr(obj) 不会用它 调试信息仍然不清晰

4. __len__

作用

定义对象的长度。

触发方式

1
len(obj)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'''
演示new方法
'''
class MyList:
def __init__(self,list_data):
self.list_data =list_data
# 返回列表的长度
def __len__(self):
return len(self.list_data)

if __name__ == '__main__':
'''
调用len会触发__len__
'''
my_list = MyList([1,2,3])
print(len(my_list))

输出:

1
3

注意

__len__ 必须返回整数。


5. __getitem__

作用

让对象支持中括号取值。

触发方式

1
obj[key]

示例:像字典一样取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'''
演示new方法
'''
class MyDict:
def __init__(self,name,age):
self.name = name
self.age = age
self.dict = {'name':name,'age':age}

def __getitem__(self,key):
if key not in self.dict:
return None
return self.dict[key]

if __name__ == '__main__':
my_dict = MyDict(name='zack',age=38)
# 当一个对象当成字典根据key获取值使用的时候会触发getitem
print(my_dict['name'])
print(my_dict['age'])

输出:

1
2
zack
38

6. __setitem__

作用

让对象支持中括号赋值。

触发方式

1
obj[key] = value

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
'''
演示new方法
'''
class MyDict:
def __init__(self,name,age):
self.name = name
self.age = age
self.dict = {'name':name,'age':age}

def __getitem__(self,key):
if key not in self.dict:
return None
return self.dict[key]

def __setitem__(self,key,value):
self.dict[key] = value

if __name__ == '__main__':
my_dict = MyDict(name='zack',age=38)
# 当一个对象当成字典根据key获取值使用的时候会触发getitem
print(my_dict['name'])
print(my_dict['age'])
# 当一个对象当成字典,通过key修改或者添加value的时候触发setitem
# 添加key和value
my_dict['hobbies'] = ['run','read','games']
# 修改key和value
my_dict['age'] = 18
print(my_dict['hobbies'])
print(my_dict['age'])

7. __delitem__

作用

让对象支持中括号删除。

触发方式

1
del obj[key]

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
'''
演示new方法
'''
class MyDict:
def __init__(self,name,age):
self.name = name
self.age = age
self.dict = {'name':name,'age':age}

def __getitem__(self,key):
if key not in self.dict:
return None
return self.dict[key]

def __setitem__(self,key,value):
self.dict[key] = value

def __delitem__(self,key):
if key not in self.dict:
return
del self.dict[key]

if __name__ == '__main__':
my_dict = MyDict(name='zack',age=38)
# 当一个对象当成字典根据key获取值使用的时候会触发getitem
print(my_dict['name'])
print(my_dict['age'])
# 当一个对象当成字典,通过key修改或者添加value的时候触发setitem
# 添加key和value
my_dict['hobbies'] = ['run','read','games']
# 修改key和value
my_dict['age'] = 18
print(my_dict['hobbies'])
print(my_dict['age'])

# 把一个对象当成字典,删除key和value会触发delitem
del my_dict['hobbies']
print(my_dict['hobbies'])

8. __contains__

作用

让对象支持 in 判断。

触发方式

1
key in obj

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
'''
演示new方法
'''
class MyDict:
def __init__(self,name,age):
self.name = name
self.age = age
self.dict = {'name':name,'age':age}

def __getitem__(self,key):
if key not in self.dict:
return None
return self.dict[key]

def __setitem__(self,key,value):
self.dict[key] = value

def __delitem__(self,key):
if key not in self.dict:
return
del self.dict[key]

if __name__ == '__main__':
my_dict = MyDict(name='zack',age=38)
# 当一个对象当成字典根据key获取值使用的时候会触发getitem
print(my_dict['name'])
print(my_dict['age'])
# 当一个对象当成字典,通过key修改或者添加value的时候触发setitem
# 添加key和value
my_dict['hobbies'] = ['run','read','games']
# 修改key和value
my_dict['age'] = 18
print(my_dict['hobbies'])
print(my_dict['age'])

# 把一个对象当成字典,删除key和value会触发delitem
del my_dict['hobbies']
print(my_dict['hobbies'])

输出:

1
2
True
False

9. __call__

作用

让对象像函数一样被调用。

触发方式

1
2
obj()
obj(参数)

示例

1
2
3
4
5
6
7
8
class Add:
def __call__(self, x, y):
return x + y


add = Add()

print(add(3, 5))

输出:

1
8

使用场景

常用于:

1
2
3
模型对象()
装饰器对象()
任务执行器()

例如:

1
2
3
4
5
6
7
8
9
10
class Greeter:
def __init__(self, name):
self.name = name

def __call__(self):
print(f"你好,{self.name}")


g = Greeter("Tom")
g()

10. __iter__

作用

让对象可以被 for 循环遍历。

触发方式

1
2
for x in obj:
...

实际会先调用:

1
iter(obj)

也就是:

1
obj.__iter__()

11. __next__

作用

定义每次迭代返回什么值。

触发方式

1
next(obj)

示例:自定义计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
'''
演示迭代器实现
'''

class Counter:
def __init__(self,max_num):
self.max_num = max_num
self.current = 0

# 返回迭代器对象
def __iter__(self):
'''
返回可迭代对象
:return:
'''
return self

# 返回一个值
def __next__(self):
'''
next函数接受的是一个可迭代对象
:return:
'''
if self.current < self.max_num:
self.current += 1
return self.current
# 抛出停止异常
raise StopIteration

if __name__ == '__main__':
counter = Counter(2)
# 可以通过next从迭代器中获取数据
print(next(counter))
print(next(counter))
try:
print(next(counter))
except StopIteration:
print('迭代停止...')
print('😔'*20)
# 可以使用for循环
counter2 = Counter(3)
for num in counter2:
print(num)


重点

迭代器必须满足:

1
2
3
__iter__ 返回自己
__next__ 返回下一个元素
没有元素时 raise StopIteration

12. __enter__

作用

进入 with 语句时执行。

触发方式

1
2
with obj as x:
...

会调用:

1
obj.__enter__()

13. __exit__

作用

离开 with 语句时执行。

即使 with 里面发生异常,也会执行。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyContext:
def __enter__(self):
print("进入 with")
return "资源对象"

def __exit__(self, exc_type, exc_val, exc_tb):
print("退出 with")
print("异常类型:", exc_type)
print("异常值:", exc_val)


with MyContext() as resource:
print(resource)

输出:

1
2
3
4
5
进入 with
资源对象
退出 with
异常类型: None
异常值: None

RAII自动加解锁

为什么要用 with + 锁

普通写法(容易出问题):

1
2
3
4
5
6
lock.acquire()
try:
# 临界区代码
...
finally:
lock.release()

问题:

  • 容易忘记释放锁
  • 出异常时容易死锁

👉 用 with 可以自动管理


基础示例:用 with 实现加锁/解锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import threading

class MyLock:
def __init__(self):
self.lock = threading.Lock()

def __enter__(self):
print("加锁")
self.lock.acquire()
return self # 可选

def __exit__(self, exc_type, exc_val, exc_tb):
print("解锁")
self.lock.release()
# return False 表示异常继续抛出(默认行为)

使用方式

1
2
3
4
lock = MyLock()

with lock:
print("临界区代码")

输出:

1
2
3
加锁
临界区代码
解锁

关键点解释

enter

1
2
def __enter__(self):
self.lock.acquire()

👉 进入 with 时执行
👉 用来“申请资源”(加锁、打开文件、连接数据库)


exit

1
2
def __exit__(self, exc_type, exc_val, exc_tb):
self.lock.release()

👉 离开 with 时执行
👉 无论是否异常都会执行

1
2
3
4
5
6
7
8
lock = MyLock()

try:
with lock:
print("执行中")
raise ValueError("出错了")
except:
print("捕获异常")

综合案例:模拟一个字典类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class MyDict:
def __init__(self):
self.data = {}

def __str__(self):
return f"MyDict内容:{self.data}"

def __repr__(self):
return f"MyDict({self.data!r})"

def __len__(self):
return len(self.data)

def __getitem__(self, key):
print(f"正在读取 key:{key}")
return self.data[key]

def __setitem__(self, key, value):
print(f"正在设置 {key} = {value}")
self.data[key] = value

def __delitem__(self, key):
print(f"正在删除 key:{key}")
del self.data[key]

def __contains__(self, key):
return key in self.data

def __call__(self):
print("当前数据:", self.data)

def __iter__(self):
return iter(self.data)


d = MyDict()

d["name"] = "Tom"
d["age"] = 18

print(d["name"])
print(len(d))
print("age" in d)

for key in d:
print(key)

d()

del d["age"]

print(d)
print(repr(d))

输出大致是:

1
2
3
4
5
6
7
8
9
10
11
12
正在设置 name = Tom
正在设置 age = 18
正在读取 key:name
Tom
2
True
name
age
当前数据: {'name': 'Tom', 'age': 18}
正在删除 key:age
MyDict内容:{'name': 'Tom'}
MyDict({'name': 'Tom'})

总结

魔术方法 对应语法 作用
__new__ Class() 创建对象
__str__ print(obj) / str(obj) 面向用户的字符串
__repr__ repr(obj) 面向开发者的字符串
__len__ len(obj) 返回长度
__getitem__ obj[key] 读取元素
__setitem__ obj[key] = value 设置元素
__delitem__ del obj[key] 删除元素
__contains__ key in obj 判断是否包含
__call__ obj() 对象像函数一样调用
__iter__ iter(obj) / for 返回迭代器
__next__ next(obj) 返回下一个元素
__enter__ with obj 进入上下文
__exit__ with 结束 退出上下文,释放资源

面向对象编程

Posted on 2026-05-26 | In python , AI

学习目标

  1. 理解什么是面向对象编程。
  2. 区分类、对象、属性、方法的概念。
  3. 能够使用 Python 定义类并创建对象。
  4. 理解 self 的作用。
  5. 掌握构造方法 __init__ 的使用。
  6. 理解封装、继承、多态三大核心思想。
  7. 能够用面向对象思想解决简单实际问题。

为什么需要面向对象

面向过程

面向过程的核心思想是:自顶向下,逐步细化。

特点:

  • 把问题拆成一个个步骤
  • 每个步骤用函数实现
  • 按顺序执行

示例(报名流程):

开始 → 提交信息 → 缴费 → 分班 → 结束

问题:

  • 数据与行为分离
  • 难以维护复杂系统

面向对象

核心思想:

不再关注“做什么步骤”,而是关注“谁来做这些事情”

例如报名系统可以抽象为4个对象:

  • 学生
  • 教师
  • 大学
  • 班级

每个对象:

  • 有属性(数据)
  • 有方法(行为)

核心对比

维度 面向过程 面向对象
思维方式 步骤导向 对象导向
复杂系统 难维护 易扩展
代码复用 较低 更高

什么是类和对象

对象

对象是现实世界中具体存在的一个事物。

例如:

  • 一个具体的学生:张三
  • 一辆具体的汽车:我的白色汽车
  • 一只具体的猫:小花
  • 一个具体的订单:订单编号 1001

在程序中,对象通常包含两部分:

组成 含义 示例
属性 对象的数据、特征 姓名、年龄、颜色
方法 对象的行为、功能 学习、跑步、打印信息

类

类是对象的模板或设计图。

例如:

  • “学生类”是所有学生的模板
  • “汽车类”是所有汽车的模板
  • “猫类”是所有猫的模板

类和对象的关系可以这样理解:

类 对象
图纸 根据图纸造出来的房子
模板 根据模板创建出来的具体内容
学生类 张三、李四、王五
汽车类 宝马、奔驰、比亚迪

一句话总结:

类是抽象的,对象是具体的。


定义类和创建对象

定义一个最简单的类

1
2
class Student:
pass

说明:

  • class 是定义类的关键字。
  • Student 是类名,通常使用大驼峰命名法。
  • pass 表示暂时不写内容。

创建对象

1
2
3
4
5
stu1 = Student()
stu2 = Student()

print(stu1)
print(stu2)

stu1 和 stu2 都是通过 Student 类创建出来的对象。

它们属于同一个类,但它们是两个不同的对象。

image-20260526145555982

给对象添加属性

对象的属性可以理解为对象的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
'''
演示类的基础操作
'''

'''
定义一个类类型
class 类名():
pass
创建对象
变量 = 类名()
'''
# 学生类
class Student():
pass

# 创建一个对象赋值给s1
s1 = Student()
# 创建一个对象赋值给s2
s2 = Student()
print(f's1 : {s1}')
print(f's2 : {s2}')

# 为s1添加name属性,属性值为'zack'
s1.name = 'zack'
# 为s1添加age属性,属性值为18
s1.age = 18
print(f's1.age : {s1.age}')
print(f's1.name : {s1.name}')
# s2因为没有添加属性,所以不能通过属性访问
# print(f's2.age : {s2.age}')

输出:

1
2
张三
18

这种方式可以添加属性,但不推荐在正式项目中大量使用,因为每个对象的属性可能不统一。

例如:

1
2
stu1.name = "张三"
stu2.score = 95

这样会导致对象结构混乱。

更推荐使用构造方法 __init__ 来统一初始化属性。


构造方法

什么是构造方法

构造方法是在创建对象时自动执行的方法。

在 Python 中,构造方法的名字固定为:

1
__init__

示例:

1
2
3
4
5
6
7
8
9
10
class Student:
def __init__(self, name, age):
self.name = name
self.age = age

stu1 = Student("张三", 18)
stu2 = Student("李四", 19)

print(stu1.name, stu1.age)
print(stu2.name, stu2.age)

输出:

1
2
张三 18
李四 19

self` 是什么

self 表示当前对象本身。

当执行:

1
stu1 = Student("张三", 18)

可以理解为:

1
2
3
self 指向 stu1
self.name = "张三"
self.age = 18

当执行:

1
stu2 = Student("李四", 19)

可以理解为:

1
2
3
self 指向 stu2
self.name = "李四"
self.age = 19

所以 self 的作用是:

区分当前操作的是哪一个对象。

image-20260526153451012

常见错误

错误写法:

1
2
3
4
class Student:
def __init__(name, age):
name = name
age = age

问题:

  1. 少写了 self。
  2. 没有把数据保存到对象中。
  3. name = name 只是局部变量赋值,没有意义。

正确写法:

1
2
3
4
class Student:
def __init__(self, name, age):
self.name = name
self.age = age

对象方法

方法就是定义在类中的函数,用来描述对象的行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
'''
讲解init方法
'''

class Student:
# 当使用Student类创建对象的时候,会调用下面的方法
def __init__(self,name, age):
'''
构造方法
:param name: 学生的名字
:param age: 学生的年龄
:param self: 对象的地址,或者self指向了对象
'''
# 为对象添加name属性
self.name = name
# 为对象添加age属性
self.age = age
# 定义对象的方法自我介绍,不接受实参
def introduce(self):
# 将来对象会自动传递给self, 比如s1.introduce, self就是s1
print(f'您好,我是{self.name}, 我今年{self.age}岁了')
'''
创建一个学生对象, 会自动调用__init__,
1. __init__需要三个参数,第一个self实际是指向了对象的地址,这个对象在调用__init__之前
通过__new__方法就已经创建好,提前在堆空间创建好的, 解释器会自动将这个对象的地址赋值给self
不需要开发者传参
2. name, age需要通过Student('zack',18)传递,将实参传递给name,age两个形参
'''
# 用s1存储Student生成的对象
s1 = Student('zack',18)
# 通过对象.属性的方式访问属性
print(s1.name)
print(s1.age)
# 调用introduce方法, s1会自动传递给self
s1.introduce()

输出:

1
大家好,我叫张三,今年18岁

说明:

  • introduce 是对象方法。
  • 对象方法的第一个参数通常也是 self。
  • 调用方法时,不需要手动传入 self。

也就是说:

1
stu1.introduce()

Python 会自动把 stu1 传给 self。

image-20260526154826227


完整示例:学生类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score

def introduce(self):
print(f"大家好,我叫{self.name},今年{self.age}岁")

def show_score(self):
print(f"{self.name}的成绩是{self.score}分")

def update_score(self, new_score):
self.score = new_score
print(f"{self.name}的成绩已更新为{self.score}分")

stu1 = Student("张三", 18, 95)
stu1.introduce()
stu1.show_score()
stu1.update_score(98)
stu1.show_score()

输出:

1
2
3
4
大家好,我叫张三,今年18岁
张三的成绩是95分
张三的成绩已更新为98分
张三的成绩是98分

类属性和实例属性

实例属性

实例属性属于具体对象,每个对象可以拥有不同的值。

1
2
3
4
5
6
7
8
9
class Student:
def __init__(self, name):
self.name = name

stu1 = Student("张三")
stu2 = Student("李四")

print(stu1.name)
print(stu2.name)

这里的 name 就是实例属性。

类属性

类属性属于类本身,所有对象可以共享。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Student:
# 类属性
# 学校
school = '第一中学'
# 班费
class_money = 200
# 定义对象属性
def __init__(self, name, age):
self.name = name
self.age = age

# 创建对象
stu1 = Student('张三', 23)
# 通过对象访问类属性
print(stu1.school)
# 也可通过类名访问类属性
print(Student.school)
# 创建对象李四
stu2 = Student('李四', 24)
print(stu2.school)
# 通过类名修改类属性
Student.school = '第二中学'
print('😀'*50)
print(stu1.school)
print(stu2.school)
# 隐藏bug,看似是通过对象修改类属性,其实是添加对象属性
# 其实是给stu1添加对象属性school
# 因为赋值操作具有二义性,被理解为为stu1添加了一个对象属性school
stu1.school = '第三中学'
print('😀'*50)
print(stu2.school)
print(Student.school)
# 如果通过对象直接使用类属性,做修改不会产生二义性
# 学生stu1花费班费20, 但是赋值会创建新的对象的属性
# 相当于 stu1.class_money = stu1.class_money - 20
# 上述表达式,=右侧的stu1.class_money是类属性,左侧的stu1.class_money是对象属性
stu1.class_money -= 20
print('😀'*50)
print(f'班费{Student.class_money}')
print(f'stu1班费{stu1.class_money}')
# stu2没有对象属性class_money,用的是类属性
print(f'stu2班费{stu2.class_money}')
print('😀'*50)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
第一中学
第一中学
第一中学
😀😀😀😀😀😀😀😀😀
第二中学
第二中学
😀😀😀😀😀😀😀😀😀
第二中学
第二中学
😀😀😀😀😀😀😀😀😀
班费200
stu1班费180
stu2班费200

类属性和实例属性的区别

类型 所属对象 是否共享 示例
实例属性 具体对象 不共享 self.name
类属性 类本身 共享 Student.school

示例:统计学生人数

1
2
3
4
5
6
7
8
9
10
11
12
class Student:
count = 0

def __init__(self, name):
self.name = name
Student.count += 1

stu1 = Student("张三")
stu2 = Student("李四")
stu3 = Student("王五")

print(Student.count)

输出:

1
3

封装

什么是封装

封装是指把数据和操作数据的方法放在一起,并对外隐藏不希望直接访问的细节。

简单理解:

不让外部随便修改对象内部的数据,而是通过方法来控制访问。

例如,学生成绩不应该被随意设置为负数。

未封装的问题

1
2
3
4
5
6
7
8
class Student:
def __init__(self, name, score):
self.name = name
self.score = score

stu = Student("张三", 90)
stu.score = -100
print(stu.score)

输出:

1
-100

这显然不合理。

使用私有属性

在 Python 中,属性名前面加两个下划线,表示私有属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Student:
def __init__(self, name, score):
self.name = name
self.__score = score

def get_score(self):
return self.__score

def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
print("成绩必须在0到100之间")

stu = Student("张三", 90)
print(stu.get_score())

stu.set_score(95)
print(stu.get_score())

stu.set_score(-100)
print(stu.get_score())

输出:

1
2
3
4
90
95
成绩必须在0到100之间
95

封装的好处

  1. 保护数据安全。
  2. 控制数据修改规则。
  3. 降低外部代码对内部实现的依赖。
  4. 让类的使用更加清晰。

继承

本质理解(结合哲学)

继承本质是:共性 + 个性

例如:

生物 → 动物 → 哺乳动物 → 人类

  • 上层:共性
  • 下层:个性

继承核心作用

  1. 代码复用
  2. 表达层级关系
  3. 提升扩展能力

基本语法

1
2
3
4
5
class 父类:
pass

class 子类(父类):
pass

经典案例(强化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
'''
演示类的继承
'''
# 父类
class Person(object):
def eat(self):
print('吃东西')
def sleep(self):
print('睡觉')

# 子类, 拥有了父类的属性和方法
class Student(Person):
pass

# 教师类
class Teacher(Person):
pass

s1 = Student()
# 拥有了父类的属性和方法
s1.eat()
s1.sleep()

t1 = Teacher()
t1.eat()
t1.sleep()

# 首先去Student中查找,如果Student中没有去Person查找,如果Person也没有去Object查找
# 如果最后都没有找到该方法,则报错
# s1.play_game()
# 打印Student对象的方法查找顺序
# (<class '__main__.Student'>, <class '__main__.Person'>, <class 'object'>)
print(Student.__mro__)

__init__流程说明

1
2
3
4
5
6
7
8
9
10
11
12
# 动物类
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age

# 猫类
class Cat(Animal):
pass

# 调用的是Animal的__init__方法
cat = Cat('小猫',5)

image-20260529094012124

方法覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 动物类
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age

# 猫类
class Cat(Animal):
pass

class Dog(Animal):
# Dog类的__init__方法会覆盖掉父类同名的__init__方法
def __init__(self, name, age):
self.name = name
self.age = age
self.type = 'dog'

# 调用的是Animal的__init__方法
cat = Cat('小猫',5)
# 创建Dog实例
dog = Dog('小狗',6)
print(dog.type)

通过子类调用父类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 动物类
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age


class Bird(Animal):
# 将共性属性的初始化放入Animal,调用Animal的__init__方法
# 将Bird特性的属性的初始化放入Bird
def __init__(self, name, age, type):
# 调用父类的__init__方法
super().__init__(name, age)
# 特性属性的初始化
self.type = type

# 创建鸟类实例
bird = Bird('小鸟',200,'金雕')
print(bird.type)
print(bird.age)
print(bird.name)

私有成员说明(进阶重点)

父类私有方法:

1
2
def __private_func(self):
pass

特点:

  • 子类不能直接访问
  • 本质被改名:_类名__方法名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 动物类
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age

# 私有方法, 咆哮
def __roar(self):
print('野性的呼唤,原始觉醒')


class Bird(Animal):
# 将共性属性的初始化放入Animal,调用Animal的__init__方法
# 将Bird特性的属性的初始化放入Bird
def __init__(self, name, age, type):
# 调用父类的__init__方法
super().__init__(name, age)
# 特性属性的初始化
self.type = type


# 创建鸟类实例
bird = Bird('小鸟',200,'金雕')
print(bird.type)
print(bird.age)
print(bird.name)
print('😔'*50)
# 在Bird的__dict__看不到从父类继承的方法
print(Bird.__dict__)
# 在Animal中看到私有方法__roar改名了_Animal__roar
print(Animal.__dict__)
# 可以理解为_Animal__roar被Bird类继承了
# 根据__mro__顺序去查找,先查找Bird类,没有_Animal__roar
# 继续去上一级Animal查找,找到_Animal__roar
bird._Animal__roar()

多继承

Python 支持多继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
class A:
def show(self):
print("A")

class B:
def show(self):
print("B")

class C(A, B):
pass

c = C()
c.show()

输出:

1
A

为什么不是 B?

因为 Python 会按照 MRO(Method Resolution Order,方法解析顺序) 查找方法。

查看 MRO:

1
print(C.__mro__)

输出类似:

1
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

所以查找顺序是:

1
C → A → B → object

小结

  • 多继承时,方法查找不是随便找的
  • Python 按照 MRO 顺序查找方法
  • 类名.__mro__ 可以查看查找顺序
  • 初学者应先掌握单继承,再理解多继承和 MRO

方法重写

什么是方法重写

如果子类对父类的方法不满意,可以在子类中重新定义同名方法。

这叫方法重写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
'''
演示方法重写
'''

class Animal(object):
def __init__(self, name):
self.name = name

def speak(self):
print('动物咆哮')

def introduce(self):
print(f'我是一只动物,我叫{self.name}')

class Dog(Animal):
def __init__(self, name, age):
self.name = name
self.age = age
# 子类重新实现了父类的introduce方法,
# 将来子类对象调用introduce,会触发子类的introduce方法
def introduce(self):
print(f'我是一只小狗,我叫{self.name}')

def speak(self):
print('小狗汪汪汪叫...')

dg = Dog('旺财',2)
# 子类对象调用自己的introduce
dg.introduce()
dg.speak()

# 子类对象具体调用哪个类的方法,取决于mro的查找顺序
print(Dog.__mro__)

输出:

1
2
3
4
我是一只小狗,我叫旺财
小狗汪汪汪叫...
(<class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>)

为什么需要方法重写

不同子类虽然拥有相同的方法名,但具体表现可能不同。

例如:

  • 狗会叫,但叫声是“汪汪”。
  • 猫会叫,但叫声是“喵喵”。
  • 鸭子会叫,但叫声是“嘎嘎”。

这为多态打下了基础。


调用父类方法

有时子类并不是完全替换父类方法,而是在父类原有功能基础上增加新功能。

这时可以使用 super()。

正确完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
'''
子类调用父类的方法
'''

'''
子类调用父类的方法
方法1
super().方法名(参数1,参数2,参数3...)
方式2
父类名.方法名(self,参数1,参数2,参数3...)
'''
class Animal:
def __init__(self, name):
self.name = name

def introduce(self):
print(f'我是一只动物,我叫{self.name}')

class Dog(Animal):
def __init__(self, name,color):
super().__init__(name)
self.color = color
def introduce(self):
super().introduce()
print(f'我的颜色是{self.color}')

dog = Dog('旺财','蓝色')
dog.introduce()

输出:

1
2
我是一只动物,我叫旺财
我的颜色是蓝色

说明:

  • super().__init__(name) 调用了父类的构造方法。
  • super().introduce() 调用了父类的 introduce 方法。

image-20260604223932481

另外一种调用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
'''
子类调用父类的方法
'''

'''
子类调用父类的方法
方法1
super().方法名(参数1,参数2,参数3...)
方式2
父类名.方法名(self,参数1,参数2,参数3...)
'''
class Animal:
def __init__(self, name):
self.name = name

def introduce(self):
print(f'我是一只动物,我叫{self.name}')

class Dog(Animal):
def __init__(self, name,color):
Animal.__init__(self, name)
self.color = color
def introduce(self):
Animal.introduce(self)
print(f'我的颜色是{self.color}')

dog = Dog('旺财','蓝色')
dog.introduce()

多继承风险

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>'''
>需求:
>1. 定义一个汽车Car基类,构造方法__init__接受汽车的名字和颜色

>2. 定义GasolineCar类,继承自Car, 实现__init__方法,接受参数名字,颜色
以及充能方式
实现run方法,输出耗油运行
实现energy方法,输出默认使用燃油

>3. 定义ElectricCar类, 实现__init__方法,接受参数名字,颜色
以及充能方式
继承自Car,实现run方法,输出耗电运行
实现energy方法,输出默认使用电能

>4. 定义HybridCar类,同时继承自ElectricCar,GasolineCar,
>实现run方法,油电混动运行

>5. 实例化一个HybridCar对象,调用energy以及run方法,看看输出

>'''

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
'''
演示多继承隐患
'''

'''
需求:
1. 定义一个汽车Car基类,构造方法__init__接受汽车的名字和颜色

2. 定义GasolineCar类,继承自Car, 实现__init__方法,接受参数名字,颜色
以及充能方式
实现run方法,输出耗油运行
实现energy方法,输出默认使用燃油

3. 定义ElectricCar类, 实现__init__方法,接受参数名字,颜色
以及充能方式
继承自Car,实现run方法,输出耗电运行
实现energy方法,输出默认使用电能

4. 定义HybridCar类,同时继承自ElectricCar,GasolineCar,
实现run方法,油电混动运行

5. 实例化一个HybridCar对象,调用energy以及run方法,看看输出

'''
# 1. 定义一个汽车Car基类,构造方法__init__接受汽车的名字和颜色
class Car(object):
def __init__(self, name, color):
self.name = name
self.color = color

# 2. 定义GasolineCar类,继承自Car, 实现__init__方法,接受参数名字,颜色
# 以及充能方式
class GasolineCar(Car):
def __init__(self, name, color,energy_type):
super().__init__(name, color)
self.energy_type = energy_type

def run(self):
print('耗油运行')

def energy(self):
print('默认使用燃油')

# 3. 定义ElectricCar类, 实现__init__方法,接受参数名字,颜色
# 以及充能方式

class ElectricCar(Car):
def __init__(self, name, color, energy_type):
super().__init__(name, color)
self.energy_type = energy_type

def run(self):
print('耗电运行')

def energy(self):
print('默认使用电能')

# 4. 定义HybridCar类,同时继承自ElectricCar,GasolineCar
class HybridCar(ElectricCar, GasolineCar):
def __init__(self, name, color, energy_type):
self.name = name
self.color = color
self.energy_type = energy_type

def run(self):
print('油电混动运行')


if __name__ == '__main__':
# gc = GasolineCar('宝马','白色','燃油')
# gc.run()
# gc.energy()
#
# ec = ElectricCar('比亚迪','黑色','电能')
# ec.run()
# ec.energy()
print(HybridCar.__mro__)
hy_car = HybridCar('比亚迪','黑色','油电混动')
hy_car.run()
hy_car.energy()

如果HybridCar没有实现__init__方法, 将会导致通过mro顺序调用上一级的__init__方法

而上一级是ElectricCar类,ElectricCar类的__init__方法内部又调用了super()所以会通过mro找到上一级

这个上一级其实是GasolineCar, 进而造成__init__调用参数不匹配而失败

规避的方式第一种即使实现HybridCar的__init__方法

第二种方式就是通过父类名.方法名方式显示调用

第二种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
'''
演示多继承隐患
'''

'''
需求:
1. 定义一个汽车Car基类,构造方法__init__接受汽车的名字和颜色

2. 定义GasolineCar类,继承自Car, 实现__init__方法,接受参数名字,颜色
以及充能方式
实现run方法,输出耗油运行
实现energy方法,输出默认使用燃油

3. 定义ElectricCar类, 实现__init__方法,接受参数名字,颜色
以及充能方式
继承自Car,实现run方法,输出耗电运行
实现energy方法,输出默认使用电能

4. 定义HybridCar类,同时继承自ElectricCar,GasolineCar,
实现run方法,油电混动运行

5. 实例化一个HybridCar对象,调用energy以及run方法,看看输出

'''
# 1. 定义一个汽车Car基类,构造方法__init__接受汽车的名字和颜色
class Car(object):
def __init__(self, name, color):
self.name = name
self.color = color

# 2. 定义GasolineCar类,继承自Car, 实现__init__方法,接受参数名字,颜色
# 以及充能方式
class GasolineCar(Car):
def __init__(self, name, color,energy_type):
Car.__init__(self, name, color)
self.energy_type = energy_type

def run(self):
print('耗油运行')

def energy(self):
print('默认使用燃油')

# 3. 定义ElectricCar类, 实现__init__方法,接受参数名字,颜色
# 以及充能方式

class ElectricCar(Car):
def __init__(self, name, color, energy_type):
Car.__init__(self, name, color)
self.energy_type = energy_type

def run(self):
print('耗电运行')

def energy(self):
print('默认使用电能')

# 4. 定义HybridCar类,同时继承自ElectricCar,GasolineCar
class HybridCar(ElectricCar, GasolineCar):

def run(self):
print('油电混动运行')


if __name__ == '__main__':
# gc = GasolineCar('宝马','白色','燃油')
# gc.run()
# gc.energy()
#
# ec = ElectricCar('比亚迪','黑色','电能')
# ec.run()
# ec.energy()
print(HybridCar.__mro__)
hy_car = HybridCar('比亚迪','黑色','油电混动')
hy_car.run()
hy_car.energy()

结论

当使用多继承时,子类调用父类的方法,一定要用父类名.方法名(参数1,参数2…)

多态

什么是多态

多态是指不同对象调用相同方法时,表现出不同的行为。

核心特点:

相同的方法名,不同的实现效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
'''
演示多态用法
'''

'''
用一个父类对象引用子类对象,通过父类对象调用方法,能够触发子类对象的方法
这种机制就是多态

多态必要条件:
1. 必须要有继承关系
2. 子类重写父类方法
3. 父类对象引用子类对象
'''

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

def speak(self):
print(f'我叫{self.name}, 通过动物咆哮')


class Dog(Animal):
def speak(self):
print(f'我叫{self.name},叫声汪汪汪')

class Cat(Animal):
def speak(self):
print(f'我叫{self.name},叫喵喵喵')

class Duck(Animal):
def speak(self):
print(f'我叫{self.name},叫声嘎嘎嘎')

def animal_speak(animal:Animal):
animal.speak()

if __name__ == '__main__':
animal_speak(Animal('动物',8))
animal_speak(Dog('小狗',7))
animal_speak(Cat('小猫',4))
animal_speak(Duck('鸭子',3))

输出:

1
2
3
4
我叫动物, 通过动物咆哮
我叫小狗,叫声汪汪汪
我叫小猫,叫喵喵喵
我叫鸭子,叫声嘎嘎嘎

多态的优势

多态可以让代码更加灵活。可以将抽象类的定义和具体类的实现分离,实现开发的解耦合

同时使用平台,不用随着逻辑的变化而修改,将来只要扩充子类类型即可。

符合高内聚和低耦合的思想。

例如:

1
2
3
4
5
6
def make_sound(animal):
animal.speak()

make_sound(Dog())
make_sound(Cat())
make_sound(Duck())

这个函数不关心传入的是狗、猫还是鸭子,只关心对象有没有 speak 方法。

like方式继承

like方式就是像的意思,不要求类一定和某一个类实现继承关系,只要这个类具备相似的方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
'''
演示like继承
'''

class Bird:
def fly(self):
print("i can fly")
# is 方式继承,Angle一定是Bird
class Angle(Bird):
def fly(self):
print('鹰会飞')
# like 方式继承, Go语言特性interface
class Plane:
def fly(self):
print('飞机也会飞')

def func_test_bird(bird:Bird):
bird.fly()

if __name__ == '__main__':
angle = Angle()
func_test_bird(angle)
plane = Plane()
func_test_bird(plane)

王者荣耀换肤案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
'''
王者荣耀换肤案例
'''
from pyarrow._flight import BasicAuth


# 皮肤基类
class BaseSkin:
def show(self):
pass

class XHOriginSkin(BaseSkin):
def show(self):
print('没错,我就是呼唤胜利的男神')

class WXJFSkin(BaseSkin):
def show(self):
print('我就差一点了,快来砍我')

class BaseHero:
def __init__(self,name,gender):
self.name = name
self.gender = gender
# 皮肤列表
self.skins = []

class XHHero(BaseHero):
def __init__(self,name,gender):
super().__init__(name,gender)
# 初始化人物原皮
skin:BaseSkin = XHOriginSkin()
self.skins.append(skin)
# 上一次展示的皮肤
self.last_skin_index = 0

# 展示人物
def show(self):
self.skins[self.last_skin_index].show()

# 购买皮肤
def buy_skin(self, skin:BaseSkin):
self.skins.append(skin)

def change_skin(self,index):
if index >= len(self.skins):
return
self.skins[index].show()
self.last_skin_index = index

if __name__ == '__main__':
xh_hero = XHHero('夏侯惇','男')
xh_hero.show()
# 购买皮肤
xh_hero.buy_skin(WXJFSkin())
# 换肤
xh_hero.change_skin(1)
# 展示英雄
xh_hero.show()

特殊方法

Python 中有一些以双下划线开头和结尾的方法,称为特殊方法,也叫魔术方法。

__str__

当使用 print() 打印对象时,会自动调用对象的 __str__ 方法。

1
2
3
4
5
6
7
8
9
10
class Student:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return f"学生姓名:{self.name},年龄:{self.age}"

stu = Student("张三", 18)
print(stu)

输出:

1
学生姓名:张三,年龄:18

如果不定义 __str__,直接打印对象通常会看到对象的内存地址信息,不利于阅读。

__repr__

__repr__ 更偏向开发者调试使用,通常返回一个更明确的对象描述。

1
2
3
4
5
6
7
8
9
10
class Student:
def __init__(self, name, age):
self.name = name
self.age = age

def __repr__(self):
return f"Student(name={self.name!r}, age={self.age!r})"

stu = Student("张三", 18)
print(repr(stu))

输出:

1
Student(name='张三', age=18)

类方法和静态方法

实例方法

最常见的方法是实例方法,第一个参数是 self。

1
2
3
class Student:
def introduce(self):
print("我是学生")

实例方法通常需要访问对象本身的数据。

类方法

类方法使用 @classmethod 装饰器,第一个参数通常是 cls,表示当前类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student:
count = 0

def __init__(self, name):
self.name = name
Student.count += 1

@classmethod
def show_count(cls):
print(f"当前学生人数:{cls.count}")

Student("张三")
Student("李四")
Student.show_count()

输出:

1
当前学生人数:2

静态方法

静态方法使用 @staticmethod 装饰器。

它不需要访问对象,也不需要访问类。

1
2
3
4
5
6
class MathTool:
@staticmethod
def add(a, b):
return a + b

print(MathTool.add(3, 5))

输出:

1
8

三种方法对比

方法类型 第一个参数 是否访问实例属性 是否访问类属性
实例方法 self 可以 可以
类方法 cls 不直接访问 可以
静态方法 无固定参数 不访问 不访问

面向对象综合案例:银行账户

需求分析

设计一个银行账户类 BankAccount,要求:

  1. 每个账户有账户名和余额。
  2. 可以存款。
  3. 可以取款。
  4. 余额不能小于 0。
  5. 可以查看账户信息。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance

def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"成功存入{amount}元")
else:
print("存款金额必须大于0")

def withdraw(self, amount):
if amount <= 0:
print("取款金额必须大于0")
elif amount > self.__balance:
print("余额不足")
else:
self.__balance -= amount
print(f"成功取出{amount}元")

def get_balance(self):
return self.__balance

def show_info(self):
print(f"账户名:{self.owner},余额:{self.__balance}元")

account = BankAccount("张三", 1000)
account.show_info()
account.deposit(500)
account.withdraw(300)
account.withdraw(2000)
account.show_info()

输出:

1
2
3
4
5
账户名: zack, 余额: 1000
账户名: zack, 余额: 1100
账户名: zack, 余额: 800
取款的金额必须大于余额
账户名: zack, 余额: 800

案例讲解重点

这个案例同时体现了:

  1. 类和对象。
  2. 构造方法。
  3. 实例属性。
  4. 私有属性。
  5. 封装思想。
  6. 方法对数据的控制。

课堂案例:图书管理

需求

  • 设计一个图书管理系统
    1. 展示菜单
    2. 添加图书,包括作者,书名,价格,编号,数量
    3. 删除图书,根据编号和数量删除
    4. 修改图书信息
    5. 根据编号查看图书信息
    6. 将图书信息存储到本地文件
    7. 从本地文件将图书信息加载到内存
    8. 分页加载图书信息列表,根据页码加载

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
'''
演示图书管理系统开发
'''

'''
思路:
1. 定义图书类,属性 书名,作者,编号,数量
2. 定义图书管理类,管理图书,将图书放入字典中,
key为图书编号,value为这个字典
3. 在图书管理类中添加各种方法
4. 将图书信息写入文件
5. 将文件内容加载到内存中
'''
import json
import os

class Book:
def __init__(self,title,author,price,isbn,count):
self.title = title
self.author = author
self.price = price
self.isbn = isbn
self.count = count

def to_dict(self):
return {
'book_id': self.isbn,
'name': self.title,
'author': self.author,
'price': self.price,
'count': self.count
}
@staticmethod
def from_dict(book_dict):
return Book(
title=book_dict['name'],
author=book_dict['author'],
price=book_dict['price'],
isbn=book_dict['book_id'],
count=book_dict['count']
)

class BookManager:
instance = None
def __new__(cls,file_path):
if BookManager.instance is None:
BookManager.instance = super().__new__(cls)
return BookManager.instance

def __init__(self,file_path):
self.books = {}
self.file_path = file_path
self.__load_from_file()

def __load_from_file(self):
if not os.path.exists(self.file_path):
print('文件不存在,暂无数据')
return
with open(self.file_path,'r',encoding='utf-8') as f:
# data是一个列表,每个元素是字典
data = json.load(f)

self.books = {item['book_id']:Book.from_dict(item) for item in data}
print('图书信息已经加载')

def showmenu(self):
while True:
print('*'*10,'图书馆里系统','*'*10)
print('1.添加图书')
print('2.删除图书')
print('3.修改图书信息')
print('4.根据编号查看图书信息')
print('5.将图书信息存储到本地')
print('6.通过页码查看图书列表')
print('7.输入quit退出系统')
input_num = input('请输入您的操作编号: ')
if input_num == '7' or input_num == 'quit':
print(f'系统退出')
break

if input_num == '1':
self.add_book()
continue

if input_num == '2':
self.del_book()
continue

if input_num == '3':
self.modify_book()
continue

if input_num == '4':
self.check_book()
continue

if input_num == '5':
self.save_to_file()
continue

if input_num == '6':
self.show_list()
continue

print('非法操作,请继续!!!')

def add_book(self):
book_id = input('请输入图书编号: ')
count = int(input('请输入录入数量: '))
name = input('请输入书名: ')
author = input('请输入作者: ')
price = float(input('请输入价格: '))
book = Book(title=name,author=author,
price=price,isbn=book_id,count=count)
self.books[book_id] = book
print('图书添加成功!!!')

def __find_book_by_id(self,book_id):
if book_id not in self.books:
print('没有找到该图书!')
return None
return self.books[book_id]

def del_book(self):
book_id = input('请输入要删除的图书编号: ')
book = self.__find_book_by_id(book_id)
if not book:
return
del_count = int(input('请输入删除的数量: '))
if del_count < book.count:
book.count -= del_count
print('图书数量删除成功')
return

del self.books[book_id]
print('该图书已经全部删除')

def check_book(self):
book_id = input('请输入要删除的图书编号: ')
book = self.__find_book_by_id(book_id)
if not book:
return

print(f'图书信息:\n书名:{book.title}\n'
f'作者:{book.author}\n'
f'价格:{book.price}\n'
f'编号:{book.isbn}\n'
f'数量:{book.count}')

def modify_book(self):
book_id = input('请输入要删除的图书编号: ')
book = self.__find_book_by_id(book_id)
if not book:
return

new_name = input('请输入要改成的名字: ')
new_price = float(input('请输入要改成的价格: '))
new_author = input('请输入图书的作者: ')
new_count = int(input('请输入图书的数量: '))
book.title = new_name
book.author = new_author
book.price = new_price
book.count = new_count
print(f'图书信息修改成功:\n')

def show_list(self):
if not self.books:
print('暂无图书信息')
return

page = int(input('请输入查看的页码: '))
page_size = 5
start = (page-1)*page_size
end = start+page_size
book_list = list(self.books.values())
page_books = book_list[start:end]
if not page_books:
print('该页没有数据')
return

for book in page_books:
print(
f"编号:{book.isbn} | "
f"书名:{book.title} | "
f"作者:{book.author} | "
f"价格:{book.price} | "
f"数量:{book.count}"
)

def save_to_file(self):
data = [book.to_dict() for book in self.books.values()]
print(data)
with open(self.file_path, 'w', encoding='utf-8') as f:
# 参1必须是列表,字典,字符串,正数,浮点,bool,None Python自带的内置类型
# 参2是要写入的文件
# 参3是中文是否需要转义
# 参4 是否格式化json,美观输出
json.dump(data,f,ensure_ascii=False,indent=4)
print('图书信息已保存')



if __name__ == '__main__':
m = BookManager('./book_datas/books.json')
m.showmenu()
#m2 = BookManager()
#print(f'm is m2 : {m is m2}')


课后作业

作业 1:商品类

定义一个 Product 类,要求:

  1. 有商品名称、价格、库存。
  2. 价格和库存不能为负数。
  3. 有展示商品信息的方法。
  4. 有购买商品的方法。
  5. 购买时库存减少。
  6. 如果库存不足,提示“库存不足”。

作业 2:学生管理小系统

定义一个 Student 类,包含:

  • 姓名
  • 年龄
  • 成绩

再定义一个 StudentManager 类,包含:

  • 添加学生
  • 删除学生
  • 查找学生
  • 展示所有学生

作业 3:动物园案例

定义父类 Animal,包含:

  • 名字
  • 年龄
  • eat() 方法
  • speak() 方法

定义子类:

  • Dog
  • Cat
  • Bird

要求每个子类重写 speak() 方法。

实现聊天机器人

Posted on 2026-05-19 | In python , AI

前情回顾

前面我们实现了聊天机器人的基本界面。

这里我们稍作修改,进行简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
"""
该模块用于充当 聊天机器人的前端模块, 即: 接收用户录入的问题, 调用 chat_utils模块的 get_response()函数, 获取模型处理结果.
并通过 streamlit 在前端页面展示即可.
"""

# 1. 导包
import streamlit as st

# 3. 标题
st.title('🤖恋恋风辰zack的小助手')
st.header("💬 AI 聊天对话窗口")

# 4. 初始化会话状态
if 'messages' not in st.session_state:
# 如果没有历史记录,创建欢迎语
welcome_msg = {'role': 'assistant', 'content': '你好, 我是恋恋风辰zack的助手, 有什么可以帮助您的吗?👋'}
st.session_state.messages = [welcome_msg]

# 5. 遍历并显示所有消息记录
for message in st.session_state.messages:
with st.chat_message(message['role']):
st.markdown(message['content'])

# 6. 接收用户录入的问题
prompt = st.chat_input('请录入您的问题: ')

# 9. 处理用户输入
if prompt:
# 添加用户消息
user_message = {'role': 'user', 'content': prompt}
st.session_state.messages.append(user_message)
st.chat_message('user').markdown(prompt)


界面效果如下

image-20260519171848959

python请求ollama

之前我们是通过apifox或者浏览器发送http请求的

这里介绍使用ollama的包发送请求

具体参考文档地址

https://github.com/ollama/ollama-python

安装ollama库

1
pip install ollama

非流式访问

1
2
3
4
5
6
7
8
9
10
11
12
from ollama import chat
from ollama import ChatResponse

response: ChatResponse = chat(model='gemma3', messages=[
{
'role': 'user',
'content': 'Why is the sky blue?',
},
])
print(response['message']['content'])
# or access fields directly from the response object
print(response.message.content)

流式访问

1
2
3
4
5
6
7
8
9
10
from ollama import chat

stream = chat(
model='gemma3',
messages=[{'role': 'user', 'content': 'Why is the sky blue?'}],
stream=True,
)

for chunk in stream:
print(chunk['message']['content'], end='', flush=True)

当然可以通过代码访问云端api, 这里不再赘述

测试案例

我们可以实现一个代码访问本地ollama的deepseek-r1:1.5b模型

1
2
3
4
5
6
7
8
9
# 1. 导包
import ollama

# 2.定义函数, 给大语言模型发送请求, 并获取其处理结果(聊天机器人的回答)
def get_response(prompt):
# 2.1 发起请求, 获取响应信息
response = ollama.chat(model='deepseek-r1:1.5b', messages=[{'role':'user', 'content': prompt}])
# 2.2 返回模型处理后的结果(即: 聊天机器人的回答)
return response['message']['content']

调用

1
print(get_response('给我一份AI学习资料'))

打印输出如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
D:\ProgramData\anaconda3\python.exe D:\workspace\github\python_ai\01_base\09_聊天机器人搭建\02_api测试.py 
<think>
嗯,用户让我给他一份关于AI学习的攻略。首先,我得弄清楚他到底需要什么。可能他是刚开始接触AI,或者想深入学习这个领域。也有可能是希望系统地规划自己的学习路径。

那我要考虑哪些部分呢?通常,学习攻略会有基础的学习方法、适合的课程类型、以及长期目标这几个方面。用户可能希望从多个角度入手,帮助他有效学习AI。

基础学习方法方面,我应该建议一些经典算法和框架,比如深度学习中的CNN、RNN这些。然后推荐一些入门书籍或文章,这样他可以有一个起点。此外,实践是关键,所以编程练习和实战项目很重要。另外,持续学习和更新知识也是必须的,毕竟AI领域变化挺快的。

适合的学习课程类型方面,系统课程可能更全面,但需要一定的时间投入。在线课程则相对灵活,容易安排学习计划。混合型课程可以结合两种方法,这样用户可以根据自己的情况选择最适合自己节奏的课程。

长期目标和持续学习部分也很重要。目标设定要明确,不能太急或太慢。定期评估进步,并进行调整,这能让学习过程更有条理。同时,保持对新技术的关注,及时了解最新的发展动态。

最后,工具推荐是必不可少的。Python、TensorFlow这些编程语言和框架已经很普及了,现在可以结合这些工具来实践。如果需要更深入的学习,可能还要考虑相关领域的书籍或者研究论文。

总的来说,这份攻略应该涵盖基础学习、适合的课程类型、长期目标以及工具推荐等方面,帮助用户系统地学习AI,并在实践中提升自己的能力。
</think>

当然!以下是一份关于AI学习的攻略,涵盖了基础知识、学习方法和实践建议:

---

### **AI学习攻略**

#### **1. 基础学习**
- **了解核心概念**:从机器学习的基本原理开始,逐步深入到深度学习、神经网络等人工智能的核心领域。
- **经典算法与框架**:
- **深度学习**:学习Convolutional Neural Networks (CNN)、Recurrent Neural Networks (RNN)、LSTM (长短期记忆网络)、Transformer等模型。
- **计算机视觉**:掌握OpenCV、PyTorch等工具,学习图像处理、目标检测、语音识别等任务。
- **自然语言处理(NLP)**:学习NLP的基本概念和框架,如Word2Vec、BERT等模型。
- **编程与实践**:
- 学习Python(特别是深度学习相关的库如PyTorch和TensorFlow)。
- 实践项目:从简单的分类任务到复杂的人工智能项目,逐步提升自己的编程能力和解决问题的能力。

#### **2. 适合的课程类型**
- **系统课程**:通过在线平台或教材购买系统化的AI基础课程,通常包含理论知识和实践训练。
- **混合型课程**:
- **深度学习课程**:结合神经网络理论与代码实践,适合对技术感兴趣的学生。
- **应用课程**:专注于特定领域(如计算机视觉、NLP、语音识别等)的AI应用。
- **在线资源**:通过Coursera、Udemy等平台获取免费或低_cost的教程和视频课程。
- **实践型课程**:
- 适合需要 hands-on 实践的学生,通常会包括编程项目和团队合作。

#### **3. 长期目标**
- **短期目标**:完成基础课程,了解核心概念并开始简单的AI应用。
- **中期目标**:学习高级模型(如GPT、Transformer)以及相关的NLP技术。
- **长期目标**:
- 进入研究机构或公司从事AI相关的工作。
- 等待更多资源后进行进一步的扩展和深入。

#### **4. 持续学习**
- AI领域不断更新,新模型、新技术层出不穷。定期回顾和反思自己的学习效果,及时调整学习计划。
- 关注行业动态:学习最新的AI工具和技术趋势,确保学习内容与市场需求同步。

#### **5. 工具推荐**
- **编程语言**:
- Python(基本)、PyTorch、TensorFlow、Keras、Rustc pytorch框架。
- **框架与库**:
- PyTorch:适合深入理解神经网络的框架。
- TensorFlow:广泛用于AI项目和研究。
- NumPy:高效的数据处理工具。
- OpenCV:处理图像相关的任务。
- **机器学习基础**:
- Scikit-learn、Pandas、Matplotlib等库,用于数据分析和可视化。

---

### **总结**
AI学习是一个系统而持续的过程。从基础的学习开始,逐步深入到高级技术,并通过实践来提升自己的能力。希望这份攻略能帮助你快速掌握AI的基础知识,开启一段有意义的AI学习之旅!

Process finished with exit code 0

列表请求

有时候我们为了让AI具备记忆,需要发送历史会话记录列表给大模型

1
2
3
4
5
6
# 3. 因为前端传过来的问题, 是 列表 + 字典的形式, 即: 已经符合Ollama API的格式了, 我们无需做处理, 直接用.
def get_response_list(prompt):
# 2.1 发起请求, 获取响应信息
response = ollama.chat(model='deepseek-r1:1.5b', messages=prompt[-20:])
# 2.2 返回模型处理后的结果(即: 聊天机器人的回答)
return response['message']['content']

比如我们组织

1
2
3
4
5
6
7
8
9
10
11
12
13
14
prompt = [
{
'role':'user',
'content': '学习AI有什么途径?'
},
{
'role':'assistant',
'content': 'AI相关资料的网站,机器学习图书,论坛等'
},
{
'role':'user',
'content': '还有哪些呢?'
}
]

大模型回答

1
2
3
4
'message' : {
'content':'还可以去github上搜索相关项目',
'role': 'assistant'
}

综合整理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
"""
该模块用于充当 聊天机器人的前端模块, 即: 接收用户录入的问题, 调用 chat_utils模块的 get_response()函数, 获取模型处理结果.
并通过 streamlit 在前端页面展示即可.
"""

# 1. 导包
import streamlit as st
import chat_utils

# 3. 标题
st.title('🤖恋恋风辰zack的小助手')
st.divider()
st.header("💬 AI 聊天对话窗口")

# 4. 初始化会话状态
if 'messages' not in st.session_state:
# 如果没有历史记录,创建欢迎语
welcome_msg = {'role': 'assistant', 'content': '你好, 我是恋恋风辰zack的助手, 有什么可以帮助您的吗?👋'}
st.session_state['messages'] = [welcome_msg]

# 5. 遍历并显示所有消息记录
for message in st.session_state.messages:
with st.chat_message(message['role']):
st.markdown(message['content'])

# 6. 接收用户录入的问题
prompt = st.chat_input('请录入您的问题: ')

# 9. 处理用户输入
if prompt:
# 添加用户消息
user_message = {'role': 'user', 'content': prompt}
# 加入历史记录
st.session_state['messages'].append(user_message)
# 将文本显示在窗口中
st.chat_message('user').markdown(prompt)
# 调用大模型获取答案
response_text = chat_utils.get_response_list(st.session_state.messages)
ai_message = {'role': 'assistant', 'content': response_text}
# 加入历史记录
st.session_state['messages'].append(ai_message)
st.chat_message('assistant').markdown(response_text)


streamlit实现聊天页面

Posted on 2026-05-17 | In python , AI

产品概述

Streamlit 是一款基于 Python 的快速 Web 应用开发框架,无需掌握前端 HTML、CSS、JavaScript 技术,仅依靠纯 Python 代码,即可在短时间内搭建可视化网页、交互系统、数据平台、AI 对话界面等应用。

核心特点

  1. 开发效率极高

    一行代码实现页面组件,开发流程简洁,几分钟即可完成功能页面搭建。

  2. 纯 Python 开发

    全程使用 Python 语法编写,后端开发者、数据分析人员可快速上手,无前端学习成本。

  3. 原生丰富交互组件

    内置文本输入、数字框、下拉选择、单选、多选、滑块、勾选框、聊天框、按钮等常用交互控件。

  4. 会话状态持久化

    支持

    1
    session_state

    全局状态存储,可保存用户输入、聊天记录、配置参数,页面刷新数据不丢失。

  5. 实时运行预览

    代码保存自动刷新页面,调试直观,开发体验流畅。

  6. 轻量化部署

    依赖少、启动快,支持本地运行、内网部署、服务器部署,适配多种办公与开发场景。

适用应用场景

  • 内部业务管理简易后台
  • 数据分析可视化平台
  • AI 聊天机器人前端界面
  • 算法调试演示平台
  • 快速原型功能验证
  • 办公自动化交互工具

环境安装

1
pip install streamlit

项目运行命令

1
streamlit run 项目文件名.py

常用核心组件说明

组件方法 功能用途
st.set_page_config 全局页面配置
st.title / st.header 页面标题设置
st.text_input 单行文本输入框
st.number_input 数字输入框
st.selectbox 下拉选择框
st.radio 单选按钮
st.multiselect 多选选择器
st.slider 数值滑动调节器
st.checkbox 复选勾选框
st.button 功能触发按钮
st.chat_input 对话聊天输入框
st.chat_message 聊天气泡展示
st.sidebar 侧边栏布局容器
st.columns 页面分栏布局
st.session_state 全局数据状态存储

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import streamlit as st

# 页面全局配置
st.set_page_config(
page_title="Streamlit 聊天机器人教学版",
page_icon="🤖",
layout="wide"
)

# 页面标题与说明
st.title("🤖 Streamlit AI 聊天机器人演示")
st.markdown("### 核心功能:各类用户输入控件 + 标准聊天界面")
st.divider()

# 侧边栏:机器人参数配置区
with st.sidebar:
st.title("⚙️ 机器人设置")
bot_name = st.text_input("机器人名称", value="智能助手")
model_mode = st.selectbox(
"选择对话模型风格",
["通用闲聊", "专业技术问答", "文案创作", "心理咨询"]
)
reply_len = st.radio("回复长度", ["简短", "中等", "详细"])
enable_func = st.multiselect(
"开启附加功能",
["联网搜索", "代码解释", "翻译", "总结"]
)
temperature = st.slider(
"AI 创意程度",
min_value=0.0,
max_value=1.0,
value=0.7,
step=0.1
)
auto_clear = st.checkbox("发送后自动清空输入", value=False)

# 主页面:用户信息输入区
st.header("👤 用户信息输入区")
col1, col2 = st.columns(2)
with col1:
user_nick = st.text_input("你的昵称", placeholder="请输入昵称...")
user_age = st.number_input(
"你的年龄",
min_value=1,
max_value=120,
value=25
)
with col2:
user_gender = st.selectbox("性别", ["男", "女", "保密"])
user_mood = st.slider("当前心情指数", 0, 10, 5)

st.divider()

# 核心模块:聊天窗口
st.header("💬 AI 聊天对话窗口")

# 初始化聊天历史
if "messages" not in st.session_state:
st.session_state.messages = []

# 渲染历史聊天记录
for msg in st.session_state.messages:
with st.chat_message(msg["role"]):
st.markdown(msg["content"])

# 聊天输入框
user_input = st.chat_input("请在这里输入想问的问题...")

# 处理用户输入与AI回复
if user_input:
st.session_state.messages.append({"role": "user", "content": user_input})
with st.chat_message("user"):
st.markdown(user_input)

# 模拟AI回复(可替换为大模型API调用)
ai_reply = f"""
你好 {user_nick if user_nick else "朋友"}!👋
你当前的心情指数:{user_mood}分(满分10分)
你发送的内容:{user_input}
---
🤖 机器人配置:
- 机器人名称:{bot_name}
- 对话模式:{model_mode}
- 回复长度:{reply_len}
- AI创意度:{temperature}
- 开启功能:{", ".join(enable_func) if enable_func else "无"}
---
提示:此为模拟回复,替换大模型API后可实现真实AI对话!
"""

st.session_state.messages.append({"role": "assistant", "content": ai_reply})
with st.chat_message("assistant"):
st.markdown(ai_reply)

if auto_clear:
st.rerun()

# 清空聊天记录按钮
if st.button("🔄 清空全部聊天记录"):
st.session_state.messages = []
st.rerun()

# 大模型对接示例(伪代码)
# import openai
# response = openai.ChatCompletion.create(
# model="gpt-3.5-turbo",
# messages=st.session_state.messages
# )
# ai_reply = response.choices[0].message.content

聊天机器人搭建

Posted on 2026-05-11 | In python , AI

项目简介

本项目基于Ollama本地大模型,利用streamlit实现前端界面,实现聊天对话机器人。

整体界面如下:

image-20260513153722042

源码地址

连接地址: https://github.com/secondtonone1/python_ai

Ollama简介

Ollama:是一款旨在简化大型语言模型本地部署和运行过程的开源软件。

中文名:羊驼

网址:https://ollama.com/

Ollama 是一款开源、跨平台的本地大语言模型(LLM)运行与管理工具,采用 MIT 许可,核心目标是让用户在个人设备上零门槛部署、管理和运行各类开源大模型,被称为 “大模型领域的 Docker”。

imgimage

核心定位

  • 本地化优先:所有数据与计算在本机完成,不上云,保障隐私安全与离线可用。
  • 极简部署:封装底层环境、格式转换、硬件适配等复杂细节,实现一条命令安装、一条命令拉取 / 运行模型。
  • 模型管家:统一管理主流开源模型,支持一键下载、删除、更新与版本控制。

主要特点

  1. 跨平台:支持 Windows、macOS、Linux,也可通过 Docker 部署。
  2. 硬件友好:自动识别并调用 NVIDIA/AMD GPU、Apple Silicon 加速;支持 GGUF 量化,降低显存门槛,普通电脑也能运行 7B–70B 模型。
  3. 丰富模型库:内置 Llama 3、Qwen、DeepSeek、Mistral、Gemma 等主流开源模型,支持社区自定义模型。
  4. 标准 REST API:默认提供 http://localhost:11434 接口,涵盖模型管理、对话、生成、流式输出等,兼容 OpenAI 接口格式,便于集成到 Apifox、Postman 或自研应用。
  5. 开发者友好:提供 Python/JS/Go 等 SDK,支持多轮对话、上下文记忆、思考链输出与工具调用。

典型用途

  • 隐私敏感场景(企业内部文档、个人数据处理)
  • 离线环境(无网 / 弱网)智能助手开发
  • 快速本地调试与原型验证(无需云端账号)
  • 构建私有知识库与 RAG 应用Ollama

Ollama部署安装

环境说明

  • 支持系统:Windows / macOS / Linux
  • 默认服务端口:11434
  • 安装后默认本地接口地址:http://localhost:11434
  • 自动后台常驻运行,开机自启

Windows 安装

  1. 官网下载安装包:https://ollama.com/download/windows
  2. 双击 OllamaSetup.exe 默认下一步安装即可
  3. 安装完成自动启动后台服务
  4. 验证安装(CMD/PowerShell 执行)
image-20260511210017799

安装好后重新开cmd测试

1
ollama --version

macOS 安装

方式 1:官网安装包

下载:https://ollama.com/download/mac

拖拽到应用程序,打开即自动部署后台服务。

方式 2:Homebrew 安装

1
brew install ollama

启动服务:

1
ollama serve

Linux 安装(推荐一键脚本)

一键安装(通用)

运行

1
curl -fsSL https://ollama.com/install.sh | sh
  • 自动配置系统服务、开机自启、后台守护进程
  • 安装完成直接可用

手动启停服务

运行

1
2
3
4
5
6
7
8
# 启动
systemctl start ollama

# 停止
systemctl stop ollama

# 开机自启
systemctl enable ollama

Docker 容器安装(服务器 / 隔离环境推荐)

1. 拉取镜像并启动

运行

1
2
3
4
5
docker run -d \
-v ollama:/root/.ollama \
-p 11434:11434 \
--name ollama \
ollama/ollama

2. 容器内操作模型

运行

1
2
3
4
5
# 进入容器
docker exec -it ollama bash

# 拉取并运行模型
ollama run llama3

安装后基础验证(全平台通用)

  1. 命令行测试拉取模型运行
1
ollama run deepseek-r1:1.5b
  1. API 接口连通性测试

运行

1
curl http://localhost:11434/api/tags

能返回本地模型列表即安装部署正常。

Ollama命令

基础版本与服务命令

运行

1
2
3
4
5
# 查看版本
ollama --version

# 手动启动服务
ollama serve

模型拉取与运行

运行

1
2
3
4
5
# 拉取模型
ollama pull 模型名

# 交互式运行模型
ollama run 模型名

模型管理命令

运行

1
2
3
4
5
6
7
8
# 查看本地已安装所有模型
ollama list

# 删除本地模型
ollama rm 模型名

# 查看模型详细信息
ollama show 模型名

进阶常用命令

运行

1
2
3
4
5
6
7
8
# 停止正在运行的模型
ollama stop 模型名

# 查看运行日志
ollama logs

# 复制模型(自定义别名)
ollama create 新模型名 -f Modelfile

编程代码助手(适合 IT 教学)

新建文件命名 Modelfile,内容:

1
2
3
4
5
6
7
FROM llama3:latest
PARAMETER temperature 0.3
SYSTEM """
你是资深编程老师,擅长Python、C++、Go、Rust、Java、嵌入式开发。
讲解代码通俗易懂,给出完整可运行示例,
会加关键注释,一步步拆解思路,适合零基础学习。
"""

创建命令:

1
ollama create code-teacher -f Modelfile

运行

1
ollama run code-teacher

HTTP协议基础

Ollama启动后将作为本地HTTP接口服务运行,所有模型管理、对话生成等操作均需通过HTTP请求完成。因此,掌握HTTP基础是实现Ollama对接的前提。

HTTP协议定义

HTTP(超文本传输协议)是客户端与服务端之间进行数据通信的通用网络协议,规定了客户端发起请求、服务端返回响应的标准格式与交互规则。在Ollama对接场景中,客户端(Apifox、代码、浏览器等)通过HTTP请求与Ollama服务端进行交互,获取模型服务。

核心角色

  • 客户端:主动发起HTTP请求的主体,包括Apifox、Python/Java代码、浏览器、嵌入式程序等。
  • 服务端:被动接收并响应HTTP请求的主体,即本地启动的Ollama服务,默认占用11434端口。

核心请求方式

Ollama接口仅涉及两种HTTP请求方式,根据业务场景选择使用:

GET请求

用途:从Ollama服务端获取已存在的数据,不修改服务端数据。

特点:无请求体,请求参数可拼接在URL末尾,适用于查询类操作。

POST请求

用途:向Ollama服务端提交数据、发起业务请求,如对话交互、文本生成、模型拉取等。

特点:支持请求体,常用JSON格式传递参数,数据传输安全、可承载内容量大,适用于提交类、指令类操作。

HTTP接口三要素

调用任何HTTP接口,需明确以下三要素,Ollama接口也遵循此规则:

  1. 请求地址(URL):Ollama服务的基础地址固定为 http://localhost:11434,接口路径拼接在基础地址后。
  2. 请求方法(GET/POST):根据接口功能选择,查询用GET,提交/指令用POST。
  3. 请求体/参数:POST请求通过JSON格式传递参数(如模型名、提问内容);GET请求参数可省略或拼接在URL后。

地址区分

image-20260513172535849

image-20260513173645449

浏览器访问Ollama服务

image-20260513173930263

数据交互格式

Ollama所有接口的请求参数与响应数据均采用JSON格式。JSON为键值对结构,具有易读性强、跨编程语言兼容的特点,可被Python、Java、嵌入式开发相关编程语言直接解析。

Ollama的核心运行机制是启动本地HTTP服务,监听11434端口。开发者通过客户端发起HTTP请求,向Ollama服务端传递指令(如查询模型、发起对话),Ollama服务端执行对应操作后,以JSON格式返回响应数据。掌握HTTP基础后,即可通过标准HTTP请求实现Ollama接口的调用。

Ollama核心API接口

以下为Ollama常用核心API接口,结合前文HTTP基础知识点,明确接口调用细节,可直接用于实操调试。

查看本地模型列表

用途:查询本地已安装的所有Ollama模型信息,包括模型名、修改时间、大小等。

  • 请求方式:GET
  • 接口地址:http://localhost:11434/api/tags
  • 实操说明:使用Apifox或浏览器,选择GET请求,输入接口地址,发起请求后,将返回JSON格式的本地模型列表。

大模型对话接口

用途:与Ollama模型进行多轮对话交互,适用于智能助手、聊天类功能开发。

  • 请求方式:POST
  • 接口地址:http://localhost:11434/api/chat
  • 请求体(JSON格式):

客户端发送请求

1
2
3
4
5
{
"model": "deepseek-r1:1.5b",
"messages": [{"role": "user", "content": "有什么减肥方案"}],
"stream": false
}

服务器回复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"model": "deepseek-r1:1.5b",
"created_at": "2026-05-17T02:15:27.140973Z",
"message": {
"role": "assistant",
"content": "<think>\n嗯,我现在想减肥,可是有点不知道从哪里开始。我记得之前没动过动脑筋,只是简单地吃得多喝得多。可是现在想减肥了,怎么办呢?我需要一个具体的计划,而不是走一遍水路。\n\n首先,我觉得要制定一个长期的计划比较好,毕竟减肥不是一蹴而就的事情。不过具体怎么制定呢?是不是需要先了解自己的身体状况,比如体重、身高、体型、还有最近吃的东西和运动的情况?\n\n然后,饮食方面我有点犹豫。以前总是吃 lots of junk food,好像长得很高。现在如果想要减肥,早餐应该怎么做?比如,早上要不要吃早饭?有没有什么容易引起肚子痛的食物,或者容易反弹的食物呢?\n\n接着,运动也很重要,可是我现在不太喜欢动。不过我可以试着找一些小技巧,比如走一下楼梯、坐一下镜子之类的。但问题是,这些小动作可能效果有限,我需要坚持下去吗?\n\n另外,还有其他方面的因素,比如体重管理和心情调整,这些也需要考虑进去吗?因为有时候只是吃不消,或者运动也不如预期,可能会导致心情不好,甚至反弹减肥。\n\n我还发现以前的饮食习惯比较单一,比如多吃肉类、蔬菜少食乳制品之类的。现在是不是应该改变一下饮食结构,多摄入一些蛋白质和健康脂肪,同时控制总热量摄入?\n\n我还不太清楚如何具体实施这些计划。或许我可以先看看有没有什么资源或者工具能帮助我制定减肥计划?比如手机上的减肥APP、专业医生的建议等等。\n\n还有一个问题是,如果体重管理不好,可能会导致反弹。这时候该怎么办呢?是不是需要给自己设定一个合理的目标,不急于减少,而是逐步减少,同时注意不要吃太多或过量的零食和饮料?\n\n总之,我觉得我需要从以下几个方面入手:\n\n1. 了解自己的身体状况,制定长期计划。\n2. 制定饮食计划,选择易消化的食物和适量的运动。\n3. 改变饮食习惯,增加健康脂肪摄入,减少高热量食物。\n4. 注意体重管理,设定合理的目标,避免反弹。\n5. 给自己时间和坚持,不要因为一点事情而放弃。\n\n不过现在还不知道具体怎么操作,可能需要更多的实际例子或者步骤。或许可以先从制定计划开始,看看有没有可行的方法。\n\n比如,我听说过有特定的饮食建议,比如每天吃3个苹果、一个橙子之类的健康水果,这样能提供足够的营养和能量。还有均衡性的饮食结构,比如多吃蔬菜、瘦肉,少食高糖高脂食物。\n\n运动方面,我可以尝试散步、跑步或者其他可以增加热量消耗的运动方式,但需要有意识地进行,不能随便动来动去,否则容易受伤或者感到不适。\n\n另外,我还记得之前有一些减肥视频,里面有很多技巧,比如定时吃饭、避免过多摄入糖分等等。或许这些视频能给我一些实用的建议和方法。\n\n还有,我还需要了解自己的体脂率,看看目前属于轻薄还是肥胖,进而调整饮食和运动计划。如果体脂率较高,可能需要更多的控制和变化,但如果是过胖的话,可能需要更严格的限制。\n\n也许我可以先从最基础的部分开始,比如每天吃3-4个苹果、一个橙子,或者更多的健康水果,来补充能量和蛋白质。同时,每周安排一定的时间进行小的运动,比如散步或做简单的拉伸,这样可以增加肌肉量,也有助于保持体重。\n\n另外,饮食方面,我可以尽量减少高脂肪食物和高糖食物,多吃一些低脂、低饱和的食物,同时避免辛辣刺激性食物,这些都是容易反弹的食物来源。\n\n关于运动,我需要找到合适的时间进行锻炼,比如在下午或者晚上,而不是早上或晚上。这样可以保证有足够的睡眠时间,有助于恢复体力。\n\n还有,如果我最近有其他健康问题,比如高血压或者糖尿病,可能会影响减肥效果,这时候需要特别注意,避免过度消耗能量或者增加饮食中的热量来源。\n\n总的来说,我觉得制定一个合理的计划是一个关键步骤,不过需要耐心和坚持。可能我会遇到一些困难,但只要逐步调整和坚持下去,应该能够达到目标体重。\n</think>\n\n为了有效进行减肥,以下是一份详细的计划建议:\n\n### 1. 定期制定长期计划\n- **设定目标**:明确减肥的目标,例如每周减少5-7公斤,或者达到某个体脂率(如20%-25%)。\n- **了解身体状况**:检查体重、身高、体型和最近饮食和运动的记录。定期复查这些数据,确保信息无误。\n\n### 2. 制定饮食计划\n- **蛋白质摄入**:每天吃3-4个健康水果(如苹果、橘子)、一个蔬菜(如胡萝卜、青菜)和1-2个瘦肉(如鸡肉、 ham),均衡增加蛋白质。\n- **高热量食物控制**:避免过多摄入糖分和油炸食品,选择低脂、低饱和的高蛋白零食,如坚果、燕麦等。\n- **健康脂肪摄入**:每天摄入至少1.5-2杯豆类或水果(如菠菜)、少量鱼肉或鸡肉,增加纤维和热量来源。\n- **控制总热量**:减少食糖和油炸食物的摄入,通过减少零食选择低糖高纤维食物来平衡热量。\n\n### 3. 改变饮食习惯\n- **定时吃饭**:吃三餐后2-3小时分餐,避免过量或突然用餐。\n- **避免过量零食**:避免吃过多盐、油和巧克力,尤其是那些容易反弹的零食。\n- **减少高脂肪食物**:如肥肉、鸡胸肉、红烧鱼等,增加健康脂肪摄入。\n\n### 4. 注意体重管理\n- **设定合理目标**:通过饮食调整和运动,设定每周或每月体重目标(如1公斤/周)。\n- **避免极端减肥**:不要过度减肥,避免暴饮暴食导致反弹。保持平和心态,逐步减少不必要的零食摄入。\n\n### 5. 给予心情调整\n- **设定小目标**:每天设定较小的减重目标,例如“今天吃10个苹果”,逐步增加难度。\n- **记录饮食和运动**:详细记录每周的饮食、运动和心理活动,帮助分析并及时调整计划。\n\n### 6. 持续坚持\n- **减少外界压力**:学会控制情绪,避免过度减肥带来的焦虑或反弹感。\n- **寻求专业建议**:如有健康问题(如高血压/糖尿病),咨询医生以确保运动和饮食的合理性。\n\n通过以上步骤,逐步制定和执行计划,有助于有效减肥。耐心和坚持是关键,调整和优化计划需要时日积累,逐步实现目标体重。"
},
"done_reason": "stop",
"done": true,
"total_duration": 14123381000,
"load_duration": 38089100,
"prompt_eval_count": 6,
"prompt_eval_duration": 15000000,
"eval_count": 1420,
"eval_duration": 14069000000
}

让模型有记忆

客户端追问问题,需要在原有的请求和回答的基础上,继续追问才可以

将客户端和ollama服务器回复的结果,做成列表,放入messages中

1
2
3
4
5
6
7
8
9
{
"model": "deepseek-r1:1.5b",
"messages": [
{"role": "user", "content": "有什么减肥方案?"},
{"role": "assistant", "content": "<think>\n嗯,我现在想减肥,可是有点不知道从哪里开始。我记得之前没动过动脑筋,只是简单地吃得多喝得多。可是现在想减肥了,怎么办呢?我需要一个具体的计划,而不是走一遍水路。\n\n首先,我觉得要制定一个长期的计划比较好,毕竟减肥不是一蹴而就的事情。不过具体怎么制定呢?是不是需要先了解自己的身体状况,比如体重、身高、体型、还有最近吃的东西和运动的情况?\n\n然后,饮食方面我有点犹豫。以前总是吃 lots of junk food,好像长得很高。现在如果想要减肥,早餐应该怎么做?比如,早上要不要吃早饭?有没有什么容易引起肚子痛的食物,或者容易反弹的食物呢?\n\n接着,运动也很重要,可是我现在不太喜欢动。不过我可以试着找一些小技巧,比如走一下楼梯、坐一下镜子之类的。但问题是,这些小动作可能效果有限,我需要坚持下去吗?\n\n另外,还有其他方面的因素,比如体重管理和心情调整,这些也需要考虑进去吗?因为有时候只是吃不消,或者运动也不如预期,可能会导致心情不好,甚至反弹减肥。\n\n我还发现以前的饮食习惯比较单一,比如多吃肉类、蔬菜少食乳制品之类的。现在是不是应该改变一下饮食结构,多摄入一些蛋白质和健康脂肪,同时控制总热量摄入?\n\n我还不太清楚如何具体实施这些计划。或许我可以先看看有没有什么资源或者工具能帮助我制定减肥计划?比如手机上的减肥APP、专业医生的建议等等。\n\n还有一个问题是,如果体重管理不好,可能会导致反弹。这时候该怎么办呢?是不是需要给自己设定一个合理的目标,不急于减少,而是逐步减少,同时注意不要吃太多或过量的零食和饮料?\n\n总之,我觉得我需要从以下几个方面入手:\n\n1. 了解自己的身体状况,制定长期计划。\n2. 制定饮食计划,选择易消化的食物和适量的运动。\n3. 改变饮食习惯,增加健康脂肪摄入,减少高热量食物。\n4. 注意体重管理,设定合理的目标,避免反弹。\n5. 给自己时间和坚持,不要因为一点事情而放弃。\n\n不过现在还不知道具体怎么操作,可能需要更多的实际例子或者步骤。或许可以先从制定计划开始,看看有没有可行的方法。\n\n比如,我听说过有特定的饮食建议,比如每天吃3个苹果、一个橙子之类的健康水果,这样能提供足够的营养和能量。还有均衡性的饮食结构,比如多吃蔬菜、瘦肉,少食高糖高脂食物。\n\n运动方面,我可以尝试散步、跑步或者其他可以增加热量消耗的运动方式,但需要有意识地进行,不能随便动来动去,否则容易受伤或者感到不适。\n\n另外,我还记得之前有一些减肥视频,里面有很多技巧,比如定时吃饭、避免过多摄入糖分等等。或许这些视频能给我一些实用的建议和方法。\n\n还有,我还需要了解自己的体脂率,看看目前属于轻薄还是肥胖,进而调整饮食和运动计划。如果体脂率较高,可能需要更多的控制和变化,但如果是过胖的话,可能需要更严格的限制。\n\n也许我可以先从最基础的部分开始,比如每天吃3-4个苹果、一个橙子,或者更多的健康水果,来补充能量和蛋白质。同时,每周安排一定的时间进行小的运动,比如散步或做简单的拉伸,这样可以增加肌肉量,也有助于保持体重。\n\n另外,饮食方面,我可以尽量减少高脂肪食物和高糖食物,多吃一些低脂、低饱和的食物,同时避免辛辣刺激性食物,这些都是容易反弹的食物来源。\n\n关于运动,我需要找到合适的时间进行锻炼,比如在下午或者晚上,而不是早上或晚上。这样可以保证有足够的睡眠时间,有助于恢复体力。\n\n还有,如果我最近有其他健康问题,比如高血压或者糖尿病,可能会影响减肥效果,这时候需要特别注意,避免过度消耗能量或者增加饮食中的热量来源。\n\n总的来说,我觉得制定一个合理的计划是一个关键步骤,不过需要耐心和坚持。可能我会遇到一些困难,但只要逐步调整和坚持下去,应该能够达到目标体重。\n</think>\n\n为了有效进行减肥,以下是一份详细的计划建议:\n\n### 1. 定期制定长期计划\n- **设定目标**:明确减肥的目标,例如每周减少5-7公斤,或者达到某个体脂率(如20%-25%)。\n- **了解身体状况**:检查体重、身高、体型和最近饮食和运动的记录。定期复查这些数据,确保信息无误。\n\n### 2. 制定饮食计划\n- **蛋白质摄入**:每天吃3-4个健康水果(如苹果、橘子)、一个蔬菜(如胡萝卜、青菜)和1-2个瘦肉(如鸡肉、 ham),均衡增加蛋白质。\n- **高热量食物控制**:避免过多摄入糖分和油炸食品,选择低脂、低饱和的高蛋白零食,如坚果、燕麦等。\n- **健康脂肪摄入**:每天摄入至少1.5-2杯豆类或水果(如菠菜)、少量鱼肉或鸡肉,增加纤维和热量来源。\n- **控制总热量**:减少食糖和油炸食物的摄入,通过减少零食选择低糖高纤维食物来平衡热量。\n\n### 3. 改变饮食习惯\n- **定时吃饭**:吃三餐后2-3小时分餐,避免过量或突然用餐。\n- **避免过量零食**:避免吃过多盐、油和巧克力,尤其是那些容易反弹的零食。\n- **减少高脂肪食物**:如肥肉、鸡胸肉、红烧鱼等,增加健康脂肪摄入。\n\n### 4. 注意体重管理\n- **设定合理目标**:通过饮食调整和运动,设定每周或每月体重目标(如1公斤/周)。\n- **避免极端减肥**:不要过度减肥,避免暴饮暴食导致反弹。保持平和心态,逐步减少不必要的零食摄入。\n\n### 5. 给予心情调整\n- **设定小目标**:每天设定较小的减重目标,例如“今天吃10个苹果”,逐步增加难度。\n- **记录饮食和运动**:详细记录每周的饮食、运动和心理活动,帮助分析并及时调整计划。\n\n### 6. 持续坚持\n- **减少外界压力**:学会控制情绪,避免过度减肥带来的焦虑或反弹感。\n- **寻求专业建议**:如有健康问题(如高血压/糖尿病),咨询医生以确保运动和饮食的合理性。\n\n通过以上步骤,逐步制定和执行计划,有助于有效减肥。耐心和坚持是关键,调整和优化计划需要时日积累,逐步实现目标体重。"},
{"role": "user", "content": "还有什么推荐的方案吗?"}
],
"stream": false
}

文本生成接口

用途:根据指定提示词,让Ollama模型生成文本内容(非对话场景,如文案、代码生成)。

  • 请求方式:POST
  • 接口地址:http://localhost:11434/api/generate
  • 请求体(JSON格式):
1
2
3
4
5
{
"model": "deepseek-r1:1.5b", // 模型名称
"prompt": "写一段Python大模型开发的入门文案", // 文本生成提示词
"stream": false // 关闭流式输出
}

接口通用注意事项

  • 调用接口前,需确保Ollama服务已启动,且端口11434未被占用。
  • POST请求必须指定请求头Content-Type: application/json,否则会导致请求失败。
  • 请求体中model参数需填写本地已安装的模型名称,否则会返回模型不存在错误。
  • stream参数设为true时,将采用流式输出(实时返回响应内容),适合前端展示;false为一次性返回完整响应。

ApiFox对接Ollama

ApiFox官网

https://apifox.com/?utm_source=bing&utm_medium=sem&utm_campaign=%E5%93%81%E7%89%8C%E8%AF%8D-Apifox&utm_content=Apifox-%E6%A0%B8%E5%BF%83&utm_term=apifox%E5%AE%98%E7%BD%91&search_term=apifox%E5%AE%98%E7%BD%91&msclkid=dee3617345c31b6b779e0b775b7a686a

image-20260513144901892

ApiFox测试HTTP

创建项目

image-20260513154216396

选择空白项目

image-20260513154509965

输入请求连接和内容

image-20260513155138937

请求内容

1
2
3
4
5
{
"model": "deepseek-r1:1.5b",
"messages": [{"role": "user", "content": "你好,介绍一下自己"}],
"stream": false
}

请求链接

1
http://localhost:11434/api/chat

回复内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"model": "deepseek-r1:1.5b",
"created_at": "2026-05-13T07:49:24.5359256Z",
"message": {
"role": "assistant",
"content": "<think>\n\n</think>\n\n你好!我是DeepSeek-R1,一个由深度求索公司开发的智能助手。关于我以及我的能力,我始终遵循人工智能的原理与规则,旨在为您提供准确和 helpful 的信息。请问有什么可以为您服务的?"
},
"done_reason": "stop",
"done": true,
"total_duration": 1597406000,
"load_duration": 967306500,
"prompt_eval_count": 7,
"prompt_eval_duration": 128000000,
"eval_count": 54,
"eval_duration": 499000000
}

使用Ollama规则构建apifox的请求项目

我用豆包生成了一个Ollama.apifox.json文件,放入资料包了,大家可以去源码中查看

https://github.com/secondtonone1/python_ai/blob/master/base/09_%E8%81%8A%E5%A4%A9%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%90%AD%E5%BB%BA/%E8%B5%84%E6%96%99/Ollama.apifox.json

可以再次新建一个项目,这次导入json文件即可

image-20260513161350844

image-20260513161419068

image-20260513161533735

image-20260513161612437

image-20260513161732797

image-20260513162155766

接口环境配置

image-20260513162312649

image-20260513162442785

image-20260513162518623

调试

调试内容和之前一致,无需修改

1
2
3
4
5
{
"model": "deepseek-r1:1.5b",
"messages": [{"role": "user", "content": "你好,介绍一下自己"}],
"stream": false
}

image-20260513165205931

Python文件读写和异常处理

Posted on 2026-05-03 | In python , AI

目录

  1. 文件读写基础
  2. 异常处理
  3. 包与模块管理
  4. 实战项目

第一部分:文件读写基础

为什么要学文件操作?

程序运行时的数据存储在内存中,程序结束后数据就消失了。文件操作让我们能够:

  • 数据持久化:保存数据到硬盘
  • 数据读取:读取之前保存的数据
  • 大数据处理:处理超过内存容量的数据
  • 日志记录:记录程序运行过程

文件操作的三个步骤

1
2
3
4
5
1. 打开文件 (open)
↓
2. 读写文件 (read/write)
↓
3. 关闭文件 (close)

open() 函数详解

基本语法

1
open(filename, mode, encoding)

参数说明

参数 说明 示例
filename 文件名或路径 ‘example.txt’, ‘./data/file.txt’
mode 打开模式 ‘r’, ‘w’, ‘a’, ‘x’
encoding 编码方式 ‘utf-8’, ‘gbk’

打开模式详解

模式 含义 文件存在 文件不存在
‘r’ 读取(默认) 打开 报错
‘w’ 写入 覆盖 创建
‘a’ 追加 追加 创建
‘x’ 创建 报错 创建

最简单的文件读取

1
2
3
4
# 方式1:一次性读取全部内容
with open('example.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content)

为什么使用 with 语句?

  • 自动打开文件
  • 自动关闭文件
  • 即使发生错误也会关闭文件
  • 代码更简洁

三种读取方式

方式1:read() - 一次性读取全部

1
2
with open('example.txt', 'r', encoding='utf-8') as f:
content = f.read() # 返回字符串

适用场景:小文件,需要全部内容
风险:大文件会占用大量内存

方式2:readline() - 逐行读取

1
2
3
4
5
with open('example.txt', 'r', encoding='utf-8') as f:
line = f.readline() # 读取一行
while line:
print(line.rstrip('\n')) # 移除换行符
line = f.readline()

适用场景:需要逐行处理
优点:内存占用少

方式3:readlines() - 读取所有行到列表

1
2
3
4
with open('example.txt', 'r', encoding='utf-8') as f:
lines = f.readlines() # 返回列表
for i, line in enumerate(lines):
print(f"第{i+1}行: {line.rstrip()}")

适用场景:需要随机访问行
风险:大文件占用内存

方式4:迭代读取(最推荐)

1
2
3
with open('example.txt', 'r', encoding='utf-8') as f:
for line in f: # 最高效的方式
print(line.rstrip('\n'))

优点:

  • 内存效率最高
  • 代码最简洁
  • 最Pythonic

处理大文件

分块读取

1
2
3
4
5
6
7
8
def read_large_file_by_chunks(filename, chunk_size=1024):
"""分块读取大文件"""
with open(filename, 'r', encoding='utf-8') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
process_data(chunk)

按行分块读取

1
2
3
4
5
6
7
8
9
10
11
12
def read_large_file_by_lines(filename, batch_size=1000):
"""按行分块读取大文件"""
batch = []
with open(filename, 'r', encoding='utf-8') as f:
# 从文件中按照行读取
for line in f:
batch.append(line.rstrip('\n'))
if len(batch) == batch_size:
process_batch(batch)
batch = []
if batch:
process_batch(batch)

文件写入

写入模式

1
2
3
4
5
6
7
8
9
10
11
12
13
# 模式1:写入(覆盖)
with open('output.txt', 'w', encoding='utf-8') as f:
f.write('第一行\n')
f.write('第二行\n')

# 模式2:追加
with open('output.txt', 'a', encoding='utf-8') as f:
f.write('追加的内容\n')

# 模式3:使用 print() 函数
with open('output.txt', 'w', encoding='utf-8') as f:
print('第一行', file=f)
print('第二行', file=f)

写入列表

1
2
3
lines = ['第一行\n', '第二行\n', '第三行\n']
with open('output.txt', 'w', encoding='utf-8') as f:
f.writelines(lines)

文件指针操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
with open('example.txt', 'r', encoding='utf-8') as f:
# tell() - 获取当前位置
print(f.tell()) # 0

# 读取 10 个字符
content = f.read(10)
print(f.tell()) # 10

# seek() - 移动指针
f.seek(0) # 回到开头
f.seek(5, 0) # 从开头移动 5 字节

# 获取文件大小
f.seek(0, 2)
file_size = f.tell()

处理 CSV 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 读取 CSV
def read_csv(filename):
students = []
with open(filename, 'r', encoding='utf-8') as f:
f.readline() # 跳过表头
for line in f:
name, age, score = line.rstrip('\n').split(',')
students.append({
'name': name,
'age': int(age),
'score': int(score)
})
return students

# 写入 CSV
def write_csv(filename, students):
with open(filename, 'w', encoding='utf-8') as f:
f.write('name,age,score\n')
for student in students:
line = f"{student['name']},{student['age']},{student['score']}\n"
f.write(line)

处理 JSON 文件

1
2
3
4
5
6
7
8
9
10
11
12
import json

# 读取 JSON
def read_json(filename):
with open(filename, 'r', encoding='utf-8') as f:
data = json.load(f)
return data

# 写入 JSON
def write_json(filename, data):
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)

第二部分:异常处理

什么是异常?

异常是程序执行过程中发生的错误事件。

常见异常类型:

  • FileNotFoundError - 文件不存在
  • PermissionError - 没有权限
  • ValueError - 值错误
  • TypeError - 类型错误
  • ZeroDivisionError - 除以零
  • IndexError - 索引超出范围
  • KeyError - 字典键不存在

为什么需要异常处理?

  1. 防止程序崩溃 - 程序可以继续运行
  2. 提供有意义的错误信息 - 帮助用户理解问题
  3. 采取恢复措施 - 尝试其他方法或保存数据

基础异常处理:try-except

基本结构

1
2
3
4
try:
# 可能出错的代码
except 异常类型:
# 处理异常的代码

示例1:捕获特定异常

1
2
3
4
5
6
7
8
9
10
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("错误:不能除以零")
return None
return result

print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # 错误:不能除以零

示例2:文件操作中的异常处理

1
2
3
4
5
6
7
8
def safe_read_file(filename):
try:
with open(filename, 'r', encoding='utf-8') as f:
content = f.read()
return content
except FileNotFoundError:
print(f"错误:文件 '{filename}' 不存在")
return None

2.4 捕获多个异常

方式1:多个 except 块

1
2
3
4
5
6
7
8
9
try:
with open(filename, 'r', encoding='utf-8') as f:
content = f.read()
except FileNotFoundError:
print("错误:文件不存在")
except PermissionError:
print("错误:没有权限")
except UnicodeDecodeError:
print("错误:编码错误")

方式2:一个 except 块捕获多个异常

1
2
3
4
5
try:
with open(filename, 'r', encoding='utf-8') as f:
content = f.read()
except (FileNotFoundError, PermissionError):
print("错误:文件访问失败")

获取异常信息

1
2
3
4
5
6
try:
with open('nonexistent.txt', 'r') as f:
content = f.read()
except FileNotFoundError as e:
print(f"异常类型: {type(e)}")
print(f"异常信息: {e}")

完整的异常处理结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try:
# 可能出错的代码
with open('example.txt', 'r', encoding='utf-8') as f:
content = f.read()
except FileNotFoundError:
print("错误:文件不存在")
except Exception as e:
print(f"发生错误: {e}")
else:
# 只有在 try 块成功执行时才运行
print(f"文件读取成功,内容长度: {len(content)} 字符")
finally:
# 无论如何都会执行
print("文件操作完成")

执行流程:

  1. 执行 try 块
  2. 如果发生异常,执行对应的 except 块
  3. 如果没有异常,执行 else 块
  4. 无论如何,最后执行 finally 块

finally 块的重要用途

1
2
3
4
5
6
7
8
9
10
11
f = None
try:
f = open('example.txt', 'r', encoding='utf-8')
content = f.read()
except FileNotFoundError:
print("错误:文件不存在")
finally:
# 即使发生异常,这里也会执行
if f is not None:
f.close()
print("文件已关闭")

注意:这种方式不如 with 语句安全,推荐使用 with 语句。

异常的传播

1
2
3
4
5
6
7
8
9
10
11
12
13
def func2():
x = 1 / 0 # 这里会抛出异常

def func1():
func2() # 异常会从这里传播出去

def main():
try:
func1()
except ZeroDivisionError:
print("捕获到异常:除以零")

main()

异常传播链:

1
2
3
4
5
6
7
8
9
main() 
↓
func1()
↓
func2() (异常发生)
↓
func1() (没有处理,继续传播)
↓
main() (捕获异常)

实战:安全的文件读写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def safe_read_file(filename):
"""完整的安全文件读取函数"""
try:
with open(filename, 'r', encoding='utf-8') as f:
content = f.read()
return content
except FileNotFoundError:
print(f"错误:文件 '{filename}' 不存在")
return None
except PermissionError:
print(f"错误:没有权限读取文件 '{filename}'")
return None
except UnicodeDecodeError:
print(f"错误:文件编码错误,尝试使用 GBK 编码")
try:
with open(filename, 'r', encoding='gbk') as f:
content = f.read()
return content
except:
return None
except IOError as e:
print(f"错误:I/O 错误 - {e}")
return None

def safe_write_file(filename, content):
"""完整的安全文件写入函数"""
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(content)
print(f"成功写入文件 '{filename}'")
return True
except PermissionError:
print(f"错误:没有权限写入文件 '{filename}'")
return False
except IOError as e:
print(f"错误:写入失败 - {e}")
return False

第三部分:包与模块管理

模块与包的概念

模块(Module)

  • 单个 .py 文件
  • 包含 Python 代码
  • 可以被导入使用

包(Package)

  • 包含 __init__.py 文件的目录
  • 可以包含多个模块和子包
  • 用于组织相关的模块

最小包结构

1
2
3
4
5
myproject/
├── mypackage/
│ ├── __init__.py
│ └── module.py
└── main.py

完整包结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
myproject/
├── README.md
├── setup.py
├── requirements.txt
├── mypackage/
│ ├── __init__.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── engine.py
│ │ └── utils.py
│ ├── io/
│ │ ├── __init__.py
│ │ ├── reader.py
│ │ └── writer.py
│ └── config.py
├── tests/
│ ├── __init__.py
│ └── test_core.py
└── examples/
└── example.py

init.py 的作用

作用1:标记目录为包

1
2
# 有 __init__.py 的目录被识别为包
# 没有 __init__.py 的目录不是包

作用2:初始化包

1
2
# mypackage/__init__.py
print("mypackage 已加载")

作用3:定义公共接口

1
2
3
4
5
# mypackage/__init__.py
from .core import engine
from .io import reader

__all__ = ['engine', 'reader']

导入方式

方式1:导入整个模块

1
2
import mypackage.core.engine
engine_obj = mypackage.core.engine.Engine()

方式2:导入特定内容

1
2
from mypackage.core.engine import Engine
engine_obj = Engine()

方式3:导入子包

1
2
from mypackage.core import engine
engine_obj = engine.Engine()

方式4:相对导入(仅在包内使用)

1
2
3
# 在 mypackage/io/reader.py 中
from . import writer # 导入同级模块
from ..core import engine # 导入上级包的模块

创建自定义包

步骤1:创建目录结构

1
2
3
4
mkdir datatools
mkdir datatools/core
mkdir datatools/io
mkdir datatools/utils

步骤2:创建 init.py 文件

1
2
3
4
5
6
7
# datatools/__init__.py
__version__ = "1.0.0"

from .core import processor
from .io import reader, writer

__all__ = ['processor', 'reader', 'writer']

步骤3:创建模块文件

1
2
3
4
5
6
7
8
9
# datatools/core/processor.py
def process_data(data):
"""处理数据"""
return data
# datatools/io/reader.py
def read_file(filename):
"""读取文件"""
with open(filename, 'r', encoding='utf-8') as f:
return f.read()

步骤4:使用包

1
2
3
4
5
import datatools
from datatools import processor, reader

data = reader.read_file('data.txt')
result = processor.process_data(data)

setup.py - 包的安装配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from setuptools import setup, find_packages

setup(
name='datatools',
version='1.0.0',
description='数据处理工具包',
author='Your Name',
author_email='your.email@example.com',
url='https://github.com/yourusername/datatools',
license='MIT',
packages=find_packages(),
python_requires='>=3.7',
install_requires=[
'numpy>=1.19.0',
],
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
],
)

安装包

1
2
3
4
5
6
7
8
9
10
11
# 本地开发安装(推荐)
pip install -e .

# 普通安装
pip install .

# 从 PyPI 安装
pip install datatools

# 指定版本
pip install datatools==1.0.0

requirements.txt

1
2
3
4
# requirements.txt
numpy>=1.19.0
pandas>=1.1.0
requests>=2.25.0

使用方式:

1
pip install -r requirements.txt

第四部分:实战项目

项目1:学生成绩管理系统

项目结构

1
2
3
4
grade_system/
├── main.py
├── students.csv
└── utils.py

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import csv
import json

def read_students_csv(filename):
"""读取学生信息"""
students = []
try:
with open(filename, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
students.append({
'name': row['name'],
'age': int(row['age']),
'score': float(row['score'])
})
return students
except FileNotFoundError:
print(f"错误:文件 '{filename}' 不存在")
return []
except ValueError:
print("错误:数据格式错误")
return []

def calculate_average(students):
"""计算平均成绩"""
if not students:
return 0
total = sum(float(s['score']) for s in students)
return total / len(students)

def find_top_student(students):
"""找出成绩最高的学生"""
if not students:
return None
return max(students, key=lambda s: s['score'])

def save_report(filename, students):
"""保存成绩报告"""
try:
report = {
'total_students': len(students),
'average_score': calculate_average(students),
'top_student': find_top_student(students),
'students': students
}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(report, f, indent=2, ensure_ascii=False)
print(f"报告已保存到 '{filename}'")
except IOError as e:
print(f"错误:保存报告失败 - {e}")

def main():
"""主函数"""
# 读取学生信息
students = read_students_csv('students.csv')

if not students:
print("没有学生数据")
return

# 显示统计信息
print(f"总学生数:{len(students)}")
print(f"平均成绩:{calculate_average(students):.2f}")

top = find_top_student(students)
if top:
print(f"最高成绩:{top['name']} - {top['score']}")

# 保存报告
save_report('report.json', students)

if __name__ == '__main__':
main()

项目2:日志记录系统

项目结构

1
2
3
4
log_system/
├── main.py
├── logger.py
└── logs/

logger.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import os
from datetime import datetime

def create_log_file(log_dir='logs'):
"""创建日志目录"""
if not os.path.exists(log_dir):
os.makedirs(log_dir)
return log_dir

def write_log(message, log_dir='logs', level='INFO'):
"""写入日志"""
try:
# 创建日志目录
create_log_file(log_dir)

# 生成日志文件名
today = datetime.now().strftime('%Y-%m-%d')
log_file = os.path.join(log_dir, f'{today}.log')

# 生成日志内容
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_content = f"[{timestamp}] [{level}] {message}\n"

# 写入日志
with open(log_file, 'a', encoding='utf-8') as f:
f.write(log_content)

print(f"日志已记录:{log_content.strip()}")
except IOError as e:
print(f"错误:写入日志失败 - {e}")

def read_logs(log_dir='logs', date=None):
"""读取日志"""
try:
if date is None:
date = datetime.now().strftime('%Y-%m-%d')

log_file = os.path.join(log_dir, f'{date}.log')

if not os.path.exists(log_file):
print(f"日志文件不存在:{log_file}")
return []

with open(log_file, 'r', encoding='utf-8') as f:
logs = f.readlines()

return logs
except IOError as e:
print(f"错误:读取日志失败 - {e}")
return []

def main():
"""主函数"""
# 写入日志
write_log("程序启动", level='INFO')
write_log("开始处理数据", level='INFO')

try:
# 模拟处理
result = 10 / 0
except ZeroDivisionError:
write_log("发生错误:除以零", level='ERROR')

write_log("程序结束", level='INFO')

# 读取日志
logs = read_logs()
print("\n今日日志:")
for log in logs:
print(log.strip())

if __name__ == '__main__':
main()

项目3:配置文件管理

项目结构

1
2
3
4
config_system/
├── main.py
├── config.json
└── config_manager.py

config_manager.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import json
import os

def load_config(filename):
"""加载配置文件"""
try:
with open(filename, 'r', encoding='utf-8') as f:
config = json.load(f)
print(f"配置已加载:{filename}")
return config
except FileNotFoundError:
print(f"错误:配置文件不存在 - {filename}")
return {}
except json.JSONDecodeError:
print(f"错误:配置文件格式错误 - {filename}")
return {}

def save_config(filename, config):
"""保存配置文件"""
try:
with open(filename, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)
print(f"配置已保存:{filename}")
return True
except IOError as e:
print(f"错误:保存配置失败 - {e}")
return False

def get_config_value(config, key, default=None):
"""获取配置值"""
return config.get(key, default)

def set_config_value(config, key, value):
"""设置配置值"""
config[key] = value
return config

def main():
"""主函数"""
config_file = 'config.json'

# 加载配置
config = load_config(config_file)

if not config:
# 创建默认配置
config = {
'app_name': 'MyApp',
'version': '1.0.0',
'debug': True,
'port': 8000
}
save_config(config_file, config)

# 获取配置值
app_name = get_config_value(config, 'app_name')
print(f"应用名称:{app_name}")

# 修改配置
config = set_config_value(config, 'debug', False)
save_config(config_file, config)

if __name__ == '__main__':
main()

Python函数

Posted on 2026-04-16 | In python , AI

内容概览

今日内容:

  • 1- 【掌握】容器的拆包
  • 2- 【知道】推导式
  • 3- 【掌握】Python的函数:
    • 3.1 基本定义与使用
    • 3.2 变量的作用域
    • 3.3 函数中各个参数设置
    • 3.4 lambda函数(匿名函数)

容器拆包

什么是拆包?

简单来说就是把一个元组中的数据一个一个拆解出来的过程,就称之为叫做拆包操作。

基本语法

1
2
3
4
5
6
7
8
9
10
tuple1 = (10, 20)
# 拆包
num1, num2 = tuple1

以上代码可以简写为
num1, num2 = (10, 20)

还可以进一步简写
num1, num2 = 10, 20

拆包典型案例

定义一个容器(元组)

1
tuple1 = ("炸鸡", True, 25.5)

将元组中每个元素分别赋值给三个变量

1
2
3
name, is_flag, price = tuple1

print(f"{name},{is_flag},{price}")

输出:炸鸡,True,25.5

其他自动拆包方式

1
2
3
4
5
a, b = (10, 20)
print(f"{a},{b}")

a, b = 10, 20
print(f"{a},{b}")

两变量值交换

初始化两个变量

1
2
c1 = '游戏手柄'
c2 = 'Switch'

直接交换(利用自动拆包)

1
2
3
4
5
6
7
c1 = '游戏手柄'
c2 = 'Switch'

c2, c1 = c1, c2

print(c1)
print(c2)

输出:

1
2
Switch
游戏手柄

使用临时变量(不用自动拆包)

1
2
3
4
5
6
7
8
9
c1 = '游戏手柄'
c2 = 'Switch'

tmp = c1
c1 = c2
c2 = tmp

print(c1)
print(c2)

输出:

1
2
Switch
游戏手柄

总结:自动拆包本质上就是把容器的每个元素”打散”分别赋值给对应位置的变量,变量数量必须和元素数量一致。变量交换时直接 a, b = b, a 即可,比传统临时变量法简洁得多

推导式

什么是推导式

​ 推导式comprehensions(又称解析式),是Python的一种独有特性。推导式是可以从一个数据序列构建另一个新的数据序列(一个有规律的列表或控制一个有规律列表)的结构体。 共有三种推导:列表推导式、集合推导式、字典推导式。

为什么需要推导式

案例:创建一个0-9的列表

while循环:

1
2
3
4
5
6
7
8
9
# 初始化计数器
i = 0
list1 = []
# 编写循环条件
while i <= 9:
list1.append(i)
# 更新计数器
i += 1
print(list1)

for循环:

1
2
3
4
5
list1 = []
# 编写for循环
for i in range(0, 10):
list1.append(i)
print(list1)

思考:我们能不能把以上代码简化为一行代码搞定这个程序呢?

答:可以,使用推导式

什么时候可以使用推导式:

当需要遍历一个容器, 然后遍历后需要返回一个新容器的场景

列表推导式

基本语法:

1
2
3
变量名 = [表达式 for 变量 in 列表]
变量名 = [表达式 for 变量 in 列表 if 条件]
变量名 = [表达式 for 变量 in 列表 for 变量 in 列表]

案例:定义0-9之间的列表

1
2
3
4
list1 = []
for i in range(10):
list1.append(i)
print(list1)

列表推导式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 推导式演示:
"""
格式:
变量名 = [表达式 for 变量 in 列表]
变量名 = [表达式 for 变量 in 列表 if 条件]
变量名 = [表达式 for 变量 in 列表 for 变量 in 列表]

"""

# 案例1:定义0-9之间的列表
# 非推导式方式
my_list = []

for i in range(0, 10):
my_list.append(i)

print(my_list)

# 推导式:
my_list = [i for i in range(0, 10)]
print(my_list)

# 案例2: 给定一个列表, 将该列表内容依次添加到另一个集合中, 实现去重效果 得到一个集合: {7,9,5,4,6}
my_list2 = [7, 9, 5, 4, 5, 7, 6]

# 非推导方式:
my_set1 = set()

for e in my_list2:
my_set1.add(e)

print(my_set1)

# 推导式方式:
my_set2 = {e for e in my_list2}
print(my_set2)

执行原理:[i for i in range(10)]

1
2
3
4
5
6
列表推导式先运行表达式右边的内容:

当第一次遍历时:i = 0,其得到变量i的结果后,会放入最左侧的变量i中,这个时候列表中就是[0]
当第二次遍历时:i = 1,其得到变量i的结果后,会追加最左侧的变量i中,这个时候列表中就是[0, 1]
...
当最后一次遍历时:i = 9,其得到变量i的结果后,会追加最左侧的变量i中,这个时候列表中就是[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

列表推导式 + if条件判断

在使用列表推导式时候,我们除了可以使用for循环,其实我们还可以在其遍历的过程中,引入if条件判断。

1
2
3
4
5
6
变量 = [表达式 for 临时变量 in 序列 if 条件判断]

等价于

for 临时变量 in 序列:
if 条件判断

案例:生成0-9之间的偶数(i%2 0)序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#  推导式 含  IF 操作
# 格式: 变量 = [表达式 for 临时变量 in 序列 if 条件判断]

# 需求: 案例:生成0-9之间的偶数(i%2 0)序列
# 非推导式方式
my_list3 = []
for e in range(0, 10):
if e % 2 == 0:
my_list3.append(e)


print(my_list3)

# 推导式:
my_list4 = [e for e in range(0, 10) if e % 2 == 0]

print(my_list4)

# 需求2: 生成 0~9的序列, 然后将 > 5的数据 存储到一个新的列表中
# 推导式
my_list5 = [ e for e in range(0,10) if e > 5 ]
print(my_list5)

for循环嵌套列表推导式

1
2
for 临时变量 in range(n):
for 临时变量 in range(n):

基本语法:

1
变量 = [表达式 for 临时变量 in 序列 for 临时变量 in 序列]

🪑 座位表生成案例

需求:生成教室座位坐标列表

假设一个 3行 × 4列 的教室,生成所有座位的坐标:

1
2
3
4
5
座位布局:
第0列 第1列 第2列 第3列
行0: (0,0) (0,1) (0,2) (0,3)
行1: (1,0) (1,1) (1,2) (1,3)
行2: (2,0) (2,1) (2,2) (2,3)

方法一:for循环嵌套

1
2
3
4
5
6
7
8
9
10
seats = []

# 外层循环:遍历每一行
for row in range(3):
# 内层循环:遍历每一列
for col in range(4):
seat = (row, col)
seats.append(seat)

print(seats)

输出:

1
2
3
[(0, 0), (0, 1), (0, 2), (0, 3),
(1, 0), (1, 1), (1, 2), (1, 3),
(2, 0), (2, 1), (2, 2), (2, 3)]

方法二:列表推导式

1
2
3
4
# 推导式方式(更简洁)
seats = [(row, col) for row in range(3) for col in range(4)]

print(seats)

输出:

1
2
3
[(0, 0), (0, 1), (0, 2), (0, 3),
(1, 0), (1, 1), (1, 2), (1, 3),
(2, 0), (2, 1), (2, 2), (2, 3)]

对比记忆 🧠

对比项 for循环嵌套 列表推导式
代码行数 多行 一行搞定
可读性 逻辑清晰 简洁但稍难读
执行效率 稍慢 更快
适用场景 复杂逻辑 简单转换

推导式本质就是:

1
2
# 外层 for 在前,内层 for 在后
[表达式 for 外层变量 in 外层范围 for 内层变量 in 内层范围]

更多练习 🎯

题目1: 生成 2×3 的棋盘坐标(从 (1,1) 开始)

1
2
3
# 推导式
chess = [(i, j) for i in range(1, 3) for j in range(1, 4)]
# 结果: [(1,1),(1,2),(1,3),(2,1),(2,2),(2,3)]

题目2: 生成所有 (0-9, 0-9) 的坐标对(排除对角线 i==j)

1
2
# 推导式
coords = [(i, j) for i in range(10) for j in range(10) if i != j]

推导式案例

案例一:使用列表推导式生成平方数集合

例如, 用户输入10, 表示要生成 1~10的每一个数字的平方的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 推导式案例: 案例一:使用列表推导式生成平方数集合
# 例如, 用户输入10, 表示要生成 1~10的每一个数字的2平方的集合
# 1- 让用户输入一个正整数
n = int(input("请输入一个正整数:"))

# 2- 遍历 从 1 ~ N
# 2.1 初始化一个空列表
my_list7 = []
for j in range(1,n+1):
# 2.2 计算 2的平方 将结果写入列表
my_list7.append(j ** 2)

print(my_list7)

# 推导式 如何解决
my_list8 = [ j ** 2 for j in range(1,n+1)]
print(my_list8)

Python中函数的作用与使用步骤

为什么需要函数

在Python实际开发中,我们使用函数的目的只有一个“让我们的代码可以被重复使用”

函数的作用有两个:

① 代码重用(代码重复使用)

② 模块化编程(模块化编程的核心就是函数,一般是把一个系统分解为若干个功能,每个功能就是一个函数)

在编程领域,编程可以分为两大类:① 模块化编程 ② 面向对象编程

什么是函数

所谓的函数就是一个被命名的、独立的、完成特定功能的代码段(一段连续的代码),并可能给调用它的程序一个返回值。

​

被命名的:在Python中,函数大多数是有名函数(普通函数)。当然Python中也存在没有名字的函数叫做匿名函数。

独立的、完成特定功能的代码段:在实际项目开发中,定义函数前一定要先思考一下,这个函数是为了完成某个操作或某个功能而定义的。(函数的功能一定要专一)

返回值:很多函数在执行完毕后,会通过return关键字返回一个结果给调用它的位置。

函数的定义

基本语法:

1
2
3
4
5
6
7
def 函数名称(参数1, 参数2, ...):
函数体
...
[return 返回值]


注意: 语法中的中括号表示的可选的意思

在Python中,函数和变量一样,都是先定义后使用。

1
2
3
4
5
6
7
8
# 定义函数
def 函数名称([参数1, 参数2, ...]):
函数体
...
[return 返回值]

# 调用函数
函数名称(参数1, 参数2, ...)

背包物品管理 — 带你一步步认识函数


1️⃣ 第一步:不用函数 — 重复代码写到手酸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 场景:游戏角色背包里有 100 个空格
# 每次捡到道具,都要用掉一个空格

# 第一次捡到药水
inventory = 100
inventory -= 1
print(f"捡到1个道具,背包还剩 {inventory} 个空格")

# 第二次捡到武器
inventory -= 1
print(f"捡到1个道具,背包还剩 {inventory} 个空格")

# 第三次捡到宝石
inventory -= 1
print(f"捡到1个道具,背包还剩 {inventory} 个空格")

# 第四次捡到装备
inventory -= 1
print(f"捡到1个道具,背包还剩 {inventory} 个空格")

问题: 每次捡道具都要写一模一样的 3 行代码,太繁琐了!💀


2️⃣ 第二步:封装成函数 — 代码复用

1
2
3
4
5
6
7
8
9
10
# 定义一个函数:捡道具
def pick_item():
"""捡起一个道具,自动占用背包一个空格"""
print("捡到1个道具!")

# 调用函数,不用重复写代码了
pick_item() # 第1次捡道具
pick_item() # 第2次捡道具
pick_item() # 第3次捡道具
pick_item() # 第4次捡道具

输出:

1
2
3
4
捡到1个道具!
捡到1个道具!
捡到1个道具!
捡到1个道具!

好处: 🎉 定义一次,随时调用!再也不用复制粘贴了!


3️⃣ 第三步:函数带参数 — 告诉函数具体捡什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 场景:捡到的东西不一样,打印的信息也要不一样
# 用参数来解决这个问题

def pick_item(item_name):
"""捡起一个道具

参数:
item_name: 道具名称
"""
print(f"捡到了 【{item_name}】!")

# 调用时传入具体道具名
pick_item("生命药水") # 捡到生命药水
pick_item("火焰剑") # 捡到火焰剑
pick_item("蓝宝石") # 捡到蓝宝石
pick_item("黄金头盔") # 捡到黄金头盔

输出:

1
2
3
4
捡到了 【生命药水】!
捡到了 【火焰剑】!
捡到了 【蓝宝石】!
捡到了 【黄金头盔】!

好处: 🎯 一个函数,变出无数种用法!


4️⃣ 第四步:函数带返回值 — 告诉调用者结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 场景:希望函数告诉调用者,捡道具到底成不成功
# 比如背包满了就捡不了

# 设定背包容量
bag_capacity = 100
current_count = 0

def pick_item(item_name):
"""捡起一个道具
返回 True 表示成功,False 表示失败(背包满了)
"""
global current_count # 声明使用全局变量

# 检查背包是否满了
if current_count >= bag_capacity:
return False # 捡不了,返回失败

current_count += 1
print(f"捡到了 【{item_name}】!背包进度:{current_count}/{bag_capacity}")
return True # 捡到了,返回成功

# 调用并判断结果
result = pick_item("生命药水")
if result:
print("✅ 捡道具成功!")
else:
print("❌ 背包已满,无法捡取!")

result = pick_item("火焰剑")
if result:
print("✅ 捡道具成功!")
else:
print("❌ 背包已满,无法捡取!")

输出:

1
2
3
4
捡到了 【生命药水】!背包进度:1/100
✅ 捡道具成功!
捡到了 【火焰剑】!背包进度:2/100
✅ 捡道具成功!

📊 总结对比

阶段 代码特点 能做什么
无函数 重复代码多 基本功能
无参函数 定义一次,调用多次 代码复用
带参函数 传入不同数据 一函多用
带返回值函数 返回执行结果 反馈给调用者

聊聊return返回值

思考1:如果一个函数如些两个return (如下所示),程序如何执行?

1
2
3
4
5
6
7
def return_num():
return 1
return 2


result = return_num()
print(result) # 1

答:只执行了第一个return,原因是因为return可以退出当前函数,导致return下方的代码不执行。

思考2:如果一个函数要有多个返回值,该如何书写代码?

答:在Python中,理论上一个函数只能返回一个结果。但是如果我们向让一个函数可以同时返回多个结果,我们可以使用return 元组的形式。

1
2
3
4
5
6
7
def return_num():
return 1, 2


result = return_num()
print(result)
print(type(result)) # <class 'tuple'>

思考3:封装一个函数,参数有两个num1,num2,求两个数的四则运算结果

四则运算:加、减、乘、除

1
2
3
4
5
6
7
8
9
10
def size(num1, num2):
jia = num1 + num2
jian = num1 - num2
cheng = num1 * num2
chu = num1 / num2
return jia, jian, cheng, chu


# 调用size方法
print(size(20, 5))

相关演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 需求: 请帮我定义一个函数: computer 计算器函数, 要求给这个函数传入 2个参数, 在函数的内部完成  + - 计算, 并且将 + -的二个结果返回

# 定义函数:
def computer(a, b):
# 编写函数功能的核心位置
jia = a + b
jian = a - b

# 将加和减结果一并返回
return jia, jian
# return [jia, jian]
# return {jia, jian}
# return {'jia':jia,'jian':jian}


# 调用函数
# res = computer(3,5)
i,j = computer(3,5) # 利用自动拆包思想

print(f'加法结果:{i},减法的结果:{j}')

函数的嵌套及案例

什么是函数的嵌套

所谓函数嵌套调用指的是一个函数里面又调用了另外一个函数。

函数嵌套的基本语法

1
2
3
4
5
6
def funca():
return 2
def funcb():
return funca()

funcb()

扩展递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1 利用递归实现阶乘运算
'''
递归: 一个函数自己调用自己,就是递归
必要条件: 要有结束条件
'''
def func_c(num:int):
'''
通过递归实现求阶乘操作
:param num:参数
:return: int
'''
if num <= 1:
return 1
return num * func_c(num-1)


print(func_c(3))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 2 利用递归青蛙台阶
'''
分治思想,分而治之,简称分治
将一个大问题,拆解为相似的小问题,逐个击破
'''

'''
需求:有一个小青蛙,每次可以跳1个台阶,或者跳2个台阶,总共10个台阶,问有多少种走法
思路:
1. 当仅有一个台阶,那么就是1中走法,fn(1) = 1
2. 当有两个台阶,那么有两种走法,f(2) = 2
3. 当有三个台阶, 那么有这些走法 f(3) = f(2) + f(1)
4. 如果有四级台阶, 那么走法为f(4) = f(3) + f(2)
5. 如果有n级台阶,那么走法为f(n) = f(n-1) + f(n-2)
'''
def fn_steps(n):
if n == 1:
return 1
if n == 2:
return 2
return fn_steps(n-1) + fn_steps(n-2)

print(fn_steps(10))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 3 实现汉诺塔
'''
汉诺塔
移动n个盘子,从A--->C盘
思路:
将前n-1个盘子从A柱借助C柱移动到B柱
将第n个盘子从A柱移动到C柱
将前n-1个盘子从B柱借助A柱移动到C柱
'''

def hanuota(num, A,B,C):
'''
这是一个汉诺塔函数,表示将num个盘子,从A柱移动到C柱的流程
:param num: 盘子个数
:param A: A柱子,起始柱子
:param B: B柱子,辅助柱子
:param C: C柱子,目的柱子
:return: None
'''
if num == 1:
print(f'将第{num}个盘子从{A}移动到{C}')
return
# 将num-1个盘子从A借助C移动到B
hanuota(num-1, A,C,B)
# 将第num个盘子,从A移动到C
print(f'将第{num}个盘子从{A}移动到{C}')
# 将num-1个盘子从B借助A移动到C
hanuota(num-1,B,A,C)

hanuota(3,'A','B','C')

变量的作用域

什么是变量的作用域

变量作用域指的是变量的作用范围(变量在哪里可用,在哪里不可用),主要分为两类:全局作用域与局部作用域。

其实作用域的划分比较简单,在函数内部定义范围就称之为局部作用域,在函数外部(全局)定义范围就是全局作用域

1
2
3
# 全局作用域
def func():
# 局部作用域

局部变量与全局变量

在Python中,定义在函数外部的变量就称之为全局变量;定义在函数内部变量就称之为局部变量。

1
2
3
4
5
6
7
# 定义在函数外部的变量(全局变量)
num = 10
# 定义一个函数
def func():
# 函数体代码
# 定义在函数内部的变量(局部变量)
num = 100

演示全局和局部的使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 演示: 全局和局部变量:
# 全局变量: 指的是定义在函数外部的变量称为全局变量
# 局部变量: 指的是定义在函数内部的变量称为局部变量
# 全局变量的作用范围: 是整个全局的范围内, 一旦定义后, 在当前这个文件的任意位置均可以使用该变量
# 局部变量的作用范围: 仅在当前这个函数的内部, 一旦出了这个函数, 该变量就相当于不存在

# 定义一个全局变量:
a = 100

# 定义一个函数
def fun1():
# 定义一个局部变量:
b = 200
# 在函数内部使用全局变量
print(f"函数内--输出a的值为:{a}")
print(f"函数内--输出b的值为:{b}")


# 调用函数
fun1()

# 在函数外部使用全局变量
print(f"函数外输出a的值:{a}")
# print(f"函数外输出a的值:{b}")

变量作用域的作用范围

全局变量:在整个程序范围内都可以直接使用

1
2
3
4
5
6
7
8
9
10
str1 = 'hello'
# 定义一个函数
def func():
# 在函数内部调用全局变量str1
print(f'在局部作用域中调用str1变量:{str1}')

# 直接调用全局变量str1
print(f'在全局作用域中调用str1变量:{str1}')
# 调用func函数
func()

局部变量:在函数的调用过程中,开始定义,函数运行过程中生效,函数执行完毕后,销毁

1
2
3
4
5
6
7
8
9
10
# 定义一个函数
def func():
# 在函数内部定义一个局部变量
num = 10
print(f'在局部作用域中调用num局部变量:{num}')

# 调用func函数
func()
# 在全局作用域中调用num局部变量
print(f'在全局作用域中调用num局部变量:{num}')

运行结果:

image-20210313145728886

普及小知识:计算机的垃圾回收机制 garbage collection

global关键字的应用场景

思考一个问题:我们能不能在局部作用域中对全局变量进行修改呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
'''
在Python中有两种类型的变量
1. 可变类型 list,dict,set,自定义的类型 ,尽管数据相同,但是占用不同的内存空间
2. 不可变类型 int, bool, str, double, tuple,相同的数据在内存中只存储1份数据
a = 1
b = 1
可以理解为a和b存储的都是1的地址
当a = 2 的时候,a指向的就是2的地址
'''
a = 1
b = 1
a = 2

list_a = [1,2,3]
list_b = [1,2,3]
list_a.append(4)
print(list_a)
print(list_b)

'''
因为Python中赋值和定义具有二义性
Python不同于C++,
C++语法
int a = 100; a = 200;
Python中
定义和赋值弄到一起
a = 100
a = 200
如果a = 200在函数内, a = 100在函数外
两个a 不是同一个a, 函数内的a = 200会被认为定义了一个新的变量a
'''

# 定义全局变量num = 10
num = 10
# 定义一个函数func
def func():
# 尝试在函数内部修改全局变量,但是不会修改,只会重新定义一个num
num = 20
print(f"函数内num:{num}, id(num): {id(num)}")

# 调用函数func
func()
# 尝试访问全局变量num
print(f"函数外num:{num}, id(num): {id(num)}")
print('*'*20)

image-20260421152252236

最终结果:弹出10,所以由运行结果可知,在函数体内部理论上是没有办法对全局变量进行修改的,所以一定要进行修改,必须使用global关键字。

1
2
3
4
5
6
7
8
9
10
11
12
# 定义全局变量num = 10
num = 10
# 定义一个函数func
def func():
# 尝试在局部作用域中修改全局变量
global num
num = 20

# 调用函数func
func()
# 尝试访问全局变量num
print(num)

记住:global关键字只是针对不可变数据类型的变量进行修改操作(数值、字符串、布尔类型、元组类型),可变类型可以不加global关键字。

image-20260421150443357

image-20260421150940765

image-20260421153338719

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 案例: 以卖票案例

# 1 定义一个票总量
ticket = 100


# 2- 定义一个卖票的程序(函数)
def sale_ticket():
# 标记为全局变量: 主要是针对的是不可变的数据类型, 如果是可变的, 不需要添加global关键词
# 不可变的数据类型: 数值 字符串 元组 布尔
# 可变的数据类型: 列表 集合 字典
global ticket

# 执行一次, 需要让 票数 -1
ticket -= 1 # 默认会认为这个变量是一个局部变量

print(f"剩余票数为:{ticket}")


# 3- 调用卖票程序
sale_ticket()
sale_ticket()
sale_ticket()
sale_ticket()

可变数据类型, 不需要添加global

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 结论: 如果变量的数据类型是可变的, 不需要添加global 就可以 对 全局变量进行修改

# 1- 定义一个全局变量: 类型为 字典
person = {"name":"张三","age":18}

# 2- 定义一个函数
def fun1():
# 向字典中添加一个属性:
person['address'] = "南京市"

print(f"函数内打印全局变量:{person}")
#print("修改全局变量的内容")


# 3- 调用函数
fun1()

# 4- 在全局范围内 打印全局变量
print(person)
1
2
3
4
5
6
7
8
9
10
11
12
如果函数内修改的是可变对象,不需要加global,(dict,set,list,自定义类型对象)
因为不可变对象有很多种修改方式,比如字典的插入,比如列表append, remove,都可以修改,
不会被当成变量定义,所以就不用写global了

如果修改的是不可变的变量,需要加global,(字符串,元组,数字,布尔类型)
因为不可变类型的变量做修改,只能通过赋值,而赋值会产生二义性
python中=(赋值操作),有两层意思,一个是定义变量,还有一个是修改变量
在函数内部,局部作用域使用=,优先理解为定义变量

内存原理:
不可变对象,数据在内存中保留1份
可变对象,数据在内存中可以相同,可以是不同地址

image-20260323113054437

image-20260323113458929

image-20260323114053389

image-20260323114327460

image-20260323120821143

作用域说明完整源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
'''
局部作用域和全局作用域
'''
'''
局部作用域: 函数内部作用域就是局部作用域
全局作用域: 在函数外部的作用域就是全局作用域
局部变量: 在函数内部定义的变量就是局部变量,可见范围只在自己定义的作用域内,
在哪个函数内定义,就在哪个函数内可见,不可被外部的全局作用域使用
全局变量: 在函数外部定义的变量就是全局变量,可以被程序的所有部分使用
'''
# 全局变量
num1 = 2000
def hello():
# 局部变量
num2 = 300
print('hello world')
# 全局变量
num3 = 400
def sum(num1,num2):
# num1和num2都是局部变量,num4
num4 = num1 + num2
return num4

# 全局变量
str1 = 'hello'
def func():
# 局部变量
str2 = 'heima'
print(f'在局部作用域内使用全局变量str1: {str1}')
print(f'在局部作用域内使用局部变量str2: {str2}')

func()
# 在全局作用域内无法使用局部变量
#print(f'在全局作用域内使用局部变量str2: {str2}')

# 如果局部变量和全局变量同名了,就会出现定义局部变量和全局变量冲突的问题
# 如果想要在函数内修改外部局部变量,用global声明
# 全局变量
name = 'Lisi'
def change_name():
# 局部变量
name = 'zs'
print(f'局部变量name: {name}')
change_name()
print(f'全局变量name: {name}')

# 使用global关键字
def change_name2():
# 声明全局变量
global name
# 修改全局变量,将lisi改为zs
name = 'zs'
print(f'函数内name: {name}')
change_name2()
print(f'全局变量name: {name}')

# 全局变量
name = 'Lisi'
# 使用形参name
def change_name3(name):
# name是形参,局部变量
# 修改全局变量,将lisi改为zs
name = 'zs'
print(f'函数内name: {name}')
# name 为实参,为全局变量
change_name3(name)
print(f'全局变量name: {name}')

'''
如果函数内修改的是可变对象,不需要加global,(dict,set,list,自定义类型对象)
因为可变对象有很多种修改方式,比如字典的插入,比如列表append, remove,都可以修改,
不会被当成变量定义,所以就不用写global了

如果修改的是不可变的变量,需要加global,(字符串,元组,数字,布尔类型)
因为不可变类型的变量做修改,只能通过赋值,而赋值会产生二义性
python中=(赋值操作),有两层意思,一个是定义变量,还有一个是修改变量
在函数内部,局部作用域使用=,优先理解为定义变量

内存原理:
不可变对象,数据在内存中保留1份
可变对象,数据在内存中可以相同,可以是不同地址
'''
# 这是可变类型的全局变量
person = {'name':'zs','age':18}
print(f'全局person地址: {id(person)}')
def func1():
# = 被当作定义,所以内部person是新定义的局部变量
person = {'name':'zs','age':18}
print(f'函数内person地址: {id(person)}')

#func1()

def func2():
person['address'] = 'beijing'
print(f'函数内person地址: {id(person)}')
func2()

函数的参数进阶

函数的参数

在函数定义与调用时,我们可以根据自己的需求来实现参数的传递。在Python中,函数的参数一共有两种形式:

① 形参 ② 实参

形参:在函数定义时,所编写的参数就称之为形式参数

实参:在函数调用时,所传递的参数就称之为实际参数

1
2
3
4
5
6
def greet(name):  # name就是在函数greet定义时,所编写的参数(形参)
return name + ',您好'

# 调用函数
name = '老王'
greet(name) # 在函数调用时,所传递的参数就是实际参数

注意:虽然我们在函数传递时,喜欢使用相同的名称作为参数名称。但是两者的作用范围是不同的。name = ‘老王’,代表实参。其是一个全局变量,而greet(name)函数中的name实际是在函数定义时才声明的变量,所以其实一个局部变量。

函数的参数类型(调用)

☆ 位置参数

理论上,在函数定义时,我们可以为其定义多个参数。但是在函数调用时,我们也应该传递多个参数,正常情况,其要一一对应。

1
2
3
4
5
def user_info(name, age, address):
print(f'我的名字{name},今年{age}岁了,家里住在{address}')

# 调用函数
user_info('Tom', 23, '美国纽约')

注意事项:位置参数强调的是参数传递的位置必须一一对应,不能颠倒

☆ 关键词参数(Python特有)

函数调用,通过“键=值”形式加以指定。可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求。

1
2
3
4
5
def user_info(name, age, address):
print(f'我的名字{name},今年{age}岁了,家里住在{address}')

# 调用函数(使用关键词参数)
user_info(name='Tom', age=23, address='美国纽约')

☆ 函数定义时缺省参数(参数默认值)

​ 缺省参数也叫默认参数,用于定义函数,为参数提供默认值,调用函数时可不传该默认参数的值(注意:所有位置参数必须出现在默认参数前,包括函数定义和调用)。

1
2
3
4
5
6
7
def user_info(name, age, gender='男'):
print(f'我的名字{name},今年{age}岁了,我的性别为{gender}')


user_info('李林', 25)
user_info('振华', 28)
user_info('婉儿', 18, '女')

谨记:我们在定义缺省参数时,一定要把其写在参数列表的最后侧

相关的演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 定义一个函数, 该函数需要传入三个参数
def fun1(name,age,address):
print(f"姓名:{name},年龄:{age},地址:{address}")


# 如何调用呢?
# 位置传参方式: 要求调用函数的时候, 传递的参数顺序 要和 函数的形参的顺序保持一致
fun1("张三",20,"南京市")

# 关键词传参: 在传递参数的时候, 可以指定给谁传递 对顺序就没有要求
fun1(age=18,name="李四",address="北京市")

# 默认值传参: 指的是在定义这个函数的时候, 可以给某些参数赋一个默认值, 然后在调用该函数就可以不需要传递参数了
# 注意: 有默认值的形参 必须放置到整个参数列表的最后面
def fun2(name,address='南京市',age=18):
print(f"姓名:{name},年龄:{age},地址:{address}")

# 调用操作: 如果不需要修改默认值, 那么在调用的时候也不需要传递, 如果需要修改 那么就正常传递即可
fun2(name="王五")
fun2(name="赵六",address="北京市")


注意: 如果函数中某个参数没有默认值, 那么在调用的时候, 必须传递 否则会直接报错

不定长参数

​ 不定长参数也叫可变参数。用于不确定调用的时候会传递多少个参数(不传参也可以)的场景。此时,可用包裹(packing)位置参数,或者包裹关键字参数,来进行参数传递,会显得非常方便。

☆ 不定长元组(位置)参数

1
2
3
4
5
6
def user_info(*args):
# print(args) # 元组类型数据,对传递参数有顺序要求
print(f'我的名字{args[0]},今年{args[1]}岁了,住在{args[2]}')

# 调用函数,传递参数
user_info('Tom', 23, '美国纽约')

☆ 不定长字典(关键字)参数

1
2
3
4
5
6
def user_info(**kwargs):
# print(kwargs) # 字典类型数据,对传递参数没有顺序要求,格式要求key = value值
print(f'我的名字{kwargs["name"]},今年{kwargs["age"]}岁了,住在{kwargs["address"]}')

# 调用函数,传递参数
user_info(name='Tom', address='美国纽约', age=23)

kw = keyword + args

综上:无论是包裹位置传递还是包裹关键字传递,都是一个组包的过程。

Python组包:就是把多个数据组成元组或者字典的过程。

相关演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 演示不定长参数:
# 不定长元组(位置)参数
# 定义函数:
def fun1(*args): # 这种 一个 * 不定长参数, 背后 是一个容器(序列) --> 元组
# 获取每一个参数的内容
print(type(args))
for e in args:
print(f"参数:{e}")


# 调用函数
fun1("张三", "李四", "王五", "赵六", "田七")


# 不定长字典(关键词)参数
def fun2(**kwargs): # 这种 一个 ** 不定长参数, 背后 是一个容器(序列) --> 字典
print(type(kwargs))
# 遍历参数
for key in kwargs:
print(f"参数的key:{key}, 对应的值为:{kwargs[key]}")

# 调用函数: 必须使用关键词传参
fun2(name="张三", age=18, address="南京市") # 在调用的时候, 创建了一个字典, 将 kv数据放置到了字典中

案例:Python中数据的传递案例

1
2
3
4
5
6
7
8
9
10
11
12
13
def func(*args, **kwargs):
print(args)
print(kwargs)


# 定义一个元组(也可以是列表)
tuple1 = (10, 20, 30)
# 定义一个字典
dict1 = {'first': 40, 'second': 50, 'third': 60}
# 需求:把元组传递给*args参数,字典传递给**kwargs
# ① 如果想把元组传递给*args,必须在tuple1的前面加一个*号
# ② 如果想把字典传递给**kwargs,必须在dict1的前面加两个**号
func(*tuple1, **dict1)

lambda函数

普通函数与匿名函数

在Python中,函数是一个被命名的、独立的完成特定功能的一段代码,并可能给调用它的程序一个返回值。

所以在Python中,函数大多数是有名函数 => 普通函数。但是有些情况下,我们为了简化程序代码,也可以定义匿名函数 => lambda表达式

lambda表达式应用场景

如果一个函数有一个返回值,并且只有一句代码,可以使用 lambda简化。

lambda表达式基本语法

1
2
3
变量 = lambda 函数参数:表达式(函数代码 + return返回值)
# 调用变量
变量()

编写lambda表达式

定义一个lambda表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'''
演示lambda表达式用法
'''

'''
变量 = lambda 函数参数:表达式(函数代码 + return返回值)
# 调用变量
变量()
说明:
变量就是函数的地址,将来可以通过变量名调用函数
'''
def func(str):
return 'hello '+ str

# fn是lambda表达式的地址,也就是函数地址
fn = lambda str: 'hello '+ str
# 打印函数调用结果
print(fn('zack'))

image-20260427112431224

编写带参数的lambda表达式

编写一个函数求两个数的和

1
2
3
4
def fn1(num1, num2):
return num1 + num2

print(fn1(10, 20))

lambda表达式进行简化:

1
2
3
4
5
6
7
8
'''
带参数的lambda表达式
计算num1和num2的加法结果
'''
# fn是lambda表达式的地址,也就是函数地址
fn = lambda num1,num2:num1+num2
# 函数调用
print(fn(1,2))

☆ 带默认参数的lambda表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 有参数 参数带有默认值:
def fn2(num1, num2=30):
return num1 + num2


print(fn2)
print(fn2(10))

# lambda 简化
'''
带默认值的参数
'''
fn2 = lambda num1, num2=20: num1-num2
# 打印函数的地址,也就是lambda表达式在堆区的地址
print(fn2)
# num2默认传递20
print(fn2(100))

☆ 带if判断(三目运算符)的lambda表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 带 if判断(三目运算符)的lambda表达式
# 普通函数
def fn3(name: str):
a = 0
if name == "张三":
a = 1
else:
a = 0
return a

# 普通函数
def fn4(name: str):
return 1 if name == "张三" else 0

print(fn4("李四"))

# 基于lambda方案实现
lambda name: 1 if name == '张三' else 0

☆ 列表数据+字典数据排序(重点)

lambda表达式经常配合泛型编程使用

不受限于数据结构

也不受限于实际参数

封装和调用解耦合

知识点:列表.sort(key=排序的key)

1
2
3
4
5
6
7
8
9
10
11
12
print("------------------------------------")
# 在一个列表中, 放置了三个元素, 每一个元素又是一个字典
students = [
{'name': 'Tom', 'age': 20},
{'name': 'Rose', 'age': 19},
{'name': 'Jack', 'age': 22}
]
print(students)
# 按name值升序排列

# 按name值降序排列

常见数据结构

Posted on 2026-04-11 | In python , AI

1. 列表及其应用场景

为什么需要列表

思考:有一个人的姓名(LiLei)怎么书写存储程序?

答:变量。

思考:如果一个班级50位学生,每个人的姓名都要存储,应该如何书写程序?声明50个变量吗?

答:No,我们使用列表就可以了, 列表一次可以存储多个数据。

在Python中,我们把这种数据类型称之为列表。但是在其他的编程语言中,如Java、PHP、Go等等中其被称之为数组。

列表的定义

1
2
3
列表序列名称 = [列表中的元素1, 列表中的元素2, 列表中的元素3, ...]

注意: 列表是可变的

案例演示:定义一个列表,用于保存橙子、葡萄以及芒果

1
2
3
4
5
fruit_list = ['orange', 'grape', 'mango']
# list列表类型支持直接打印
print(fruit_list)
# 打印列表的数据类型
print(type(fruit_list)) # <class 'list'>

注意:列表可以一次存储多个数据且可以为不同的数据类型

image-20260411215004239

相关操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 演示: 如何定义列表
# 格式: 变量名称 = [元素1,元素2,元素3......]
# 需求: 构建一个列表, 保存 小明 小红 小刚 小丽 四个名字
name_list = ['小明','小红','小刚','小丽']
print(name_list)

# 如何获取列表中某一个元素呢? 与 字符串 一致
print(name_list[0])
print(name_list[1])
print(name_list[2])

# 如何遍历列表呢?
print("-----------------")
# for循环
for name in name_list:
print(name)

# while循环
print("-----------------")
# 计数器
i = 0

while i < len(name_list):
print(name_list[i])

# 更新计数器
i += 1

列表的相关操作

列表的作用是一次性存储多个数据,程序员可以对这些数据进行的操作有:

增、删、改、查。

☆ 查操作

列表在计算机中的底层存储形式,列表和字符串一样,在计算机内存中都占用一段连续的内存地址,我们向访问列表中的每个元素,都可以通过”索引下标”的方式进行获取。

如果我们想获取列表中的某个元素,非常简单,直接使用索引下标:

1
2
3
fruit_list = ['orange', 'grape', 'mango']
# 获取列表中的grape
print(fruit_list[1])

查操作的相关方法:

编号 函数 作用
1 index() 指定数据所在位置的下标
2 count() 统计指定数据在当前列表中出现的次数
3 in 判断指定数据在某个列表序列,如果在返回True,否则返回False
4 not in 判断指定数据不在某个列表序列,如果不在返回True,否则返回False

☆ 增操作

编号 函数 作用
1 append() 增加指定数据到列表中
2 extend() 列表结尾追加数据,如果数据是一个序列,则将这个序列的数据逐一添加到列表
3 insert() 指定位置新增数据

☆ 删操作

编号 函数 作用
1 del 列表[索引] 删除列表中的某个元素
2 pop() 删除指定下标的数据(默认为最后一个),并返回该数据
3 remove() 移除列表中某个数据的第一个匹配项。

☆ 改操作

编号 函数 作用
1 列表[索引] = 修改后的值 修改列表中的某个元素
2 reverse() 将数据序列进行倒叙排列
3 sort() 对列表序列进行排序

相关功能的演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 演示列表的API
name_list = ['小明', '小红', '小刚', '小丽', '小强', '小芳', '小亮', '小红']
# 查询相关方法:
# 1- index(): 查询指定数据所在位置的下标 如果不存在 直接报错
index = name_list.index("小丽")
print(index) # 3

# 2- count(): 统计数据在当前列表中出现的次数
count = name_list.count("小红")
print(count) # 2

# 3- in 和 not in 判断是否存在或不存在
bool = "小刚" in name_list
print(bool)
bool = "小刚" not in name_list
print(bool)

# 增加函数
# 1- append() 增加指定数据到列表中
# 如果 小宇 不在列表中, 请增加到列表中
if "小宇" not in name_list:
# 能进来, 说明 小宇 不在, 如果不在 添加到列表中
name_list.append("小宇")

print(name_list)

# 2- extend(): 将一个序列 追加到另一个序列中
# 2.1 先定义一个新的序列
name2_list = ['小浩', '小琪']

# 2.2 将新列表中数据 增加 到 原有列表中
name_list.extend(name2_list)

print(name_list)

# 3- insert() 在指定位置新增数据
name_list.insert(4, '小诺')

print(name_list)

# 删除操作
# 1- del 列表[索引]
del name_list[-1]

print(name_list)

# 2- pop() 删除指定下标的数据, 默认是删除最后一个 并且会返回删除的数据
element = name_list.pop()
print(element)
print(name_list)

element = name_list.pop(2)
print(element)
print(name_list)

# 3- remove() 移除列表中某个指定数据 如果不存在, 也不会报错 无效果
name_list.remove("小诺")
print(name_list)

# 修改操作
# 1- 修改某个索引的值
name_list[0] = '大明'
print(name_list)

# 2- reverse: 反转操作
name_list.reverse()

print(name_list)

# 3- 排序操作: reverse 设置 是否反转 True 反转(倒序) False 不反转(正序)
num_list = [3, 7, 10, 56, 2, 9, 4, 5]
num_list.sort(reverse=True)
print(num_list)

列表的循环遍历

什么是循环遍历?答:循环遍历就是使用while或for循环对列表中的每个数据进行打印输出

while循环:

1
2
3
4
5
star_list = ['西施', '王昭君', '杨玉环']
i = 0
while i < len(star_list):
print(star_list[i])
i += 1

for循环(个人比较推荐):

1
2
3
star_list = ['西施', '王昭君', '杨玉环']
for star in star_list:
print(star)

列表的嵌套

列表的嵌套:列表中又有一个列表,我们把这种情况就称之为列表嵌套

在其他编程语言中,称之为叫做二维数组或多维数组

应用场景:要存储班级一、二、三 => 三个班级学生姓名,且每个班级的学生姓名在一个列表。

1
2
3
4
5
6
7
8
9
10
11
12
classes = ['第一个班级','第二个班级','第三个班级']

一班:['小明', '小红']
二班:['小刚', '小丽']
三班:['小强', '小芳']

把班级和学员信息合并在一起,组成一个嵌套列表
students = [['小明', '小红'],['小刚', '小丽'],['小强', '小芳']]

students = [x,y,z]
students[0] == ['小明', '小红']
students[0][1]

问题:嵌套后的列表,我们应该如何访问呢?

1
2
3
4
5
# 访问小红
print(students[0][1])
# 嵌套列表进行遍历,获取每个班级的学员信息
for i in students:
print(i)

相关演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 演示 列表嵌套
# 说明: 容器内部嵌套容器方式
# 需求: 有 三个班级, 分别为 前端1班 前端2班 后端1班, 每个班级分别有5位学生
# 定义 班级列表
front1_class = ['小明','小红','小刚','小丽','小强']
front2_class = ['小芳','小亮','小宇','小浩','小琪']
back1_class = ['小诺','小泽','小琳','小航','小彤']

# 定义 学校列表, 学校列表中有三个班级
school_list = [front1_class,front2_class,back1_class]

# 以上内容 如果直接使用列表嵌套写法
school_list = [
['小明','小红','小刚','小丽','小强'],
['小芳','小亮','小宇','小浩','小琪'],
['小诺','小泽','小琳','小航','小彤']
]

# 如何 获取里面的数据呢? 比如 我想获取到 小浩
front2_class = school_list[1]
name = front2_class[3]
print(name)

# 可以简写
name = school_list[1][3]
print(name)

# 如何遍历打印每一个元素
for classes in school_list:
for name in classes:
print(name)
print("-----------")

列表案例

案例需求(全新)

判断相邻元素的变化趋势

给定一个正整数数组,判断每一对相邻元素是“上升”“下降”还是“相等”,并输出结果列表

例如:
输入: [3, 5, 5, 2, 4]
输出: ['上升', '相等', '下降', '上升']


实现思路

① 定义原始列表

② 创建一个空列表用于保存结果

③ 遍历相邻元素(当前值和下一个值)

④ 比较大小关系

  • 当前 < 下一个 → 上升
  • 当前 > 下一个 → 下降
  • 相等 → 相等

⑤ 将结果添加到列表中

⑥ 输出结果


代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 判断相邻元素变化趋势

num_list = [3, 5, 5, 2, 4]

result_list = []

i = 0

while i < len(num_list) - 1:
if num_list[i] < num_list[i + 1]:
result_list.append("上升")
elif num_list[i] > num_list[i + 1]:
result_list.append("下降")
else:
result_list.append("相等")

i += 1

print(result_list)

巩固练习(全新)

找出列表中第一次出现的重复元素

给定一个列表,找到第一个重复出现的元素(只要出现第二次就算重复),并输出该元素

例如:
输入: [2, 3, 5, 3, 6, 5, 7]
输出: 3


实现思路

① 创建一个空列表用于记录已经出现过的元素

② 遍历原始列表

③ 判断当前元素是否已经出现过

  • 如果出现过 → 直接输出并结束
  • 如果没有 → 添加到记录列表

④ 如果循环结束都没找到,说明没有重复


代码实现

1
2
3
4
5
6
7
8
9
10
11
12
# 找第一个重复元素

num_list = [2, 3, 5, 3, 6, 5, 7]

seen_list = []

for e in num_list:
if e in seen_list:
print(e)
break
else:
seen_list.append(e)

2.元组的定义与使用

为什么需要元组

思考:如果想要存储多个数据,但是这些数据是不能修改的数据,怎么做?

答:列表?列表可以一次性存储多个数据,但是列表中的数据允许更改。

1
2
num_list = [20, 30, 40]
num_list[0] = 200

那这种情况下,我们想要存储多个数据且数据不允许更改,应该怎么办呢?

答:使用元组,元组可以存储多个数据且元组内的数据是不能修改的。

元组的定义

元组特点:定义元组使用小括号,且使用逗号隔开各个数据,数据可以是不同的数据类型。

基本语法:

1
2
3
4
5
# 多个数据元组
tuple1 = (20, 30, 40)

# 单个数据元组
tuple2 = (20,)

注意:如果定义的元组只有一个数据,那么这个数据后面也要添加逗号,否则数据类型为唯一的这个数据的数据类型。

元组的相关操作方法

由于元组中的数据不允许直接修改,所以其操作方法大部分为查询方法。

编号 函数 作用
1 元组[索引] 根据索引下标查找元素
2 index() 查找某个数据,如果数据存在返回对应的下标,否则报错,语法和列表、字符串的index方法相同
3 count() 统计某个数据在当前元组出现的次数
4 len() 统计元组中数据的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 1- 如何定义元组
# 格式:
# 变量名称 = (元素1,元素2,元素3......)
# 变量名称 = (元素1,) : 如果元组中只有一个元素, 必须在后面添加一个逗号 否则编译器会自动转换为 数据的本身类型, 而不是元组类型
user_tuple = ('小明','小红','小刚',20,30,40,True,False)
print(user_tuple)
print(type(user_tuple))

tuple1 = ('小明',)
print(tuple1)
print(type(tuple1))

# 2- 如何使用元组
# 2.1 获取元组中某个元素: 与 列表 和 字符串 一致 都是通过索引的方式来获取
print(user_tuple[0])
print(user_tuple[1])
print(user_tuple[2])

# 2.2 遍历: 与 列表 一致
for e in user_tuple:
print(e)

# 2.3 获取某个元素在元组中索引值: index
print(user_tuple.index(True))

# 2.4 获取某个元素在元组中出现了几次
print(user_tuple.count('小刚'))

# 2.5 获取元组长度
print(len(user_tuple))

元组案例

案例需求1

编写一个程序,计算嵌套元组中所有数字的总和。

例如:在嵌套元组 ((2,3,4),(3,5,7),(3,4,6)) 中,所有元素相加的结果为:

1
2+3+4+3+5+7+3+4+6 = 37

输出结果为:37

实现思路

① 定义一个嵌套元组

② 定义一个变量,用来保存总和,初始值为 0

③ 使用外层循环遍历每个子元组

④ 使用内层循环遍历子元组中的每个元素

⑤ 将每个元素依次累加到总和变量中

⑥ 输出最终结果

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 编写一个程序,计算嵌套元组中所有数字的总和
# 例如: ((2,3,4),(3,5,7),(3,4,6)) 的总和为 37

# 1- 初始化嵌套元组
tuple1 = (
(2, 3, 4),
(3, 5, 7),
(3, 4, 6)
)

# 2- 定义变量,用于保存总和
total = 0

# 3- 遍历嵌套元组中的每个子元组
for tup_e in tuple1:
# 4- 遍历子元组中的每个元素
for e in tup_e:
# 5- 累加每个元素
total += e

# 6- 打印结果
print(total)

案例需求2

给定一个元组 my_tuple,里面包含 2, 3, 4, 5, 6, 7, 8, 9, 10 元素,要求找出其中所有大于 5 的数字,并放入一个新列表中。

输出结果为:[6, 7, 8, 9, 10]


实现思路

① 定义一个元组

② 创建一个空列表,用于保存大于 5 的数字

③ 遍历元组中的每个元素

④ 判断当前元素是否大于 5

⑤ 如果大于 5,就添加到结果列表中

⑥ 输出结果列表


代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 给定一个元组my_tuple,找出其中所有大于5的数字

my_tuple = (2, 3, 4, 5, 6, 7, 8, 9, 10)

# 1- 创建空列表,用于保存结果
result_list = []

# 2- 遍历元组中的每个元素
for e in my_tuple:
# 3- 判断是否大于5
if e > 5:
# 4- 如果大于5,则添加到列表中
result_list.append(e)

# 5- 打印结果
print(result_list)

这两个新案例和原来的区别比较明显:

3.【熟悉】集合的定义与使用

什么是集合

集合(set)是一个无序的不重复元素序列。

① 天生去重

② 无序

集合的定义

在Python中,我们可以使用一对花括号{}或者set()方法来定义集合,但是如果你定义的集合是一个空集合,则只能使用set()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1- 如何定义集合
# 格式:
# 格式一: 变量名称 = {元素1,元素2,元素3......}
# 格式二: 变量名称 = set() 空集合
# 特性: 无序 + 去重
# 定义一个有内容的集合
name_set = {'小明','小红','小刚','小明','小刚','小丽'}

print(name_set) # 去重 和 无序 发现每次都是不一样的 所以是无序的
print(type(name_set))
# 定义 空集合
# set2 = {} # 此种定义方法 并不是定义空集合, 而是空的字典
empty_set = set()
print(type(empty_set))

# 获取数据: 遍历的方式
# for 循环
for name in name_set:
print(name)

集合操作的相关方法(增删查)

☆ 集合的增操作

add()方法:向集合中增加一个元素(单一)

1
2
3
name_set = {'小明','小红','小刚'}
name_set.add('小强')
print(name_set)

☆ 集合的删操作

remove()方法:删除集合中的指定数据,如果数据不存在则报错。

1
2
3
name_set = {'小明','小红','小刚'}
name_set.remove('小红')
print(name_set)

☆ 集合中的查操作

① in :判断某个元素是否在集合中,如果在,则返回True,否则返回False

② not in :判断某个元素不在集合中,如果不在,则返回True,否则返回False

1
2
3
name_set = {'小明','小红','小刚'}
print('小明' in name_set) # True
print('小丽' not in name_set) # True

③ 集合的遍历操作

1
2
3
name_set = {'小明','小红','小刚'}
for name in name_set:
print(name)

相关操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 演示 集合的API
num_set = {3,4, True,'小明',False}
# 1- 添加数据的API: add
num_set.add("小红")
print(num_set)

# 2- 删除数据的API: remove 在删除的时候 如果在集合中没有该值 会直接报错
num_set.remove(False)

print(num_set)

# 3- 判断是否存在和不存在操作: in 和 not in
# 如果 小刚在集合中不存在, 请添加该元素
if '小刚' not in num_set:
# 能进来, 说明 小刚 不在集合中
num_set.add('小刚')

print(num_set)

集合案例

案例 1:找连续整数的断点

1
2
3
4
"""
💡 题目:给定一个连续递增的整数列表,找出中断的位置。
例如:[1, 2, 3, 5, 6, 8] 中,3→5 断了,6→8 也断了。
"""

案例 2:找出数组中出现奇数次的数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
'''
定义集合方式
方式1 空集合: empty_set = set()
方式2 包含元素的集合 my_set = {元素1, 元素}
'''

# 定义空集合
empty_set = set()
# 打印集合
print(empty_set)
print(type(empty_set))

# 定义空字典
empty_dict = {}
print(empty_dict)
print(type(empty_dict))

# 定义一个包含元素的集合
name_set = {'小明','小红','小刚','小明','小刚','小刚'}
print(name_set)
print(type(name_set))

# 遍历
for name in name_set:
print(name)

# 增加元素
name_set = {'小明','小红','小刚'}
name_set.add('小强')
print(name_set)

# 删除元素
name_set.remove('小强')
print(name_set)

# 判断元素是否在集合中, in和not in
if '小明' in name_set:
print('小明在集合中')
else:
print('小明不在集合中')

# 判断元素不在集合中
if '小强' not in name_set:
print('小强不在集合中')
else:
print('小强在集合中')

"""
💡 题目:一个数组中除了两个数字只出现一次外,其他数字都出现两次。
找出这两个只出现一次的数字。
输入: [4, 1, 2, 1, 2, 3, 5, 5]
输出: 3 和 4

思路:
1. 准备集合A和列表B,集合A用来存储出现的数字,集合B用来记录只出现过1次的数字
2. 将出现的数字和集合A判断,如果集合A中有数字,说明这个数字出现过,将数字从集合B
中删除。
如果A中没有这个数字,说明数字没出现过,将数字放入集合A和集合B
3. 最后循环结束,打印集合B,就是只出现一次的数字了
"""
num_list = [4, 1, 2, 1, 2, 3, 5, 5]
# 存储所有出现的数字
set_a = set()
# 存储只出现一次的数据
set_b = set()
for num in num_list:
# num没有出现过
if num not in set_a:
set_a.add(num)
set_b.add(num)
continue
# num出现过,则从set_b中移除该数字
set_b.remove(num)

print('出现过1次的数字: ')
for num in set_b:
print(num,end='\t')
print()

案例 3:统计每个数字出现的次数(去重后排序)

1
2
3
4
5
6
"""
💡 题目:统计列表中每个数字出现的次数,并按数字大小排序输出。
输入: [4, 2, 2, 8, 3, 3, 1, 4, 3]
"""



案例 4:判断是否为连续序列

1
2
3
4
5
6
"""
💡 题目:判断一个列表中的数字是否能组成连续序列。
例如:[4, 2, 3, 1] → True(可以重排成 [1,2,3,4])
[5, 3, 2, 6] → False(范围是 2~6,缺了 4)
"""


案例 5:找出数组中消失的数字(变形)

1
2
3
4
5
"""
💡 题目:给你一个 1 到 n 的数组,但缺少了一些数字。
找出所有 1~n 范围内消失的数字。
输入: [4, 3, 2, 7, 8, 2, 3, 1] (n=8,但数组里缺了 5, 6)
"""

4.字典的定义与使用

为什么需要字典(dict)

思考1:比如我们要存储一个人的信息,姓名:ZhangSan,年龄:19周岁,性别:男,家庭住址:上海市浦东新区,如何快速存储。

1
person = ['ZhangSan', 19, '男', '上海市浦东新区']

思考2:在日常生活中,姓名、年龄以及性别同属于一个人的基本特征。但是如果使用列表对其进行存储,则分散为3个元素,这显然不合逻辑。我们有没有办法,将其保存在同一个元素中,姓名、年龄以及性别都作为这个元素的3个属性。

答:使用Python中的字典

Python中字典(dict)的概念

特点:

① 符号为大括号(花括号) => {}

② 数据为键值对形式出现 => {key:value},key:键名,value:值,在同一个字典中,key必须是唯一(类似于索引下标)

③ 各个键值对之间用逗号隔开

在字典中,键名除了可以使用字符串的形式,还可以使用数值的形式来进行表示

定义:

1
2
3
4
5
6
7
# 有数据字典
dict1 = {'name': 'ZhangSan', 'age': 19, 'gender': '男'}

# 空字典
dict2 = {}

dict3 = dict()

在Python代码中,字典中的key必须使用引号引起来

相关操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 1- 演示: 如何定义字典
# 格式:
# 格式1: 变量名称 = {key1:value1,key2:value2,key3:value3.....}
# 格式2: 变量名称 = {} 或者 变量名称 = dict() 空字典

# 构建一个有内容的字典
person = {'name': '小明', 'age': 19, 'address': "上海市浦东新区张江镇", 'sex': '男', 'hobby': '篮球、足球、游泳'}
print(person)
print(type(person))

# 构建空字典
dict1 = {}
dict2 = dict()
print(dict1)
print(type(dict1))
print(dict2)
print(type(dict2))

# 遍历操作
for key in person:
print(key)
print(person[key])

字典的增操作(重点)

基本语法:

1
2
字典名称[key] = value
注:如果key存在则修改这个key对应的值;如果key不存在则新增此键值对。

字典的删操作

① del 字典名称[key]:删除指定元素

1
2
3
person = {'name': '小明', 'age': 19}
del person['age']
print(person)

② clear()方法:清空字典中的所有key

1
2
3
person = {'name': '小明', 'age': 19}
person.clear()
print(person)

字典的改操作

基本语法:

1
2
字典名称[key] = value
注:如果key存在则修改这个key对应的值;如果key不存在则新增此键值对。

案例:定义一个字典,里面有name、age以及address,修改address这个key的value值

1
2
3
person = {'name': '小明', 'age': 19, 'address': '上海市浦东新区'}
person['address'] = '上海市黄浦区'
print(person)

字典的查操作

① 查询方法:使用具体的某个key查询数据,如果未找到,则直接报错。

1
字典序列[key]

② 字典的相关查询方法

编号 函数 作用
1 keys() 以列表返回一个字典所有的键
2 values() 以列表返回字典中的所有值
3 items() 以列表返回可遍历的(键, 值) 元组数据

案例1:提取person字典中的所有key

1
2
person = {'name': '小明', 'age': 19, 'address': '上海市浦东新区'}
print(person.keys())

案例2:提取person字典中的所有value值

1
2
person = {'name': '小明', 'age': 19, 'address': '上海市浦东新区'}
print(person.values())

案例3:使用items()方法提取数据

1
2
person = {'name': '小明', 'age': 19, 'address': '上海市浦东新区'}
print(person.items())

相关的API操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 字典的相关API:
# 如何增加元素:
# 需求: 为person 字典增加一个 birthday 属性 值为 2005-08-20
person = {'name': '小明', 'age': 19, 'address': "上海市浦东新区张江镇", 'sex': '男', 'hobby': '篮球、足球、游泳'}
person["birthday"] = '2005-08-20'

print(person)

# 如何修改元素: 与添加一直, 如果key存在就是修改 如果不存在 就是添加
# 需求: 修改性别为 女
person['sex'] = "女"
print(person)

# 如何删除元素
# 删除某个元素: 根据key
del person['birthday']
print(person)
# 清空字典
# person.clear()
# print(person)

# 如何查询:
# 1- 根据 key 直接获取value: 如果key 不存在 直接报错
# 获取 address的值
print(person['address'])

# 2- 获取所有的keys
keys_list = person.keys()
print(keys_list)
# 3- 获取所有的value
values_list = person.values()
print(values_list)
# 4- 获取字典中每一个 kv对
# 快速返回变量: ctrl + alt + v
items = person.items()
print(items)

for kv in items:
print(kv)
print(f'{kv[0]},{kv[1]}')

字典案例

案例需求

给定一个字符串my_string,现在要求统计每个字符出现的次数: 形成结果: {‘字符’:出现次数,’字符2’:次数}例如: ‘abcdecf’ ==> {‘a’:1,’b’:1,’c’:2,’d’:1,’e’:1,’f’:1}

实现思路

①定义一个字符串

②初始化空字典,来存储对应字符和出现次数

③循环遍历字符串中每个字符

④如果字符串已经在字典中,计数加1,如果不在,初始化计数1: 字典[‘key’] += 1

⑤输出统计每个字符出现的次数

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"""
给定一个字符串my_string,现在要求统计每个字符出现的次数: 形成结果: {'字符':出现次数,'字符2':次数}

例如: 'abcdecf' ==> {'a':1,'b':1,'c':2,'d':1,'e':1,'f':1}
"""
# 1- 创建一个字符串
my_string = 'abcdecf'

# 2- 遍历字符串, 获取每一个字符
# 2.1 初始化一个用于保存结果的空字典
my_dict = {}
for e in my_string:

# 2.2 判断 当前遍历的这个元素 是否在 字典中存在呢?
if e in my_dict.keys():
# 说明 当前遍历的元素 在 字典中是存在的
my_dict[e] += 1
else:
# 说明 当前遍历的元素 在 字典中是不存在的
my_dict[e] = 1

print(my_dict)

巩固练习

需求: 编写一个程序将字符串转换为字典 例如:输入: ‘8=Eight 9=Nine 10=Ten’ 输出: {‘8’: ‘Eight’, ‘9’: ‘Nine’, ‘

字符串用法

Posted on 2026-04-08 | In python , AI

【掌握】字符串定义和切片

字符串的定义

字符串是 Python 中最常用的数据类型。我们一般使用引号来创建字符串。创建字符串很简单,只要为变量分配一个值即可。

案例1:使用单引号或双引号定义字符串变量

1
2
3
4
5
6
7
8
9
10
11
12
# 方式一: 使用 单引号 或者 双引号创建
student_name1 = '李明'
student_name2 = "李明"

# 思考: 如果在字符串里面也有单引号或者双引号怎么办呢?
# 解决方案一: 使用转义符 \
# msg = 'It\'s a wonderful day'
# 解决方案二:如果里面是单引号,那么外部就使用双引号,反之也可以
# msg = "It's a wonderful day"

# 如果字符串里面既有单引又有双引: 也可以针对跟外部引号一致的内容使用转义符
# msg = " He said \"It's great\" "

案例2:使用3个引号定义字符串变量

1
2
3
4
5
6
7
8
9
10
11
# 创建方式二: 使用三个引号(引号可以是单引也可以双引)
# msg = """ He said "It's great" """
# msg = ''' He said "It's great" '''
# 三引号支持换行
msg = '''
Python编程语言
学习资料
第一章内容
'''

print(msg)

注意:三引号形式的字符串支持换行操作

案例3:思考如何使用字符串定义”It’s sunny”

使用单引号情况

1
weather = 'It's sunny'

运行结果:会出现语法错误

出现以上问题的主要原因在于,以上字符串的定义代码出现了(syntax)语法错误。单引号在字符串定义中必须成对出现,而且Python解析器在解析代码时,会自动认为第一个单引号和最近的一个单引号是一对!

如果一定要在单引号中在放入一个单引号,必须使用反斜杠进行转义。

1
weather = 'It\'s sunny'

使用双引号情况

1
weather = "It's sunny"

注:在Python中,如果存在多个引号,建议① 单引号放在双引号中 ② 双引号放在单引号中。


字符串在计算机底层的存储形式

在计算机中,Python中的字符串属于序列结构。所以其底层存储占用一段连续的内存空间。

1
course = 'python'

结构原理图:

索引的最大值 = len(字符串) - 1

6个字符,则索引下标的最大值为6-1 = 5

1
2
3
4
+---+---+---+---+---+---+
| p | y | t | h | o | n |
+---+---+---+---+---+---+
0 1 2 3 4 5

注意:索引下标从0开始。

聊聊索引下标

索引下标,就是编号。比如电影院座位号,座位号的作用:按照编号快速找到对应的座位。同理,下标的作用即是通过下标快速找到对应的数据。

举个例子:

1
2
3
text = 'hello'
print(text[0]) # h
print(text[3]) # l

什么是字符串切片

所谓的切片是指对操作的对象截取其中一部分的操作。字符串、列表、元组都支持切片操作。

字符串切片基本语法

顾头不顾尾:

1
2
3
4
5
6
7
8
9
10
11
序列名称[开始位置下标:结束位置下标:步长(步阶)]

# 三个参数都有默认值:
# 开始位置默认值: 0
# 结束位置默认值: -1 (最后一个)
# 步长: 1

digits = '0123456789'
digits[0:3:1] # 012 => range方法非常类似,步长:每次前进1步
digits[0:3:2] # 02 => 每次前进2步
# 步长可以为负数,正数代表从左向右截取,负数代表从右向左截取

① 不包含结束位置下标对应的数据,正负整数均可;

② 步长是选取间隔,正负整数均可,正数从左向右,负数从右向左。默认步长为1。

还是有点陌生,没关系,给你举个栗子:

1
digits = '0123456789'

字符串切片示意图:

1
2
3
索引(正索引):  0    1   2   3   4   5   6   7   8   9
字符: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'
索引(逆索引): -10 -9 -8 -7 -6 -5 -4 -3 -2 -1

字符串切片案例

  • 案例1:基础切片操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    # 案例一:
    digits = '0123456789'

    # 1、从2到5开始切片,步长为1
    print(digits[2:5]) # 输出: 234

    # 2、只有结尾的字符串切片:代表从索引为0开始,截取到索引为5的位置(不包含索引为5的数据)
    print(digits[:5]) # 输出: 01234

    # 3、只有开头的字符串切片:代表从起始位置开始(例如 1),一直截取到字符串的结尾
    print(digits[1:]) # 输出: 123456789

    # 4、获取或拷贝整个字符串
    print(digits[:]) # 输出: 0123456789

    # 5、调整步阶:类似求偶数
    print(digits[::2]) # 输出: 02468

    # 6、把步阶设置为负整数:类似字符串翻转
    print(digits[::-1]) # 输出: 9876543210

    # 7、起始位置与结束位置都是负数
    print(digits[-5:-1]) # 输出: 5678

    # 8、结束字符为负数,如截取012345678
    print(digits[:-1]) # 输出: 012345678
  • 案例2:给定一个图片的名称为”photo.jpg”,代码实现获取这个图片的名称(photo)以及这个图片的后缀(.jpg)。

    • 分析:

      • ① 建议先获取点号的位置(目前还未学习,只能一个一个数)
      • ② 从开头切片到点号位置,得到的就是文件的名称
      • ③ 从点号开始切片,一直到文件的结尾,则得到的就是文件的后缀
    • 代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      # 案例二: 给定一个图片的名称为"photo.jpg",代码实现获取这个图片的名称(photo)以及这个图片的后缀(.jpg)。
      filename = "photo.jpg"

      # 方法一:已知点号位置在索引5
      name = filename[:5] # photo
      suffix = filename[5:] # .jpg

      print(f"文件名: {name}")
      print(f"后缀名: {suffix}")

      # 方法二:使用字符串方法(后续会学习)
      dot_index = filename.find('.')
      name = filename[:dot_index]
      suffix = filename[dot_index:]

【掌握】字符串的操作方法(内置)

字符串中的查找方法

所谓字符串查找方法即是查找子串在字符串中的位置或出现的次数。

基本语法:

1
字符串.find(要查找的字符或者子串)
编号 函数 作用
1 find() 检测某个子串是否包含在这个字符串中,如果在返回这个子串开始的位置下标,否则则返回-1。
2 index() 检测某个子串是否包含在这个字符串中,如果在返回这个子串开始的位置下标,否则则报异常。

☆ find()方法

作用:检测某个子串是否包含在这个字符串中,如果在返回这个子串开始的位置下标,否则则返回-1。

1
2
3
4
5
6
7
8
9
10
# 定义一个字符串
message = 'welcome to Python programming, Python is great'

# 查找Python子串是否出现在字符串中
position = message.find('Python')
print(f"Python首次出现的位置: {position}") # 输出: 11

# 在message中查找不存在的子串
position2 = message.find('Java')
print(f"Java出现的位置: {position2}") # 输出: -1

案例:使用input方法输入任意一个文件名称,求点号的索引下标

1
2
3
4
5
6
7
8
filename = input("请输入文件名: ")
dot_position = filename.find('.')
if dot_position != -1:
print(f"点号的位置在索引: {dot_position}")
print(f"文件名: {filename[:dot_position]}")
print(f"扩展名: {filename[dot_position:]}")
else:
print("该文件名中没有扩展名")

☆ index()方法

index()方法其功能与find()方法完全一致,唯一的区别在于当要查找的子串没有出现在字符串中时,find()方法返回-1,而index()方法则直接报错。

1
2
3
4
5
6
7
8
fruits = 'apple, banana, orange, grape'

# 判断apple是否出现在字符串fruits中
position = fruits.index('apple')
print(f"apple的位置: {position}") # 输出: 0

# 查找不存在的子串会报错
# position = fruits.index('mango') # 这行会引发ValueError异常

字符串的修改方法

所谓修改字符串,指的就是通过函数(方法)的形式修改字符串中的数据。

编号 函数 作用
1 replace() 返回替换后的字符串
2 split() 返回切割后的列表序列
3 title() 所有单词首字母大写

☆ replace()方法

基本语法:

1
字符串.replace(要替换的内容, 替换后的内容, 替换的次数-可以省略)

案例:编写一个字符串,然后把字符串中的Java替换为Python

1
2
3
4
5
6
7
8
9
10
11
12
13
text = 'I love Java programming and Java is powerful'

# 把字符串中所有Java字符替换为Python
new_text = text.replace('Java', 'Python')
print(new_text) # 输出: I love Python programming and Python is powerful

# 把字符串中的第一个Java进行替换为Python
new_text2 = text.replace('Java', 'Python', 1)
print(new_text2) # 输出: I love Python programming and Java is powerful

# 把and字符串替换为&
new_text3 = text.replace('and', '&')
print(new_text3) # 输出: I love Java programming & Java is powerful

目前在工作中,replace主要用于实现关键字替换或过滤功能。例如:敏感词过滤、地名简写替换等。

☆ split()方法

作用:对字符串进行切割操作,返回一个list()列表类型的数据

1
2
3
4
5
6
7
8
9
10
# 示例:按照逗号分隔日期
date_str = '2024-03-15'
date_parts = date_str.split('-')
print(date_parts) # 输出: ['2024', '03', '15']

# 示例:分割邮箱地址
email = 'user@example.com'
parts = email.split('@')
print(f"用户名: {parts[0]}") # 输出: user
print(f"域名: {parts[1]}") # 输出: example.com

☆ join()方法

作用:和split()方法正好相反,其主要功能是把序列拼接为字符串

1
字符串.join(数据序列)

案例:把水果列表[‘apple’, ‘banana’, ‘orange’]拼接成’apple-banana-orange’

1
2
3
4
5
6
7
8
fruit_list = ['apple', 'banana', 'orange']
result = '-'.join(fruit_list)
print(result) # 输出: apple-banana-orange

# 其他示例
words = ['Hello', 'Python', 'World']
sentence = ' '.join(words)
print(sentence) # 输出: Hello Python World

字符串案例

案例需求

生成一个6位随机验证码,要求包含大写字母、小写字母和数字的组合。

示例:

1
2
生成的验证码: aB3xK9
生成的验证码: Q7mN2p

实现思路

  1. 定义包含所有可用字符的字符串(大写字母、小写字母、数字)
  2. 使用切片和索引随机选取字符
  3. 将选取的字符拼接成最终的验证码

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import random

def generate_code(length=6):
"""
生成指定长度的随机验证码
参数:
length: 验证码长度,默认为6
返回:
生成的验证码字符串
"""
# 定义所有可用字符
uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
lowercase = 'abcdefghijklmnopqrstuvwxyz'
digits = '0123456789'

# 合并所有字符
all_chars = uppercase + lowercase + digits

# 生成验证码
code = ''
for i in range(length):
# 随机选择一个索引位置
random_index = random.randint(0, len(all_chars) - 1)
# 通过索引获取字符并添加到验证码中
code += all_chars[random_index]

return code

# 测试:生成5个验证码
print("生成5个随机验证码:")
for i in range(5):
print(f"验证码{i+1}: {generate_code()}")

# 生成不同长度的验证码
print("\n生成不同长度的验证码:")
print(f"4位验证码: {generate_code(4)}")
print(f"8位验证码: {generate_code(8)}")

扩展练习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 练习1:确保验证码至少包含一个数字和一个字母
def generate_secure_code(length=6):
uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
lowercase = 'abcdefghijklmnopqrstuvwxyz'
digits = '0123456789'
all_chars = uppercase + lowercase + digits

while True:
code = ''
for i in range(length):
random_index = random.randint(0, len(all_chars) - 1)
code += all_chars[random_index]

# 检查是否包含数字和字母
has_letter = any(c in uppercase + lowercase for c in code)
has_digit = any(c in digits for c in code)

if has_letter and has_digit:
return code

print("安全验证码:", generate_secure_code())

# 练习2:验证用户输入的验证码是否正确(不区分大小写)
def verify_code(user_input, correct_code):
"""验证验证码(不区分大小写)"""
return user_input.lower() == correct_code.lower()

# 模拟验证码验证
real_code = "aB3xK9"
user_code = "AB3XK9"
if verify_code(user_code, real_code):
print("验证码正确!")
else:
print("验证码错误!")
12…41>

401 posts
18 categories
21 tags
RSS
GitHub ZhiHu
© 2026 恋恋风辰 本站总访问量次 | 本站访客数人
Powered by Hexo
|
Theme — NexT.Muse v5.1.3