python閉包和裝飾器

一、python函數作用域LEGB

python解釋器查找變量的原則(順序):
L→E→G→B
L:Local函數內部作用域
E:enclosing函數內部與內嵌函數之間
G:gobal全局作用域
B:build-in內置作用域

example:

value1 = 5
def my_func():
    value2 = 6
    print(id(value2))
    def in_func():
        a = max(value1, value2)
        print(a)
    return in_func

上面示例代碼中max函數為python的內建函數,在in_func函數中,max這個變量python解釋器首先會在in_func這個函數中查找,即local函數內部作用域中查找,然后按順序再到E-G-B查找,最后在build-in內置作用域查找到。

二、閉包

1、概念:內部函數中對enclosing作用域的變量進行引用

上述例子中my_func函數中的內部函數in_func引用了enclosing作用域中的value2變量,這就叫做閉包。
由于函數執行完后變量會被回收,當執行完my_func這個函數后,value2變量會被回收。那么如果我們再次調用in_func這個函數時,需用引用到value2這個變量,是否會報錯?
看以下代碼:

example:

value1 = 5
def my_func():
    value2 = 6
    print(id(value2)) #打印value2的ID值
    def in_func():
        a = max(value1, value2)
        print(a)
    return in_func

f = my_func()    #my_func返回的是in_func,此時f指向in_func函數
f()    #f()相當于in_func(),調用了infunc函數
print(f.__closure__)

ouput:

501351024
6
(<cell at 0x0000000000AF8D98: int object at 0x000000001DE20270>,)

如果內部函數引用了enclosing作用域的變量,會將變量添加到函數__closure__的屬性中去。當再次查找這個變量時,會直接去函數__closure__的屬性中查找。我們可以看到代碼的輸出結果第一行即為value2的內存地址(501351024轉換為16進制:1DE20270)和__closure__屬性中的int對象的地址是一樣的( int object at 0x000000001DE20270)。

2、那么閉包到底有什么用呢?

我們來看下以下兩段代碼:

(1)、

def func_100(value):
    passline = 60
    if value >= passline:
        print('pass')
    else:
        print('failed')

def func_150(value):
    passline = 90
    if value >= passline:
        print('pass')
    else:
        print('failed')
func100(59)
func150(89)

(2)、

def set_passline(passline):
    def in_func(value):
        if value >= passline:
            print('pass')
        else:
            print('failed')
    return in_func
f_100 = set_passline(60) #passline=60被存儲在f_100的__closure__屬性中
f_150 = set_passline(90) #passline=90被存儲在f_150的__closure__屬性中
f_100(59)
f_150(89)

兩段代碼都能正確判斷滿分是100或150時,分數是否及格。但是第二段代碼,由于運用閉包,代碼復用性更高。

3、閉包更高級的應用

將閉包的概念中的變量變成函數,同樣適用。即:內部函數中對enclosing作用域的函數進行引
example:

def my_sum(*args):
    return(sum(args))

def my_average(*args):
    return sum(args)/len(args)

def dec(func):
    def in_dec(*args):
        if len(args) == 0:    #對參數進行判斷,如果沒有參數直接返回0
            return 0
        for i in args:
            if not isinstance(i, int):    #對參數進行判斷,如果有一個參數不是int類型,直接返回0
                return 0
        return func(*args)    #此處對enclosing作用域的func函數進行引用
    return in_dec

evo_my_sum = dec(my_sum)
evo_my_average = dec(my_average)

PS:此處求和函數(my_sum)和求平均值函數(my_average)只對參數是否int類型進行判斷。
我們來分析下代碼:evo_my_sum = dec(my_sum)
由于函數dec返回的是in_dec函數
所以evo_my_sum指向的是in_dec函數
=》evo_my_sum = in_dec
=》evo_my_sum(1, 2, 3) = in_dec(1, 2, 3)
那么in_dec(1, 2, 3)evo_my_sum(1, 2, 3)會對求和的參數先進行判斷后,再調用my_sum函數。
同樣道理:
evo_my_average會對求平均的參數先進行判斷后,再調用my_average函數。
這樣就可以對參數統一進行判斷后再各自調用不同的函數。

三、裝飾器

裝飾器是用來裝飾函數的,它返回一個函數對象。語法:@Decorator
現在我們已經定義了一個my_sum函數

def my_sum(*args):
    return(sum(args))

假設我們要增加my_sum函數的功能,比如,在函數調用前對參數進行一個判斷,但又不希望修改my_sum函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。
我們要定義一個能判斷參數的decorator,如下:

def dec(func):
    def in_dec(*args):
        if len(args) == 0:
            return 0
        for i in args:
            if not isinstance(i, int):
                return 0
        return func(*args)
    return in_dec

按照python裝飾器的語法:

@dec
def my_sum(*args):
    return(sum(args))

這樣,調用my_sum函數,不僅會運行my_sum函數本身,還會在運行my_sum函數前,對參數進行判斷。
把@dec放到my_sum函數的定義處,相當于執行了語句:
my_sum = dec(my_sum)
看到這里是否覺得有點熟悉?
其實這個語句和上述閉包的高級應用是一樣的,只不過那部分把my_sum改成了evo_my_sum
裝飾器到這里還差最后一步:
my_sum函數經過裝飾后,由于dec(my_sum)返回的是in_dec函數,此時my_sum.__name__屬性將從‘my_sum’變成‘in_dec’,為了保證此屬性不變,需在in_dec函數定義前加上語句@functools.wraps(func),保證my_sum.__name__屬性不變。否則,有些依賴函數簽名的代碼執行就會出錯。一個完整的decorator的寫法如下:

import functools

def dec(func):
    @functools.wraps(func)
    def in_dec(*args):
        if len(args) == 0:
            return 0
        for i in args:
            if not isinstance(i, int):
                return 0
        return func(*args)
    return in_dec

decorator可以增強函數的功能,定義起來雖然有點復雜,但使用起來非常靈活和方便。
以上是觀看慕課網《python裝飾器》以及廖雪峰教程python裝飾器的總結。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 閉包: 在函數內部在定義一個函數,并且這個函數用到了外面函數的變量,這個函數和用到的變量,稱為閉包. deftes...
    界面大叔閱讀 229評論 0 0
  • 前幾天學習python裝飾器時,看各種例子,上來就是一個嵌套函數,還返回一個函數對象(返回內嵌函數),學得我是一臉...
    喵小琪閱讀 364評論 0 1
  • 本文為《爬著學Python》系列第四篇文章。從本篇開始,本專欄在順序更新的基礎上,會有不規則的更新。 在Pytho...
    SyPy閱讀 2,519評論 4 11
  • 曾經,在酷暑的夏天,寒冷的冬季,每天早上出門等公交車的時候我就會想要是有輛自行車就好了。這樣我就可以騎車在家和地鐵...
    葉子綠漫天閱讀 218評論 0 0