1. 程式人生 > >python中的閉包和裝飾器

python中的閉包和裝飾器

原函數 com 持久 sys 判斷 關於 success 常用 參數傳遞

閉包函數介紹

什麽是閉包

維基百科中關於閉包的概念:

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

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

閉包函數示例:

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

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

Tip:funx 返回的函數不僅僅是函數本身,返回的函數外面還包了一層作用域關系~

所有的閉包函數都有這個屬性:closure(若沒有就不是閉包函數,這是閉包函數的特點),a.closure為元組,每個元素包含了閉包外面的那層作用域中的一個變量的值,a.closure[0].cell_contents 和 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

閉包的應用

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

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

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

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

個人總結了python的閉包大致有如下2個應用:
(1)通過自帶的作用域保留狀態

def foo(sum = 0):
    def add(x):
        nonlocal sum                 # nonlocal 指定這裏使用的 sum 為外部函數中的 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

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

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

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]‘)

開放封閉原則

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

例如現在需要對如下 index 函數進行擴展,計算這個函數的執行時間~

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

# 調用 index 函數
foo()      # index()

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

現改用閉包函數,可為 多個函數添加相同的功能:

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)   # 原本直接調用index(),現在需要添加這麽一行
index()

error = timmer(error)
error()

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

裝飾器

裝飾器語法

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

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()           # 調用的是warpper()
error()

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

多個裝飾器的使用

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

def timmer(func):
    def warpper():
        print(‘timmer_before_codes‘)
        func()                 # 執行時這裏的 func 就是 deco (即 auth(index))
        print(‘timmer_after_codes‘)
    return warpper

def auth(func):
    def deco():
        print(‘auth_before_codes‘)
        func()                # 執行時這裏是原始的index()
        print(‘auth_after_codes‘)
    return deco

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

index()     # 調用 index()

調用index後輸出結果如下:

timmer_before_codes
auth_before_codes
index page
auth_after_codes
timmer_after_codes

原始函數有參數的場景

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

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‘)

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

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()

原始函數有返回值的場景

若原始函數有返回值,在內部函數中 調用原始函數,獲取返回值,並通過內部函數進行返回即可~

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 的常用功能

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

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__)

輸出結果:
warpper function

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

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__()    # 調用原始函數

輸出結果:
index function
welcome to index page

有參裝飾器

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

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函數上方的 @timmer(is_debug = True) 相當於 index = timmer(is_debug=True)(index),開啟debug後輸出結果如下:

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

@timmer(is_debug = False),關閉 debug 後,函數的輸出與裝飾前一致:

welcome to index page

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

python中的閉包和裝飾器