
装饰器Decorator是 Python 中非常强大且灵活的特性它能让我们在不修改原函数代码的前提下为函数增加额外的功能。它常用于日志记录、权限校验、性能测试、缓存等场景。很多初学者觉得装饰器既神奇又难懂其实只要掌握了闭包和高阶函数的基础就能轻松理解。本文会从最简单的函数讲起逐步带你掌握装饰器的各种进阶用法并配有丰富示例和场景拓展。一、一切始于“函数是一等公民”在 Python 中函数和其他变量一样可以赋值给另一个变量、作为参数传递甚至作为返回值。这是实现装饰器的前提。defgreet(name):returnfHello,{name}!# 将函数赋值给新变量say_hellogreetprint(say_hello(World))# 输出Hello, World!# 函数作为参数defexecute(func,arg):returnfunc(arg)print(execute(greet,Python))# 输出Hello, Python!当函数能作为参数传递也就意味着我们可以编写一个“函数工厂”动态地包装其他函数的行为。二、闭包装饰器的核心闭包是指在一个内部函数中引用了外部函数的变量并且外部函数返回了这个内部函数。这种结构可以“记住”外部变量的状态。defouter(message):definner():print(f记住的信息是:{message})returninner fnouter(hello closure)fn()# 输出记住的信息是: hello closureinner函数使用了外部作用域的message即便outer已经执行完毕message依然被保留。装饰器的本质就是这种“接受一个函数返回一个闭包包装后的函数”的模式。三、最简单的装饰器现在我们来写一个“无参装饰器”它在函数执行前后打印日志。defsimple_logger(func):defwrapper():print(f调用函数{func.__name__}之前)resultfunc()print(f调用函数{func.__name__}之后)returnresultreturnwrapper# 使用装饰器语法糖 simple_loggerdefsay_hi():print(Hi!)returndoneresultsay_hi()print(返回值:,result)输出调用函数 say_hi 之前 Hi! 调用函数 say_hi 之后 返回值: done为什么加上 函数就可以作为装饰器使用 是语法糖 其本质是 函数调用。simple_loggerdefsay_hi():print(Hi!)returndoneresultsay_hi()print(返回值:,result)等价于:defsay_hi():print(Hi!)returndonesay_hisimple_logger(say_hi)# 返回值 接收变量 必须和 函数名 一致say_hiresultsay_hi()print(返回值:,result)simple_logger 放在函数定义上方等价于在函数定义完之后手动执行一行任何函数都可以加上 成为装饰器吗不是的。 能被 使用的函数必须满足装饰器的签名契约能成为 装饰器 的 函数 要求1、输入函数第一个参数必须是函数 2、输出函数四、给装饰的函数传递参数上面的装饰器只适用于无参数的函数。如果原函数有参数呢很简单让wrapper接受任意参数*args, **kwargs并转发即可。deflogger(func):defwrapper(*args,**kwargs):print(f调用{func.__name__}参数:{args},{kwargs})resultfunc(*args,**kwargs)print(f结束{func.__name__}返回值:{result})returnresultreturnwrapperloggerdefadd(a,b):returnab add(3,5)输出调用 add参数: (3, 5), {} 结束 add返回值: 8这样一来装饰器就可以作用于任意参数的函数了。*args收集所有位置参数为一个元组**kwargs收集所有关键字参数为一个字典转发时解包即可。五、保留原函数的元信息经过装饰后say_hi.__name__变成了wrapper文档字符串也会丢失。这会给调试和依赖元信息的工具带来困扰。我们可以使用标准库functools.wraps来解决。fromfunctoolsimportwrapsdeflogger(func):wraps(func)defwrapper(*args,**kwargs):print(f调用{func.__name__})returnfunc(*args,**kwargs)returnwrapperloggerdefmultiply(x,y):返回两数乘积returnx*yprint(multiply.__name__)# multiplyprint(multiply.__doc__)# 返回两数乘积wraps(func)将原函数的__name__、__doc__、__module__等属性拷贝到wrapper上保持了元信息。六、带参数的装饰器有时我们希望装饰器本身能接受一些参数比如日志级别、重试次数等。这需要在普通装饰器外再包装一层函数用来接收这些配置参数然后返回真正的装饰器。fromfunctoolsimportwrapsimporttimedefretry(max_attempts3,delay1):装饰器工厂返回一个根据参数配置的装饰器defdecorator(func):wraps(func)defwrapper(*args,**kwargs):attempts0whileattemptsmax_attempts:try:returnfunc(*args,**kwargs)exceptExceptionase:attempts1print(f第{attempts}次失败:{e})ifattemptsmax_attempts:raisetime.sleep(delay)returnwrapperreturndecoratorretry(max_attempts4,delay0.5)defunstable_network_call():# 模拟偶发异常importrandomifrandom.random()0.7:raiseConnectionError(网络不稳定)return数据获取成功print(unstable_network_call())这里retry(max_attempts, delay)返回decoratordecorator才是真正的装饰器。记住带参数的装饰器本质就是“装饰器工厂”多一层函数调用。七、多个装饰器的叠加与顺序一个函数可以应用多个装饰器它们会自下而上地执行包装。离函数最近的装饰器先包装然后依次向外。defupper_decorator(func):wraps(func)defwrapper(*args,**kwargs):resultfunc(*args,**kwargs)ifisinstance(result,str):returnresult.upper()returnresultreturnwrapperdefbold_decorator(func):wraps(func)defwrapper(*args,**kwargs):resultfunc(*args,**kwargs)ifisinstance(result,str):returnfb{result}/breturnresultreturnwrapperbold_decoratorupper_decoratordefgreeting(name):returnfhello{name}print(greeting(World))执行流程greeting先被upper_decorator包装结果变为大写然后被bold_decorator包装最终结果变成bHELLO WORLD/b。如果交换装饰器顺序结果会不同你可以自己试试。八、类装饰器装饰器不仅可以是函数也可以是类。类装饰器依靠__call__方法实现可调用对象。1. 无参类装饰器classCountCalls:def__init__(self,func):self.funcfunc self.count0def__call__(self,*args,**kwargs):self.count1print(f调用次数:{self.count})returnself.func(*args,**kwargs)CountCallsdefsay_hello():print(Hello!)say_hello()say_hello()__init__接收被装饰的函数__call__让实例本身可以像函数一样被调用。类装饰器便于保存状态比如这里的调用计数。2. 带参数的类装饰器classPrefixPrinter:def__init__(self,prefix):self.prefixprefixdef__call__(self,func):wraps(func)defwrapper(*args,**kwargs):print(f[{self.prefix}] 开始执行)returnfunc(*args,**kwargs)returnwrapperPrefixPrinter(DEBUG)defdo_something():print(do_something 执行中)do_something()__init__接收装饰器参数__call__接收函数并返回包装后的函数。理解这种模式后你可以自由选择函数或类来实现装饰器。九、实战场景拓展场景一性能计时器测量函数执行时间用于性能分析。importtimefromfunctoolsimportwrapsdeftimer(func):wraps(func)defwrapper(*args,**kwargs):starttime.perf_counter()resultfunc(*args,**kwargs)endtime.perf_counter()print(f{func.__name__}耗时{end-start:.6f}秒)returnresultreturnwrappertimerdefslow_calculation():total0foriinrange(1000000):totalireturntotal slow_calculation()场景二权限验证在 Web 开发中可用装饰器检查用户是否登录或具有权限。current_user{username:Alice,is_admin:True}defrequire_admin(func):wraps(func)defwrapper(*args,**kwargs):ifnotcurrent_user.get(is_admin):raisePermissionError(需要管理员权限)returnfunc(*args,**kwargs)returnwrapperrequire_admindefdelete_user(user_id):returnf用户{user_id}已被删除print(delete_user(42))场景三缓存函数结果备忘避免重复计算提高效率。defmemoize(func):cache{}wraps(func)defwrapper(*args):ifargsincache:print(从缓存返回)returncache[args]resultfunc(*args)cache[args]resultreturnresultreturnwrappermemoizedeffibonacci(n):ifn2:returnnreturnfibonacci(n-1)fibonacci(n-2)print(fibonacci(30))# 第二次调用时速度极大提升场景四单例模式利用类装饰器实现单例也可用装饰器修饰类。defsingleton(cls):instances{}wraps(cls)defget_instance(*args,**kwargs):ifclsnotininstances:instances[cls]cls(*args,**kwargs)returninstances[cls]returnget_instancesingletonclassDatabaseConnection:def__init__(self,db_name):self.db_namedb_nameprint(f连接数据库{db_name})conn1DatabaseConnection(mydb)conn2DatabaseConnection(mydb)print(conn1isconn2)# True场景五参数类型校验在函数调用时自动校验参数类型。deftype_check(*param_types):defdecorator(func):wraps(func)defwrapper(*args,**kwargs):forarg,expected_typeinzip(args,param_types):ifnotisinstance(arg,expected_type):raiseTypeError(f参数{arg}不是{expected_type})returnfunc(*args,**kwargs)returnwrapperreturndecoratortype_check(int,int)defadd_numbers(a,b):returnabprint(add_numbers(1,2))# 正常# add_numbers(1, 2) # 抛出 TypeError十、装饰器与上下文管理、描述符的联动装饰器并不是孤立的它经常和上下文管理器with、描述符等特性一起使用构建更优雅的代码。例如用装饰器实现一个临时修改环境变量执行后自动恢复的功能importosfromcontextlibimportcontextmanagercontextmanagerdefset_env_var(key,value):oldos.environ.get(key)os.environ[key]valuetry:yieldfinally:ifoldisNone:os.environ.pop(key,None)else:os.environ[key]olddefwith_env(key,value):装饰器在函数执行期间设置环境变量defdecorator(func):wraps(func)defwrapper(*args,**kwargs):withset_env_var(key,value):returnfunc(*args,**kwargs)returnwrapperreturndecoratorwith_env(MY_VAR,hello)defread_env():print(os.environ.get(MY_VAR))read_env()# 输出helloprint(os.environ.get(MY_VAR))# None已恢复这种组合让资源的准备和清理变得极其安全。十一、注意事项与最佳实践保持元信息务必使用wraps(func)便于调试和文档生成。不要滥用装饰器会引入额外抽象如果不是必要的横切关注点直接用普通函数调用更清晰。注意参数传递设计通用装饰器时永远使用*args, **kwargs避免限制被装饰函数的签名。装饰器顺序多个装饰器叠加时注意执行顺序外层的先执行内层的先包装。调试支持Python 3.10 可以使用functools.update_wrapper或wraps保留更好的内省信息functools.cache等内置装饰器也可以直接使用无需自己实现缓存。类型提示如果你使用类型检查可以用Callable、泛型等为装饰器标注类型提高代码可读性。十二、总结装饰器本质上就是“接受一个函数返回一个新函数”的高阶函数或可调用对象。从无参装饰器到带参数的装饰器工厂再到类装饰器它们都遵循这个核心思想。掌握了闭包和*args, **kwargs的用法后你可以用装饰器优雅地实现日志、缓存、权限、事务、性能分析等横切关注点。Python 标准库中就有许多装饰器比如staticmethod、classmethod、property、functools.lru_cache等它们都是装饰器应用的典范。建议多动手实践把装饰器融入到日常开发中你会发现代码的复用性和可读性都会有质的飞跃。