[ Python ] 裝飾器詳解

1. 什麼是裝飾器 

裝飾器自己是函數,是爲其餘函數添加功能的函數,裝飾器的好處就是在不用更改原函數的代碼前提下給函數增長新的功能。python

裝飾器原則:
    (1)不能修改被裝飾函數的源代碼;
    (2)不能修改被裝飾函數的調用方式閉包

抓住裝飾器的兩大原則來學習裝飾器。裝飾器的預備知識:
    裝飾器 = 高階函數 + 嵌套函數 + 閉包app

2. 什麼是高階函數

 (1)函數自己能夠賦值給變量,賦值後變量爲函數;
(2)容許將函數自己做爲參數傳入另外一個函數;
(3)容許返回一個函數;
之內置函數 abs 求絕對值爲例:函數

>>> abs(-10)
10
>>> a = abs
>>> a(-20)
20

內置函數 abs 賦值給變量 a ,在經過變量 a 執行,如今變量 a  和函數 abs 具備一樣的功能;
結論:函數自己也能夠賦值給變量,即:變量能夠指向函數。當變量指向函數時, 變量要能夠想函數同樣調用學習

 

傳入函數
變量便可以指向函數,函數的參數也能接收變量,那麼一個函數就能夠接收另外一個函數做爲參數,這種函數就稱爲 高階函數spa

def foo():
        print('from foo')

def test(func):
        return func

# foo 函數做爲參數傳入另外一個函數
t = test(foo)
print(t)

# 運行結果:
# <function foo at 0x0000026A2A4E9048>

 

在上面的例子中,test 就是一個高階函數,接收一個函數做爲參數,並返回一個函數,最後的打印是函數的內存地址。

嘗試經過高階函數來實現裝飾器的功能:

例1 嘗試經過裝飾器的方式計算 foo 函數的運行時間設計

import time

def timmer(func):
        start_time = time.time()
        func()
        print('foo函數運行時間:', time.time()-start_time)


def foo():
        time.sleep(2)
        print('hello, foo.')

timmer(foo)

# 運行結果:
# hello, foo.
# foo函數運行時間: 2.0004444122314453

 

裝飾器是爲其餘函數添加附加功能的函數。上面的例子中,timmer 函數確實爲 foo 添加了附加功能,計算出了 foo 函數運行的時間。
咱們在經過裝飾器的兩大原則來比較:
    (1)不修改被裝飾函數的源代碼,上面的例子中 foo 函數的源代碼沒有被修改 -- 知足
    (2)不修改被裝飾函數的調用方式; 上面 foo 函數調用方式應該是 foo()  而上面爲了實現附加功能,調用方式修改成 timmer(foo),調用方式發生了修改 -- 不知足
因此說,高階函數不能知足裝飾器的兩大原則。blog

3. 嵌套函數

 在一個函數中,定義另外一個函數

例:這就是一個嵌套函數ip

def foo():
        def test():
                print('test.')

 說到嵌套函數就必定會提到做用域;
(1)做用域
一個標識符的可見範圍,這就是標識符的做用域。通常常說的是變量的做用域
全局做用域(global):在整個程序運行環境中均可見;
局部做用域:在函數、類等內部可見;局部變量使用範圍不能超過其所在的局部做用域內存

def foo():
        name = 'hkey'
        def test():
                print(locals())
                print(name)
        print(locals())
        test()
foo()

# 執行結果:
# {'test': <function foo.<locals>.test at 0x000002494DA55950>, 'name': 'hkey'}
# {'name': 'hkey'}
# hkey

 

上面的實例運行步驟以下:
(1)首先打印 foo 函數中 print(locals()) --> {'name': 'hkey', 'test': <function foo.<locals>.test at 0x000001C6F4BF5950>}
打印的是 foo 函數中的局部變量,有變量 name 以及 test 函數的內存地址
(2)執行 foo 函數中的 test() 函數
(3)執行嵌套函數 test 中的 print(locals()) --> {'name': 'hkey'} 發如今嵌套函數 test 中可以獲取上一級函數定義的變量
(4)最後在嵌套函數 test 中,打印 name 變量 --> 'hkey'

結論:在嵌套函數中,若是沒有定義須要調用局部變量的值,則會去上一層中的局部變量找是否存在,若是上一層不存在,則會去全局變量中找。
就像找一種微量元素,首先在地球上找,若是地球沒有則會去太陽系找,若是太陽系沒有則會去銀河系找。這裏面 銀河系包括太陽系,太陽系又包括地球

 

4. 閉包的做用

>>> def foo(n):
...     def test():
...         return n + 1
...     return test
...
>>> f = foo(10)
>>> f
<function foo.<locals>.test at 0x000001D5958EF0D0>
>>> f()
11
>>> f = foo(20)
>>> f()
21

 在這段程序中,函數 test 是函數 foo 的內嵌函數,而且 test 是 foo 函數的返回值。
foo 函數只是返回了內嵌函數 test 的地址,在單獨執行 test 函數時將會因爲在其做用域中找不到 n 變量而出錯。而在函數式語言中,當內嵌函數體內引用到體外的變量時,將會把定義時涉及的引用環境和函數體打包成一個總體返回, 這個實例就是閉包的做用。

 

5. 編寫一個裝飾器

 經過上面 高階函數、嵌套函數、閉包的簡介及舉例,嘗試寫一個裝飾器

例:計算 foo 函數運行的時間

import time

def timmer(func):
        def wrapper():
                start_time = time.time()
                func()
                print('程序運行時間:', time.time()-start_time)
        return wrapper

def foo():
        time.sleep(2)
        print('foo run finish.')

foo = timmer(foo)
foo()

# 運行結果:
# foo run finish.
# 程序運行時間: 2.0003890991210938

 

 (1)首先,定義 timmer函數,定義func爲參數,返回值是內嵌函數 wrapper, 被裝飾函數爲 foo
(2)在定義 foo = timmer(foo) 時,其實是 foo = wrapper 函數內存地址,這裏使用高階函數和閉包的概念
(3)foo()  等於在執行 wrapper() 在 wrapper 函數中執行了timmer函數中的參數 func , 而這裏參數func 就是 foo

 

foo = timmer() 使用語法糖的方式:

無參數的裝飾器:

import time
def timmer(func):
        def wrapper():
                start_time = time.time()
                func()
                print('程序運行時間:', time.time()-start_time)
        return wrapper

@timmer
def foo():
        time.sleep(2)
        print('foo run finish.')

foo()

# 運行結果:
# foo run finish.
# 程序運行時間: 2.0003890991210938

 這樣一個無參數的裝飾器就完成了, 由於被裝飾的函數無需傳入任何參數;

 

有參數的裝飾器:

import time
def timmer(func):
        def wrapper(*args, **kwargs):
                start_time = time.time()
                func(*args, **kwargs)
                print('app runtime:', time.time()-start_time)
        return wrapper

@timmer
def foo(name):
        time.sleep(2)
        print('hello,', name)

foo('hkey')

# 運行結果:
# hello, hkey
# app runtime: 2.0005948543548584

 

在有參數的裝飾器中 *args, **kwargs 表示接收了任意類型的參數,關於 *args, **kwargs 含義請參考:函數的參數

 

 有返回值的裝飾器:

import time
def timmer(func):
        def wrapper(*args, **kwargs):
                start_time = time.time()
                res = func(*args, **kwargs)
                print('app runtime:', time.time()-start_time)
                return res
        return wrapper

@timmer
def foo(name):
        time.sleep(2)
        print('hello,', name)
        return '返回foo'

f = foo('hkey')
print(f)

# 運行結果:
# hello, hkey
# app runtime: 2.0003833770751953
# 返回foo

 

6. 幾種不一樣的裝飾器及執行流程

被裝飾函數的視角:

  經過 被裝飾函數 的特徵分爲如下幾種類型

    (1)被裝飾的函數不帶參數和返回值

    (2)被裝飾的函數帶參數但沒有返回值

    (3)被裝飾的函數帶參數和返回值

 

def log(f):
    def wrapper(*args, **kwargs):
        print('func 函數運行前')
        ret = f(*args, **kwargs)
        print('func 函數運行後')
        return ret
    return wrapper


@log
def func(x, y):
    print('hello', x, y)
    return 'func: 返回值'


print(func('xiaofei', 'hkey'))


# 執行結果:
# func 函數運行前
# hello xiaofei hkey
# func 函數運行後
# func: 返回值

 

以上 3 種狀況被裝飾函數的類型,均可以經過上面的裝飾器 log 來裝飾。具體執行流程以下圖:

 

裝飾器視角:
  根據裝飾器的使用分爲如下

    (1)裝飾器帶有參數

    (2)多個裝飾器裝飾一個函數

 

(1)裝飾器帶有參數

def log(text):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print('函數以前執行')
            print('裝飾器說明:', text)
            ret = func(*args, **kwargs)
            print('函數以後執行')
            return ret
        return wrapper
    return decorator


@log('我是帶參數的裝飾器')
def func(x, y):
    print('hello', x, y)
    return 'func: 返回值'


print(func('xiaofei', 'hkey'))


# 執行結果:
# 函數以前執行
# 裝飾器說明: 我是帶參數的裝飾器
# hello xiaofei hkey
# 函數以後執行
# func: 返回值

 

帶參數的裝飾器執行流程以下:

 

 (2)多個裝飾器同時裝飾同一個函數

def log1(func): # log(wrapper2)
    def wrapper1(*args, **kwargs):
        print('before: log1')
        ret = func(*args, **kwargs)
        print('after: log1')
        return ret
    return wrapper1

def log2(func): # log2(func)
    def wrapper2(*args, **kwargs):
        print('before: log2')
        ret = func(*args, **kwargs)
        print('after: log2')
        return ret
    return wrapper2

@log1   # --> func = log1(func) --> log1(wrapper2) 首先執行
@log2   # --> func = log2(func) --> wrapper2
def func():
    print('hello')

func()


# 執行結果:
# before: log1
# before: log2
# hello
# after: log2
# after: log1

 

多個裝飾器同時裝飾同一個函數流程以下圖:

 

 

7. 裝飾器的幾個實例

 (1)使用裝飾器實現登陸驗證的功能。當用戶調用 home 函數的時候必須通過登陸驗證

def auth(func):
        def wrapper(*args, **kwargs):
                username = input('用戶名:').strip()
                passwd = input('密碼:').strip()
                if username == 'admin' and passwd == '123':
                        res = func(*args, **kwargs)
                        return res
                else:
                        print('用戶名密碼錯誤!')

        return wrapper

@auth
def home():
        print('welcome home.')

home()

# 執行結果:
# (1)用戶名密碼正確
# 用戶名:admin
# 密碼:123
# welcome home.
#
# (2)用戶名密碼錯誤
# 用戶名:admin
# 密碼:111

 

 (2)使用裝飾器實現登陸功能,當調用 home 函數時,驗證方式爲 filedb ,當登陸購物車時,驗證方式爲 ldap

def auth(auth_type):
        def decorator(func):
                def wrapper(*args, **kwargs):
                        username = input('用戶名:').strip()
                        passwd = input('密碼:').strip()
                        if auth_type == 'filedb':
                                print('filedb 驗證中...')
                                if username == 'filedb' and passwd == '123':
                                        res = func()
                                        return res
                        elif auth_type == 'ldap':
                                print('ldap 驗證中...')
                                if username == 'ldap' and passwd == '123':
                                        res = func()
                                        return res

                return wrapper

        return decorator

@auth('filedb')
def home():
        print('welcome home.')
@auth('ldap')
def shopping_cars():
        print('我的購物車.')
home()
shopping_cars()

# 運行結果:
# 用戶名:filedb
# 密碼:123
# filedb 驗證中...
# welcome home.
# 用戶名:ldap
# 密碼:123
# ldap 驗證中...

 

(3)寫了一個裝飾器,在不少函數中都使用了, 如何簡單的所有關閉,如何設計裝飾器

Flag = False  # Flag = True的時候使用裝飾器中附加功能,不然不使用裝飾器中的附加功能


def log(flag):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if flag:  # 經過 flag 參數來控制是否要使用裝飾器中的內容。
                print('----')
                ret = func(*args, **kwargs)
                print('#####')
                return ret
            else:
                ret = func(*args, **kwargs)
                return ret

        return wrapper

    return decorator


@log(Flag)
def func1():
    print('func: func1.')


@log(Flag)
def func2():
    print('func: func2.')


@log(Flag)
def func3():
    print('func: func3.')


func1()
func2()
func3()
相關文章
相關標籤/搜索