python-装饰器

装饰器定义: 在不希望修改函数的定义,在代码运行期间动态增加函数功能的方式,称之为“装饰器”(Decorator)。本质上,decorator就是一个返回函数的高阶函数。
如何使用装饰器: decorator接受一个函数作为参数,并返回一个函数。 借助Python的@语法,把decorator置于函数的定义处,实现功能扩展的目的。

首先明确函数嵌套以及闭包:

嵌套函数

如果在一个函数的内部还定义了另一个函数(注意: 是定义,不是引用!),这个函数就叫嵌套函数。外部的我们叫它外函数,内部的我们叫他内函数。

如下outer函数里又定义了一个inner函数,并调用了它。内函数在自己作用域内查找局部变量失败后,会进一步向上一层作用域里查找。

def outer():
    x = 1
    def inner():
        y = x + 1
        print(y)
    inner()

outer() #输出结果 2

如果在外函数里不直接调用内函数,而是通过return inner返回一个内函数的引用,会得到一个内函数对象,而不是运行结果,如下运行outer() 时,内部 inner函数只是在outer()中定义,没有被调用,不会运行,所以运行outer() 只会执行 return inner;运行f1 = outer()时返回inner,再运行f1()时,相当于:f1() = outer()(),即f1() = (inner)(),f1() = inner(),即运行inner函数

def outer():
    x = 1
    def inner():
        y = 2
        print(y)
    return inner

outer() # 输出<function outer.<locals>.inner at 0x039248E8>
f1 = outer()
f1() # 输出2

##############################################

def outer(x):
    a = x

    def inner(y):
        b = y
        print(b)

    return inner

f1 = outer(1) # 返回inner函数对象
f1(10) # 相当于inner(10)。输出10
 

闭包(closure):

闭包是Python编程一个非常重要的概念。如果一个外函数中定义了一个内函数,且内函数体内引用到了体外的变量,这时外函数通过return返回内函数的引用时,会把定义时涉及到的外部引用变量和内函数打包成一个整体(闭包)返回。如下outer方法返回的只是内函数对象吗? 错。我们的outer函数返回的实际上是一个由inner函数和外部引用变量(a)组成的闭包!

def outer(x):
    a = x

    def inner(y):
        b = y
        print(a+b)

    return inner


f1 = outer(1) # 返回inner函数对象+局部变量(外部引用变量(a))1(闭包)
f1(10) # 相当于inner(10)。输出11

一般一个函数运行结束的时候,临时变量会被销毁。但是闭包是一个特别的情况。当外函数发现,自己的临时变量会在将来的内函数中用到,自己在结束的时候,返回内函数的同时,会把外函数的临时变量同内函数绑定在一起。这样即使外函数已经结束了,内函数仍然能够使用外函数的临时变量。这就是闭包的强大之处。

例子:

运行test() 是装载装饰器的过程,相当于执行了test=dect1(dect2(test)),此时先执行dect2(test),结果是输出aaaa、将func指向函数test、并返回函数two,然后执行dect1(two),结果是输出1111、将func指向函数two、并返回函数one,然后进行赋值。 
最终运行运行test() 实际是用函数one替代了函数名test。这时实际上执行的是函数one,运行到函数one()中的func()时执行函数two,再运行到函数two中的func()时执行未修饰的函数test,运行完函数test,再返回函数two,之后再返回函数one。

def dec1(func):  
    print("1111")  
    def one():  
        print("2222")  
        func()  
        print("3333")  
    return one  
  
def dec2(func):  
    print("aaaa")  
    def two():  
        print("bbbb")  
        func()  
        print("cccc")  
    return two  
 
@dec1  
@dec2  
def test():  
    print("test test")  
  
test()

输出:

aaaa  
1111  
2222  
bbbb  
test test  
cccc  
3333

**********************************************************************************************

如何编写一个通用的装饰器

我们现在可以开始动手写个名为hint的装饰器了,其作用是在某个函数运行前给我们提示。这里外函数以hint命名,内函数以常用的wrapper(包裹函数)命名。

def hint(func):
    def wrapper(*args, **kwargs):
        print('{} is running'.format(func.__name__))
        return func(*args, **kwargs)
    return wrapper

@hint
def hello():
    print("Hello!")

我们现在对hello已经进行了装饰,当我们调用hello()时,我们可以看到如下结果。

>>> hello()
hello is running.
Hello!

值得一提的是被装饰器装饰过的函数看上去名字没变,其实已经变了。当你运行hello()后,你会发现它的名字已经悄悄变成了wrapper,这显然不是我们想要的(如下图所示)。这一点也不奇怪,因为外函数返回的是由wrapper函数和其外部引用变量组成的闭包。

>>> hello.__name__
'wrapper'

为了解决这个问题保证装饰过的函数__name__属性不变,我们可以使用functools模块里的wraps方法,先对func变量进行wraps。下面这段代码可以作为编写一个通用装饰器的示范代码,注意收藏哦。

from functools import wraps

def hint(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print('{} is running'.format(func.__name__))
        return func(*args, **kwargs)
    return wrapper


@hint
def hello():
    print("Hello!")

恭喜你,你已经学会写一个比较通用的装饰器啦,并保证装饰过的函数__name__属性不变啦。当然使用嵌套函数也有缺点,比如不直观。这时你可以借助Python的decorator模块(需事先安装)可以简化装饰器的编写和使用。如下所示。

from decorator import decorator

@decorator
def hint(func, *args, **kwargs):
    print('{} is running'.format(func.__name__))
    return func(*args, **kwargs)

编写带参数的高级装饰器

前面几个装饰器一般是内外两层嵌套函数。如果我们需要编写的装饰器本身是带参数的,我们需要编写三层的嵌套函数,其中最外一层用来传递装饰器的参数。现在我们要对@hint装饰器做点改进,使其能通过@hint(coder="John")传递参数。该装饰器在函数运行前给出提示的时候还显示函数编写人员的名字。完整代码如下所示:

from functools import wraps


def hint(coder):
    def wrapper(func):
        @wraps(func)
        def inner_wrapper(*args, **kwargs):
            print('{} is running'.format(func.__name__))
            print('Coder: {}'.format(coder))
            return func(*args, **kwargs)
        return inner_wrapper
    return wrapper


@hint(coder="John")
def hello():
    print("Hello!")

下面这段代码是一段经典的Python装饰器代码,显示了@cache这个装饰器怎么编写和工作的。它需要使用缓存实例做为一个参数,所以也是三层嵌套函数。

import time
from functools import wraps


# 装饰器增加缓存功能
def cache(instance):
    def wrapper(func):
        @wraps(func)
        def inner_wrapper(*args, **kwargs):
            # 构建key: key => func_name::args::kwargs
            joint_args = ','.join((str(x) for x in args))
            joint_kwargs = ','.join('{}={}'.format(k, v) for k, v in sorted(kwargs.items()))
            key = '{}::{}::{}'.format(func.__name__,joint_args, joint_kwargs)
            # 根据key获取结果。如果key已存在直接返回结果,不用重复计算。
         result = instance.get(key)
            if result is not None:
                return result
            # 如果结果不存在,重新计算,缓存。
         result = func(*args, **kwargs)
            instance.set(key, result)
            return result
        return inner_wrapper
    return wrapper


# 创建字典构造函数,用户缓存K/V键值对
class DictCache:
    def __init__(self):
        self.cache = dict()

    def get(self, key):
        return self.cache.get(key)

    def set(self, key, value):
        self.cache[key] = value

    def __str__(self):
        return str(self.cache)

    def __repr__(self):
        return repr(self.cache)


# 创建缓存对象
cache_instance = DictCache()


# Python语法糖调用装饰器
@cache(cache_instance)
def long_time_func(x):
    time.sleep(x)
    return x

# 调用装饰过函数
long_time_func(3)

 
基于类实现的装饰器

Python的装饰器不仅可以用嵌套函数来编写,还可以使用类来编写。其调用__init__方法创建实例,传递参数,并调用__call__方法实现对被装饰函数功能的添加。

from functools import wraps


#类的装饰器写法, 不带参数
class Hint(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('{} is running'.format(self.func.__name__))
        return self.func(*args, **kwargs)


#类的装饰器写法, 带参数
class Hint(object):
    def __init__(self, coder=None):
        self.coder = coder

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('{} is running'.format(func.__name__))
            print('Coder: {}'.format(self.coder))
            return func(*args, **kwargs)     # 正式调用主要处理函数
        return wrapper

小结

本文总结了什么是Python的装饰器及其工作原理,并重点介绍了嵌套函数和闭包原理。最后详细展示了如何编写一个通用装饰器及带参数的高级装饰器, 包括使用类来编写装饰器。大家要熟练掌握哦。看不懂的可以先加入微信收藏以后再反复阅读。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
闭包装饰是一种特殊的装饰,它使用闭包的概念来实现。闭包是指一个函数可以访问并操作其外部函数中定义的变量。在Python中,闭包装饰可以用于给函数添加额外的功能,同时保持函数的原始定义不变。 引用中的示例展示了装饰传参的形式。在这个例子中,outer函数是一个装饰,它将inner函数作为子函数返回,并在inner函数中添加了额外的功能。通过使用@outer装饰语法,我们可以在add函数上应用outer装饰,从而在调用add函数时执行装饰中的代码。 引用中的示例展示了多层装饰的使用。在这个例子中,outer1和outer2函数分别是两个装饰,他们都返回一个inner函数。通过使用@outer1和@outer2装饰语法,我们可以在outers函数上应用这两个装饰,并在调用outers函数时按照装饰的定义顺序执行相关的代码。 引用提供了关于Python闭包装饰的使用方法的总结。这篇文章通过示例代码详细介绍了闭包装饰的使用,对于学习和工作有一定的参考价值。 引用中的示例展示了装饰的形式。在这个例子中,outer函数是一个装饰,它将inner函数作为子函数返回,并在inner函数中添加了额外的功能。通过使用@outer装饰语法,我们可以在add函数上应用outer装饰,从而在调用add函数时执行装饰中的代码。 综上所述,Python闭包装饰是一种利用闭包概念实现的特殊装饰,可以用于给函数添加额外的功能。这种装饰可以通过装饰传参的形式、多层装饰的形式或普通的装饰形式来实现。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值