在 Python 中,函数是一等公民(first-class object)——这意味着函数可以像整数、字符串一样被赋值、传递、返回,甚至嵌套定义。正是这一特性,催生了闭包、装饰器、高阶函数等强大机制。
今天,我们就从最基础的函数嵌套出发,一步步揭开 闭包、装饰器、@classmethod 与 @staticmethod 的面纱。
一、函数嵌套:作用域的起点
Python 允许在函数内部定义另一个函数:
name = "武涛齐"defrun(): name = "alex"definner(): print(name) # 访问外层函数的局部变量return innerv1 = run() # 返回 inner 函数对象v1() # 输出: alex
inner() 是 run() 的内部函数,对外部不可见,避免命名冲突。- 每次调用
run(),都会创建一个全新的 inner 函数实例(即使代码相同)。
这看似简单,却引出了一个关键概念:闭包(Closure)。
二、闭包(Closure):把数据“打包”带走
闭包 = 内部函数 + 外层函数的自由变量
当内部函数引用了外层函数的局部变量(如上面的 name),并且这个内部函数被返回或传递出去时,就形成了闭包。
✅ 闭包的核心价值:
- 封装数据:将状态“打包”进函数,避免污染全局命名空间;
⚠️ 修改外层变量?你需要 nonlocal
默认情况下,内部函数只能读取外层变量,不能修改。若要修改,必须用 nonlocal 声明:
defouter(): x = 10definner():nonlocal x # 声明 x 来自外层作用域 x = 20 inner() print(x) # 输出: 20
❌ 注意:nonlocal 只能用于嵌套函数中的外层函数作用域,不能用于全局变量。若想修改全局变量,请用 global。
三、作用域规则:LEGB 原则
Python 查找变量遵循 LEGB 规则:
x = "global"defouter(): x = "enclosing"definner(): print(x) # 输出 "enclosing"(Enclosing 优先于 Global)return inner
理解 LEGB,是掌握闭包和装饰器的前提。
四、装饰器:不改代码,增强功能
装饰器的本质是一个接收函数并返回新函数的高阶函数。
1. 函数式装饰器(最常用)
from functools import wrapsdefmy_decorator(func): @wraps(func) # 保留原函数的 __name__、__doc__ 等元信息defwrapper(*args, **kwargs): print("Before function call") result = func(*args, **kwargs) print("After function call")return resultreturn wrapper@my_decoratordefgreet(name):"""Say hello"""returnf"Hello, {name}!"print(greet("Alice"))# 输出:# Before function call# After function call# Hello, Alice!
✅ 为什么用 @wraps?否则 greet.__name__ 会变成 "wrapper",影响调试和文档生成。
2. 类装饰器:用 __call__ 实现
classCountCalls:def__init__(self, func): self.func = func self.count = 0def__call__(self, *args, **kwargs): self.count += 1 print(f"{self.func.__name__} 被调用了 {self.count} 次")return self.func(*args, **kwargs)@CountCallsdefsay_hi(): print("Hi!")say_hi() # say_hi 被调用了 1 次say_hi() # say_hi 被调用了 2 次
类装饰器适合需要维护状态(如计数、缓存)的场景。
3. 装饰器的常见用途
五、@classmethod vs @staticmethod:别再混淆!
| | @classmethod | @staticmethod |
|---|
| self | cls | |
| | | |
| | | |
| | | |
✅ @classmethod 的典型用途:替代构造函数
classPerson:def__init__(self, name, age): self.name = name self.age = age @classmethoddeffrom_string(cls, s): name, age = s.split('-')return cls(name, int(age)) # 自动调用当前类的 __init__# 使用p = Person.from_string("Bob-25")
💡 优势:继承时自动适配子类(cls 指向实际调用的类)。
✅ @staticmethod 的定位:逻辑相关的工具函数
classMathUtils: @staticmethoddefadd(a, b):return a + b# 调用MathUtils.add(1, 2) # 3
它本质上就是一个放在类里的普通函数,只是语义上属于该类。
六、总结:一张表看懂所有
| | |
|---|
| 闭包 | | |
| 装饰器 | | |
| @classmethod | | |
| @staticmethod | | |
👉 点击公众号主页【合集】→【Python 工程师修炼手册】,系统学习进阶知识。