閉包與裝飾器

閉包

什麼是閉包

閉是封閉(函數中的函數),包是包含(該內部函數對外部函數作用域而非全局作用域變量的引用。)

閉包:

  • 內部函數外部函數作用域裏的變量的引用
  • 函數內的屬性,都是有生命週期,都是在函數執行期間
  • 閉包內的閉包函數私有化了變量,完成了數據的封裝,類似面向對象

demo:

def foo():
    print("in foo()")
    def bar():
        print("in bar()")
# 1.直接運行內部函數報錯
# bar()

# 2.考慮先運行內部函數,在運行外部函數,依然會報錯
# foo()
# bar()

由於作用域的問題,函數內的屬性,都是有生命週期的,只有在函數執行期間

再考慮在這段代碼,只有調用foo()時,內部的print()及bar()才能存活。

現在爲了讓foo()內的bar()存活,就是調用bar(),我們該怎麼做?

[外鏈圖片轉存失敗(img-yOx9xCiJ-1564800430268)(file:///C:\Users\11980\AppData\Roaming\Tencent\Users\1198081282\QQ\WinTemp\RichOle`BOUPYX(C}M5AOKWH}0S~)]G.png)

把bar()函數返回給函數

def foo():
    print("in foo()")
    def bar():
        print("in bar()")
    return bar
var = foo()
var()
# in foo()
# in bar()

前面說,內部函數對外部函數作用域變量的引用, —> 如果是變量呢?

def foo():
    a = 66
    print("in foo()")
    def bar(num):
        print("in bar()")
        print(a + num)
    return bar

var = foo()
var(22)
# in foo()
# in bar()
# 88
'''閉包實現棋子移動'''
def outer():
    # 在外部函數中定義一個保存座標的列表
    position = [0, 0]
    # 定義一個內部函數,參數爲移動方式和步長
    # 移動方式爲列表[x, y] x,y分別只能去 -1,0,1三個值,表示反向,不動,正向
    def inner(direction, step):
        # 計算座標值
        position[0] = position[0] + direction[0] * step
        position[1] = position[1] + direction[1] * step
        # 返回移動後的座標
        return position
    # 返回內部函數
    return inner
# 獲取內部函數
move = outer()

# 移動
print(move([1, 0], 10))
print(move([0, 1], 10))
print(move([-1, 0], 10))

裝飾器

首先,看一個demo:

@func1
def func():
    print('aaa')

裝飾器存在的意義

  • 不影響原有函數的功能
  • 可以添加新功能

一般常見的,比如拿到第三方的API接口,第三方不允許修改這個接口。這個時候,裝飾器就派上用場了。

裝飾器本身也是一個函數,作用是爲現有存在的函數,在不改變函數的基礎上,增加一些功能進行裝飾。

它是以閉包的形式去實現的。

在使用裝飾器函數時,在被裝飾的函數的前一行,使用@裝飾器函數名形式來進行裝飾。

Demo:

現在在一個項目中,有很多函數,由於我們的項目越來越大,功能也越來越多,導致程序越來越慢。

其中一個功能函數的功能,是實現一百萬次的累加。

def my_count():
    s = 0
    for i in range(1000001):
        s += i
    print("sum:", s)

假如我們要計算函數的運行時間

import time
start = time.time()
my_count()
end = time.time()
print("執行時間爲:", (end-start))
# sum: 500000500000
# 執行時間爲: 0.05837249755859375

成果的實現時間的計算。But,假如有成千上萬個函數。每個函數寫一遍,非常麻煩,代碼量也憑空增加很多。

考慮將上述代碼中的時間計算封裝爲函數,如下

import time
def my_count():
    s = 0
    for i in range(1000001):
        s += i
    print("sum:", s)

def count_time(func):
    start = time.time()
    func()
    end = time.time()
    print("執行時間爲:", (end - start))

count_time(my_count)
# sum: 500000500000
# 執行時間爲: 0.057378530502319336

經過修改後,定義一個函數來實現時間計算功能。

初看上去,比之前好很多。

只是說在使用的時候,需要將對應的函數傳入到時間計算函數中去。

但任然影響了原來的使用。

接下來思考,能不能在使用時不影響函數原來的使用方式,同時實現時間的計算?

考慮閉包

import time
def count_time(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print("執行時間爲:", (end - start))
    return wrapper()

def my_count():
    s = 0
    for i in range(1000001):
        s += i
    print("sum:", s)
count_time(my_count)  # 仍然改變了調用方式
# sum: 500000500000
# 執行時間爲: 0.057892560958862305

在使用時,讓my_count函數重新指向count_time函數返回的函數引用。

import time
def count_time(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print("執行時間爲:", (end - start))
    return wrapper
@count_time
def my_count():
    s = 0
    for i in range(1000001):
        s += i
    print("sum:", s)
my_count()
# sum: 500000500000
# 執行時間爲: 0.07386374473571777

這樣實現的好處,定義閉包函數後,只需要通過@裝飾器函數名形式的裝飾器語法,就可以將@裝飾器函數名加到要裝飾的函數前即可。

這種不改變原有函數功能,對函數進行拓展的形式,就稱爲裝飾器

再執行@裝飾器函數名時,就是將原函數傳遞到閉包中。然後,原函數的引用指向閉包返回的裝飾過的內部函數的引用。

裝飾器的幾種形式

1.無參數無返回值

def setfunc(func):
    def wrapper():
        print("start")
        func()
        print("end")
    return wrapper
@setfunc
def show():
    print("show")
show()
# start
# show
# end

2.無參有返回值

def setfunc(func):
    def wrapper():
        print("start")
        func()
        print("end")  
    return wrapper
@setfunc
def show():
    return "show"
print(show())  # show指向wrapper,show雖然有返回值,但wrapper並沒有返回值,所以輸出None
# start
# end
# None

3.有參無返回值

def setfunc(func):
    def wrapper(s):
        print("start")
        func(s)
        print("end")
    return wrapper
@setfunc
def show(s):
    print("hello %s" % s)
show("zucc")
# start
# hello zucc
# end

4.有參有返回值

def setfunc(func):
    def wrapper(x, y):
        print("start")
        return func(x, y)
        print("end")  # 遇到return,該語句不執行
    return wrapper
@setfunc
def myAdd(x, y):
    return x + y
print(myAdd(2, 3))
# start
# 5

萬能裝飾器

根據被裝飾函數的定義不同,分出了四種形式。

能不能實現一種,適用於任何形式函數定義的裝飾器?

通過可變參數(*args/**kwargs)來接收不同的參數類型。

def setFunc(func):
    def wrapper(*args, **kwargs):
        print("Wrapper context.")
        return func(*args, **kwargs)
    return wrapper
@setFunc
def func(name, age, job='IT'):
    print(name, age, job)
func("liyue", 21, "student")
# Wrapper context.
# liyue 21 student
@setFunc
def demo(a, b, *c, **d):
    print((a, b))
    print(c)
    print(d)
demo('city', 'college', 1999, 1, 1, school= 'zucc')
# Wrapper context.
# ('city', 'college')
# (1999, 1, 1)
# {'school': 'zucc'}

函數被多個裝飾器所裝飾

一個函數在使用時,通過一個裝飾器來拓展,可能並不能達到預期。

一個函數被多個裝飾器所裝飾。

def setFunc1(func):
    def wrapper1(*args, **kwargs):
        print("Wrapper Context 1 Start".center(40, '*'))
        func(*args, **kwargs)
        print("Wrapper Context 1 End".center(40, '*'))
    return wrapper1
def setFunc2(func):
    def wrapper2(*args, **kwargs):
        print("Wrapper Context 2 Start".center(40, '*'))
        func(*args, **kwargs)
        print("Wrapper Context 2 End".center(40, '*'))
    return wrapper2
@setFunc1
@setFunc2
def show(*args, **kwargs):
    print("show run")
show()
# ********Wrapper Context 1 Start*********
# ********Wrapper Context 2 Start*********
# show run
# *********Wrapper Context 2 End**********
# *********Wrapper Context 1 End**********

類裝飾器

首先,__call__方法。

引子

def func():
    print("Hello")

func()
print(func())
# 調用 ---> call

對應的,類實例對象的調用,需要使用到__call__特殊方法

class Student:
    def __init__(self, name):
        self.name = name

    def __call__(self, classmate):
        print("我的名字是%s,我的同桌名字是%s" % (self.name, classmate))


stu = Student("樑子恢")
stu("楊琪")

用類實現裝飾器

通過__init____call__方法實現

import time

class Test:

    def __init__(self, func):
        print("裝飾器")
        self.__func = func

    def __call__(self, *args, **kwargs):
        print("Start")
        start_time = time.time()
        self.__func(*args, **kwargs)
        time.sleep(1)
        end_time = time.time()
        print(end_time - start_time)


@Test
def show():
    print("Hello")


print("flag")
show()

# 裝飾器
# flag
# Start
# Hello
# 1.0004355907440186

總結

1.函數可以像普通變量一樣作爲函數的參數或者返回值進行傳遞。

2.函數的內部可以去定義另外一個函數。目的,隱藏函數功能的實現。

3.閉包實際上也是函數定義的一種形式。

4.閉包定義的規則,在外部函數內定義一個內部函數,內部函數使用外部函數的變量,並返回內部函數的引用。

5.Python中,裝飾器就是用閉包來實現的。

6.裝飾器的作用,不改變現有函數的基礎上,爲函數增加功能。

7.裝飾器的使用,通過@裝飾器函數名的形式,來給已有函數進行裝飾,添加功能。

8.裝飾器四種形式,根據參數的不同以及返回值的不同。

9.萬能裝飾器,通過可變參數(*args/**kwargs)來實現。

10.一個裝飾器可以爲多個函數提供裝飾功能。

11.一個函數也可以被多個裝飾器所裝飾。

12.通過類實現裝飾器,重寫__init____call__函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章