python装饰器

什么是装饰器

本质上,装饰器(decorator)就是一个返回函数的高阶函数。当我们想对已有程序进行功能扩展时,我们往往不会选择修改函数或者模块内部的源代码,这样是不科学也是不现实的(可以看一下开闭原则)。装饰器由此而来,它满足了以下3点原则:

  1. 不修改被装饰的函数的源代码
  2. 不修改被装饰的函数的调用方式
  3. 在满足1、2的情况下给程序增加额外的功能

装饰器经常被用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等。

装饰器的使用

上面对装饰器简单介绍了一下,接下来我们来看看装饰器具体长什么样,以及怎么去装饰函数。

先看一个简单的装饰器:

# 装饰器函数, 接受函数名作为参数
def decorator(func):
    def wrapper():
        # 提示:wrapper函数中的print()操作只是最简单的示例,根据不同的需求可以换成更有实际意义的操作。
        print('before doing something')
        func()   # 在包装函数中调用被装饰函数
        print('something finished')
    return wrapper

@decorator    
def something():
    print('doing something')

print(something.__name__) 
something()
'''输出:
        wrapper
        before doing something
        doing something
        something finished
'''

看完代码,我们来说明一下装饰器是怎么装饰函数的:

# 方式一: 使用语法糖,在被修饰函数定义前加上 @ + 装饰器的名字
@decorator 
def func():
    pass

# 方式二: 手动将被修饰函数的名字作为参数传入装饰器中
func = decorator(func) 

这两种方式的实际效果是一样的,但是方式一明显更为简捷。

要注意的是,因为返回的那个wrapper()函数名就是wrapper,所以我们需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。Python内置的functools.wraps方法就能替我们解决这个问题,只需在定义wrapper()的前面加上@functools.wraps(func)即可。下面的示例程序都将使用wraps方法。

接下来实现一个更实用的装饰器,它可以修饰带参数且有返回值的函数:

import functools

def decorator(func):
    @functools.wraps(func) 
    def wrapper(*args, **kwargs):       # 使用可变参数,更具扩展性
        print('before doing something')
        ret = func(*args, **kwargs)     # 使用变量ret存放被装饰函数的返回
        print('something finished')
        return ret                      # 包装函数再返回ret,实现返回的传递
    return wrapper

@decorator
def get_plus(a, b):            # 返回两数之和
    print('call get_plus')
    return a + b

@decorator
def get_sum(*args, **kwargs):  # 返回n个数的和
    print('call get_sum')
    return sum(args)

print(get_plus.__name__)    
print(get_sum.__name__)  
sum1 = get_plus(1, 3)
sum2 = get_sum(2, 3, 4)
print(sum1, sum2)

'''输出:
        get_plus
        get_sum
        before doing something
        call get_plus
        something finished
        before doing something
        call get_sum
        something finished
        4   9
'''

可以看到,为wrapper函数添加了可变参数后,装饰器能修饰有固定参数的get_plus函数,也能修饰参数不定的get_sum函数,而且在使用了functools.wraps方法后,被修饰函数的__name__等属性也一并复制了过去。

如果针对不同的函数,我们需要装饰器来区别对待,那么可以这样做:

import functools

def decorator(param):                 # 装饰器接受参数param
    def outer_wrapper(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            if param == 'task1':      # 根据参数param判断当前修饰的是哪个函数
                func(*args, **kwargs)
            elif param == 'task2':
                func(*args, **kwargs)
            print(param,"is finished.")
        return wrapper
    return outer_wrapper

@decorator(param='task1')
def task1():
    print("doing task1 right now..")

@decorator(param='task2')
def task2():
    print("doing task2 right now..")

print(task1.__name__, task2.__name__)
task1()
task2()
'''输出: 
        task1   task2
        doing task1 right now..
        task1 is finished.
        doing task2 right now..
        task2 is finished.
'''

分析一下给装饰器传参的操作:

@decorator(param = arg)    # 1
def func(*args, **kwargs):
    pass
#   #1处的语句与#2处的语句是等价的
#   为了容易理解,我把#2右边的逐步化简如下:
func = decorator(arg)(func)   # 2
     = outer_wrapper(func)    # 注意以下两句直接执行是会报错的,此处只是为了方便理解
     = wrapper(*args, **kwargs)

通过上面的代码我们可以看到,实际上func经过多层的嵌套最终还是被“包”在最里层的wrapper里面,传递给装饰器的参数param只是为了wrapper可以对不同的函数有区别地“包装”,这也算是对装饰器功能的进一步扩展。

你可能感兴趣的:(python-面试必会知识点,Python面试知识点整理)