闭包
闭包是指一个内层函数能够记住并访问其外层函数的变量,即使外层函数已经执行完毕
在了解闭包之前,先回顾函数的特点:函数内部定义的变量(局部变量)在函数调用结束后就会销毁。但闭包打破了这一规则——它可以"抓住"外层函数的变量,让它们在内存中留存。
-
闭包的构成条件
- 有内层函数(在函数内部再定义一个函数)
- 内层函数使用了外层函数的变量
- 外层函数返回了内层函数本身(不调用,只返回函数对象)
1 2 3 4 5 6 7 8 9 10 11
| def outer(): msg = "Hello" def inner(): print(msg) return inner
f = outer() f()
|
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() c() c()
|
- 通过闭包创建带参数配置的函数
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 return inner
square = power_n(2) cube = power_n(3)
print(square(5)) print(square(10)) print(cube(5))
|
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("程序启动") err_log("文件未找到")
|
- 闭包 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 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()
|
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 def say_hi(): print("Hi!")
say_hi()
|
- 装饰有参数的函数
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)
greet("PyTs1n9", greeting="Hello")
|
- 带参数的装饰器
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) def say_hey(): print("Hey!")
say_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") delete_user(guest, "test_user")
|
- 使用 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__) print(foo.__doc__) print(bar.__name__) print(bar.__doc__)
|
- 多个装饰器叠加
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 --")
test()
|
property 属性
property 将一个方法调用伪装成属性访问,让你在"读取属性"时自动触发方法的执行
- 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) c.radius = 10 print(c.area)
|
- 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 @property def fahrenheit(self): return self._celsius * 9/5 + 32 @fahrenheit.setter def fahrenheit(self, value): self.celsius = (value - 32) * 5/9
t = Temperature(25) print(f"{t.celsius}°C = {t.fahrenheit}°F")
t.fahrenheit = 98.6 print(f"{t.celsius:.1f}°C")
del t.celsius print(t.celsius)
|
- 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) print(rect.perimeter) print(rect.diagonal) print(rect.is_square)
|
上下文管理器
上下文管理器用于自动管理资源的获取和释放,确保使用完毕后资源被正确释放
- 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 open("test.txt", "w", encoding="utf-8") as f: f.write("Hello")
|
- 自定义上下文管理器(类方式)
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 def __exit__(self, exc_type, exc_val, exc_tb): if self.file: self.file.close() print(f"文件已关闭:{self.filename}") return False
with FileManager("demo.txt", "w") as f: f.write("Hello Python") 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
| 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}")
|
- 自定义上下文管理器(生成器方式)
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 finally: f.close() print(f"文件已关闭:{filename}")
with file_manager("demo.txt", "r") as f: print(f.read())
|
深浅拷贝
拷贝对象时,浅拷贝只复制表层,深拷贝递归复制所有层级,两者在嵌套对象上有本质区别
- 直接赋值 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) print(assigned) print(shallow) print(deep)
|
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) print(shallow) print(deep)
|
- 三种方式的对比
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() d = original[:]
e = copy.deepcopy(original)
print(a is original) print(b is original) print(e is original)
|
1 2 3 4 5 6 7 8 9 10 11
| """ 三种方式总结:
┌──────────┬────────────┬──────────────┬────────────┐ │ 方式 │ 外层独立? │ 内层独立? │ 适用场景 │ ├──────────┼────────────┼──────────────┼────────────┤ │ = 赋值 │ 否 │ 否 │ 只想用别名 │ │ 浅拷贝 │ 是 │ 否 │ 单层数据 │ │ 深拷贝 │ 是 │ 是 │ 嵌套数据 │ └──────────┴────────────┴──────────────┴────────────┘ """
|
- 实际应用
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["equipment"].append("铁盾")
print(template["equipment"]) print(player2["equipment"])
|
eval 函数
eval() 将字符串当作 Python 表达式来执行并返回结果,功能强大但需谨慎使用
- 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)
x = 10 y = 20 print(eval("x * y + 5"))
print(eval("len('Hello')"))
print(eval("[1, 2, 3] + [4, 5]"))
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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}")
|
- 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("输入:")
def safe_eval(expr): """安全地计算简单的数学表达式""" allowed = { "abs": abs, "round": round, "min": min, "max": max, "pow": pow, } return eval(expr, {"__builtins__": {}}, allowed)
|
- 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
data = ast.literal_eval("[1, 2, 3]") print(data)
json_str = '{"name": "PyTs1n9", "age": 18}' parsed = json.loads(json_str) print(parsed["name"])
|
综合练习
将高阶技巧融会贯通,通过实际案例巩固闭包、装饰器、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
|
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)) print(fibonacci(10)) print(f"已缓存 {len(fibonacci.cache)} 个结果")
|
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 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)
tags = user.tags tags.append("非法添加") print(user.tags)
|
高阶技巧是 Python 从"能写"到"写好"的关键一跃。闭包让函数有了记忆,装饰器在不改源码的前提下扩展功能,property 让方法调用像属性一样自然,上下文管理器确保资源安全释放,深浅拷贝帮你精确控制数据的独立性,而 eval 则是一把需要谨慎使用的瑞士军刀。将这些技巧融入日常编码,你的 Python 代码将变得更简洁、更优雅、更可靠。
Python Study Note 系列文章
- Python Study Note
- Python 函数使用(四)
- Python 判断语句(二)
- Python 基础语法(一)
- Python 异常处理(七)
- Python 循环语句(三)
- Python 数据容器(五)
- Python 文件基础操作(六)
- Python 模块与包(八)
- Python 类型注解(十)
- Python 面向对象(九)重点
- Python 高阶技巧(十一)