1. 程式人生 > >Python小白學習之路(二十四)—【裝飾器】

Python小白學習之路(二十四)—【裝飾器】

裝飾器

一、裝飾器的本質

裝飾器的本質就是函式,功能就是為其他函式新增附加功能。

利用裝飾器給其他函式新增附加功能時的原則:

        1.不能修改被修飾函式的原始碼
        2.不能修改被修飾函式的呼叫方式


舉例:計算以下一段程式執行時間

#程式:計算0—19的和
def cal(l):
    res = 0
    for i in l:
        res += i
    return res
print(cal(range(20)))

 

#給上述程度增加時間模組
import time def cal(l): start_time = time.time() res = 0 for i in l: time.sleep(0.1) res += i stop_time = time.time() print('函式的執行時間是%s'%(stop_time - start_time)) return res print(cal(range(20))) #執行結果 函式的執行時間是2.0001144409179688 190

 

#上述增加的時間模組
違反了開放封閉原則,改變了cal()函式的原始碼,不是實現裝飾器的功能。
因此可以把統計時間的函式單獨寫出來。並且滿足以上兩個原則

 

二、怎麼樣實現一個基本的裝飾器(裝飾器的知識儲備)

 

裝飾器 = 高階函式 + 函式巢狀 + 閉包

 

1.高階函式

 

  • 函式接受的引數是一個函式名
  • 函式的返回值是一個函式名
  • 滿足上述條件的任何一個都可以是高階函式


高階函式型別一:函式接受的引數是一個函式名

#舉例:
def name1(n):
    print(n)
    n('xhg')
def name2(name):
    print('my name is %s' %name)
name1(name2)
#執行結果
<function name2 at 0x00E194F8>
my name 
is xhg

 

#name1()是一個高階函式,其接受的引數是函式名name2
#程式分析
#函式名name2是函式name2的記憶體地址。傳給函式name1,即引數n=函式name2的記憶體地址,因此列印的結果為函式name2的記憶體地址
#n('xhg'),只執行name2('xhg')

 

利用‘函式接受的引數是一個函式名’這個思想,為函式name2()新增一個統計時間的功能

import time 
def name1(n):
    start_time = time.time()
    n('xhg')
    stop_time = time.time()
    print('函式的執行時間是%s'%(stop_time - start_time))
def name2(name):
    time.sleep(2)
    print('my name is %s' %name)
name1(name2)

#執行結果
my name is xhg
函式的執行時間是2.0001144409179688
#上述程式雖然實現了增加時間模組功能,雖然不改變函式name2()的程式碼,但是改變了函式name()的呼叫方式
#因此函式name1()不是裝飾器

 

高階函式型別二:函式的返回值是一個函式名

#舉例:
def name1():
    print('from name1')
def name2():
    print('from name2')
    return name1       
n = name2()
n()

#執行結果
from name2
from name1

 

#程式分析:
#函式name2()中的返回值中包含函式名name1,所以函式name2()為高階函式
#將函式名name1賦值給變數n,函式名為該函式記憶體地址
#n()為執行函式name1

 

利用‘函式的返回值是一個函式名’這個思想,為函式name2()新增一個統計時間的功能

import time
def name1(n):
    start_time = time.time()
    n('xhg')
    stop_time = time.time()
    print('函式的執行時間是%s' % (stop_time - start_time))
    return n
def name2(name):
    time.sleep(2)
    print('my name is %s' %name)
name2 = name1(name2)
name2('xhg')

#執行結果
my name is xhg
函式的執行時間是2.0001144409179688
my name is xhg

 

#程式分析:計算函式name2()執行時間的函式模組name1,沒有函式name2()改變呼叫方式,也沒有改變數name2()的原始碼。
#但是多執行了一次,該裝飾器設計不合格

 

#結論:單獨利用高階函式無法實現裝飾器的功能

 

2.函式巢狀

函式巢狀實際上就是在函式中又定義了一個函式

#舉例:

def first():
    print('from first ')
    def second():
        print('from second')
        def third():
            print('from third')
        third()
    second()
first()

#執行結果
from first 
from second
from third

 

#程式分析

 


3.閉包

關於閉包這塊,我沒有太理解了。以下的知識摘自這個博文,對理解閉包挺有幫助的。

https://www.cnblogs.com/guobaoyuan/articles/6756763.html

閉包:首先必須是內部定義的函式,該函式包含對外部作用域而不是全域性作用域名字的引用

定義:內部函式的程式碼包含對外部函式的程式碼的引用,但一定不是對全域性作用域的引用

閉包的基本形式是:

在函式F1中,定義F2,F2只能引用F1定義的變數,之後F1函式返回F2的函式名字

這樣就保證了可以將F1的執行結果賦予給一個變數,該變數可以在之後的任何時刻隨時可以執行

#舉例理解:
x = 1000
def f1():
    x = 1
    def f2():
        print(x)
    return f2
f = f1()
print(f)
f()
x = 123

#執行結果
<function f1.<locals>.f2 at 0x003394B0>
1

 

#順便提一句,要想充分理解這邊程式執行的情況,需要對作用域以及變數那邊有清楚的認識

使用閉包的好處:自帶狀態即變數,可以不用傳參就用,方便。

  • 閉包(closure)是函數語言程式設計的重要的語法結構。
  • 不同的語言實現閉包的方式不同。
  • Python以函式物件為基礎,為閉包這一語法結構提供支援的
  • (我們在特殊方法與多正規化中,已經多次看到Python使用物件來實現一些特殊的語法)。
  • Python一切皆物件,函式這一語法結構也是一個物件。
  • 在函式物件中,我們像使用一個普通物件一樣使用函式物件,比如更改函式物件的名字,或者將函式物件作為引數進行傳遞。

三、裝飾器的框架

有了以上三個知識為基礎鋪墊,我們可以搭出來一個裝飾器的框架模型

以計算程式執行時間的功能函式為例

def timmer(func)
    def wrapper():
        func()
    return wrapper

 

根據上述框架來寫一個計算程式執行時間的功能的裝飾器

import time
def timmer(func):
    def wrapper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print('程式執行時間是%s' % (stop_time - start_time))
    return wrapper
def test():
    time.sleep(3)
    print('test程式執行完畢')
test = timmer(test)
test()

#執行結果
test程式執行完畢
程式執行時間是3.000171661376953

 

#程式分析

#timmer(test) 將test()函式的函式名傳給timmer函式,實際上是傳遞的是test的地址
#執行timmer函式的結果是得到wrapper的地址,即test=wrapper的地址
#test()實際上是在執行wrapper函式

 

注意:@timmer <===> test = timmer(test)

所以,完美的裝飾器誕生啦!!!

import time
def timmer(func):
    def wrapper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print('程式執行時間是%s' % (stop_time - start_time))
    return wrapper
@timmer
def test():
    time.sleep(3)
    print('test程式執行完畢')
test()

 

四、帶返回值的裝飾器

#對上面寫的這個程式進行一個小小的加工,使得該裝飾器有返回值
import time
def timmer(func):
    def wrapper():
        start_time = time.time()
        res = func()
        stop_time = time.time()
        print('程式執行時間是%s' % (stop_time - start_time))
        return res
    return wrapper
@timmer
def test():
    time.sleep(3)
    print('test程式執行完畢')
    return '這是test函式的返回值'
res = test()
print(res)

#執行結果
test程式執行完畢
程式執行時間是3.0001718997955322
這是test函式的返回值

 

#程式理解
#該程式的理解重點還是要清楚 test() 這一步執行的是哪個函式,分析同上,實際執行 wrapper() 函式
#res = func() 實際執行test()函式,並將test函式的返回值賦值給res變數
#wrapper函式執行完,將區域性變數res的值通過return返回給全域性變數res

 

五、帶可變長度引數的裝飾器

 

#回顧:

  • 引數組(非固定長度的引數)
  • *args 針對位置引數傳遞
  • **kwargs 針對關鍵字引數傳遞
  • *表傳遞的資料型別為列表
  • **表傳遞的資料型別為字典
  • 位置引數,必須一一對應,缺一不行,多一也不行
  • 關鍵字引數,無需一一對應,缺一不行,多一也不行
  • 關鍵字引數和位置引數混合使用時,位置引數必須在關鍵字引數左邊
#將上述程式進行修改
import time
def timmer(func):
    def wrapper(*args,**kwargw):
        start_time = time.time()
        func(*args,**kwargw)
        stop_time = time.time()
        print('程式執行時間是%s' % (stop_time - start_time))
    return wrapper
@timmer
def test1(name,age):
    time.sleep(3)
    print('test程式執行完畢,名字是%s,年齡是%s' %(name,age))
@timmer
def test2(name,age,gender):
    time.sleep(3)
    print('test程式執行完畢,名字是%s,年齡是%s,性別是%s' %(name,age,gender))
test1('xhg',age = 18)
test2('xhg', 18, gender = 'male')

#執行結果
test程式執行完畢,名字是xhg,年齡是18
程式執行時間是3.000171422958374
test程式執行完畢,名字是xhg,年齡是18,性別是male
程式執行時間是3.000171422958374
    

 

 

#寫在後面

時間很快,2018年很快結束

研究生的日子,過得那麼單調乏味

感覺還是本科那會逍遙自在

我是一個很少追劇 追星 追綜藝的人

感覺不像這個時代的

但奇葩說是我一直堅持看的

不管別人怎麼評價這個節目  反正我很喜歡

他會讓我思考  讓我從不同的角度去看待這個世界

在辯論死忙時間這一期節目

我覺得特別精彩

我淚點好像有點低  也可能有些話確實觸及到我內心深處

蟲仔生病的那段經歷講述  讓我看到了每一個人的不容易

每一個成年人都有自己的祕密  都生活的那麼不容易 每個人都在扛著  沒有放棄  每個人也都扛著很好

每一個辯手都有自己的故事

每一個普通人也有自己的故事

關鍵是 看你以什麼樣的心態去看待

我特別喜歡黃執中,他說,美好的事物,不是沒有裂痕,而是滿是裂痕,卻沒有崩開

通過別人的故事,來更加清楚豁達看待自己的生活

加油,小夥郭

加油,每一個在努力的人

當你覺得很累的時候,躺下來好好休息一下。因為一切都會好起來的!努力的人,運氣都不會太差