python中的装饰器

本文深入浅出地介绍了Python装饰器的概念及其应用场景,包括如何通过装饰器实现日志记录、性能测试等功能,同时还探讨了装饰器的高级用法,如带参数的装饰器及类装饰器。


装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。


先来看一个简单例子:

def foo():
    print('i am foo')

现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:

def foo():
    print('i am foo')
    logging.info("foo is running")

bar()、bar2()也有类似的需求,怎么做?再写一个logging在bar函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码

def use_logging(func):
    logging.warn("%s is running" % func.__name__)
    func()

def bar():
    print('i am bar')

use_logging(bar)

逻辑上不难理解, 但是这样的话,我们每次都要将一个函数作为参数传递给use_logging函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行bar(),但是现在不得不改成use_logging(bar)。那么有没有更好的方式的呢?当然有,答案就是装饰器。


简单装饰器
def use_logging(func):

    def wrapper(*args, **kwargs):
        logging.warn("%s is running" % func.__name__)
        return func(*args, **kwargs)
    return wrapper

def bar():
    print('i am bar')

bar = use_logging(bar)
bar()

函数use_logging就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像bar被use_logging装饰了。在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。

@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作

def use_logging(func):

    def wrapper(*args, **kwargs):
        logging.warn("%s is running" % func.__name__)
        return func(*args)
    return wrapper

@use_logging
def foo():
    print("i am foo")

@use_logging
def bar():
    print("i am bar")

bar()


如上所示,这样我们就可以省去bar = use_logging(bar)这一句了,直接调用bar()即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。


装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。


带参数的装饰器

装饰器还有更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,比如@use_logging,该装饰器唯一的参数就是执行业务的函数。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。

def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator

@use_logging(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

foo()

上面的use_logging是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@use_logging(level="warn")调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。


类装饰器

再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的\_\_call\_\_方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

class Foo(object):
    def __init__(self, func):
    self._func = func

def __call__(self):
    print ('class decorator runing')
    self._func()
    print ('class decorator ending')

@Foo
def bar():
    print ('bar')

bar()


functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:

装饰器

def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging

函数

@logged
def f(x):
   """does some math"""
   return x + x * x

该函数完成等价于:

def f(x):
    """does some math"""
    return x + x * x
f = logged(f)

不难发现,函数f被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。

print f.__name__    # prints 'with_logging'
print f.__doc__     # prints None

这个问题就比较严重的,好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__ + " was called"
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
    """does some math"""
    return x + x * x

print f.__name__  # prints 'f'
print f.__doc__   # prints 'does some math'


内置装饰器

@staticmathod、@classmethod、@property

装饰器的顺序
@a
@b
@c
def f ():

等效于

f = a(b(c(f)))


作者:zhijun liu
链接:http://www.zhihu.com/question/26930016/answer/99243411
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
<think> 首先,用户的问题是:“我想查找Python装饰器的详细使用方法 请问Python装饰器的详细使用方法”。系统级指令要求我使用中文回答,并遵循特定格式:行内数学表达式用$...$,独立公式用$$...$$并单独成段,确保LaTeX语法正确。另外,我需要生成相关问题,并在回答中引用的段落末尾添加引用标识。 用户提供了几个引用:[^1], [^2], [^3], [^4],这些是关于Python装饰器的参考资料。我需要参考这些引用来构建回答。 关键点: - 使用中文回答。 - 结构清晰,逐步解决问题。 - 确保回答真实可靠。 - 在引用的段落末尾添加引用标识。 - 生成相关问题。 回答结构: 1. **介绍装饰器**:解释什么是装饰器,它的基本概念。 2. **使用方法**:详细描述如何使用装饰器,包括语法、示例代码。 3. **应用场景**:讨论装饰器的常见用途。 4. **高级用法**:如果有空间,可以提到一些高级特性。 5. **总结**:简要回顾。 6. **生成相关问题**:在回答结束时添加相关问题。 引用处理: - 在回答中,当我参考某个引用时,在段落末尾添加引用标识,如[^1], [^2]等。 - 引用来自用户提供的列表:引用[1], [2], [3], [4]。 内容提纲: 1. **介绍装饰器**: - 装饰器Python的一种特性,允许修改或增强函数或类的行为,而不改变其原始代码。 - 参考引用[1]和[2],它们提到装饰器通过函数包装器添加额外功能。 - 添加引用标识。 2. **装饰器语法**: - 使用@符号,后跟装饰器函数名。 - 示例代码展示基本装饰器。 - 解释装饰器的工作原理:装饰器函数接受一个函数作为参数,并返回一个新函数。 - 参考引用[2]和[3]。 - 添加引用标识。 3. **装饰器示例**: - 提供一个简单的装饰器示例,比如计时器装饰器。 - 代码格式:用代码块展示Python代码。 - 解释代码。 4. **应用场景**: - 常见场景:日志记录、性能测试、权限检查、缓存等。 - 参考引用[2]提到的应用场景。 - 添加引用标识。 5. **高级用法**: - 类装饰器、带参数的装饰器。 - 参考引用[4]提到无参装饰器。 - 简要提及。 6. **总结**: - 重申装饰器的好处。 - 鼓励实践。 7. **生成相关问题**: - 在结束时添加3-5个相关问题,格式如示例。 - 相关问题示例: - 1. 如何创建带参数的装饰器? - 2. 装饰器Python框架中的常见应用? - 3. 装饰器与闭包的关系是什么? - 4. 装饰器可能遇到的陷阱有哪些? 确保语言自然、流畅、专业。 最终输出: - 回答部分。 - §§相关问题§§部分。 </think> 以下是关于Python装饰器的详细使用方法的完整解答。我将基于提供的引用资料和Python官方文档,以结构清晰的方式逐步介绍。装饰器Python的一种强大特性,允许开发者在不修改原函数代码的前提下,为其添加额外功能(如日志记录、性能测试等)。它通过函数包装器的形式实现,语法简洁优雅[^1][^2]。 ### 1. **装饰器的基本概念** 装饰器(Decorator)是一个函数或类,用于修饰其他函数或类。它接受一个函数作为输入,并返回一个新函数,从而改变原函数的行为。装饰器的核心优势是代码复用和模块化,避免重复代码。例如,一个简单的装饰器可以记录函数执行时间: - **原理**:装饰器本质上是高阶函数,利用Python的闭包特性(closure)实现。 - 参考引用[1]和[2],装饰器通过包装原函数来添加功能,而不改变其内部逻辑[^1][^2]。 ### 2. **装饰器的使用方法** #### (1) 基本语法 装饰器使用`@`符号后跟装饰器函数名,放置在函数定义前。语法格式如下: ```python @decorator_func # 装饰器应用 def my_func(): # 函数体 pass ``` 这里,`decorator_func`是装饰器函数,它会在调用`my_func`时自动执行。 #### (2) 创建装饰器的步骤 - **步骤1**: 定义一个装饰器函数,它接受一个函数作为参数(通常命名为`func`)。 - **步骤2**: 在装饰器内部定义一个包装函数(wrapper),用于添加额外逻辑(如日志、权限检查)。 - **步骤3**: 在包装函数中调用原函数,并返回包装函数。 - **示例代码**:创建一个简单的日志装饰器,记录函数调用信息。 ```python def log_decorator(func): # 定义装饰器 def wrapper(*args, **kwargs): # 包装函数 print(f"调用函数: {func.__name__}") # 添加额外操作 return func(*args, **kwargs) # 调用原函数 return wrapper @log_decorator # 应用装饰器 def say_hello(name): print(f"Hello, {name}!") # 测试 say_hello("Alice") # 输出: 调用函数: say_hello \n Hello, Alice! ``` 在这个示例中,`log_decorator`包装了`say_hello`函数,每次调用时都会打印日志。参考引用[2],这展示了装饰器如何在不修改原函数的情况下增强功能[^2]。 #### (3) 多装饰器使用 多个装饰器可以堆叠使用,按从下到上的顺序执行: ```python @decorator1 @decorator2 def my_func(): pass ``` 例如,同时添加日志和计时功能: ```python import time def time_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"执行时间: {end - start:.2f}秒") return result return wrapper @log_decorator @time_decorator def compute_sum(n): return sum(range(n)) compute_sum(1000000) # 输出日志和执行时间 ``` 参考引用[3],装饰器的堆叠使用体现了其灵活性和复用性[^3]。 ### 3. **装饰器的应用场景** 装饰器Python开发中非常广泛,常见应用包括: - **日志记录**:自动记录函数调用参数和结果。 - **性能测试**:测量函数执行时间,如上述`time_decorator`示例。 - **权限验证**:检查用户权限再执行函数(常见于Web框架如Flask)。 - **缓存**:缓存函数结果以提升性能(例如Python的`functools.lru_cache`)。 - **输入验证**:确保函数参数符合要求。 参考引用[2],装饰器常用于AOP(面向切面编程),将横切关注点(如日志)与业务逻辑分离[^2]。 ### 4. **高级用法** #### (1) 带参数的装饰器 装饰器本身可以接受参数,这需要定义一个返回装饰器的函数: ```python def repeat(n): # 装饰器工厂函数 def decorator(func): # 实际装饰器 def wrapper(*args, **kwargs): for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) # 应用带参数的装饰器 def greet(message): print(message) greet("Hello!") # 输出三次 "Hello!" ``` 此例中,`repeat`是一个装饰器工厂,它返回一个装饰器函数。 #### (2) 类装饰器 装饰器也可以用类实现,通过定义`__call__`方法: ```python class CountCalls: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 print(f"函数被调用次数: {self.count}") return self.func(*args, **kwargs) @CountCalls # 类装饰器 def example(): print("示例函数") example() # 输出调用次数 ``` 参考引用[4],类装饰器提供了更面向对象的方式管理状态[^4]。 #### (3) 内置装饰器 Python标准库提供了一些常用装饰器: - `@staticmethod` 和 `@classmethod`:定义静态方法和类方法。 - `@property`:将方法转换为属性。 - `functools.wraps`:保留原函数的元数据(如函数名),避免在装饰器中丢失。 ```python from functools import wraps def my_decorator(func): @wraps(func) # 保留原函数信息 def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper ``` ### 5. **总结** 装饰器Python的核心特性,它通过`@`语法实现函数的动态扩展,支持代码复用和解耦。初学者应从简单装饰器入手,逐步探索带参数或类装饰器的高级用法。实践中,参考官方文档和社区资源(如Python Cookbook)能加深理解[^3][^1]。记住,装饰器的本质是函数组合,理解闭包是掌握它的关键[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值