Python 装饰器(Decorator)

要想玩装饰器,那就得了解一下闭包,我们不得不这样做!

闭包(Closure)

对象是附加了方法的数据。闭包是附带数据的函数。

闭包是引用了自由变量的函数。大白话说,只要某个函数中引用了一些不在当前代码全局中定义的变量,那么这个函数就是闭包函数。

def f1(x):
    x1 = x
    def f2(y):
        y = x1 + y
        return y
    return f2

In [1]: def f1(x):
   ...:     x1 = x
   ...:     def f2(y):
   ...:         y = x1 + y
   ...:         return y
   ...:     return f2
   ...:

In [2]: fun = f1("你好")

In [3]: fun("世界")
Out[3]: '你好世界'

In [4]: f1("武汉")("加油")
Out[4]: '武汉加油'

In [5]: f1(f1("武汉")("加油"))("!!!")
Out[5]: '武汉加油!!!'    

这是怎么回事呢 ?

Python 中一切皆对象

fun = f1("你好")fun 是指向 f1 函数返回的一个对象,也就是 f2 这个函数,通过传递参数 武汉fun 与以下代码概念一致

def f2(y):
    y = "武汉" + y
    return y

fun("世界") 大家都明白,“你好世界”

f1("武汉")("加油") 是另一种写法而已。

当调用 fun 时,f1 的作用域已经结束,x1 就成了孤鸿野鬼,它不在当前代码全局中被定义,x1 是调用 fun 被使用的变量,所以 fun 是附带数据的函数。

装饰器

装饰器语法只是一种语法糖,其返回值为另一个函数的函数,通常使用 @wrapper 语法形式来进行函数变换。装饰器通俗的讲就是某一个函数在不改动其代码的情况下而为它增加额外的功能。

现在有一段代码,它的功能是打印一些内容

def f1():
    print("你好")

如果想为其增加功能,打印内容中要包含当前时间。这不很简单吗!!!

import time

def f1():
    print("你好")
    print(time.ctime())

import time

def print_time():
    print(time.ctime())

def f1():
    print("你好")
    print_time()

No !!! 这也很不Pythonic。

语法糖

import time

def print_time(f):
    def print_content():
        print(time.ctime())
        return f()
    return print_content
    
@print_time
def f1():
    print("你好")

其实它和以下代码原理相同

import time

def print_time(f):
    def print_content():
        print(time.ctime())
        return f()
    return print_content
    
def f1():
    print("你好")
    
f1 = print_time(f1)

又是那句话 Python 中一切皆对象

f1 作为参数传递给 print_timeprint_time 返回了内层函数 print_content 的引用重新赋给 f1 ,而 print_content 函数内包含了函数 f1 的引用,也就是闭包所讲的。

这个代码使时间输出在 f1 函数的上一行,如果想让先执行 f1 的内容呢

import time

def print_time(f):
    def print_content():
        rlt = f()
        print(time.ctime())
        return rlt
    return print_content
    
def f1():
    print("你好")
    
f1 = print_time(f1)

如果被额外增加功能的函数有参数,那上面的方法就凉了,那怎么办?

让其内层函数可以接收任意个参数就OK了

import time

def print_time(f):
    def print_content(*args, **kwargs):
        print(time.ctime())
        return f(*args, **kwargs)
    return print_content
    
@print_time
def f1(x=''):
    print("你好" + str(x))

这要做无论被装饰的函数是否有参数都不影响,岂不是美滋滋。

带参数的装饰器

import time

def fun(a):
    def print_time(f):
        def print_content(*args, **kwargs):
            print(time.ctime())
            print(a)
            return f(*args, **kwargs)
        return print_content
    return print_time
    
@fun("朋友")
def f1(x=''):
    print("你好" + str(x))

带参数的装饰器无非是外面有多了一层函数,用于接收参数

这也好理解,原理和以下代码相同

import time
def fun(a):
    def print_time(f):
        def print_content(*args, **kwargs):
            print(time.ctime())
            print(a)
            return f(*args, **kwargs)
        return print_content
    return print_time
    
def f1(x=''):
    print("你好" + str(x))

f1 = fun("朋友")(f1)

当调用带参数的 fun 函数时返回了 print_time 函数的引用,所以他变成了 f1 = print_time(f1) ,这和上面的内容一致。

使用场景

  • 授权(Authorization)
from functools import wraps
 
def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            authenticate()
        return f(*args, **kwargs)
    return decorated
  • 日志(Logging)
from functools import wraps
 
def logit(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
 
@logit
def addition_func(x):
   """Do some math."""
   return x + x
 
result = addition_func(4)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章