python閉包和裝飾器

本文目錄:html

1. 閉包的解析和用法python

2. 函數式裝飾器編程

3. 類裝飾器閉包

 

1、閉包app

閉包是一種函數,從形式上來講是函數內部定義(嵌套)函數,實現函數的擴展。在開發過程當中,考慮到兼容性和耦合度問題,若是想在原有的函數基礎上添加東西而又不改動原有函數的結構,一般會使用閉包。但閉包的功能還不僅是這個。實際上,閉包會保留定義函數時存在的自由變量的綁定,這樣在調用函數時,雖然定義做用域不可用了,可是仍然可使用那些綁定的變量。簡單來講,普通函數在調用完後,函數內部的變量就釋放了(由於直接調用的函數沒有綁定在某一個對象上,Cpython解析器就會把它回收了),而閉包內部的變量仍然保存着。函數

 

普通函數不會保存變量的值:spa

例如:
def function(value):
    nums = []
    nums.append(value)
    return nums

func = function(2)
func2 = function(3)
print(func)     # [2]
print(func2)    # [3]
兩次調用函數返回的值都是不相同的

 

在閉包中,外部函數的變量一直會爲內部函數「保留」着,每次調用函數均可以獲取這些變量code

def closure():
    nums =[]
    def function(value):
        nums.append(value)
        return nums
    return function

close = closure()
close(5)
close(6)
close(7)
# 元組形式返回嵌套函數function的變量
print(close.__code__.co_varnames)       #('value',)
print(close.__code__.co_freevars)        #('nums',)
# 列表形式保存嵌套函數中自由變量的值
print(close.__closure__[0].cell_contents)      #[5, 6, 7]

閉包的執行順序能夠理解爲:htm

closure(function(value))對象

所以close = closure()至關於爲閉包建立了一個綁定的對象,這個對象內部有變量nums和函數function。當變量在下一次調用的時候,這些量還會保存着。所以當屢次調用close()的時候,nums列表會更新。

 

以上代碼的解析:

對象審查(反射)

_ _code_ _返回對象中的函數

_ _co_varnames 返回內部函數中保存的變量,例如function中的value

_ _co_freenames 返回內部函數中的自由變量,自由變量是編程中的一個專業名詞。

如上面的代碼中,nums並非在function函數中綁定的,它是在它的外部函數的做用域範圍內綁定的,因此在function內部,nums是一個自由變量。而close對象爲function函數保留了這個自由變量,在每次調用函數時,均可以更新這個自由變量。

自由變量(全部的)實際保存在閉包中,能夠經過_ _closure_ _來獲取,它是一個列表,每一個元素都表示一個自由變量,如上面的nums。每一個元素都是一個cell,它的屬性cell_contents保存着這個自由變量的值,所以有:

_ _closure_ _[0].cell_contents

 

更多關於函數/類/生成器的審查能夠參考官方文檔:

https://docs.python.org/2/library/inspect.html

 

 

2、函數形式的裝飾器

上面講了如何經過嵌套函數實現一個閉包,下面將裝飾器是如何實現的。實際上,裝飾器離不開對閉包的理解,函數形式的裝飾器看起來像是閉包換了一種表達形式,調用起來更靈活和更方便。

例如:

# 函數形式的閉包
registry = []

def decorator(func):
    print('registe %s'%func)
    registry.append(func)
    return func              # 返回的量必須是一個函數,不然會報錯

@decorator
def fun1():
    print('running fun1')

@decorator
def fun2():
    print('running fun2')

@decorator
def fun3():
    print('running fun3')

fun1()
fun2()
fun3()

 

結果:

registe <function fun1 at 0x000002234D891378>
registe <function fun2 at 0x000002234D891400>
registe <function fun3 at 0x000002234D891488>
running fun1
running fun2
running fun3

裝飾器看起來有點像閉包,只不過是加了一個@的外殼,而這個外殼函數的參數必須是一個函數,而且必需要有返回函數(返回的通常是內部函數)

裝飾器的執行順序:

decorator(func)

 

內部函數的參數能夠在函數調用時傳入,而沒必要像閉包那樣必須由對象傳入。

值得注意的是:裝飾器函數有導入時運行和運行時運行的區別,裝飾器在模塊導入的時候就執行了,而被裝飾的函數則在調用的時候才執行。

 

上面這個被裝飾器「裝飾」的函數彷佛看起來跟裝飾器「互動」不多,那麼下面結合裝飾器和閉包實現一個更復雜的裝飾器:

def decorator(func):
    def outerFunc(*args):      # 裝飾器內部實現閉包,閉包的外部函數接受任意定位變量
        outerFunName = outerFunc.__name__
        innerFunName = func.__name__
        print('change innerFunc:%s to outerFunc:%s'%(innerFunName, outerFunName))
        result = func(*args)            # 能夠實現,由於閉包中保存了自由變量func
        result += " and start running"
        return result
    return outerFunc          # 將改變返回的函數,返回外部函數

@decorator
def fun(str):
    return str

str = fun('This is funciton1')   # change innerFunc:fun to outerFunc:outerFunc
print(str)                      # This is funciton1 and start running

裝飾器執行順序:

decorator(outerFunc(func(args)))

 

這個裝飾器內部的閉包實現仍是比較簡單的,只是爲了說明原理,在編程過程當中能夠根據實際添加更多的功能實現。

 

繼續改造,讓裝飾器也帶上參數

# 帶參數裝飾器
def decorator(name):
    def _decorator(func):
        def outerFunc(*args):
            print(name)
            outerFunName = outerFunc.__name__
            innerFunName = func.__name__
            print('change innerFunc:%s to outerFunc:%s'%(innerFunName, outerFunName))
            result = func(*args)
            result += " and start running"
            return result
        return outerFunc
    return _decorator

@decorator(name='@Author:Tom')
def fun(str):
    return str

str = fun('This is funciton1')
print(str)

結果:
@Author:Tom
change innerFunc:fun to outerFunc:outerFunc
This is funciton1 and start running

 

 

3、類形式的裝飾器

講完了函數形式的裝飾器,那麼接下來說講類形式的裝飾器

通常類的定義以下:

class Test(object):
    def __init__(self,name):
        self._m = 0
        self._n = 0
        self.name = name

    def test_print(self):  
        print(self.name)

 

而若是想要將一個類變成一個裝飾器,那麼就須要一個很關鍵的魔法方法_ _call_ _(),它的做用是將一個類實例變成可調用的,改造一下上面的類:

class Test(object):
    def __init__(self):
        self.count = 0

    def __call__(self):
        # print(self.count)      
        self.count += 1         # 每一次調用這個類實例都記錄一次
        return self.count

# __call__函數將類實例變成可調用形式,而實際上還會有一個返回量(變量/函數),所以須要寫return,不然返回爲None
test = Test()
# 以函數調用的形式直接調用類實例
print(test())    # 1
print(test())    # 2

這樣看起來,類形式的裝飾器有點像函數形式的裝飾器,它也保存了一些變量。實際上這點不足爲奇,由於,原本類實例的變量已經綁定在類實例對象中。

 

還能夠這樣用:

class Average:
    def __init__(self):
        self.values = []

# 每次調用average實例都會更新self.values
    def __call__(self, newvalue):
        self.values.append(newvalue)
        total = sum(self.values)
        average = total / len(self.values)
        return average

average = Average()
print(average(6))
print(average(7))
print(average(8))

結果:
6.0
6.5
7.0

 

類裝飾器:

class Decorator:
    def __init__(self, add=1):  # 定義能夠傳入的參數
        self.count = 0
        self.add = add

    def __call__(self, fun):
        self.fun = fun
        return self._call_func

    def _call_func(self):
        self.count += self.add
        return self.fun(self.count)


# 至關於Decorate(count)
@Decorator(add=2)   # 改變傳入的參數值
def count(cnt):
    print(cnt)

count()   # 2
count()   # 4

 

筆者認爲類形式的裝飾器會比函數形式的裝飾器更加靈活和方便,由於它的內部實現能夠更靈活,看起來也比較符合平常使用的習慣,由於函數式的裝飾器看起來總有一點怪怪的(筆者本人見解而已)。實際使用中就要根據業務需求來選擇了

 

參考文章:

1. 《流暢的python》

2.  https://docs.python.org/2/library/inspect.html

相關文章
相關標籤/搜索