1. 程式人生 > 實用技巧 >閉包,開放封閉原則和裝飾器

閉包,開放封閉原則和裝飾器

閉包定義:

  • 閉包是巢狀在函式中的函式
  • 內層函式對外層函式非全域性變數的使用(引用),就是閉包
def func1():
    li = []
    def func2(x):
        li.append(x)
        return li
    ruturn func2
f = func1()
print(f(100))   # [100]
print(f(100))   # [100, 100]
print(f(100))   # [100, 100, 100]
#li就是自由變數,並沒有消失,但無法從函式外訪問。

閉包的作用:

  • 被引用的非全域性變數也稱作自由變數,這個自由變數會與內層函式產生一個繫結關係。
    自由變數不會在記憶體中消失。
  • 保證資料安全。函式外無法訪問自由變數

如何判斷一個巢狀函式是不是閉包:

print(func.__code__.co_freevars)
# 只要返回值有自由變數那麼就是閉包

開放封閉原則:

  • 開放:對程式碼的拓展開放,例如增加新功能
  • 封閉:對原始碼的修改封閉

裝飾器:

  • 完全遵循開放封閉原則
  • 定義:
    • 在不改變原函式的程式碼以及其呼叫方式的前提下,為其增加新功能
    • 裝飾器就是一個函式

例如:給函式新增一個功能用來測試函式的執行效率,計算效率的方法就是用函式執行完畢後的時間減去函式執行前的時間

import time
print(time.time()) # 顯示此時此刻距離1970年1月1日0點0分0秒的秒數,也叫時間戳。
# 第一個版本
import time
def func1():
    time.sleep(1)  # 讓程式停止1秒,模擬程式的執行耗時
    print('Hello,word!')

start_time = time.time()
func1()
end_time = time.time()
print(end_time - start_time)

# 實現了測試效率的功能,但程式碼寫死,只能測試func1
# 第二個版本
import time
def func1():
    time.sleep(1)
    print('Hello,word!')

def timer(f):
    start_time = time.time()
    f()
    end_time = time.time()
    print(end_time - start_time)

timer(func1)
# 實現了功能並且可以測試其他函式,但改變了函式func的呼叫方式
# 第三個版本
import time
def func1():
    time.sleep(1)
    print('Hello,word!')

def timer(f):
    def inner():
        start_time = time.time()
        f()			# 裝飾器的本質就是閉包,此時引數f是個自由變數
        end_time = time.time()
        print(end_time - start_time)
    return inner

func1 = timer(func1)	# 傳進函式內的func1並不會改變
func1()		# 此時的func1()相當於inner()

# 在未改變原始碼和呼叫方式的基礎上,增加了功能,但多了一個賦值語句func1 = timer(func1)
# 若是呼叫其他函式則需要重複寫程式碼,所以Python做了個優化:提出來語法糖的概念
# 第四個版本
import time

def timer(f):		# timer 就是個裝飾器
    def inner():
        start_time = time.time()
        f()
        end_time = time.time()
        print(end_time - start_time)
    return inner

@timer	# 相當於 func1 = timer(func1) 寫在函式之前
def func1():
    time.sleep(1)
    print('Hello,word!')
    return 'in func1'

print(func1())
# 此時func1實際上為inner,原func1的返回值是返回給原func1,並非返回給inner,所以值為None

# 	加語法糖才會呼叫timer
def func2():
    time.sleep(1)
    print("I'm fine")
    return 'in func1'
func2()

# 此時迭代器基本實現,但會影響函式返回值,並且影響傳參,還需要繼續修改

# 版本六
import time

def timer(f):
    def inner():
        start_time = time.time()
        r = f()   # func1的呼叫實際在這一行,所以將func()的返回值賦值給r,將r返回給inner
        end_time = time.time()
        print(end_time - start_time)
        return r
    return inner

@timer
def func1():
    time.sleep(0.5)
    print('hello')
    return 'in func1'

print(func1())

# 此時func1實際上為inner,inner的返回值為r,也就是原func1的返回值
# 沒有改變func1的呼叫方式,也沒有改變func1的返回值,符合裝飾器定義,接下來需要解決向func1傳參

# 版本七
import time

def timer(f):
    def inner(*args,**kwargs): # 被裝飾函式可能會接收各種引數,利用*的魔性用法可以接收所有引數
        start_time = time.time()
        r = f(*args,**args)   # *放在函式呼叫時表示打散
        end_time = time.time()
        print(end_time - start_time)
        return r
    return inner

@timer
def func1(n,a='18'):
    time.sleep(0.5)
    print(f'hello,{n},今年{a})
    return 'in func1'

print(func1('李白',n='33'))
          
# 此時裝飾器timer已全部完成,當需要測試某函式的執行效率時,則在被裝飾函式定義前加上語法糖語句即可
# 最後標準格式的裝飾器

def wrapper(f):
    def inner(*args,**args):
        '''被裝飾函式執行前新增的功能'''
        ret = f(*args,**args)
        '''被裝飾函式執行後新增的功能'''
        return ret
    return inner