溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

python中的閉包和裝飾器

發(fā)布時間:2020-07-16 18:31:07 來源:網(wǎng)絡(luò) 閱讀:1118 作者:LJ_baby 欄目:編程語言

閉包函數(shù)介紹

什么是閉包

維基百科中關(guān)于閉包的概念:

在一些語言中,在函數(shù)中可以(嵌套)定義另一個函數(shù)時,如果內(nèi)部的函數(shù)引用了外部的函數(shù)的變量,則可能產(chǎn)生閉包。閉包可以用來在一個函數(shù)與一組 “私有” 變量之間創(chuàng)建關(guān)聯(lián)關(guān)系。在給定函數(shù)被多次調(diào)用的過程中,這些私有變量能夠保持其持久性。

對上面這段話總結(jié)一下,即python中的閉包需要滿足3個條件:
1) 內(nèi)嵌函數(shù),即函數(shù)里定義了函數(shù) —— 這對應(yīng)函數(shù)之間的嵌套
2) 內(nèi)嵌函數(shù)必須引用定義在外部函數(shù)里中的變量(不是全局作用域中的引用)—— 內(nèi)部函數(shù)引用外部變量
3) 外部函數(shù)必須返回內(nèi)嵌函數(shù)

閉包函數(shù)示例:

def funx():
    x = 1
    y = 2
    def funy():
        print(x, y)
    return funy       # 返回的函數(shù)就是 閉包函數(shù)

a = funx()          # 這里的 變量 a 就是閉包函數(shù)

Tip:funx 返回的函數(shù)不僅僅是函數(shù)本身,返回的函數(shù)外面還包了一層作用域關(guān)系~

所有的閉包函數(shù)都有這個屬性:__closure__(若沒有就不是閉包函數(shù),這是閉包函數(shù)的特點),a.__closure__為元組,每個元素包含了閉包外面的那層作用域中的一個變量的值,a.__closure__[0].cellcontents 和 a.__closure_\[1].cell_contents 分別引用作用域中 x 和 y 變量

print(type(a.__closure__))       #  <class 'tuple'>
print(a.__closure__) 
# (<cell at 0x000001CA6EAA64F8: int object at 0x000000006F036DE0>, <cell at 0x000001CA6EAA6528: int object at 0x000000006F036E00>)

print(a.__closure__[0].cell_contents)    # 1
print(a.__closure__[1].cell_contents)    # 2

閉包的應(yīng)用

閉包函數(shù)不光光是函數(shù),還帶了一層作用域;在調(diào)用外部函數(shù)時,可以通過參數(shù)傳遞不同的值,使得返回的閉包函數(shù)的作用域中所帶的變量各不相同。上面示例中的 x,y 也可以通過參數(shù)傳入:

def funx(x,y):
    def funy():
        print(x, y)
    return funy       # 返回的函數(shù)就是 閉包函數(shù)

a = funx(1,2)          # 這里的 變量 a 就是閉包函數(shù)

所以可以總結(jié)一下,閉包的特點有2個:
1)自帶作用域
2)延遲計算

個人總結(jié)了python的閉包大致有如下2個應(yīng)用:
(1)通過自帶的作用域保留狀態(tài)

def foo(sum = 0):
    def add(x):
        nonlocal sum                 # nonlocal 指定這里使用的 sum 為外部函數(shù)中的 sum 變量
        sum = sum + x
        print('sum: ' + str(sum))
    return add

g = foo(sum = 10)
g(4)             # sum: 14
g(5)             # sum: 19
g(6)             # sum: 25

如上示例中,每次調(diào)用add函數(shù)(g())都會將所傳遞的參數(shù)與外部函數(shù)中的 sum 變量相加,并打印,參數(shù)的總和會保留在閉包函數(shù)自帶作用域的 sum 變量中。在獲取閉包函數(shù)時,還可以指定sum的初始大小~

(2)根據(jù)自帶作用域的局部變量來得到不同的結(jié)果
使用自帶作用域存儲文件名,每次返回的閉包函數(shù)僅用于 針對一個文件、不同關(guān)鍵字的過濾

import os, sys
def foo(filename):
    def find_line(parse_str):
        # 判斷文件是否存在
        if not os.path.exists(filename):
            print('file not exist~')
            sys.exit(1)
        with open(file=filename, mode='r', encoding='utf-8') as f:
            for line in f:
                if line.find(parse_str) != -1:
                    print(line, end='')
    return find_line

log_1 = foo(filename=r'F:\tmp\abc.txt')    # 專用于對文件 'F:\tmp\abc.txt' 的過濾
log_1('[ERROR]')
log_1('[INFO]')

log_2 = foo(filename=r'F:\tmp\abc.txt')    # 專用于對文件 'F:\tmp\abc.txt' 的過濾
log_2('[ERROR]')
log_2('[INFO]')

開放封閉原則

開放封閉原則:對擴展是開放的,對修改是封閉的,即源代碼不能修改,留出擴展的可能性~
在維護過程中,很多時候需要對原有的功能(例如函數(shù))進行擴展,但是直接修改某個函數(shù)的源代碼存在一定風險,理想的狀況是在不修改源碼的情況下對函數(shù)的原有功能進行擴展~

例如現(xiàn)在需要對如下 index 函數(shù)進行擴展,計算這個函數(shù)的執(zhí)行時間~

import random, time

def index():
    time.sleep(random.randrange(1,5))
    print('welcome to index page')

def foo():
    start_time = time.time()
    index()
    stop_time = time.time()
    print('run time is %s' % (stop_time - start_time))

# 調(diào)用 index 函數(shù)
foo()      # index()

這樣的話,源代碼沒有發(fā)生改變,新功能也添加了,但是調(diào)用方式發(fā)生了改變,原本調(diào)用 index(),現(xiàn)在變成了 foo() ~,且若要為很多個函數(shù)添加相同的功能,只能一個一個的添加

現(xiàn)改用閉包函數(shù),可為 多個函數(shù)添加相同的功能:

import random, time
def timmer(func):
    def warpper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
    return warpper

def index():
    time.sleep(random.randrange(1,5))
    print('welcome to index page')

def error():
    time.sleep(random.randrange(2, 10))
    print('welcome to error page')

index = timmer(index)   # 原本直接調(diào)用index(),現(xiàn)在需要添加這么一行
index()

error = timmer(error)
error()

這樣還是存在缺陷,就是每次執(zhí)行前都需要 生成閉包函數(shù)(index = timmer(index))。那如何解決呢?就是使用裝飾器~

裝飾器

裝飾器語法

裝飾器的語法,在被裝飾對象的正上方 添加 '@裝飾器名字';將正下方的函數(shù)名當做一個參數(shù)傳遞給裝飾器,并將返回值重新賦值給函數(shù)名~
上面的示例通過裝飾器實現(xiàn):

import random, time
def timmer(func):
    def warpper():
        start_time = time.time()
        func()
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
    return warpper

@timmer            # 等效于 index = timmer(index)
def index():
    time.sleep(random.randrange(1,5))
    print('welcome to index page')

@timmer
def error():
    time.sleep(random.randrange(2, 10))
    print('welcome to error page')

index()           # 調(diào)用的是warpper()
error()

這樣就滿足了開放封閉原則~

多個裝飾器的使用

裝飾器可以添加多個,執(zhí)行順序是 從下往上執(zhí)行,如下示例中是 先添加auth,再執(zhí)行timmer ~,即 index 函數(shù)先由 auth 進行封裝,而后在這個基礎(chǔ)之上再由 timmer 進行封裝~

def timmer(func):
    def warpper():
        print('timmer_before_codes')
        func()                 # 執(zhí)行時這里的 func 就是 deco (即 auth(index))
        print('timmer_after_codes')
    return warpper

def auth(func):
    def deco():
        print('auth_before_codes')
        func()                # 執(zhí)行時這里是原始的index()
        print('auth_after_codes')
    return deco

@timmer
@auth
def index():
    print('welcome to index page')

index()     # 調(diào)用 index()

調(diào)用index后輸出結(jié)果如下:

timmer_before_codes
auth_before_codes
index page
auth_after_codes
timmer_after_codes

原始函數(shù)有參數(shù)的場景

很簡單,就是將參數(shù)傳遞到被裝飾的函數(shù)當中~

def auth(func):
    def warpper(user):
        print('before_user_login')
        func(user)
        print('after_user_login')
    return warpper

@auth
def login(user):
    print('%s login success' % user)

login('kitty')

但是這個時候無參函數(shù)無法再使用這個裝飾器進行裝飾~,*agrs, **kwargs,可以使用可變長參數(shù)解決這個問題:

def auth(func):
    def warpper(*agrs, **kwargs):
        print('before_user_login')
        func(*agrs, **kwargs)
        print('after_user_login')
    return warpper

@auth
def login(user):
    print('%s login success' % user)

@auth
def index():
    print('welcome to index page')

login('kitty')
index()

原始函數(shù)有返回值的場景

若原始函數(shù)有返回值,在內(nèi)部函數(shù)中 調(diào)用原始函數(shù),獲取返回值,并通過內(nèi)部函數(shù)進行返回即可~

def timmer(func):
    def warpper(*agrs, **kwargs):
        start_time = time.time()
        res = func(*agrs, **kwargs) 
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
                return res
    return warpper

wraps 的常用功能

原始函數(shù)被裝飾以后,原有的一些屬性會被 裝飾后的函數(shù)所替代,例如文檔字符串~

def timmer(func):
    def warpper(*agrs, **kwargs):
        'warpper function'  # 文檔字符串
        start_time = time.time()
        res = func(*agrs, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res
    return warpper

@timmer
def index():
    'index function'      # 文檔字符串
    print('welcome to index page')

print(index.__doc__)

輸出結(jié)果:
warpper function

原本想獲取index函數(shù)的說明信息,而返回的卻是 warpper 函數(shù)的。這里可以使用 @wraps(func) ,用以保留原函數(shù)自己的一些原始信息;若函數(shù)已被裝飾,又想調(diào)用原始的函數(shù),可以在調(diào)用函數(shù)時使用函數(shù)的 wrapped 屬性 就能夠使用原始的函數(shù),而不是被裝飾后的函數(shù)~

def timmer(func):
    @wraps(func)
    def warpper(*agrs, **kwargs):
        'warpper function'  # 文檔字符串
        start_time = time.time()
        res = func(*agrs, **kwargs)
        stop_time = time.time()
        print('run time is %s' % (stop_time - start_time))
        return res
    return warpper

@timmer
def index():
    'index function'      # 文檔字符串
    print('welcome to index page')

print(index.__doc__)
index.__wrapped__()    # 調(diào)用原始函數(shù)

輸出結(jié)果:
index function
welcome to index page

有參裝飾器

之前用到的都是無參裝飾器,有參裝飾器,簡單的說就是在原有的裝飾器外面再套上一層帶參數(shù)的函數(shù)~
還是這個函數(shù),現(xiàn)在除了添加計時功能外,還需要添加debug功能,debug是否啟用通過參數(shù)來實現(xiàn)~

def index():
    time.sleep(random.randrange(1,5))
    print('welcome to index page')

添加有參裝飾器:

import time, random
import logging

logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

def timmer(is_debug):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if is_debug:
                begin = time.time()
                res = func(*args, **kwargs)
                logging.debug( "[" + func.__name__ + "] --> " + str(time.time() - begin) )
            else:
                res = func(*args, **kwargs)
            return res
        return wrapper
    return decorator

@timmer(is_debug = True)
def index():
    time.sleep(random.randrange(1, 5))
    print('welcome to index page')

index()

index函數(shù)上方的 @timmer(is_debug = True) 相當于 index = timmer(is_debug=True)(index),開啟debug后輸出結(jié)果如下:

welcome to index page
DEBUG:root:[index] --> 3.000962018966675

@timmer(is_debug = False),關(guān)閉 debug 后,函數(shù)的輸出與裝飾前一致:

welcome to index page

.................^_^

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI