1. 程式人生 > >python中閉包函式與裝飾器函式

python中閉包函式與裝飾器函式

閉包

首先知道閉包函式的語法特徵:

  • 函式巢狀定義
  • 外部函式返回內部函式的引用
  • 內部函式可以呼叫外部函式的自由變數

外部函式的作用是建立內部函式並且返回內部函式的引用。

def line(k, b):
    """外部函式的作用: 建立內部函式並且返回內部函式的引用"""
    def line_in(x):
        y = k * x + b
        print(y)
    return line_in

line1 = line(1,1)
line1(10)
line1(20)

line2 = line(2,2)
line2(10)
line1(10)

line1和line2都是inner函式程式碼的引用 為什麼執行結果不一樣呢? 因為兩個閉包為不同的物件且使用自由變數不一樣,所以閉包運算的結果就不一樣

閉包的內部函式可以修改自由變數的值,在內部函式使用nonlocal 關鍵字

def line(k, b):
    """外部函式的作用: 建立內部函式並且返回內部函式的引用"""
    # d = [k]
    def line_in(x):
        # py3中 nonlocal關鍵字用以修改 自由變數<不可變型別>
        nonlocal k
        k = k + 1
        y = k * x + b

        # py2 py3都支援的方式  -  間接使用 並沒有修改真正k的值
        # d[0] += 1
        # y = d[0] * x + b
        print(y)
    return line_in

l1 = line(1,1)

# 列印內部函式的自由變數
print(l1.__closure__)
print(l1.__closure__[0])
print(l1.__closure__[0].cell_contents)
l1(99)

 (<cell at 0x000001EEFC387738: int object at 0x000000006E556DE0>, <cell at 0x000001EEFC387C48: int object at 0x000000006E556DE0>) <cell at 0x000001EEFC387738: int object at 0x000000006E556DE0> 1 199

裝飾器

裝飾器是基於閉包函式,遵循封閉開放的程式碼設計原則,在保證不修改原始碼的基礎上擴充套件程式碼功能。

def check(func):
#def check(*args, **kwargs):
    def wrapper(*args, **kwargs):
        print("新增驗證功能")
        func(*args, **kwargs)
        print("新增結束處理功能")
    return wrapper

@check
def func1(num):
    """不容易發生改變的功能"""
    print("this is fun1")
    print("fun1 num %s" % num)

@check
def func2(num1,num2):
    print("this is func2")
    print("func2 num1 %s num2 %s" % (num1, num2))


# func1 = check(func1)
func1(2)
func2(3, 4)

新增驗證功能 this is fun1 fun1 num 2 新增結束處理功能 新增驗證功能 this is func2 func2 num1 3 num2 4 新增結束處理功能

check是基於閉包的裝飾器函式,func是被裝飾函式的引用。

裝飾器函式只能接收一個引數,即被裝飾函式的引用。

裝飾器可以裝飾有任意引數的函式。

使用裝飾器的方式,@裝飾器名 放在被裝飾函式的上一行。

裝飾器的靈魂程式碼即為: func1 = check(func1)   

@check 就是對靈魂程式碼的封裝,很方便

裝飾器demo 統計氣泡排序耗時

import time
import random

def resume_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        # 接收func函式的返回值
        result = func(*args, **kwargs)
        end_time = time.time()
        cost_time = end_time - start_time
        print("cost_time:%s" % cost_time)
        # 返回func函式的返回值
        return result
    return wrapper


@resume_time
def bubble_sort(list):
    time.sleep(0.6)
    n = len(list)
    for i in range(n-1):
        for j in range(n-1-i):
            j = i + 1
            if list[j] > list[j+1]:
                list[j], list[j+1] = list[j+1], list[j]
    return list


lyst = [i for i in range(1000)]
random.shuffle(lyst)
print(bubble_sort(lyst))

由於裝飾器函式只能接收一個引數,若想要多傳入幾個引數,使裝飾器功能更加豐富如何做?

可以使用裝飾器工廠,傳入一個flag引數,當flag為0時耗時時間取浮點形式,flag為1時取整型。

import time

# 接收引數生成裝飾器
def factory(flag):
    # flag 向下傳遞
    def resume_time(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            cost_time = end_time - start_time
            # 接收上層傳來的flag
            if flag == 0:
                print("cost_time:%s" % cost_time)
            elif flag == 1:
                print("cost_time:%d" % int(cost_time))
            return result
        return wrapper
    return resume_time

# 呼叫factory函式傳入flag為1
@factory(1)
def bubble_sort(list):
    time.sleep(0.6)
    n = len(list)
    for i in range(n-1):
        for j in range(n-1-i):
            j = i + 1
            if list[j] > list[j+1]:
                list[j], list[j+1] = list[j+1], list[j]
    return list


lyst = [i for i in range(1000)]
random.shuffle(lyst)
print(bubble_sort(lyst))
 下面的裝飾過程 1. 呼叫factory(1) 2. 將步驟1得到的返回值,即resume_time返回, 然後resume_time(bubble_sort) 3. 將resume_time(bubble_sort)的結果返回,即wrapper 4. 讓bubble_sort = wrapper,即bubble_sort現在指向wrapper

類裝飾器 

裝飾器函式其實是這樣一個介面約束,它必須接受一個callbale物件作為引數,然後返回一個callable物件。在Pthon中yi一般callable物件都是函式,但還有一種情況就是某個物件重寫了__call__()方法,那麼這個物件就是callable的。

class Test(object):
    def __init__(self, func):
        print("---初始化---")
        print("func name is %s"%func.__name__)
        self.__func = func
    def __call__(self):
        print("---裝飾器中的功能---")
        self.__func()

@Test
def test():
    print("----test---")
test()

---初始化--- func name is test ---裝飾器中的功能--- ----test---

說明: 1. 當用Test來裝作裝飾器對test函式進行裝飾的時候,首先會建立Test的例項物件    並且會把test這個函式名當做引數傳遞到__init__方法中    即在__init__方法中的屬性__func指向了test指向的函式 2. test指向了用Test創建出來的例項物件 3. 當在使用test()進行呼叫時,就相當於讓這個物件(),因此會呼叫這個物件的__call__方法 4. 為了能夠在__call__方法中呼叫原來test指向的函式體,所以在__init__方法中就需要一個例項屬性來儲存這個函式體的引用    所以才有了self.__func = func這句程式碼,從而在呼叫__call__方法中能夠呼叫到test之前的函式體