大家好,我是程序员晚枫。
Python 装饰器,新手的"拦路虎",老手的"瑞士军刀"。
今天这篇文章,给你 5 个层次,让你彻底掌握。
一、什么是装饰器? 装饰器 = 给函数穿"衣服",不改函数本身,但能让它多些能力 。
通俗解释 人穿衣 :
你(函数)+ 外套(装饰器)= 穿了外套的你 你还是你,但多了保暖功能 函数装装饰器 :
1 2 3 @my_decorator def my_func (): pass
等价于 :
1 2 3 def my_func (): pass my_func = my_decorator(my_func)
装饰器就是"包装函数"的函数 。
二、层次 1:基础装饰器 第 1 个装饰器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def my_decorator (func ): def wrapper (): print ("Before" ) func() print ("After" ) return wrapper @my_decorator def say_hello (): print ("Hello!" ) say_hello()
5 大要点 要点 1:装饰器是高阶函数 接受函数,返回函数 :
1 2 3 4 def my_decorator (func ): def wrapper (): return func() return wrapper
要点 2:@ 语法糖 1 2 3 @my_decorator def my_func (): pass
等价于 :
1 2 3 def my_func (): pass my_func = my_decorator(my_func)
要点 3:装饰器不改变原函数 原函数还在 ,只是被"包装"了 。
要点 4:装饰器可以叠加 1 2 3 4 5 @decorator_a @decorator_b def my_func (): pass
保留原函数元信息 :
1 2 3 4 5 6 7 import functoolsdef my_decorator (func ): @functools.wraps(func ) def wrapper (): return func() return wrapper
三、层次 2:带参数的装饰器 普通装饰器的问题 1 2 3 @my_decorator def add (x, y ): return x + y
装饰器怎么接收 x, y ?
解决方案:三层嵌套 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def my_decorator (func ): def wrapper (*args, **kwargs ): print (f"Calling {func.__name__} with {args} " ) result = func(*args, **kwargs) print (f"Got {result} " ) return result return wrapper @my_decorator def add (x, y ): return x + y add(2 , 3 )
*args, **kwargs 接收所有参数 。
四、层次 3:带参数的装饰器(外层) 场景 不同函数要不同"装饰" :
1 2 3 4 5 6 7 @repeat(3 ) def hello (): print ("Hello!" ) @repeat(5 ) def goodbye (): print ("Goodbye!" )
实现:4 层嵌套 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def repeat (times ): def decorator (func ): @functools.wraps(func ) def wrapper (*args, **kwargs ): for i in range (times): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3 ) def hello (): print ("Hello!" ) hello()
4 层结构 1 2 3 4 5 6 7 8 9 10 def repeat (times ): def decorator (func ): def wrapper (*args, **kwargs ): return func(*args, **kwargs) return wrapper return decorator
五、层次 4:类装饰器 装饰类的装饰器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def singleton (cls ): instances = {} @functools.wraps(cls ) def wrapper (*args, **kwargs ): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper @singleton class Database : pass db1 = Database() db2 = Database() print (db1 is db2)
类作为装饰器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class CountCalls : def __init__ (self, func ): self.func = func self.count = 0 def __call__ (self, *args, **kwargs ): self.count += 1 print (f"Call {self.count} of {self.func.__name__} " ) return self.func(*args, **kwargs) @CountCalls def hello (): print ("Hello!" ) hello() hello()
类装饰器可以 保存状态**(如调用次数)**。
六、层次 5:内置装饰器 Python 内置的常用装饰器 1. @property 将方法转为属性 :
1 2 3 4 5 6 7 8 9 10 class Circle : def __init__ (self, radius ): self.radius = radius @property def area (self ): return 3.14 * self.radius ** 2 c = Circle(5 ) print (c.area)
2. @staticmethod 静态方法 :
1 2 3 4 5 6 class Math : @staticmethod def add (x, y ): return x + y print (Math.add(2 , 3 ))
3. @classmethod 类方法 :
1 2 3 4 5 6 7 8 9 10 class Person : def __init__ (self, name ): self.name = name @classmethod def from_string (cls, s ): name, age = s.split('-' ) return cls(name) p = Person.from_string('Alice-30' )
缓存函数结果 :
1 2 3 4 5 6 7 8 9 from functools import lru_cache@lru_cache(maxsize=128 ) def fib (n ): if n < 2 : return n return fib(n-1 ) + fib(n-2 ) fib(35 )
5. @dataclasses.dataclass 自动生成 __init__ :
1 2 3 4 5 6 7 8 from dataclasses import dataclass@dataclass class User : name: str age: int user = User('Alice' , 30 )
七、5 大常用装饰器模式 模式 1:日志装饰器 1 2 3 4 5 6 7 8 9 10 11 12 import functoolsimport timedef log (func ): @functools.wraps(func ) def wrapper (*args, **kwargs ): start = time.time() result = func(*args, **kwargs) end = time.time() print (f"{func.__name__} took {end-start:.2 f} s" ) return result return wrapper
模式 2:缓存装饰器 1 2 3 4 5 6 7 8 def cache (func ): cached = {} @functools.wraps(func ) def wrapper (*args ): if args not in cached: cached[args] = func(*args) return cached[args] return wrapper
模式 3:重试装饰器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import timedef retry (times=3 , delay=1 ): def decorator (func ): @functools.wraps(func ) def wrapper (*args, **kwargs ): for i in range (times): try : return func(*args, **kwargs) except Exception as e: if i == times - 1 : raise time.sleep(delay) return wrapper return decorator
模式 4:权限装饰器 1 2 3 4 5 6 7 def require_login (func ): @functools.wraps(func ) def wrapper (user, *args, **kwargs ): if not user.is_authenticated: raise PermissionError("请先登录" ) return func(user, *args, **kwargs) return wrapper
模式 5:缓存属性 1 2 3 4 5 6 7 8 9 10 11 class CachedProperty : def __init__ (self, func ): self.func = func def __get__ (self, instance, owner ): if instance is None : return self attr = f'_cached_{self.func.__name__} ' if not hasattr (instance, attr): setattr (instance, attr, self.func(instance)) return getattr (instance, attr)
八、5 大真实项目案例 案例 1:Flask 路由 1 2 3 @app.route('/' ) def home (): return 'Hello, World!'
Flask 用装饰器定义路由 。
案例 2:Django 视图 1 2 3 @login_required def profile (request ): return render(request, 'profile.html' )
Django 用装饰器实现登录验证 。
案例 3:pytest 测试 1 2 3 4 5 6 @pytest.fixture def user (): return User('Alice' ) def test_user (user ): assert user.name == 'Alice'
pytest 用装饰器定义 fixture 。
案例 4:click CLI 1 2 3 4 5 6 import click@click.command() @click.option('--name' , default='World' ) def hello (name ): click.echo(f'Hello {name} !' )
click 用装饰器定义命令行参数 。
1 2 3 4 5 6 from functools import lru_cache@lru_cache(maxsize=None ) def expensive_function (n ): return n ** 2
functools 用装饰器缓存 。
九、5 大常见误区 误区 1:装饰器改变原函数 误区 3:装饰器无法接受参数 误区 4:装饰器 = 性能优化 误区 5:装饰器很难 十、5 个学习路径 路径 1:完全新手 1 基础装饰器(3 天)→ 带参数装饰器(3 天)→ 简单项目
路径 2:Web 开发者 1 Flask/Django 装饰器(1 周)→ 自定义装饰器(2 周)→ 实战
路径 3:测试驱动 1 pytest fixture(1 周)→ 自定义 fixture(1 周)→ 实战
路径 4:AI 工程师 1 functools.lru_cache(1 周)→ 异步装饰器(1 周)→ 模型部署
路径 5:库开发者 1 类装饰器(1 周)→ 元类(1 周)→ 高级装饰器(2 周)
十一、给 Python 装饰器学习者的 4 个建议 建议 1:先学基础 建议 2:再学带参数 建议 4:多看内置装饰器 @property、@staticmethod 理解原理 十二、最后的最后 Python 装饰器,3 句话总结 :
5 个层次 :基础→带参数→外层参数→类→内置本质是函数包装 :不改原函数用 functools.wraps :保留元信息学 Python 6 年,我学到的最重要的事:
"装饰器是 Python 高级编程的'分水岭'。"
掌握装饰器,你看代码的眼光都不一样 。
Flask、Django、pytest、click 全是装饰器。
学装饰器,5 年后你看 Python 框架如看"自己写的代码" 。
相关阅读 科技不高冷,AI很好用。 我是晚枫,关注我,带你一起玩AI!
🎓 AI 编程实战课程 程序员晚枫专注AI编程培训,通过 《50讲 · AI编程训练营》 ,让小白也能用AI做出实际项目。帮你从零上手!