12 - 閉包&裝飾器📄目錄1 遞歸函數1.1 含義和條件1.2 例子1.2.1 實現斐波那契數列1.3 優缺點2 閉包 B-Box2.1 條件和格式2.1.1 條件2.1.2 格式2.2 函數引用2.3 每次開啟內函數都在使用同一份閉包變量3 裝飾器3.1 標準版裝飾器3.2 語法糖3.3 被裝飾的函數有參數3.4 被裝飾的函數有可變參數*args,**kwargs3.5 多個裝飾器導航連結:
💡 含義:一個函數在內部不調用其他函數,而調用其本身,就是遞歸函數。
💡 條件:
必須有一個明確的結束條件(遞歸出口)
每進行更深一層的遞歸,問題規模相比上次遞歸都要有所有減少
相鄰兩次重複之間有緊密的聯繫

xxxxxxxxxx# 普通函數實現1-100累加和def add(): s = 0 for i in range(1, 101): s += i print(s)add()輸出結果:
xxxxxxxxxx5050
遞歸函數
xxxxxxxxxx# def add2(n): # 要累加到第n項# return n + add2(n)# print(add2(100)) # 報錯,遞歸超出最大深度
def add2(n): # 如果是1,就返回1(明確的結束條件) if n == 1: return 1 return n + add2(n-1)print(add2(100))輸出結果:
xxxxxxxxxx50501, 1, 2, 3, 5, 8, 13, ...
規律:從第三項開始,每一項都等於前兩項之和,即n = (n-2) + (n-1),n代表當前項

xxxxxxxxxxdef func1(n): # n代表第n項 if n <= 1: return n return func1(n-2) + func1(n-1) # func1(1) + func1(2)print(func1(20))輸出結果:
xxxxxxxxxx6765優點:簡潔、邏輯清晰、解題更具有思路
缺點:使用遞歸函數的時候,需要反復調用函數,耗內存,運行效率低
❗ 代碼不冗長的時候不建議使用遞歸
函數嵌套(函數裡再定義函數)
內層函數使用外層函數的局部變量
外層函數的返回值是內層函數的函數名

xxxxxxxxxxdef outer(): # 外層函數 n = 10 # 外層函數的絕對變量 def inner(): # (內層函數) print(n) # 內層函數使用外套函數的局部變量 return inner # 返回值是內層函數的函數名print(outer()) # 返回的是內部函數的內存地址# 第一種調用寫法:outer()()# 第二種調用寫法:ot = outer()ot()輸出結果:
xxxxxxxxxx<function outer.<locals>.inner at 0x1051fe160>1010
xxxxxxxxxxdef outer(m): # 外函數,m是形參,也是外函數的局部變量 n = 10 def inner(o): print('計算結果:', m + n + o) return inner # 返回函數名,而非inner(),因為inner函數裡面參數比較多/受到限制的時候,寫法不太規範ot = outer(20) # 調用函數,先外函數的參(m)ot(30) # 調用內函數,再次傳參(o)# 如果內函數無需傳參,就return inner(),調用時就用ot輸出結果:
xxxxxxxxxx計算結果: 60
xxxxxxxxxxdef func2(): print(123)print(func2) # 函數名裡面保存了函數所在位置的引用# id():判斷兩個變量是否是同一個值的引用a = 1 # a只不過是一個變量名,存的是1這個數值所在的地址,就是a裡面存了數值1的引用print(a)print(id(a))a = 2print(id(a)) # 修改a,生成了新的值,重新賦值給變量aprint(id(2), id(a))輸出結果:
xxxxxxxxxx<function func2 at 0x10499d940>1438986075243898607844389860784 4389860784💡 留意引用位值與函數值相關,但與函數名無直接關係

xxxxxxxxxxdef test1(): # test1只不過是一個函數名,裡面存了這個函數所在位置的引用 print('這是test函數')test1()print(test1)輸出結果:
xxxxxxxxxx這是test函數<function test1 at 0x104deb880>
xxxxxxxxxxdef outer(m): print('outer()函數中的值:', m) def inner(n): print('inner()函數中的值:', n) return m + n return innerot = outer(1) # 調用外函數,給outer()傳值ot(19) # 調用內函數,給inner()傳值print(ot(19)) # 這時候20就出來了,1+19print(ot(29)) # 第二次調用,1+29print(ot(39)) # 第三次調用,1+39輸出結果:
xxxxxxxxxxouter()函數中的值: 1inner()函數中的值: 19inner()函數中的值: 1920inner()函數中的值: 2930inner()函數中的值: 3940總結:
使用閉包的過程中,一旦外函數被調用一次,返回了內函數的引用,雖然每次調用內函數,會開啟一個函數,執行後消亡
但是閉包變量實際上只有一份,每次開啟內函數都在使用同一份閉包變量
含義:本質上是一個*python閉包函數*,好處是在不修改原有代碼的前提下增加額外功能,其返回值也是一個函數對象
作用:在不改變原有代碼的情況下添加新功能
條件:
不修改源程序或函數的代碼
不改變函數或程序的調用方法

👉🏻 沒有裝飾器:
xxxxxxxxxx def test2(): print('發信息') def test(fn): print('開始注冊') print('登錄') fn() # 調用要傳入的函數 test(test2)輸出結果:
xxxxxxxxxx開始注冊登錄發信息
xxxxxxxxxx# send 被裝飾的函數def send(): print('發送消息')def send2(): print('轉賬')def outer(fn): # fn是形參,但是要傳入被裝飾的函數名 # 既包含原有功能,也包含新功能 def inner(): # 閉包條件1:函數嵌套 print('登錄', end='|') # 新增功能 fn() # 閉包條件2:內函數要使用外函數的局部變量;執行被裝備的函數 return inner # 閉包條件3:外函數的返回值是內函數的函數名
print(outer(send))# 調用方式一:outer(send)() # 直接調用# 調用方式二:ot = outer(send2) # 調用外函數ot() # 調用內函數輸出結果:
xxxxxxxxxx<function outer.<locals>.inner at 0x102616160>登錄|發送消息登錄|轉賬💡 裝飾器的原理就是將原有的函數名重新定義為以原函數為參數的閉包
格式:@裝飾器名稱

xxxxxxxxxxdef outer(fn): def inner(): print('登錄', end=" | ") # 執行被裝飾的函數 fn() return inner # 裝飾器名稱後不要加上(),前都是引用,後者是調用函數,返回該函數要返回的值def send(): print('發送消息:笑死我了')send() # 跟下面的函數之間不要有空行def send2(): print('發送消息:哈哈哈')send2()輸出結果:
xxxxxxxxxx登錄 | 發送消息:笑死我了登錄 | 發送消息:哈哈哈
xxxxxxxxxxdef outer(fn): def inner(name): print(f'{name}是inner函數中的參數', end=" | ") fn(name) # 要輸入參數名 return inner# 語法糖:def func(name): # 與上方一致 print('這是被裝飾的函數')func('長崎良尾') # 需要傳參# 標準裝飾器def func(name): print('這是被裝飾的函數')ot = outer(func) # ot = innerot('長崎良尾') # 調用內函數,要傳參輸出結果:
xxxxxxxxxx長崎良尾是inner函數中的參數 | 這是被裝飾的函數長崎良尾是inner函數中的參數 | 這是被裝飾的函數
xxxxxxxxxxdef func(*args,**kwargs): print(args) # 這裡不能加星號 print(kwargs)# func(name = 'Victoria')# 加一個裝飾器函數def outer(fn): def inner(*args,**kwargs): print('登錄...') fn(*args,**kwargs) return inner# 函數必須被調用才會執行# print(outer('test')) # 返回值變成inner的地址# outer('test')() # 返回函數的值ot = outer(func)ot('username', name = 'Victoria') # username以元組形式傳遞給args,name = 'Victoria'以鍵值對的形式傳遞給kwargs輸出結果:
xxxxxxxxxx登錄...('username',){'name': 'Victoria'}💡多個裝飾器的裝飾過程:離函數最近的裝飾器先裝飾,然後由內至外的裝飾器逐個裝飾

xxxxxxxxxx# 第一個裝飾器def deco1(fn): def inner(): return '外左 ' + fn() + ' 外右' return inner# 第二個裝飾器def deco2(fn): def inner(): return '內左 ' + fn() + ' 內右' return inner# 被裝飾的函數一def test1(): return '中央'print(test1())輸出結果:
xxxxxxxxxx外左 內左 中央 內右 外右@deco2先裝飾:
'內左 ' + fn() + ' 內右' -> '內左 ' + '中央' + ' 內右'
@deco1後裝飾:
'外左 ' + fn() + ' 外右' -> '外左 ' + '內左 ' + '中央' + ' 內右' + ' 外右'
| 目的地 | 超連結 |
|---|---|
| 首頁 | 返回主頁 |
| Python學習 | Python學習 |
| 上一篇 | 11 - 異常、模塊、包 |
| 下一篇 | 13 - 面向對象基礎 |