Python中的装饰器

一、装饰器的功能

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

概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能

二、装饰器的两种调用方式

1、早期给函数或对象添加额外的功能

def say_hello():
    print('hello')


def debug(func):
    def wrapper():
        print('[DEBUG]:enter {}()'.format(func.__name__))
        return func()
    return wrapper


say_hello = debug(say_hello)  # 给say_hello函数添加功能(输出debug),并且保持原函数名不变
if __name__ == '__main__':
    say_hello()

2、后期的@语法糖

def debug(func):
    def wrapper():
        print('[DEBUG]:enter {}()'.format(func.__name__))
        return func()
    return wrapper


@debug
def say_hello():
    print('hello')


if __name__ == '__main__':
    say_hello()

二、初级装饰器(带参数)

Python的装饰器是一个固定的函数接口形式。它要求你的装饰器函数(或装饰器类)必须接受一个函数并返回一个函数。

'''
def wrapper(func1):
    return func2

# 调用方式一,直接包裹
def target_func(args):
    pass

result = wrapper(target_func)(args)

# 调用方式二,使用@语法,等同于方式一
@wrapper
def target_func(args):
    pass
result = target_func()
'''

 具体例子:指定装饰器函数wrapper接受和原函数一样的参数。

def debug(func):
    def wrapper(something):
        print('[DEBUG]:enter {}()'.format(func.__name__))
        return func(something)
    return wrapper

def say_hello(something):
    print('hello, {}'.format(something))

# 调用方式一(早期)
say_hello = debug(say_hello)

if __name__ == '__main__':
    say_hello('C++')


# 调用方式二(@语法糖)
@debug
def say_hello(something):
    print('hello, {}'.format(something))


if __name__ == '__main__':
    say_hello('C++')

三、高级装饰器

在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解。(参见Python的闭包)

1、带参数的装饰器

不仅需要能在进入某个函数后打出log信息,而且还需指定log的级别。这需要用到带参数的装饰器。

def logging(level):
    def wrapper_(func):
        def wrapper(*args, **kwargs):
            print('[{level}]:enter function {func}()'.format(level=level, func=func.__name__))
            return func(*args, **kwargs)
        return wrapper
    return wrapper_

@logging(level='INFO')  # @logging(level='DEBUG'),返回的结果是一个装饰器。它其实是一个函数,会马上被执行。
def say(something):
    print('say {}'.format(something))

@logging(level='DEBUG')
def do(something):
    print('do {}'.format(something))

# 不用语法糖
# say_result = logging(level='INFO')(say)('hello')
# do_result = logging(level='do')(do)('homework')

# 语法糖
if __name__ == '__main__':
    say('hello')
    do('homework')

 

2、基于类实现的装饰器

回到装饰器的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象。那么我们也可以用类来实现。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。

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

    def __call__(self, *args, **kwargs):
        print('[DEBUG]: enter function {func}()'.format(func=self.func.__name__))
        return self.func(*args, **kwargs)

@logging
def say(something):
    print('say {}'.format(something))

# 不用语法糖调用
# log = logging(func=say)('hello')

if __name__ == '__main__':
    say('hello')

  

----------------------------------------------------callable对象------------------------------------------------
在Python中,callable对象指的是可以像函数一样被调用的对象。具体来说,只要一个对象实现了__call__()方法,就可以将其视为callable对象。当我调用一个类的实例时,实际上是调用了它的__call__方法。

除了函数之外,Python中还有很多其他类型的callable对象,比如:
(1)类:如果一个类实现了__call__()方法,那么它的实例也可以向函数一样被调用。
(2)实现了__call__()方法的对象:任何实现了__call__()方法的对象都可以被视为callable对象。这种情况比较少见。
(3)内置函数和内置方法:Python中的一些内置函数和内置方法也是callable对象。例如,abs()、len() str()等函数都是callable对象。
(4)函数对象:函数本身也是callable对象,因为它们实现了__call__()方法。

3、带参数的类装饰器

通过类实现带参数的装饰器时,在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后重载__call__方法时需要接受一个函数并返回一个函数。

class logging(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print('[{level}]: enter function {func}()'.format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
        return wrapper


@logging(level='INFO')
def say(something):
    print('say {}'.format(something))

@logging(level='DEBUG')
def do(something):
    print('do {}'.format(something))

if __name__ == '__main__':
    say('hello')
    do('homework')

  

4、内置装饰器

 内置装饰器是指一些装饰器函数,可以直接使用而无需自己编写。这些内置装饰器通常用于实现一些通用的功能,例如缓存、计时、类型检查等。

常见的内置装饰器有:
@staticmethod:用于将一个方法定义为静态方法,即不需要使用类实例来调用该方法,可以直接使用类名调用。
@classmethod:用于将一个方法定义为类方法,即可以使用类名调用该方法,并且该方法可以访问类的属性和方法。
@property:用于将一个方法定义为属性,即可以通过实例对象访问该方法,就像访问一个属性一样。
@abstractmethod:用于将一个方法定义为抽象方法,即该方法必须在子类中被实现,否则会抛出异常。
@wraps:用于将一个装饰器函数的元信息(如函数名、文档字符串等)复制到被装饰的函数中,以便于调试和文档生成。

四、装饰器的应用场景

1、函数执行时间的统计

import time

def time_it(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print("用时:{}秒".format(end-start))
    return wrapper

@time_it
def func1():
    time.sleep(2)
    print('func1 is running...')

if __name__ == '__main__':
    func1()

2、输出日志信息(使用logging模块控制日志输出)

# 1)生成一个日志记录器,并配置日志等级
import logging
# 获取日志记录器,并配置日志等级
logger = logging.getLogger(__name__)
logger.setLevel('DEBUG')

# 2) 配置日志格式、增加handler控制输出流
# 默认日志格式
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - %(message)s')
# 输出到控制台的handler
chlr = logging.StreamHandler()
# 配置默认日志格式
chlr.setFormatter(formatter)

# 3) 最后将此handler所需要处理的日志等级,没有设置则默认使用logger自身的Level,即DEBUG等级。
# 日志记录器增加此handler
logger.addHandler(chlr)

def log(func):
    def inner(*args, **kwargs):
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        res = func(*args, **kwargs)
        logger.debug(f'func: {func.__name__} {args}->{res}')
        return res
    return inner
@log
def add(num1, num2):
    res = num1 + num2
    return res


if __name__ == '__main__':
    add(1, 2)

 五、python中的装饰器、Java中的注解(Annotation)、C#中的特性(Attribute)之间的区别。

装饰器装饰器的理念是对原函数、对象的加强,相当于重新封装,所以一般装饰器函数都被命名为wrapper(),意义在于包装。函数只有在被调用时才会发挥其作用。比如@logging装饰器可以在函数执行时额外输出日志,@cache装饰过的函数可以缓存计算结果等等。

注解+特性:注解和特性是对目标函数或对象添加一些属性,相当于将其分类。这些属性可以通过反射拿到,在程序运行时对不同的特性函数或对象加以干预。比如带有Setup的函数就当成准备步骤执行,或者找到所有带有TestMethod的函数依次执行等等。

以下是一些常见的 Python 装饰器面试问题及其回答: 1. 什么是装饰器装饰器Python 中一种高级的语法结构,它允许在不修改原函数代码的情况下,对函数进行增强或修改。装饰器本质上是一个函数,它接收一个函数作为参数,并返回一个函数。 2. 装饰器有哪些应用场景? 装饰器可以用于实现各种功能,如: - 日志记录:记录函数的调用时间、输入参数、输出结果等信息。 - 计时器:统计函数的运行时间。 - 缓存:缓存函数的输出结果,避免重复计算。 - 权限验证:验证用户是否有权调用函数。 - 接口限流:限制函数的调用频率。 3. 装饰器的语法是什么? 装饰器的语法如下: ```python @decorator def func(): pass ``` 其中,`decorator` 是装饰器函数,`func` 是被装饰函数。装饰器函数可以在函数执行前后对函数进行修改或增强。如果需要传递参数,可以使用带参数的装饰器,如: ```python @decorator(arg1, arg2, ...) def func(): pass ``` 4. 装饰器的执行顺序是怎样的? 多个装饰器会按照从下往上的顺序依次执行,即先执行最外层的装饰器,然后执行内层的装饰器,最后执行被装饰的函数。例如: ```python @a @b @c def func(): pass ``` 执行顺序为 `func = a(b(c(func)))`。 5. 装饰器中如何传递参数? 装饰器可以使用带参数的形式,例如: ```python def my_decorator(arg1, arg2): def decorator(func): def wrapper(*args, **kwargs): # 在这里对函数进行增强或修改 return func(*args, **kwargs) return wrapper return decorator @my_decorator(arg1, arg2) def my_func(): pass ``` 在这个例子中,`my_decorator` 是一个带参数的装饰器,它接收两个参数 `arg1` 和 `arg2`。`decorator` 是一个不带参数的装饰器函数,它接收一个函数作为参数,并返回一个函数。`wrapper` 是被返回的函数,它接收任意数量的位置参数和关键字参数,并在其中对函数进行增强或修改。最后,使用 `@my_decorator(arg1, arg2)` 语法将装饰器应用到函数上。 6. 装饰器中如何保留被装饰函数的元信息? 当使用装饰器时,有时候需要保留被装饰函数的元信息,例如函数名、文档字符串等。为了实现这个功能,可以使用 `functools.wraps` 装饰器,它可以将被装饰函数的元信息复制到装饰器函数中。例如: ```python import functools def my_decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): # 在这里对函数进行增强或修改 return func(*args, **kwargs) return wrapper @my_decorator def my_func(): """这是一个示例函数""" pass ``` 在这个例子中,使用 `@functools.wraps(func)` 语法将被装饰函数的元信息复制到 `wrapper` 函数中。这样,当调用 `help(my_func)` 时,可以看到函数的文档字符串和函数名。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值