程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

[python]十八、閉包和裝飾器

編輯:Python

目錄

1、閉包

1.1、什麼是閉包?

1.2、形成閉包的條件

1.3、閉包的使用

1.4、閉包的結論和好處

2、裝飾器

2.1、什麼是裝飾器?

2.2、裝飾器有什麼用?

2.2.1、編寫一個裝飾器實現權限控制

2.2.2、多個裝飾器的使用

2.3、元數據

2.3.1、裝飾器後為什麼元數據會丟失以及怎麼保留元數據

3、裝飾器帶參數

3.1、裝飾器帶參數的使用方法

3.1.1、用裝飾器帶參數的方法重新編寫上文中權限控制代碼

4、用類實現裝飾器和裝飾器帶參數

4.1、用類實現裝飾器

4.2、用類實現裝飾器帶參數

5、裝飾類


1、閉包

1.1、什麼是閉包?

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

1.2、形成閉包的條件

1、必須要有一個內嵌函數
2、內函數必須引用外函數的變量
3、外函數必須返回內函數(注意這裡是返回內函數,而不是內函數的調用)

# 生命周期
def outer():
a = 10
print(f"a is {a}")
outer()
print(a) # 這行代碼運行會出現錯誤
# 這個代碼運行會出現錯誤,是因為外部並沒有定義a這個變量。內部使用完a這個變量,就已經把這個變量釋放掉了。
def outer(x):
a = 300 # 變量名的解析原則LEGB
def inner():
# x = 90
print(f"兩數之和為:{x + a}") # 運行結果為310
print(__name__) #運行結果為__main__
return inner
d = outer(10)
d()
print(dir(d))
print(d.__closure__) # 輸出的結果是一個元組,裡面存儲著變量的內存地址,
# 形成閉包之後,閉包函數會得到一個非空的__closure__屬性

1.3、閉包的使用

def outer():
tmp_list = []
def inner(name):
tmp_list.append(1)
print(f"{name} -- {tmp_list}")
return inner
d1 = outer()
d2 = outer()
d1("d1")
d2("d2")
print(id(d1),id(d2))
# 雖然代碼一樣,但是每次調用外函數都會重新執行,都會創建一個新的tmp_list和inner
print(id(outer()),id(outer())) # 這個是匿名變量
print(id(d1),id(d2))
# python變量分為匿名變量和命名變量
# 匿名變量一般都放到統一的內存空間
# 運行結果
d1 -- [1]
d2 -- [1]
2383138097616 2383138098048
2383426954432 2383426954432
2383138097616 2383138098048

1.4、閉包的結論和好處

結論

  • 元組裡面的對象為cell對象,而訪問cell對象的cell_contents屬性則可以得到閉包變量的當前值(即上次調用之後的值)。
  • 隨著閉包的繼續被調用,變量會再次更新。
  • 一旦形成閉包之後,python確實會將__closure__和閉包函數綁定作為儲存閉包變量的場所。

 好處

  • 閉包不是必須的
  • 沒了閉包,python的功能一點都不會被影響
  • 有了閉包,只是提供給你一種額外的解決辦法

2、裝飾器

2.1、什麼是裝飾器?

定義:裝飾器是一種程序設計模式,它的本質就是閉包,它在不改變函數或者類的源代碼的基礎上,添加額外功能。

2.2、裝飾器有什麼用?

  • 可以考慮在裝飾器中置入通用功能的代碼來降低程序復雜度。
  • 引入日志
  • 增加計時邏輯來檢測性能
  • 給函數加入事務的能力
  • 權限控制

2.2.1、編寫一個裝飾器實現權限控制

條件:

  • 定義一個全局變量:username
  • 定義add函數,實現兩個數相加
  •  實現login_required裝飾器,如果username值為root,提示"歡迎" ,並計算結果,否則"沒有權限"
def login_required(func):
def inner(*args,**kwargs):
if username.lower() == "root":
result = func(*args,**kwargs)
print(f"歡迎執行{func.__name__}函數")
return result
else:
return f"執行{func.__name__}沒有權限"
return inner
username = input("請輸入你的用戶名:")
# 函數被修飾符裝飾後,返回的都是內函數的值
@login_required # add = login_required(add),相當於執行了這個操作。
def add(a=int(input("請輸入加數1:")),b=int(input("請輸入加數2:"))):
time.sleep(2)
return f"兩數和為:{a+b}"
print(add())
######## 執行結果
請輸入你的用戶名:root
請輸入加數1:2
請輸入加數2:3
歡迎執行add函數
兩數和為:5
請輸入你的用戶名:fdjk
請輸入加數1:1
請輸入加數2:2
執行add沒有權限

2.2.2、多個裝飾器的使用

可以應用多個裝飾器,但是要注意裝飾器的執行順序:從最上面的裝飾器的內函數開始執行

# 統計運行時間的裝飾器
import time
import functools
def runtime(func):
# 保留傳遞進來的函數的元數據,將它的元素據賦值給inner。元數據為原來的名字、文檔注釋等。
@functools.wraps(func)
def inner(*args,**kwargs): # 加上可變長位置參數可以讓裝飾器更加的通用
start_time = time.time() # 這個是時間戳,從1970年一月一日到至今的總秒數
result = func(*args,**kwargs)
end_time = time.time()
print(f"函數執行花了{end_time-start_time}s")
return result
return inner
def login_required(func):
def inner(*args, **kwargs):
if username == 'root':
print(f"歡迎執行{func.__name__}函數") # 這裡要是沒有functools.wraps(func),執行結果就會變成"歡迎執行inner函數"
result = func(*args, **kwargs)
return result
else:
return f"執行{func.__name__}權限不夠"
return inner
# 限制性login_required的內部函數,執行到func,就跳到runtime的內函數
@login_required # add = login_required(runtime(add))
@runtime # add = runtime(add)
def add( a, b):
return a + b
username = input("請輸入您要使用的用戶:")
a = int(input("請輸入要計算的數字1:"))
b = int(input("請輸入要計算的數字2:"))
print(add(a, b))
###### 執行結果
請輸入您要使用的用戶:root
請輸入要計算的數字1:2
請輸入要計算的數字2:2
歡迎執行add函數
函數執行花了0.0s
4

2.3、元數據

上面這個練習中使用到了" @functools.wraps(func)",這個東東是用來保留元數據的。

定義:元數據(Metadata),又稱中介數據、中繼數據,為描述數據的數據(data about data),主要是描述數據屬性(property)的信息

函數的重要的元信息,比如:名字、文檔注釋、字符串和參數簽名等。

2.3.1、裝飾器後為什麼元數據會丟失以及怎麼保留元數據

為什麼會丟失

  • 因為return執行的是經過調用封裝的函數

怎麼保留元數據

  • 利用@functools.wraps*(fun),將一個函數的重要內容復制到另一個函數。

溫馨提示:任何時候你定義裝飾器的時候,都應該使用functools庫中的@wraps裝飾器來注釋底層包裝函數。

3、裝飾器帶參數

原則:

  • 自身不傳入參數的裝飾器,使用兩層的函數定義
  • 自身傳入參數的的裝飾器,使用三層的函數定義

3.1、裝飾器帶參數的使用方法

錯誤使用方法:

import functools
import time
def runtime(func):
#保留傳遞進來的函數的元數據,將它的元數據賦值給inner
@functools.wraps(func)
def inner(*args, **kwargs): #讓裝飾器更加通用
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"函數執行花了{end -start}s")
return result
return inner
# 若是兩層的帶了參數的話,是這麼執行的
# a = runtime("name")
# add = a(add)
@runtime("name") # 對於兩層的函數,這樣寫是會報錯的。因為func若是為字符串的,func(*args, **kwargs)就沒有意義。因為字符串不是一個可調用對象(callable)
def add(a,b):
return a+b

正確使用方法:

import functools
import time
def decp(name):
def runtime(func):
#保留傳遞進來的函數的元數據,將它的元數據賦值給inner
@functools.wraps(func)
def inner(*args, **kwargs): #讓裝飾器更加通用
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"函數執行花了{end -start}s")
print(f"name is {name}")
print("1")
return result
print("2")
return inner
print("3")
return runtime
@decp(name="sc") # 這個相當於# runtime = deco(name="sc"), func1 = runtime(func1)
def func1():
time.sleep(3)
print("this is func1")
func1()
### 執行結果
3
2
this is func1
函數執行花了8.803873777389526s
name is sc
1

3.1.1、用裝飾器帶參數的方法重新編寫上文中權限控制代碼

import time
def test(username):
def login_required(func):
def inner(*args, **kwargs):
if username == 'root':
print(f"歡迎執行{func.__name__}函數")
result = func(*args, **kwargs)
return result
else:
return f"執行{func.__name__}權限不夠"
return inner
return login_required
@test(username=input("請輸入你的用戶名:"))
def add(a=int(input("請輸入加數1:")),b=int(input("請輸入加數2:"))):
time.sleep(2)
return f"兩數和為:{a+b}"
result = add()
print(result)
# 執行結果
請輸入你的用戶名:root
請輸入加數1:2
請輸入加數2:3
歡迎執行add函數
兩數和為:5

4、用類實現裝飾器和裝飾器帶參數

4.1、用類實現裝飾器

class rumtime():
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
start = time.time()
self.func()
end = time.time()
print(f"用時{end-start}")
@rumtime
def add():
time.sleep(2)
print("day day up")
add()
#### 執行結果
day day up
用時2.001725912094116

4.2、用類實現裝飾器帶參數

class rumtime():
def __init__(self, name):
self.func = name
def __call__(self, func):
def deco(*args,**kwargs):
start = time.time()
result = func(*args,**kwargs)
end = time.time()
print(f"用時{end-start}")
return result
return deco
@rumtime("name")
def add():
time.sleep(2)
print("day day up")
add()
# 執行結果
day day up
用時2.0109517574310303

5、裝飾類

def outer(cls):
def inner(*args,**kwargs):
print(f"class name is:{cls.__name__}")
return cls(*args,**kwargs)
return inner
@outer # A = outer(A)
class A:
def __init__(self, name):
self.name = name
print(type(A))
m = A("sc")
print(m.name)
### 執行結果
<class 'function'>
class name is:A
sc


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved