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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章