DAY 27 函數專題2:裝飾器
ps:第一期day27對應5月16日
知識點回顧:
- 裝飾器的思想:進一步復用
- 函數的裝飾器寫法
- 注意內部函數的返回值
作業:
編寫一個裝飾器 logger,在函數執行前后打印日志信息(如函數名、參數、返回值)
@logger
def multiply(a, b):return a * bmultiply(2, 3)
# 輸出:
# 開始執行函數 multiply,參數: (2, 3), {}
# 函數 multiply 執行完畢,返回值: 6
本期內容如果無法理解,可以參考如下視頻教學
B站-裝飾器的使用
豆包:
以下是用生活場景比喻解釋裝飾器相關知識點,幫你秒懂這個「代碼魔術」:
1. 裝飾器的思想:給函數「穿外套」的復用藝術
本質:在不修改原函數代碼的前提下,給函數「添加額外功能」,就像給衣服縫上口袋、給蛋糕加奶油。
?
核心目的:避免重復寫相似代碼(比如多個函數都需要「日志記錄」功能),實現代碼復用。比喻:
??
- 裝飾器 =?禮物包裝紙
- 原函數 = 「禮物本身」(比如一本書)。
- 裝飾器 = 「包裝紙 + 蝴蝶結」(給禮物增加「美觀」功能)。
- 關鍵規則:不能拆開禮物修改內容(不修改原函數代碼),但可以在外面套包裝。
生活場景舉例:
?
- 原函數?
read_book()
?是「看書」,裝飾器可以是「記錄看書時間」的功能:python
運行
def 記錄時間(原函數): # 裝飾器函數,參數是原函數def 包裝函數():print("開始時間:2025-06-10 10:00")原函數() # 調用原函數(看書)print("結束時間:2025-06-10 11:00")return 包裝函數@記錄時間 # 用裝飾器包裝函數 def read_book():print("正在閱讀《Python入門》")read_book() # 調用時會自動觸發裝飾器的「記錄時間」功能
輸出:plaintext
開始時間:2025-06-10 10:00 正在閱讀《Python入門》 結束時間:2025-06-10 11:00
2. 函數裝飾器的寫法:三步搭出「包裝流水線」
寫法模板(以 Python 為例):
??
- 定義裝飾器函數:接收原函數作為參數。
- 定義內部包裝函數:在原函數前后添加新功能。
- 返回包裝函數:用?
@裝飾器名
?語法糖應用到目標函數。比喻拆解:包裝禮物的流水線
?python
運行
?# 1. 定義「包裝紙」函數(裝飾器) def 包裝紙(禮物): # 禮物=原函數# 2. 定義「包裝過程」函數(內部函數)def 包禮物():print("? 放上絲帶") # 新增功能1禮物() # 調用原禮物(不修改原內容)print("🎀 貼上祝福卡片") # 新增功能2# 3. 返回「包裝好的禮物」return 包禮物# 用裝飾器包裝「送書」函數 @包裝紙 # 等價于 send_book = 包裝紙(send_book) def send_book():print("📚 送出《Python書》")# 調用包裝后的函數 send_book()
執行效果:
?plaintext
? 放上絲帶 📚 送出《Python書》 🎀 貼上祝福卡片
3. 注意內部函數的返回值:別讓禮物「消失在包裝里」
常見錯誤:如果內部函數不返回原函數的結果,原函數的返回值會丟失(就像把禮物放進盒子卻不裝進去,最后送出空盒子)。
?比喻對比:
??
- 正確做法:包裝禮物時必須把「禮物本身」放進盒子,否則收禮人拿到的是空盒子。
- 錯誤做法:包裝時只放包裝紙,沒放禮物,結果丟失了禮物。
代碼示例:
?python
運行
# ? 錯誤寫法:內部函數不返回原函數結果 def 錯誤裝飾器(原函數):def 包裝函數():print("開始裝飾")原函數() # 調用原函數,但沒保存結果print("裝飾結束")return 包裝函數@錯誤裝飾器 def get_gift():return "鉆石戒指" # 原函數返回一個值result = get_gift() print(result) # 輸出:None(原函數的返回值丟失了!)# ? 正確寫法:必須返回原函數的調用結果 def 正確裝飾器(原函數):def 包裝函數():print("開始裝飾")gift = 原函數() # 保存原函數的返回值print("裝飾結束")return gift # 返回原函數的結果return 包裝函數@正確裝飾器 def get_gift():return "鉆石戒指"result = get_gift() print(result) # 輸出:鉆石戒指(結果正確傳遞)
終極比喻總結:裝飾器是「函數的變裝皇后」
?
裝飾器要素 生活比喻 核心作用 原函數 素顏的人 原本的功能主體 裝飾器函數 造型師 負責給函數「化妝」(添加功能) 內部包裝函數 化妝過程 在原功能前后插入新操作 @裝飾器語法 造型師的「魔法咒語」 一鍵應用變裝效果 必須返回原結果 化妝不能改變人的本質 裝飾后必須保留原函數的返回值 通過「包裝禮物」的比喻,可以直觀理解裝飾器的「無侵入式增強」特性 —— 就像我們不會為了系蝴蝶結而拆開禮物盒,裝飾器也不會修改原函數的代碼,卻能讓函數「穿上新功能的外衣」,實現高效復用~ 🎁
另回答:
好的!我會用生活中的例子來比喻這三個知識點,盡量讓抽象的概念變得直觀~
1. 裝飾器的思想:進一步復用
知識點解釋:
裝飾器是一種不修改原有函數代碼,卻能為其動態添加新功能的編程技巧。它的核心是 “復用代碼邏輯”,比如給多個函數統一添加日志記錄、權限校驗等功能,避免重復寫重復代碼。
比喻:
就像給蛋糕加裝飾:
- 蛋糕本身(原有函數)的配方(代碼)不需要改變,
- 但可以通過裱花(裝飾器)給不同蛋糕(不同函數)加上奶油、水果(新功能),
- 而且同一套裱花工具(裝飾器邏輯)可以重復用在多個蛋糕上,避免每次都重新設計裝飾。
場景舉例:
比如多個函數都需要記錄執行時間,用裝飾器就像給每個函數 “貼” 一個計時器,不用每個函數里都寫一遍計時代碼。
2. 函數的裝飾器寫法
知識點解釋:
裝飾器本質是一個高階函數(參數或返回值是函數的函數),寫法分兩步:
- 定義裝飾器函數:它接受一個函數(被裝飾的函數)作為參數,返回一個內部函數(包裹了原函數的新功能)。
- 用
@裝飾器名
語法糖給目標函數 “裝飾”。
比喻:
就像做三明治:
- 面包片 1(裝飾器外層函數):接受食材(原函數)作為參數,
- 夾心菜肉(內部函數):在食材前后添加面包片(新功能),比如先抹醬(前置邏輯),再放食材(原函數執行),最后蓋另一片面包(后置邏輯),
- 成品三明治(返回的內部函數):比原來的食材更豐富,但保留了食材本身的味道(原函數功能)。
代碼示例:
python
# 定義裝飾器:給函數加“吃前洗手”和“吃完擦嘴”的邏輯
def sandwich_decorator(func):def wrapper(): # 內部函數,包裹原函數print("? 先洗手(前置邏輯)")func() # 執行原函數(吃三明治)print("? 再擦嘴(后置邏輯)")return wrapper # 返回包裹后的函數# 用@語法糖裝飾目標函數
@sandwich_decorator
def eat_sandwich():print("🍔 正在吃三明治(原函數邏輯)")# 調用裝飾后的函數
eat_sandwich()
輸出:
plaintext
? 先洗手(前置邏輯)
🍔 正在吃三明治(原函數邏輯)
? 再擦嘴(后置邏輯)
3. 注意內部函數的返回值
知識點解釋:
如果原函數有返回值(比如計算結果),裝飾器的內部函數必須顯式返回原函數的調用結果,否則調用裝飾后的函數會得到None
(空值)。
比喻:
就像送快遞:
- 原函數是 “打包商品” 的函數,返回值是 “包裹里的商品”,
- 裝飾器是 “貼快遞單” 的流程,如果貼單后不把包裹(返回值)交給快遞員(調用者),買家收到的就是空包裹(
None
)。
代碼示例:
python
# 錯誤示范:內部函數不返回原函數結果
def bad_decorator(func):def wrapper():func() # 執行原函數,但不返回結果return wrapper@bad_decorator
def add(a, b):return a + b # 原函數有返回值result = add(1, 2)
print(result) # 輸出:None(丟失了返回值!)
python
# 正確示范:內部函數返回原函數結果
def good_decorator(func):def wrapper(*args, **kwargs): # 通用參數接收return func(*args, **kwargs) # 必須返回原函數調用結果return wrapper@good_decorator
def add(a, b):return a + bresult = add(1, 2)
print(result) # 輸出:3(正確保留返回值)
總結
- 裝飾器思想:像給函數 “穿外套”,復用功能代碼,不修改原函數。
- 寫法關鍵:裝飾器是高階函數,用
@
語法糖簡化調用,內部函數包裹原函數。 - 返回值注意:內部函數必須 “接住” 原函數的返回值并返回,否則結果會 “丟失”。
如果還有疑問,可以隨時問我哦! 😊
代碼:
def logger(func):def wrapper(*args, **kwargs):print(f"調用函數: {func.__name__},參數: {args}, {kwargs}")result = func(*args, **kwargs)print(f"函數返回值: {result}")return resultreturn wrapper
@logger
def multiply(a, b):return a * b multiply(2, 3)
?另代碼:
def logger(func):def wrapper(*args, **kwargs):# 打印函數執行前的日志print(f"開始執行函數: {func.__name__}")print(f"位置參數: {args}")print(f"關鍵字參數: {kwargs}")# 執行原函數result = func(*args, **kwargs)# 打印函數執行后的日志print(f"函數 {func.__name__} 執行完畢")print(f"返回值: {result}")return resultreturn wrapper# 示例函數,用于測試裝飾器
@logger
def add_numbers(a, b):return a + b# 調用示例函數
add_numbers(3, 5)