闭包

闭包是指一个内层函数能够记住并访问其外层函数的变量,即使外层函数已经执行完毕

在了解闭包之前,先回顾函数的特点:函数内部定义的变量(局部变量)在函数调用结束后就会销毁。但闭包打破了这一规则——它可以"抓住"外层函数的变量,让它们在内存中留存。

  1. 闭包的构成条件

    1. 有内层函数(在函数内部再定义一个函数)
    2. 内层函数使用了外层函数的变量
    3. 外层函数返回了内层函数本身(不调用,只返回函数对象)
1
2
3
4
5
6
7
8
9
10
11
# 一个简单的闭包
def outer():
msg = "Hello" # 外层函数的变量

def inner(): # 内层函数
print(msg) # 使用了外层函数的 msg

return inner # 返回内层函数(不调用它)

f = outer() # outer() 执行完毕,按理说 msg 应该被销毁
f() # 输出 Hello ← 但闭包让 inner 记住了 msg!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 闭包保存的不是值,而是变量本身
def counter():
count = 0

def increment():
nonlocal count # 声明要修改外层变量
count += 1
print(f"第 {count} 次调用")

return increment

c = counter()
c() # 输出 第 1 次调用
c() # 输出 第 2 次调用
c() # 输出 第 3 次调用
# count 的值在多次调用间被"记住"了
  1. 通过闭包创建带参数配置的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"""
闭包的常见用法:用外层参数"定制"内层函数的行为
"""

# 创建不同次方的计算器
def power_n(n):
"""返回一个计算 n 次方的函数"""
def inner(x):
return x ** n # n 被闭包"留住"
return inner

square = power_n(2) # 创建一个平方函数
cube = power_n(3) # 创建一个立方函数

print(square(5)) # 输出 25
print(square(10)) # 输出 100
print(cube(5)) # 输出 125
1
2
3
4
5
6
7
8
9
10
11
12
# 使用闭包实现带前缀的日志函数
def make_logger(prefix):
"""创建带指定前缀的日志函数"""
def logger(message):
print(f"[{prefix}] {message}")
return logger

info_log = make_logger("INFO")
err_log = make_logger("ERROR")

info_log("程序启动") # 输出 [INFO] 程序启动
err_log("文件未找到") # 输出 [ERROR] 文件未找到
  1. 闭包 vs 类
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 make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment

# 类方式:同样功能
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count

# 两者使用方式一样
c1 = make_counter()
print(c1()) # 输出 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
27
"""
装饰器的本质:
装饰器 = 接受函数 → 包装新功能 → 返回新函数

下面的 @decorator 语法糖等价于 func = decorator(func)
"""

# 手动方式理解装饰器原理
def my_decorator(func):
"""装饰器:包装传入的函数"""
def wrapper():
print("函数执行前...")
func()
print("函数执行后...")
return wrapper

def say_hello():
print("Hello!")

# 手动装饰
say_hello = my_decorator(say_hello)
say_hello()

# 输出:
# 函数执行前...
# Hello!
# 函数执行后...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用 @ 语法糖(等价写法,更简洁)
def my_decorator(func):
def wrapper():
print("函数执行前...")
func()
print("函数执行后...")
return wrapper

@my_decorator # ← 等价于 say_hi = my_decorator(say_hi)
def say_hi():
print("Hi!")

say_hi()

# 输出:
# 函数执行前...
# Hi!
# 函数执行后...
  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
27
28
29
30
"""
要让装饰器支持任意参数,wrapper 使用 *args 和 **kwargs 接收
"""

def log_call(func):
"""记录每次函数调用的参数和返回值"""
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__},参数:{args}, {kwargs}")
result = func(*args, **kwargs)
print(f"返回值:{result}")
return result
return wrapper

@log_call
def add(a, b):
return a + b

@log_call
def greet(name, greeting="你好"):
return f"{greeting}{name}!"

add(3, 5)
# 输出:
# 调用 add,参数:(3, 5), {}
# 返回值:8

greet("PyTs1n9", greeting="Hello")
# 输出:
# 调用 greet,参数:('PyTs1n9',), {'greeting': 'Hello'}
# 返回值:Hello,PyTs1n9!
  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
"""
当装饰器本身需要参数时,需要在外面再包一层函数
三层结构:最外层接收装饰器参数 → 中间层接收函数 → 最内层接收调用参数
"""

def repeat(n):
"""让函数重复执行 n 次的装饰器"""
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(n):
print(f"第 {i+1} 次:", end="")
func(*args, **kwargs)
return wrapper
return decorator

@repeat(3) # ← repeat(3) 返回 decorator,decorator 再装饰 say_hey
def say_hey():
print("Hey!")

say_hey()

# 输出:
# 第 1 次:Hey!
# 第 2 次:Hey!
# 第 3 次:Hey!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 带参数的装饰器 — 权限校验
def require_role(role):
"""检查用户角色是否匹配"""
def decorator(func):
def wrapper(user, *args, **kwargs):
if user.get("role") != role:
print(f"权限不足:需要 {role} 角色")
return None
return func(user, *args, **kwargs)
return wrapper
return decorator

@require_role("admin")
def delete_user(user, username):
print(f"用户 {username} 已被删除")

admin = {"name": "Admin", "role": "admin"}
guest = {"name": "Guest", "role": "guest"}

delete_user(admin, "test_user") # 输出 用户 test_user 已被删除
delete_user(guest, "test_user") # 输出 权限不足:需要 admin 角色
  1. 使用 functools.wraps 保留原函数信息
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
"""
装饰器会替换原函数,导致原函数的 __name__、__doc__ 等丢失
使用 @functools.wraps(func) 来保留这些信息
"""

import functools

# 不保留信息的装饰器(有问题)
def bad_decorator(func):
def wrapper(*args, **kwargs):
"""这是 wrapper 的文档"""
return func(*args, **kwargs)
return wrapper

# 保留信息的装饰器(推荐)
def good_decorator(func):
@functools.wraps(func) # ← 保留原函数的元信息
def wrapper(*args, **kwargs):
"""这是 wrapper 的文档"""
return func(*args, **kwargs)
return wrapper

@bad_decorator
def foo():
"""foo 的文档"""
pass

@good_decorator
def bar():
"""bar 的文档"""
pass

print(foo.__name__) # 输出 wrapper ← 丢失了!
print(foo.__doc__) # 输出 这是 wrapper 的文档
print(bar.__name__) # 输出 bar ← 保留!
print(bar.__doc__) # 输出 bar 的文档
  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
27
28
29
30
31
32
33
34
35
36
37
"""
多个装饰器可以同时使用,执行顺序从下到上(从靠近函数的开始)
"""

def deco_a(func):
print("deco_a 执行")
def wrapper():
print("A 前")
func()
print("A 后")
return wrapper

def deco_b(func):
print("deco_b 执行")
def wrapper():
print("B 前")
func()
print("B 后")
return wrapper

@deco_a # ← 后执行(外层)
@deco_b # ← 先执行(内层)
def test():
print("-- 执行 test --")

# 输出(装饰阶段):
# deco_b 执行
# deco_a 执行

test()
# 输出(调用时):
# A 前
# B 前
# -- 执行 test --
# B 后
# A 后
# 结论:执行顺序是 外→内→核心→内→外(洋葱模型)

property 属性

property 将一个方法调用伪装成属性访问,让你在"读取属性"时自动触发方法的执行

  1. property 的基本使用
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
"""
@property 装饰器让方法被当作属性一样访问
核心理解:表面上 obj.attr,实际上调用了 obj.getter()
"""

class Circle:
def __init__(self, radius):
self._radius = radius

@property
def radius(self):
"""读取半径(方法变成了属性)"""
return self._radius

@radius.setter
def radius(self, value):
"""设置半径(带校验)"""
if value <= 0:
raise ValueError("半径必须大于 0")
self._radius = value

@property
def area(self):
"""面积 — 只读(没有 setter 就不能赋值)"""
import math
return math.pi * self._radius ** 2

c = Circle(5)
print(c.radius) # 输出 5 (像属性一样读,实际调用了 getter)
c.radius = 10 # 像属性一样赋值,实际调用了 setter
print(c.area) # 输出 314.159... (只读属性)
# c.area = 100 # 报错 AttributeError,只读
  1. property 的三种访问模式
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
"""
property 支持三种访问控制:
- @property → getter(读取)
- @xxx.setter → setter(赋值)
- @xxx.deleter → deleter(删除)
"""

class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius

@property
def celsius(self):
return self._celsius

@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("温度不能低于绝对零度 -273.15°C")
self._celsius = value

@celsius.deleter
def celsius(self):
print("温度数据已重置")
self._celsius = 0

# 扩展:华氏度(利用 celsius 计算,不需要额外存储)
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32

@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = (value - 32) * 5/9 # 复用 celsius 的 setter 校验

t = Temperature(25)
print(f"{t.celsius}°C = {t.fahrenheit}°F") # 输出 25°C = 77.0°F

t.fahrenheit = 98.6
print(f"{t.celsius:.1f}°C") # 输出 37.0°C

del t.celsius # 输出 温度数据已重置
print(t.celsius) # 输出 0
  1. property 的数学计算属性
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
"""
property 非常适合表示"计算属性"——由其他属性推导出来的值
"""

class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height

@property
def width(self):
return self._width

@width.setter
def width(self, value):
if value <= 0:
raise ValueError("宽度必须大于 0")
self._width = value

@property
def height(self):
return self._height

@height.setter
def height(self, value):
if value <= 0:
raise ValueError("高度必须大于 0")
self._height = value

# 以下都是计算属性(只读)
@property
def area(self):
"""面积"""
return self._width * self._height

@property
def perimeter(self):
"""周长"""
return 2 * (self._width + self._height)

@property
def is_square(self):
"""是否为正方形"""
return self._width == self._height

@property
def diagonal(self):
"""对角线长度"""
return (self._width ** 2 + self._height ** 2) ** 0.5

rect = Rectangle(3, 4)
print(rect.area) # 输出 12
print(rect.perimeter) # 输出 14
print(rect.diagonal) # 输出 5.0
print(rect.is_square) # 输出 False

上下文管理器

上下文管理器用于自动管理资源的获取和释放,确保使用完毕后资源被正确释放

  1. with 语句的本质
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"""
with 语句会:
1. 调用对象的 __enter__() — 获取资源,返回值绑定到 as 后的变量
2. 执行 with 块中的代码
3. 无论是否发生异常,调用 __exit__() — 释放资源
"""

# 文件操作是最常见的 with 用法
with open("test.txt", "w", encoding="utf-8") as f:
f.write("Hello")
# 缩进结束后,文件自动关闭(相当于 f.close())

# 等价的手动写法
# f = open("test.txt", "w")
# try:
# f.write("Hello")
# finally:
# f.close()
  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
27
28
29
30
31
32
33
34
35
36
37
"""
实现一个上下文管理器,只需要定义两个方法:
__enter__(self) — 进入 with 块时调用,返回值赋给 as 变量
__exit__(self, exc_type, exc_val, exc_tb) — 退出时调用,处理异常
"""

class FileManager:
"""自定义文件管理器(演示 with 原理)"""

def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None

def __enter__(self):
print(f"打开文件:{self.filename}")
self.file = open(self.filename, self.mode, encoding="utf-8")
return self.file # 返回的值赋给 as 后面的变量

def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
print(f"文件已关闭:{self.filename}")

# 返回 True 可以吞掉异常(不推荐,除非有意为之)
# return False 让异常正常传播
return False

# 使用自定义的上下文管理器
with FileManager("demo.txt", "w") as f:
f.write("Hello Python")
print("写入成功")

# 输出:
# 打开文件:demo.txt
# 写入成功
# 文件已关闭:demo.txt
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
# 实用的上下文管理器:计时器
import time

class Timer:
"""测量代码块执行时间的上下文管理器"""

def __enter__(self):
self.start = time.time()
return self

def __exit__(self, *args):
self.end = time.time()
self.elapsed = self.end - self.start
print(f"耗时:{self.elapsed:.3f} 秒")
return False

with Timer():
total = 0
for i in range(1000000):
total += i
print(f"计算结果:{total}")

# 输出:
# 计算结果:499999500000
# 耗时:0.045 秒
  1. 自定义上下文管理器(生成器方式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"""
使用 @contextmanager 装饰器,用生成器写上下文管理器更简洁
yield 之前的代码是 __enter__,之后的代码是 __exit__
"""
from contextlib import contextmanager

@contextmanager
def file_manager(filename, mode):
"""使用生成器实现的上下文管理器"""
print(f"打开文件:{filename}")
f = open(filename, mode, encoding="utf-8")
try:
yield f # yield 返回值给 as 后的变量
finally:
f.close()
print(f"文件已关闭:{filename}")

# 和类方式一样使用
with file_manager("demo.txt", "r") as f:
print(f.read())

深浅拷贝

拷贝对象时,浅拷贝只复制表层,深拷贝递归复制所有层级,两者在嵌套对象上有本质区别

  1. 直接赋值 vs 浅拷贝 vs 深拷贝
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
"""
理解三种"复制"方式的区别:

直接赋值 — 只复制引用,两个变量指向同一个对象
浅拷贝 — 复制外层对象,内层的嵌套对象仍然共享(只复制一层)
深拷贝 — 完全独立的新对象,递归复制所有层级
"""
import copy

# 原始列表(包含嵌套列表)
original = [[1, 2, 3], [4, 5, 6]]

# 直接赋值 — 完全相同
assigned = original

# 浅拷贝 — 外层独立,内层共享
shallow = copy.copy(original)

# 深拷贝 — 完全独立
deep = copy.deepcopy(original)

# 修改原始列表的外层
original.append([7, 8, 9])
print(original) # 输出 [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(assigned) # 输出 [[1, 2, 3], [4, 5, 6], [7, 8, 9]] ← 跟着变了
print(shallow) # 输出 [[1, 2, 3], [4, 5, 6]] ← 外层独立
print(deep) # 输出 [[1, 2, 3], [4, 5, 6]] ← 完全独立
1
2
3
4
5
6
7
8
9
10
11
12
13
# 修改内层 — 浅拷贝和深拷贝的关键区别
import copy

original = [[1, 2, 3], [4, 5, 6]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)

# 修改原始列表的内层
original[0][0] = 999

print(original) # 输出 [[999, 2, 3], [4, 5, 6]]
print(shallow) # 输出 [[999, 2, 3], [4, 5, 6]] ← 内层共享,跟着变了!
print(deep) # 输出 [[1, 2, 3], [4, 5, 6]] ← 完全独立
  1. 三种方式的对比
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import copy

original = [1, 2, [3, 4]]

# 方式一:直接赋值
a = original

# 方式二:浅拷贝(三种等效写法)
b = copy.copy(original)
c = original.copy() # 列表的 copy 方法
d = original[:] # 切片也是浅拷贝

# 方式三:深拷贝
e = copy.deepcopy(original)

# is 运算符检查是否为同一个对象
print(a is original) # 输出 True ← 赋值,同一对象
print(b is original) # 输出 False ← 浅拷贝,不同对象
print(e is original) # 输出 False ← 深拷贝,不同对象
1
2
3
4
5
6
7
8
9
10
11
"""
三种方式总结:

┌──────────┬────────────┬──────────────┬────────────┐
│ 方式 │ 外层独立? │ 内层独立? │ 适用场景 │
├──────────┼────────────┼──────────────┼────────────┤
│ = 赋值 │ 否 │ 否 │ 只想用别名 │
│ 浅拷贝 │ 是 │ 否 │ 单层数据 │
│ 深拷贝 │ 是 │ 是 │ 嵌套数据 │
└──────────┴────────────┴──────────────┴────────────┘
"""
  1. 实际应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import copy

# 场景:游戏中的角色模板
# 浅拷贝 — 嵌套属性(如装备列表)仍然共享,修改一个角色的装备影响其他
template = {
"hp": 100,
"mp": 50,
"equipment": ["木剑", "布衣"] # 嵌套对象
}

player1 = template.copy() # 浅拷贝
player2 = copy.deepcopy(template) # 深拷贝

# 添加装备到 player1
player1["equipment"].append("铁盾")

print(template["equipment"]) # 输出 ['木剑', '布衣', '铁盾'] ← 被影响了!
print(player2["equipment"]) # 输出 ['木剑', '布衣'] ← 不受影响
# 结论:嵌套对象应该用深拷贝,避免意外的数据共享

eval 函数

eval() 将字符串当作 Python 表达式来执行并返回结果,功能强大但需谨慎使用

  1. eval 的基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"""
eval(expression, globals, locals)
- expression:要执行的 Python 表达式字符串
- globals:可选的全局命名空间(字典)
- locals:可选的局部命名空间(字典)
返回值是表达式的结果
"""

# 计算数学表达式
result = eval("3 + 5 * 2")
print(result) # 输出 13

# 访问变量
x = 10
y = 20
print(eval("x * y + 5")) # 输出 205

# 调用函数
print(eval("len('Hello')")) # 输出 5

# 操作列表
print(eval("[1, 2, 3] + [4, 5]")) # 输出 [1, 2, 3, 4, 5]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# eval 实现简易计算器
def calculator():
"""让用户输入表达式并计算结果"""
while True:
expr = input("请输入表达式(输入 q 退出):")
if expr.lower() == "q":
print("退出计算器")
break
try:
result = eval(expr)
print(f"= {result}")
except Exception as e:
print(f"错误:{e}")

# calculator()
# 输入 2 ** 10 → = 1024
# 输入 (50 - 12) / 2 → = 19.0
  1. eval 的安全限制
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
"""
eval 可以执行任意 Python 代码,具有安全隐患
永远不要对不可信的用户输入使用 eval!
使用 globals 参数限制可用的函数和变量
"""

# 不安全的用法(永远不要这样做!)
user_input = input("输入:")
# result = eval(user_input) # 危险!可执行任意代码,如 __import__('os').system('rm -rf /')

# 安全限制:传递空的 globals 和只允许的 locals
def safe_eval(expr):
"""安全地计算简单的数学表达式"""
allowed = {
"abs": abs,
"round": round,
"min": min,
"max": max,
"pow": pow,
}
return eval(expr, {"__builtins__": {}}, allowed)

# 只能使用允许的数学函数
# print(safe_eval("abs(-5) + pow(2, 10)")) # 允许
# print(safe_eval("open('file.txt')")) # 被阻止,因为 open 不在 allowed 中
  1. eval 的替代方案
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
"""
对于特定场景,使用更安全的替代方案:
- 数学表达式 → 使用 ast.literal_eval() 或自己解析
- 字面量解析 → 使用 ast.literal_eval()(只解析字面量,安全)
- JSON 数据 → 使用 json.loads()
"""

import ast
import json

# ast.literal_eval — 安全地解析 Python 字面量(字符串、数字、列表、字典等)
# 它只能解析字面量,不能执行函数调用,更加安全
data = ast.literal_eval("[1, 2, 3]")
print(data) # 输出 [1, 2, 3]

# 对比 eval(允许函数调用,危险)
# eval("__import__('os').system('ls')") # 可以执行系统命令!

# 对比 literal_eval(禁止函数调用,安全)
# ast.literal_eval("__import__('os').system('ls')") # 报错,不允许

# json.loads — 解析 JSON 字符串
json_str = '{"name": "PyTs1n9", "age": 18}'
parsed = json.loads(json_str)
print(parsed["name"]) # 输出 PyTs1n9

综合练习

将高阶技巧融会贯通,通过实际案例巩固闭包、装饰器、property、上下文管理器的综合使用

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 time
import functools
from contextlib import contextmanager

# 上下文管理器版计时器
@contextmanager
def timer_block(name="代码块"):
"""测量代码块执行时间"""
start = time.time()
try:
yield
finally:
elapsed = time.time() - start
print(f"[{name}] 耗时:{elapsed:.3f} 秒")

# 装饰器版计时器
def timer_decorator(func):
"""测量函数执行时间的装饰器"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
with timer_block(f"函数 {func.__name__}"):
return func(*args, **kwargs)
return wrapper

@timer_decorator
def slow_function(n):
"""模拟耗时操作"""
total = 0
for i in range(n * 100000):
total += i
return total

# result = slow_function(10)
# 输出 [函数 slow_function] 耗时:0.042 秒
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
"""
练习二:带缓存的函数(综合 闭包 + 装饰器)
实现一个 memoize 装饰器,缓存函数计算结果,相同参数不再重复计算
"""
import functools

def memoize(func):
"""缓存装饰器:相同参数只计算一次"""
cache = {} # 闭包变量,保存缓存结果

@functools.wraps(func)
def wrapper(*args):
if args in cache:
print(f"命中缓存:{args}{cache[args]}")
return cache[args]
result = func(*args)
cache[args] = result
print(f"计算并缓存:{args}{result}")
return result

wrapper.cache = cache # 暴露缓存,方便外部查看
return wrapper

@memoize
def fibonacci(n):
"""第 n 个斐波那契数(递归,但用缓存避免重复计算)"""
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)

# 测试
print(fibonacci(10)) # 输出 55
print(fibonacci(10)) # 输出 55(命中缓存)
print(f"已缓存 {len(fibonacci.cache)} 个结果") # 输出 已缓存 11 个结果
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
"""
练习三:数据验证类(综合 property + 深浅拷贝)
"""
import copy

class UserProfile:
"""用户资料,使用 property 确保数据合法性"""

def __init__(self, name, age, email, tags=None):
self.name = name
self.age = age
self.email = email
# 注意:使用深拷贝确保 tags 独立存储
self._tags = copy.deepcopy(tags) if tags else []

@property
def name(self):
return self._name

@name.setter
def name(self, value):
if not value or not value.strip():
raise ValueError("姓名不能为空")
self._name = value.strip()

@property
def age(self):
return self._age

@age.setter
def age(self, value):
if not isinstance(value, int) or value < 0 or value > 150:
raise ValueError(f"年龄不合法:{value}")
self._age = value

@property
def email(self):
return self._email

@email.setter
def email(self, value):
if "@" not in value:
raise ValueError(f"邮箱格式错误:{value}")
self._email = value

@property
def tags(self):
"""标签列表(返回浅拷贝,防止外部直接修改)"""
return self._tags.copy()

def add_tag(self, tag):
"""通过方法添加标签(受控的修改方式)"""
if tag not in self._tags:
self._tags.append(tag)

def remove_tag(self, tag):
if tag in self._tags:
self._tags.remove(tag)

def __str__(self):
return f"User({self.name}, {self.age}岁, {self.email}, tags={self._tags})"

# 使用
user = UserProfile("PyTs1n9", 25, "pyts1n9@example.com", ["Python", "学习"])
print(user) # 输出 User(PyTs1n9, 25岁, pyts1n9@example.com, tags=['Python', '学习'])

# 尝试非法数据
# user.age = -5 # ValueError: 年龄不合法:-5
# user.email = "invalid" # ValueError: 邮箱格式错误:invalid

# tags 是只读的(返回副本)
tags = user.tags
tags.append("非法添加") # 修改的是副本,不影响原始数据
print(user.tags) # 输出 ['Python', '学习'] ← 不受影响

高阶技巧是 Python 从"能写"到"写好"的关键一跃。闭包让函数有了记忆,装饰器在不改源码的前提下扩展功能,property 让方法调用像属性一样自然,上下文管理器确保资源安全释放,深浅拷贝帮你精确控制数据的独立性,而 eval 则是一把需要谨慎使用的瑞士军刀。将这些技巧融入日常编码,你的 Python 代码将变得更简洁、更优雅、更可靠。

Python Study Note 系列文章

  1. Python Study Note
  2. Python 函数使用(四)
  3. Python 判断语句(二)
  4. Python 基础语法(一)
  5. Python 异常处理(七)
  6. Python 循环语句(三)
  7. Python 数据容器(五)
  8. Python 文件基础操作(六)
  9. Python 模块与包(八)
  10. Python 类型注解(十)
  11. Python 面向对象(九)重点
  12. Python 高阶技巧(十一)