python中的装饰器

需求:

定义了一个函数,想要在运行的时候动态增加功能,又不想去改动本身的代码。

例如:我们拥有三个函数,

def f1(x):
    return x*2

def f2(x):
    return x*x

def f3(x):
    return x*x*x

那么我们想要给每一个函数增加一个log信息功能,即打印出xxx函数被调用,那么我们有什么方法呢?

方法1. 修改原函数,在每一个函数中添加一个print语句:
def f1(x):
    print('f1()被调用')
    return x*2

def f2(x):
    print('f2()被调用')
    return x*x

def f3(x):
    print('f3()被调用')
    return x*x*x

但是这样方便我们管理,并且会重复编写大量的重复代码,那么又没有更简单的方法呢?

方法2. 使用高级函数

因为高级函数我们可以返回一个函数或者将函数作为参数进行调用,然后我们在原函数内部接收一个函数,并对其进行包装,然后返回一个新的函数。就能够达到我们的目地了。

def f1(x):
    return x*2

def f2(x):
    return x*x

def f3(x):
    return x*x*x

def new_fn(f):
    def fn(x):
        print('%s函数被调用' % f.__name__)
        return f(x)  # return f(x) 和 return f 是不一样的
                     # return f(x) 返回f(x)函数执行完返回的结果
                     # retuen f 返回f这个函数,并没有执行f函数
    return fn

然后我们来调用这个函数,调用这个函数也有两种方法。

方法1.

g1 = new_fn( f1 ) # 调用我们编写的高级函数,并且传入f1函数作为参数
                  # g1现在就相当于new_fn()函数中的fn()函数
print(g1)       # 这里将打印出来一个地址
result = g1(5)
print(result)   # 显示结果

以此类推,f2,f3函数也这样调用。

方法2.

print(f1)         # 首先显示原函数的地址
f1 = new_fn(f1)   # 因为函数名也是一个变量,所以我们给f1重新赋值
print(f1)         # 显示重新赋值之后的f1函数的地址
result = f1(5)    # 调用f1函数,返回结果
print(result)     # 显示结果

方法2和方法1的不同之处在与,方法2调用new_fn()函数之后,继续使用f1这个变量来接接收返回的函数,也就是对f1这个变量重新赋值了,从而彻底隐藏了原函数,而返会了一个和原函数功能相同的一个函数,并且新的函数还增加了一个打印log信息的功能。
而方法1就是没有隐藏原函数,还是能使用原函数,用的是一个新的变量来接收返回的函数。

需求总结:

可以看到,使用高级函数函数之后,我们要给每个函数增加一个打印log信息的功能,我们只需要再定义一个函数即可,不用修改每个函数的代码,并且这种方式的可移植性也比较强。

那么问题来了,相信点开这篇博客的朋友都是想了解装饰器的,而扯了半天我们都还没有说装饰器是什么,怎么定义,怎么使用。

其实在上面的例子中我们就已经定义了装饰器,并且已经在使用装饰器了。

上面例子中的方法2就是装饰器的定义,方法2中的方法2就是装饰器的调用了。

python中给我们提供了一个很简答的方法来使用装饰器,那就是使用@符号

@new_fn
def f3(x):
    return x*x*x

相当于

def f3(x):
	return x*x*x
f3 = mew_fn(f3)

这就是python中的装饰器了。

所以我们要使用装饰器的时候,直接定义一个装饰函数,然后载使用@符号给相应的函数进行装饰,就成功的使用了装饰器了。

实例:

Python的 decorator 本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。

  1. 编写一个无参的decorator。打印log信息
def log(f):
    def fn(x):
        print(f.__name__ + '()函数被调用')
        return f(x)
    return fn

@log
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print(factorial(10))

对于上面的装饰器,如果我们换一个需要两个参数的函数进行装饰,那么就会报错:

@log
def add(x,y):
    return x+y

print(add(5,7))

因为我们定义的装饰器中的函数只有一个参数,那么我们传入两个参数是肯定会报错的,所以我们可以修改一下我们的装饰器,使它可以适用与任意参数。
修改装饰器如下:

def log(f):
    def fn(*args,**kwargs):  # *args,**kwargs这两个参数组合在一起就能表示任意参数
        print(f.__name__ + '()函数被调用')
        return f(*args,**kwargs)
    return fn

然后我们再给add()函数进行装饰:

@log
def add(x,y):
    return x+y

print(add(5,7))

这样,我们定义的装饰器也不会因为参数问题从而导致无法使用了。

  1. 编写一个带参数的decorator

在上面的@log装饰器中,我们对于被装饰的函数,log打印的语句是不能变的(除了函数的名字以外)。

而有的函数非常重要,希望打印[INFO]:xxx函数被调用,有的函数不太重要,希望打出[DEBUG]:xxx函数被调用
这个时候@log函数函数就需要传入一个INFO或者DEBUG这样的参数了。
类似与这样传入参数:

@log('DEBUG')
def my_func():
    pass

把上面的定义翻译为高级函数的调用就是:

my_func = log('DEBUG')(my_func)

在将上面的语句展开一下:

log_decorator = log('DEBUG')
my_func = log_decorator(my_func)

上面的语句又相当于:

log_decorator = log('DEBUG')
@log_decorator
def my_func():
    pass

所以,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收my_func并返回新函数。

所以我们应该这样定义装饰器函数:

def log1(prefix):
    def log_decorator(f):
        def wrapper(*args, **kw):
            print('[%s]:%s()函数被调用...' % (prefix, f.__name__))
            return f(*args, **kw)
        return wrapper
    return log_decorator

@log1('DEBUG')
def test():
    return 'hello decorator'
    
print(test())

上面的代码就相当与:

def log1(prefix):
    def log_decorator(f):
        def wrapper(*args, **kw):
            print('[%s]:%s()函数被调用...' % (prefix, f.__name__))
            return f(*args, **kw)
        return wrapper
    return log_decorator

# @log1('DEBUG')
def test():
    return 'hello decorator'

log_decorator = log1('INFO')
test = log_decorator(test)

print(test())

或者相当于:

def log1(prefix):
    def log_decorator(f):
        def wrapper(*args, **kwargs):
            print('[%s]:%s()函数被调用...' % (prefix, f.__name__))
            return f(*args, **kwargs)
        return wrapper
    return log_decorator

log_decorator = log1('INFO')
@log_decorator
def test():
    return 'hello decorator'

# log_decorator = log1('INFO')
# test = log_decorator(test)

print(test())

python中完善装饰器

@decorator可以动态实现函数功能的增加,但是,经过@decorator“改造”后的函数,和原函数相比,除了功能多一点外,有没有其它不同的地方?

答案是肯定的。

在没有decorator的情况下,打印函数名:

def f4():
    pass

print(f4.__name__)

打印的结果是:f4。

在被decorator装饰的情况下,打印函数名:

def decorator(func):
    def wrapper(*args,**kwargs):
        return func()
    return wrapper

@decorator
def f4():
    pass

print(f4.__name__)

打印的结果是:wrapper

可见,由于decorator返回的新函数函数名已经不是’f2’,而是@decorator内部定义的’wrapper’。这对于那些依赖函数名的代码就会失效。decorator还改变了函数的__doc__等其它属性。如果要让调用者看不出一个函数经过了@decorator的“改造”,就需要把原函数的一些属性复制到新函数中:

def decorator(func):
    def wrapper(*args,**kwargs):
        return func()
    wrapper.__name__ = func.__name__
    wrapper.__doc__ = func.__doc__
    return wrapper

@decorator
def f4():
    pass

print(f4.__name__)

但是这样写decorator很不方便,因为我们也很难把原函数的所有必要属性都一个一个复制到新函数上,所以Python内置的functools可以用来自动化完成这个“复制”的任务:

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper(*args,**kwargs):
        return func()
    # wrapper.__name__ = func.__name__
    # wrapper.__doc__ = func.__doc__
    return wrapper

@decorator
def f4():
    pass

print(f4.__name__)

那么如果是一个带参数的装饰器呢,我们应该将@functools.wraps(func)放在哪个位置呢?

import functools

def demo(prefix):
    def demo_decorator(func):
        @functools.wraps(func)
        def wrapper(*args,**kwargs):
            print("[%s]:函数被调用..." % prefix)
            return func(*args,**kwargs)
        return wrapper
    return demo_decorator

@demo('INFO')
def f5():
    pass

print(f5.__name__)

以上就是装饰器的大概内容,谢谢大家的阅读。

参考链接:https://www.imooc.com/learn/317

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值