1. 程式人生 > 其它 >python-閉包和裝飾器

python-閉包和裝飾器

閉包和裝飾器

目錄

閉包

形式和作用

閉包是指函式中定義了一個內函式,內函式裡運用了外函式的臨時變數,並且外函式的返回值是內函式的引用。

閉包的使用,可以隱藏內部函式的工作細節,只給外部使用者提供一個可以執行的內部函式的引用。

1.外函式的內部定義了一個內函式。

2.內函式使用了外函式的臨時變數。

3.外函式的返回值是內函式的引用。

# 外部函式
def func_out(num1):
	
    # 內部函式
    def func_in(num2):
        
        # 內部函式使用外部的函式變數
        num=num1+num2
        print(num)
        return  num
    # 返回內部函式的地址
    return  func_in

func=func_out(10)

res1=func(20)
res2=func(30)

# 30
# 40

判斷閉包,__closure__內建屬性

def func():
    name = 'python'
    def inner():
        print(name)
    print(inner.__closure__) 
    # (<cell at 0x0000027C14EB85E8: str object at 0x0000027C14F54960>,)
    return inner

f = func()
f()

# (<cell at 0x000002A2455DC820: str object at 0x000002A25DFD03F0>,)
# python
 

閉包內修改外部變數

閉包的作用——儲存函式的狀態資訊

def Maker(step):  # 包裝器
    num = 1

    def fun1():  # 內部函式
        nonlocal num
        # 而是外部巢狀函式內的變數。

        num = num + step  # 改變外部變數的值
        print(num)

    return fun1


# =====================================#
j = 1
func2 = Maker(3)  # 呼叫外部包裝器
while (j < 5):
    func2() 
    # 呼叫內部函式4次 輸出的結果是 4、7、10、13
    j += 1


通常來講,閉包的內部變數對於外界來講是完全隱藏的。但是,你可以通過編寫訪問函式並將其作為函式屬性繫結到閉包上來實現這個目的。

# 來至cookbook-p227

def sample():
    n = 0

    # Closure function
    def func():
        print('n=', n)
        # Accessor methods for n

    def get_n():
        return n

    def set_n(value):
        nonlocal n
        n = value
        # Attach as function attributes

    func.get_n = get_n
    func.set_n = set_n

    return func


f = sample()
f()

f.set_n(10)
print(f.get_n())
f()

裝飾器

裝飾器本身就是一個函式

作用和功能

  • 引入日誌
  • 統計計算函式執行時間
  • 執行函式前的預處理工作
  • 執行函式後清理工作
  • 許可權校驗設定
  • 快取

基礎用法-時間計時器

# 這是裝飾函式
from functools import wraps

def timer(func):
    @wraps
    def wrapper(*args, **kw):
        t1=time.time()
        func(*args, **kw)
        t2=time.time()

        # 計算函式執行時長
        cost_time = t2-t1 
        print("花費時間:{}秒".format(cost_time))
    return wrapper

@timeer

def add(a,b)
	return a,b

add(1,2)
# 花費時間:0秒

解除裝飾器

functools.wraps

使用裝飾器極大地複用了程式碼,但是他有一個缺點就是原函式的元資訊不見了,比如函式的docstring、name、引數列表

通過@wraps__wrapped__ 可以解除裝飾器

注意:多個裝飾器接觸時,使用 @wraps__wrapped__ 可能會報錯。

from functools import wraps


def decorator(func):
    @wraps(func)  # 儲存函式的元資訊
    def wrapper(*args, **kwargs):
        print('Decorator 1')
        return func(*args, **kwargs)

    return wrapper

@decorator
def add(x, y):
    return x + y

res=add(3,4)
print(res)
#Decorator 1
#7
res = add.__wrapped__(2,4)  
print(res)
#6py

不帶引數裝飾器

# from cookbook-p302
import time
from functools import wraps

def timethis(func):
    @wraps(func)   # 儲存函式的元資訊
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end - start)
        return result

    return wrapper


@timethis
def countdown(n):
    sumn = 0
    while n > 0:
        n -= 1
        sumn += n
    return sumn

sumn=countdown(100000)
print(sumn)
# countdown 0.009002208709716797
# 4999950000

帶可選引數的裝飾器

寫法1、三層結構

def lggerd(msg=None):
    def dector(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            func(*args, **kwargs)
            print("呼叫函式", func.__name__)
            if msg:
                print(msg)

        return wrapper

    return dector


@lggerd(msg='info')
def add0(a,b):
    print(a+b)
add0(1,2)
# 呼叫函式 add0
# 3

@lggerd()
def add0(a,b):
    print(a+b)
add0(1,2)

# 呼叫函式 add0
# 3

這種結構的寫法在使用裝飾器時必須帶括號 lggerd()

寫法2 partial修飾結構

def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
    if func is None:
        return partial(logged, level=level, name=name, message=message)
    logname = name if name else func.__module__
    log = logging.getLogger(logname)
    logmsg = message if message else func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        print("呼叫 wrapper")
        log.log(level, logmsg)
        return func(*args, **kwargs)

    return wrapper


# Example use
@logged
def add(x, y):
    return x + y


# print(add(1, 2))


@logged(level=logging.CRITICAL, name='example')
def spamfun():
    print('Spam!')
    return 1

初始呼叫 logged() 函式時,被包裝函式並沒有傳遞進來。因此在裝飾器內,它必須是可選的。這個反過來會迫使其他引數必須使用關鍵字來指定。並且,但這些引數被傳遞進來後,裝飾器要返回一個接受一個函式引數幷包裝它的函式。為了這樣做,我們使用了一個技巧,就是利用 functools.partial 。它會返回一個未完全初始化的自身,除了被包裝函式外其他引數都已經確定下來了。

設定類方法為裝飾器

from functools import wraps
class A:
    # Decorator as an instance method
    def decorator1(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('Decorator 1')
            return func(*args, **kwargs)
        return wrapper
# Decorator as a class method
    @classmethod
    def decorator2(cls, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('Decorator 2')
            return func(*args, **kwargs)
        return wrapper
    
    

a = A()
@a.decorator1
def spam():
    pass
# As a class method
@A.decorator2
def grok():
    pass

類裝飾器

基於類裝飾器的實現,必須實現 __call____init__兩個內建函式。

  • __init__ :接收被裝飾函式 __call__ :實現裝飾邏輯

不帶引數的類裝飾器

class Logger(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("[INFO]: the function {func}() is running..."\
            .format(func=self.func.__name__))
        return self.func(*args, **kwargs)

@Logger
def say(something):
    print("say {}!".format(something))

say("hello")

# [INFO]: the function say() is running...
# say hello!

帶引數的類裝飾器

帶引數和不帶引數的類裝飾器有很大的不同。

__init__ :不再接收被裝飾函式,而是接收傳入引數。

__call__ :接收被裝飾函式,實現裝飾邏輯。

class logger(object):
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func): # 接受函式
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running..."\
                .format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
        return wrapper  #返回函式

@logger(level='WARNING')
def say(something):
    print("say {}!".format(something))

say("hello")

使用偏函式與類實現裝飾器

實現了 __call__ 的類,delay 返回一個偏函式,在這裡 delay 就可以做為一個裝飾器

import time
import functools

class DelayFunc:
    def __init__(self,  duration, func):
        self.duration = duration
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f'Wait for {self.duration} seconds...')
        time.sleep(self.duration)
        return self.func(*args, **kwargs)

    def eager_call(self, *args, **kwargs):
        print('Call without delay')
        return self.func(*args, **kwargs)

def delay(duration):
    """
    裝飾器:推遲某個函式的執行。
    同時提供 .eager_call 方法立即執行
    """
    # 此處為了避免定義額外函式,
    # 直接使用 functools.partial 幫助構造 DelayFunc 例項
    return functools.partial(DelayFunc, duration)


add(3,5)  # 直接呼叫例項,進入 __call__
# Wait for 2 seconds...
# 8

參考文獻:

https://www.cnblogs.com/wj-1314/p/8538716.html