Python基礎(十四)
- 2019 年 10 月 3 日
- 筆記
今日主要內容
- 裝飾器擴展
- 有參裝飾器
- 多個裝飾器裝飾一個函數
- 遞歸
一、裝飾器擴展
(一)含有參數的裝飾器
-
先來回顧一下裝飾器的標準模式
def wrapper(fn): def inner(*args, **kwargs): """擴展內容""" ret = fn(*args, **kwargs) """擴展內容""" return inner @wrapper def func(): pass func()
-
回顧一下之前的遊戲模擬,通過裝飾器給我的遊戲過程擴展了開掛的功能,裝飾之後每次想玩遊戲的時候調用函數都會給你先把掛打開,此時你的遊戲函數已經被裝飾了,但是現在有一個問題,我今天想自己玩一把,不想開掛了,怎麼辦?我們可以給裝飾器傳一個參數,來控制我的裝飾器的開啟和關閉就可以了
def wrapper_outer(argv): # 給裝飾器加一個參數,控制裝飾器開啟關閉 def wrapper(fn): def inner(hero): if argv: # 如果是True執行添加裝飾 print("開啟外掛!") ret = fn(hero) print("關閉外掛!") return ret else: # 如果是False,執行原函數 ret = fn(hero) return ret return inner return wrapper @wrapper_outer(True) def play_lol(hero): # 基礎函數參數 print("登陸遊戲") print("開始排位...") print(f"選擇英雄:{hero}") print("遊戲中...") print("勝利!!!") print("結束遊戲") return "坑比隊友:xxx" # 基礎函數返回值 print(play_lol("蓋倫")) 運行結果: 開啟外掛! 登陸遊戲 開始排位... 選擇英雄:蓋倫 遊戲中... 勝利!!! 結束遊戲 關閉外掛! 坑比隊友:xxx
-
刨析一下:
- 先來看裝飾器和語法糖
@wrapper_outer(True)
先執行函數調用,函數調用返回的是我內層裝飾器的函數名,相當於@wrapper
- 裝飾器最外層的參數控制內層包裝函數inner裏面的函數部分是否執行,如果argv為真,相當於執行了包裝,如果argv為假,執行原函數
- 通過給裝飾器傳參起到了控制裝飾器是否生效的功能
def wrapper_outer(argv): def wrapper(fn): def inner(hero): if argv: # 為真執行這裡 print("開啟外掛!") ret = fn(hero) print("關閉外掛!") return ret else: # 為假執行這裡 ret = fn(hero) return ret return inner return wrapper @wrapper_outer(True) # 先執行函數調用
- 注意:一旦給函數裝飾過,裝飾器的參數是不能變化的,因為閉包的原因參數已經被閉進去了,只能調用內層函數,無法再修改最外層的裝飾器參數
flag = True def wrapper_outer(argv): def wrapper(fn): def inner(*args, **kwargs): if argv: """擴展功能""" ret = fn(*args, **kwargs) """擴展功能""" return ret else: ret = fn(*args, **kwargs) return ret return inner return wrapper @wrapper_outer(flag) def func(): pass flag = False func() # 此時flag依然是True,裝飾過就不能修改參數的值
- 先來看裝飾器和語法糖
-
-
有參裝飾器的標準模式
def wrapper_outer(argv): def wrapper(fn): def inner(*args, **kwargs): if argv: """擴展功能""" ret = fn(*args, **kwargs) """擴展功能""" return ret else: ret = fn(*args, **kwargs) return ret return inner return wrapper @wrapper_outer(True) def func(): pass func()
(二)多個裝飾器裝飾一個函數
-
執行原理:從裡到外進行包裝
def wrapper1(fn): def inner(*args, **kwargs): print("擴展功能1") ret = fn(*args, **kwargs) print("擴展功能4") return ret return inner def wrapper2(fn): def inner(*args, **kwargs): print("擴展功能2") ret = fn(*args, **kwargs) print("擴展功能3") return ret return inner @wrapper1 @wrapper2 def func(): print("目標函數") func() 運行結果: 擴展功能1 擴展功能2 目標函數 擴展功能3 擴展功能4
-
刨析一下:
- 從里往外看,先用第一層裝飾器
@wrapper2
裝飾目標函數func()
,裝飾完將其看作成一個整體,在被上層裝飾器@wrapper1
裝飾 - 返回值:執行完目標函數,將目標函數的返回值先反給最近的裝飾器
@wrapper2
內部的inner包裝函數中,之後再將@wrapper2
內部的inner包裝函數的返回值返回給上一層裝飾器@wrapper1
內部的inner中,最終得到的返回值是我調用函數的返回值 - 最終調用目標函數其實真正執行的是最外層裝飾器中的包裝函數
inner
,而最外層裝飾器中的包裝函數inner
包裝着內層裝飾器的包裝函數inner
,而內層裝飾器的包裝函數inner
包裝着真正的目表函數func
# 偽代碼: def 裝飾器1(傳入目標函數): def 內層包裝函數1,也是真正執行的函數(目標函數的參數): """前擴展功能""" 目標函數(目標函數的參數) """後擴展功能""" return 包裝函數的函數名 def 裝飾器2(傳入目標函數): def 內層包裝函數2,也是真正執行的函數(目標函數的參數): """前擴展功能""" 目標函數(目標函數的參數) """後擴展功能""" return 包裝函數的函數名 @裝飾器1 @裝飾器2 def 目標函數(形參): 函數體 目標函數(實參) # 真正執行過程: 先執行:裝飾器1的內層包裝函數1,而傳入的目標函數是:裝飾器2的內層包裝函數2 再執行:裝飾器2的內層包裝函數2,而傳入的目標函數是:目標函數
- 從里往外看,先用第一層裝飾器
-
二、遞歸
(一)什麼是遞歸
- 首先遞歸是一個函數,只要滿足兩個要求的函數就是遞歸函數:
- 不斷調用自己本身
- 有明確的結束條件
(二)遞歸深度
-
如果只是在不斷的調用自己本身,沒有一個明確的結束條件,那麼就是一個死遞歸(無限循環)。
-
Python官方規定,為了避免無限制的調用自己本身,遞歸的最大深度為1000(最多只能調用自己本身1000次),實際遞歸深度為998
def func(): print(1) func() func() 運行結果: [Previous line repeated 994 more times] 1 1 1 ...(共打印998個1)
-
可以通過導入sys模塊,修改最大遞歸深度
import sys sys.setrecursionlimit(100) # 修改遞歸深度 def func(): print(1) func() func() 運行結果: [Previous line repeated 94 more times] 1 1 1 ...(實際打印98個1)
(三)遞歸的應用
-
求n的階乘
def factorial(n): if n == 1: return 1 return factorial(n - 1) * n print(factorial(5)) 運行結果: 120
-
計算斐波那契序列
def fib(n): if n <= 2: return 1 return fib(n-1) + fib(n-2) print(list(map(fib,range(1, 6)))) 運行結果: [1, 1, 2, 3, 5]
-
打印列表嵌套的每一個元素
l1 = [1, 2, [3, 4, [5, [6, 7, [8, 9], 10], 11, 12], 13], 14, 15] def func(lst): for el in lst: if type(el) == list: func(el) else: print(el, end=" ") func(l1) 運行結果: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
-
給列表去重,不能使用集合
l1 = [1, 1, 2, 3, 4, 5, 6, 3, 3, 5, 6, 3, 4, 5] def del_repetition(lst): for el in lst: if lst.count(el) > 1: lst.remove(el) del_repetition(lst) del_repetition(l1) print(l1) 運行結果: [1, 2, 6, 3, 4, 5]
-
遍歷文件夾中所有的文件
import os def read(filepath, n): files = os.listdir(filepath) # 獲取到當前文件夾中的所有文件 for fi in files: # 遍歷文件夾中的文件, 這裡獲取的只是本層文件名 fi_d = os.path.join(filepath, fi) # 加入文件夾 獲取到文件夾文件 if os.path.isdir(fi_d): # 如果該路徑下的文件是文件夾 print("t" * n, fi) read(fi_d, n + 1) # 繼續進行相同的操作 else: print("t" * n, fi) # 遞歸出口. 最終在這裡隱含着return # 遞歸遍歷目錄下所有文件 read('../day16/', 0)
-
二分查找
# 普通遞歸版本⼆二分法 lst = [22, 33, 44, 55, 66, 77, 88, 99, 101, 238, 345, 456, 567, 678, 789] n = 567 left = 0 right = len(lst) - 1 def binary_search(n, left, right): if left <= right: middle = (left + right) // 2 if n < lst[middle]: right = middle - 1 elif n > lst[middle]: left = middle + 1 else: return middle return binary_search(n, left, right) # 這個return必須要加. 否則接收到的永遠是None. else: return -1 print(binary_search(567, 0, len(lst) - 1))
-
三級菜單進入返回
menu = { '北京': { '海淀': { '五道口': { 'soho': {}, '網易': {}, 'google': {} }, '中關村': { '愛奇藝': {}, '汽車之家': {}, 'youku': {}, }, '上地': { '百度': {}, }, }, '昌平': { '沙河': { '北郵': {}, '北航': {}, }, '天通苑': {}, '回龍觀': {}, }, '朝陽': {}, '東城': {}, }, '上海': { '閔行': { "人民廣場": { '炸雞店': {} } }, '閘北': { '火車戰': { '攜程': {} } }, '浦東': {}, }, '天津': { "和平": { "小白樓": {}, "五大道小洋樓": {}, "濱江道": {}, }, "南開": { "天大": {}, "南開": {}, "理工": {}, }, "河北": { "天津之眼": {}, "海河": {}, "意式風情區": {}, "世紀鐘": {}, "大悲院": {}, }, }, } def menu_func(menu): while True: for k in menu: print(k) key = input('input>>').strip() if key == 'b' or key == 'q': return key elif menu.get(key): ret = menu_func(menu[key]) if ret == 'q': return 'q' menu_func(menu)