社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python-S01E22神奇的装饰器

一个普普通通简简单单平平凡凡的神 • 8 年前 • 304 次点击  

python,不管你懂没懂,反正妹子是搞懂了

【要点抢先看】

1.神奇的装饰器到底是什么
2.装饰器的用法和语法糖
3.装饰器如何添加参数

装饰器是python里的一个非常有意思的部分,他用于封装函数代码,显式的将封装器应用到被封装的函数上,从而使得他们选择加入到装饰器指定的功能中。对于在函数运行前处理常见前置条件(例如确认授权),或在函数运行后确保清理(输出清除或异常处理),装饰器都非常有用。

【妹子说】听不明白,太绕了!

简单来说,装饰器就是实现了一个通用的功能,然后将这个通用的功能应用到不同的、需要使用这个功能的函数上,从而避免每次都在不同函数上反复写相同的功能代码。

装饰器的本质是一个函数,他接受被装饰的函数作为位置参数,装饰器通过使用该参数来执行某些操作,然后返回一个函数引用,这个函数可以是原始函数,或者是另外一个函数。

我们举例子说明,装饰器是这样的函数,他们接受被装饰的可调用函数作为唯一的参数,并且返回一个可调用函数,

registry = []
def register(decorated):
    registry.append(decorated)
    return decorated

def foo():
    return 3

foo = register(foo)
print(registry[0])

<function foo at 0x00000000025D51E0>

register方法是一个简单的装饰器,它把被装饰的函数添加到一个列表中,然后这里是将未改变的被装饰函数返回,可以看出,装饰器一般是传入被装饰函数的引用,然后经过一些指定的处理,最后返回值也是一个函数引用。

还有一种更简单的语法形式:

装饰器的语法糖:我们这里看到的对foo进行装饰的方法是运用

foo = register(foo)语句,还有一种简单的用法是在声明函数的位置应用装饰器,从而使得代码更容易阅读,并且让人立刻意识到使用了装饰器

registry = []
def register(decorated):
    registry.append(decorated)
    return decorated

@register
def foo(x=3):
    return x

@register
def bar(x=5):
    return 5

for func in registry:
    print(func())

3
5

再看一个更复杂、更一般化的装饰器函数。装饰器的本质是在执行原有函数(被装饰的函数)的同时,再加上一些额外的功能。

def requires_ints(decorated):
    def inner(*args, **kwargs):
        kwarg_values = [i for i in kwargs.values()]
        for arg in list(args) + kwarg_values:
            if not isinstance(arg, int):
                raise TypeError('{} only accepts integers as arguments'.format(decorated.__name__))
        return decorated(*args, **kwargs)
    return inner

在这个装饰器函数requires_ints我们可以看到,他定义了一个内嵌函数inner,这个内嵌函数的参数首先收集被装饰函数的所有参数,然后对其进行判断,判断其是否为整数类型(这就是装饰器添加的额外功能),然后再调用被装饰的函数本身,最后将这个内嵌函数返回。因此当我们再用原函数名进行调用的时候,原来的被装饰函数的引用就能指向这个新的内嵌函数,就能在实现原函数功能的基础上,加上附加的功能了。

同时,我们再提炼一下这里面的几个重难点:

第一,requires_ints中,decorated这个变量是内嵌作用域的变量,在他调用退出后,返回的inner函数是可以记住这个变量的。

第二,python不支持函数的参数列表的多态,即一个函数名只能对应唯一的参数列表形式。

第三,在内嵌函数内部调用被装饰函数的时候,使用了解包参数,关于这*args, **kwargs,的参数形式,前面章节中细讲过。

【妹子说】那我们也用这个装饰器来装饰一个函数。

@requires_ints
def foo(x,y):
    print(x+y)

foo(3,5)

8

这里将名称foo赋给inner函数,而不是赋给原来被定义的函数,如果运行foo(3,5),将利用传入的这两个参数运行inner函数,inner函数执行类型检查,然后运行被装饰方法,如果传入的不是整形数,例如下面这个例子,那么装饰器的附加功能就会进行类型检查:

@requires_ints
def foo(x,y):
    print(x+y)

foo('a',5)

Traceback (most recent call last):
  File "E:/12homework/12homework.py", line 15, in <module>
    foo('a',5)
  File "E:/12homework/12homework.py", line 7, in inner
raise TypeError('{} only accepts integers as arguments'.format(decorated.__name__))
TypeError: foo only accepts integers as arguments

其次内嵌的函数和被装饰的函数的参数形式必须完全一样,这里用的*args, **kwargs概况函数参数的一般形式,因此也是完全对应的。

最后说说装饰器参数

最后来介绍这个复杂一些的话题,装饰器参数。之前我们列举的常规例子里,装饰器只有一个参数,就是被装饰的方法。但是,有时让装饰器自身带有一些需要的信息,从而使装饰器可以用恰当的方式装饰方法十分有用。

这些参数并不是和被装饰的函数并列作为参数签名,而是在原有装饰器的基础上额外再增加一层封装,那么,实质是这个接受其他参数的装饰器并不是一个实际的装饰器,而是一个返回装饰器的函数。

最终返回的内嵌函数inner是最终使用indent和sort_keys参数的函数,这没有问题

import json

def json_output(indent=None, sort_keys=False):
    def actual_decorator(decorated):
        def inner(*args, **kwargs):
            result = decorated(*args, **kwargs)
            return json.dumps(result, indent=indent, sort_keys=sort_keys)
        return inner
    return actual_decorator

@json_output(indent=8)
def do_nothing():
    return {'status':'done','func':'yes'}

print(do_nothing())

{
        "status": "done",
        "func": "yes"
}

我们在这里详细解释说明的是操作顺序,看上去我们使用的是@json_output(indent=8),作这和之前的装饰器语法糖看上去有些不同,实际上这个不是最终的装饰器函数,通过调用json_output(indent=8),返回函数指针actual_decorator,这个函数才是真正放在@后的装饰器函数,原始的被装饰函数最终获得了内涵更丰富的inner函数对象,完成了装饰过程,值得一提的是,所谓的装饰器参数最终传给了最内层的inner函数。

记住,在定义装饰器函数后,真正的装饰器函数只有一个参数,那就是被装饰的函数指针,而有其他参数的函数实质上只是装饰器的外围函数,他可以依据参数对装饰器进行进一步的定制。一句话:一个函数不可能接受被装饰的方法,又接受其他参数

在语法糖中@func这种不带括号的,就是直接使用装饰器函数进行装饰,如果是@func()带括号的,实质上是先调用func()函数返回真正的装饰器,然后再用@进行调用。

【妹子说】这个装饰器的功能可真的是在python里才见到,真得好好学学呀!


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/YYjqNCOBsM
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/10703