python閉包函數(shù)與裝飾器函數(shù)

一、閉包函數(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),只不過更加的簡潔。

  1. 帶一個參數(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
  1. 帶多個參數(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
  1. 帶自定義參數(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#元信息保留了
  1. 裝飾器的嵌套

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ù)。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容