一、閉包函數(shù)
什么是閉包:python是一種面向對象的編程語言,在python中一切皆對象,這樣就使得變量所擁有的屬性,函數(shù)也同樣擁有。這樣我們可以理解,在函數(shù)內創(chuàng)建一個函數(shù)的行為是完全合法的,而這種函數(shù)就叫內嵌函數(shù)。內嵌函數(shù)(可以理解為內部函數(shù))可以在外部函數(shù)作用域內正常調用,在外部函數(shù)作用域外則會報錯。如果內嵌函數(shù)(可以理解為內部函數(shù))引用了外部函數(shù)定義的對象(可以是外層之外,但不是全局變量),那么此時的函數(shù)就叫閉包函數(shù)。
總之,一句話。如果外部函數(shù)的變量,被內部函數(shù)所引用,這種方式就是閉包。
比如:實現(xiàn)一個常規(guī)的累加函數(shù),這是常規(guī)寫法:
def func():
num1 = 1
num2 = 2
return num1 + num2
print(func())
3
使用閉包函數(shù)的寫法:
def func(num1):
def add(num2):
return num1 + num2
return add
a = func(1)
print(a(2))
3
在這里,func()相當于外部函數(shù),add()相當于內部函數(shù),外部函數(shù)(func)的變量num1被內部函數(shù)(add)引用,這種方式就是閉包。另外,需要注意的是外部函數(shù)返回的是內部函數(shù)名。寫法可以固定為:
def func1():
def func2():
...
return func2 #外部函數(shù)返回的是內部函數(shù)名
對比以上兩種寫法,其實看不出閉包有什么優(yōu)勢,反而變得更加繁瑣?,F(xiàn)在再舉個例子,加深對閉包優(yōu)勢的了解。
比如,設計一個計數(shù)器:
def counter():
cnt = [0]
def add_one():
cnt[0] += 1
return cnt[0]
return add_one
num1 = counter()
print(num1())
print(num1())
print(num1())
print(num1())
1
2
3
4
其實用常規(guī)方法設計的函數(shù)也可以實現(xiàn),但換一種思路,在實際生活中,需求是不斷變化的,現(xiàn)在需要一種從10開始的計數(shù)器,難道我們又要重新開發(fā)一套程序嗎?沒有更好的辦法了嗎?現(xiàn)在閉包的優(yōu)勢就來了。
def counter(FIRST=0):
cnt = [FIRST]
def add_one():
cnt[0] += 1
return cnt[0]
return add_one
num1 = counter() #從1開始的計數(shù)器
num10 = counter(10)#從10開始的計數(shù)器
print(num1())
print(num1())
print(num1())
print(num10())
print(num10())
print(num10())
1
2
3
11
12
13
在這里,我們只需要簡單的更改少量的代碼counter(10),就可以實現(xiàn)新的需求。
在實際測試工作中,我們會經(jīng)常測試程序的性能,其中一個主要的指標是看程序的運行時間,在此場景中,閉包函數(shù)就會經(jīng)常被使用,它會大大提高工作的效率。比如:
import time
def cost_time(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print(f'程序運行時間為:{end_time - start_time}')#f字符串格式化在pyhton3.7開始使用
return wraper
@cost_time #裝飾器
def my_func():
time.sleep(2)
my_func()
程序運行時間為:2.0004215240478516
這里引用了裝飾器,一般閉包函數(shù)和裝飾器是配套使用的,這會大大提高python程序的效率。下面介紹裝飾器的用法:
二、裝飾器函數(shù)
什么是裝飾器:python裝飾器(function decorators)就是用來擴展函數(shù)功能的一種函數(shù),其目的就是不改變原函數(shù)名(或類名)的情況下,給函數(shù)增加新的功能。裝飾器就是通過閉包函數(shù)來給原來函數(shù)增加功能。因為調用函數(shù)不美觀,所以引用了語法糖,也就是在需要添加功能的函數(shù)前面加上@即可。
需要注意,@修飾符的用法。
- '@’符號用作函數(shù)修飾符,必須出現(xiàn)在函數(shù)定義的前一行。不允許和函數(shù)定義在同一行。什么意思?還是用上面的例子舉例:
@cost_time #'@’符號用作函數(shù)修飾符,必須出現(xiàn)在函數(shù)定義的前一行,這就是第一行。
def my_func():#這是函數(shù),應該在'@’函數(shù)修飾符的下一行。
time.sleep(2)
- '@’符號用作函數(shù)修飾符,必須模塊或者類定義層內對函數(shù)進行修飾,不允許修飾一個類。
- 一個修飾符就是一個函數(shù),它將被修飾的函數(shù)作為參數(shù),并返回修飾的同名函數(shù)或其他可調用的東西。
@cost_time #裝飾器
def my_func():
time.sleep(2)
這里的@,我們稱之為語法糖,@cost_time就相當于cost_time(my_func),只不過更加的簡潔。
- 帶一個參數(shù)的裝飾器
在之前計算程序運行時間的例子中,my_func()函數(shù)沒有帶參數(shù),如果需要帶參數(shù)呢?
import time
def cost_time(func):
def wrapper(info):
print("this is decorator")
start_time = time.time()
func(info)
end_time = time.time()
print(f'程序運行時間為:{end_time - start_time}')
return wraper
@cost_time
def my_func(info):
print(info)
time.sleep(1)
my_func("hello world")
this is decorator
hello world
程序運行時間為:1.0005288124084473
- 帶多個參數(shù)的裝飾器
在這里, my_func(info)帶了info參數(shù),如果要實現(xiàn)的程序需要多個參數(shù)怎么辦呢?一般情況下,我們會把*args和**kwargs,,作為裝飾器內部函數(shù)wrapper()的參數(shù)。
def decorator(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return wrapper
- 帶自定義參數(shù)的裝飾器
裝飾器可以接受原函數(shù)任意類型和數(shù)量的參數(shù),還可以接受自定義的參數(shù),比如我要想控制程序運行的次數(shù)。
import time
def repeat(num):
def cost_time(func):
def wrapper(*args,**kwargs):
start_time = time.time()
for i in range(num):
print("this is decorator")
func(*args,**kwargs)
end_time = time.time()
print(f'程序運行時間為:{end_time - start_time}')
return wrapper
return cost_time
@repeat(3)
def my_func(info):
print(info)
time.sleep(1)
my_func("hello world")
print(my_func.__name__)
this is decorator
hello world
this is decorator
hello world
this is decorator
hello world
程序運行時間為:3.006178617477417
wrapper#不再是my_func函數(shù)
這里需要注意的是my_func(info)函數(shù)被裝飾后,元信息發(fā)生了改變,print(my_func.name)顯示的結果是wrapper函數(shù),不再是原來的my_func函數(shù)。為了解決這個問題,可以使用內置的裝飾器@functools.wrap,它會幫助保留原函數(shù)的元信息,其本質也就是將原函數(shù)的元信息,拷貝到對應的裝飾器函數(shù)里。
import time
import functools
def cost_time(func):
@functools.wraps(func)#使用內置的裝飾器@functools.wrap,它會幫助保留原函數(shù)的元信息
def wraper(info):
print("this is decorator")
start_time = time.time()
func(info)
end_time = time.time()
print(f'程序運行時間為:{end_time - start_time}')
return wraper
@cost_time
def my_func(info):
print(info)
time.sleep(1)
my_func("hello world")
print(my_func.__name__)
this is decorator
hello world
程序運行時間為:1.0009491443634033
my_func#元信息保留了
- 裝飾器的嵌套
python也支持多個裝飾器,比如:
@decorator1
@decorator2
def hello(info):
print(info)
它等同于
decorator1(decorator2(hello))
執(zhí)行的順序是從左到右執(zhí)行,比如:
import functools
def decorator1(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print("this is decorator1")
func(*args,**kwargs)
return wrapper
def decorator2(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print("this is decorator2")
func(*args,**kwargs)
return wrapper
@decorator1
@decorator2
def hello(info):
print(info)
hello("hello world!")
this is decorator1
this is decorator2
hello world!
先執(zhí)行的是decorator1,然后再執(zhí)行decorator2,最后執(zhí)行hello函數(shù)。