Python (進階 第二部)
目錄
函式
函式的定義: 功能( 包括一部分程式碼,實現某種功能,達成某個目的)
函式的特點: 函式可以反覆呼叫 ,提高程式碼的複用性,提高開發效率,便於維護和管理.
函式的基本格式:
# 定義一個函式
def 函式名():
code1
code2
# 函式的呼叫
函式名()
函式的命名規範:
字母數字下劃線, 首字母不能為數字.
嚴格區分大小寫, 且不能使用關鍵字.
函式命名有意義, 且不能使用中文.
函式的引數:( 引數:配合函式運算的值)
形參實參一一對應
形參: 形式引數,(在函式的定義處)
普通形參(位置) 預設形參 普通收集形參 命名關鍵字形參 關鍵字收集形參
實參: 實際引數,(在函式的呼叫處)
普通實參 關鍵字實參
普通形參
var = 你好 def func(var): print(var) func(var)
預設形參
var = '你好' def func(var='大家好'): print(var) func()
普通收集形參
list = ['徐峰','學峰','妮妮'] def func(*args): print(list[:]) func(list)
命名關鍵字形參
命名關鍵字引數一般情況下跟在普通收集形參的後面, 在函式的呼叫時,必須使用命名關鍵子引數;來進行賦值
#方式一: def func(a,b,*,d,c): print(a,b) # 1 2 print(d) # 3 print(c) # 10 func(1,2,d = 3,c=10) 方式二: def func(*args, c, **kwargs): print(args) # (1, 2, 3, 4, 5, 6) print(c) # 100 print(kwargs) # {'a': 1, 'b': 2, 'd': 6} func(1, 2, 3, 4, 5, 6, a=1, b=2, d=6, c=100)
關鍵字收集形參
def func(a,b,*,c,d):print(a,b) print(c,d) dic = {"c":3,"d":4} # **把字典裡面的所有元素拿出來,拼裝成鍵=值的引數形式,賦值給func進行呼叫 func(1,2,**dic,) # func( c=3, d=4 )
locals globals nonlocal (瞭解)
locals
locals 如果在全域性,呼叫locals之後,獲取的是列印之前的所有變數 返回字典,全域性空間作用域.
def func(): ff = 123 a =1 b =2 res = locals() c =3 print(res) #是一個大字典 截止到列印之前 c =4 # 列印之後的c 就獲取不到
locals 如果在區域性,呼叫locals之後, 獲取的是呼叫之前的所有變數, 返回字典, 區域性空間作用域.
a1 = 10 def func (): a = 1 b = 2 res =locals() c = 3 print(res) #截止到locals() 的呼叫之前( 就是locals之前的區域性變數 d =4 # 獲取不到 b2 =22 # 獲取不到 func() a3 =33 #獲取不到
globals
globals如果在全域性, 呼叫globals之後,獲取的是列印之前的所有變數,返回字典,全域性空間作用域
def func(): ff = 123 a =1 b =2 res= globals() # 獲取列印之前的所有變數 c =3 print(res) d =4 # c =4 將獲取不到
globals 如果在區域性, 呼叫globals之後,獲取的是呼叫之前的所有變數,返回字典,全域性空間作用域
a1 = 10 def func(): a =1 b =2 res=globals() c =3 print(res) d = 4 a2 =20 func() # 獲取的是呼叫之前的所有變數 a3=30 # 獲取不到
nonlocal
nonlocal遵循LEGB就近找變數的原則
- 找當前空間上一層的區域性變數進行修改
- 找不到繼續向再外一層找
- 最後一層也沒有時,就會報錯
1. 找當前空間上一層的區域性變數進行修改
def outer(): a =100 def inner(): nonlocal a a =200 print(a) inner() print(a) outer() # outer層中的變數被inner中的nonlocal修改後,inner和outer在呼叫時都將得到的是修改之後的值
2. 如果找不到繼續向上一層找
def outer(): a=10 def inner(): b=20 def func(): nonlocal a a =1 print(a) func() print(a) inner() print(a) outer() # func() 中的nonlocal 將 a 修改為1 在inner層中沒有,就就找的outer中找到並修改 ,此時可以到的都是修改後的變數
3. 注意點: nonlocal只能修改區域性變數
a =1 def outer(): b =2 def inner(): c = 3 def func(): nonlocal d d =4 print(d) func() print(a) inner() print(a) outer() # SyntaxError: no binding for nonlocal 'd' found
4. 不使用nonlocal 修改區域性變數
def func(): lst = [1,2,3,4] def inner(): lst[-1] = 10 inner() print(lst) func()
函式的巢狀
1. 函式之間可以互相巢狀
外層的函式叫做外函式,內層的叫做內函式
- (!)內部函式不可以在函式外部呼叫
- (2)呼叫外函式後, 內函式不可以在函式外部呼叫
- (3)內函式只能在函式內使用
- (4)內函式在函式內部呼叫時,必須先定義再使用
2. 外層是outer ,內層是inner ,最裡層是smaller ,呼叫smaller 裡面的所有程式碼
def outer(): def inner(): def smaller(): print('我是smaller函式') smaller() inner() outer()
3. LEGB 原則(就近找變數原則)
B —— Builtin(Python);Python內建模組的名稱空間 (內建作用域)
G —— Global(module); 函式外部所在的名稱空間 (全域性作用域)
E —— Enclosing function locals;外部巢狀函式的作用域(巢狀作用域)
L —— Local(function);當前函式內的作用域 (區域性作用域)
依據就近原則,從下往上 從裡向外 依次尋找
函式的返回值 return
自定義函式的返回值 return , 可以把值返回到函式的呼叫處
(1) return + 六大標準資料型別 , 類 , 物件,函式
如果不定義return ,預設返回的None
(2) 執行完return 之後, 立刻終止函式, 後面的程式碼將不會被執行
利用return 模擬簡單
def func(sign,num1,num2):
if sign =='+':
res = num1 + num2
elif sign =='-':
res = num1 - num2
elif sign =='*':
res = num1 * num2
elif sign=='/':
if num2 ==0:
return '除數不能為0'
res = num1 / num2
else:
return '這個計算不出來'
return res
res = func("-",10,20)
print(res)
全域性變數和區域性變數
區域性變數 : 在函式內部定義的變數(區域性名稱空間)
全域性變數 : 在函式外部定義的或者使用global在函式內部定義(全域性名稱空間)
作用域: 作用的範圍
區域性變數作用域: 在函式的內部
全域性變數作用域: 橫跨整個檔案
生命週期:
內建變數 > 全域性變數 > 區域性變數總結:
可以使用global 關鍵字在函式內部定義一個全域性變數
也可以使用global關鍵字在函式內部修改一個全域性變數
函式名的使用
-
函式名是個特殊的變數,可以當做變數賦值
a = “你好”
print(a)
a = func
a()函式可以像變數一樣銷燬
-
函式名可以作為容器型別資料的元素
-
函式名可以作為函式的引數
-
函式名可作為函式的返回值
閉包
如果內函式使用了外函式的區域性變數,並且外函式把內函式返回的過程,叫做閉包.
被外函式返回出的函式就是閉包函式
基礎語法
def songyunjie_family(): father = "王健林" def f_hobby(): print("我們先頂一個小目標,比如賺它一個億,這是我爸爸{}".format(father)) return f_hobby func = songyunjie_family() func() obj = func.__closure__[0] print(obj.cell_contents,"<111>")
複雜版
def mashengping_family(): father = "馬雲" jiejie_name = "馬蓉" meimei_name = "馬諾" money = 1000 def jiejie(): nonlocal money money -= 700 print("買包包,買名錶,買首飾,把錢都敗光了,家裡的錢還剩下{}元".format(money)) def meimei(): nonlocal money money -= 200 print("要找只找有錢人,寧願在寶馬裡面哭,也不願意在自行車上撒歡,家裡的敗光了還剩下{}元".format(money)) def big_master(): return (jiejie,meimei) return big_master func = mashengping_family() print(func) # 返回的是元組 tup = func() # big_master() print(tup) # tup = (jiejie,meimei) # 獲取姐姐 jiejie = tup[0] # jiejie 函式 # 獲取妹妹 meimei = tup[1] # meimei 函式
獲取閉包函式使用的變數 closure
res= func.closure
print(res,'<222>')
cell_contents 用來獲取單元格物件當中的閉包函式
jiejie = res[0].coll_contenrs
meimei = res[1].cell_contents
通過獲取單元格物件 獲取單元格物件中的內容 實際的呼叫
jiejie()
meimei()
print(jiejie.closure[0].coll_contents)
print(meimei.closure[1].coll_contents)
閉包的特徵
內函式使用了外函式的區域性變數 ,那麼該函式與閉包函式就發生了繫結,延長了這個函式的生命週期
def outer(val): def inner(num): return val + num return inner func = outer(10) # func = inner res = func(15) # res = func(15) = inner(15) print(res)
閉包的意義
閉包可以優先使用外函式中的變數,並對閉包函式中的值起到了封裝保護的作用,外部無法訪問.
模擬滑鼠點選次數
num = 0 def click_num(): global num num += 1 print(num) click_num() click_num() click_num() num = 100 click_num() click_num()
匿名函式
用一句話來表達只有返回值的函式
- 語法: lambda 引數: 返回zhi
- 追求程式碼簡潔高效
- 無參的lambda表示式
-
def func(): return "123" func = lambda : "123" res = func() print(res)
-
- 有參的lambda表示式
-
def func(n): return type(n) func = lambda n : type(n) print( func([1,2,3]) )
-
- 帶有判斷條件的lambda表示式
-
def func(n): if n % 2 == 0: return "偶數" else: return "奇數" func = lambda n : "偶數" if n % 2 == 0 else "奇數" res = func(17) print(res)
-
- 三元運算
-
n = 16 res = "偶數" if n % 2 == 0 else "奇數" print(res) def func(x,y): if x>y: return x else: return y func = lambda x,y : x if x>y else y res = func(200,100) print(res)
-
迭代器
能被next 呼叫,並不斷返回下一個值的物件
迭代器的概念:迭代器指的就是迭代取值的工具,迭代是一個重複的過程,每一次迭代都是基於上一次的結果而繼續的
迭代器的特徵:不依賴索引,而是通過next 的指標迭代所有的資料,一次只能取一個值,大大節省了空間
dir 獲取當前型別物件中的所有成員
“”“iter 方法用來判斷是否是可迭代性資料”"" lst = dir(setvar) print(dir(“123”)) res = “iter” in dir(setvar) print(res)
迭代器
- for 迴圈能夠遍歷一切可迭代性資料的原因在於,底層呼叫了迭代器,
- 通過next方法中的指標實現資料的獲取
- 可迭代物件 -> 迭代器 不能夠被next直接呼叫 -> 可以被next直接呼叫的過程
- 如果是一個可迭代物件不一定是迭代器
- 但如果是一個迭代器就一定是一個可迭代物件
如何定義一個迭代器
setvar = {“a”,“b”,“c”,“d”} it = iter(setvar) print(it)
如何判斷一個迭代器
print(dir(it)) res = “iter” in dir(it) and “next” in dir(it) print(res)
如何來呼叫一個迭代器
“”“next在呼叫迭代器中的資料時,是單向不可逆,一條路走到黑的過程”"" res = next(it) print(res) res = next(it) print(res) res = next(it) print(res) res = next(it) print(res) “”" #StopIteration 報錯 停止迭代 res = next(it) print(res) “”"
重置迭代器
it = iter(setvar) res = next(it) print(res)
使用其他方法判斷迭代器或者可迭代物件
“”“Iterator 迭代器 Iterable 可迭代物件”"" from … 從哪裡 import 引入 … from collections import Iterator,Iterable from collections.abc import Iterator,Iterable python3.7 res = isinstance(it,Iterator) print(res) res = isinstance(it,Iterable) print(res)
使用其他方法呼叫迭代器中的資料
for i in it: print(i)
for+next
lst = [1,2,3,4,5,6,7,7,8,9,10] it = iter(lst) for i in range(10): res = next(it) print(res) print(next(it)) print(next(it))
高階函式
1. map 元素加工
map(func,lierable)
功能:處理資料
把iterable 中的資料一個一個取出來,放到func函式中進行加工處理後將結果放到迭代器當中,最後返回迭代器
引數: fuunc 自定義函式,或者內建函式
iterable: 可迭代性資料( 容器類,range,迭代器)
返回值: 迭代器
# 常規寫法 lst_new = [] for i in lst: lst_new.append(int(i)) print(lst_new) # map改造 it = map(int,lst)
2. filter 資料過濾
filter(func, iterable)
功能: return Turn 當前這個資料保留
return False 當前這個資料捨棄
func: 自定義函式
iterable: 可迭代性資料( 容器型, range, 迭代器)
返回值: 迭代器
# 常規寫法 lst_new = [] for i in lst: if i % 2 == 0: lst_new.append(i) print(lst_new) # filter改寫 def func(i): if i % 2 == 0: return True else: return False it = filter(func,lst) # (1) next res = next(it) print(res)
3. reduce
reduce(func, iterable)
功能: 計算資料
先把iterable中的前倆個值取出,放到func中計算,把結算的結果和iterable中的第三個元素在放到func中計算,依次類推,直到所有結果都運算完畢,返回結果
func: 自定義函式
iterable: 可迭代資料(容器,range, 迭代器)
返回值: 計算之後的結果
# 常規寫法 lst = [5,4,8,8] # => 整型5488 # 方法一 strvar = "" for i in lst: strvar += str(i) print(strvar , type(strvar)) res = int(strvar) print(res , type(res))
4. sorted
sorted( iterable , key=func,reverse = False)
功能 : 排序
iterable : 可迭代資料( 容器,range,迭代器)
key:指定自定義函式,內建函式
reverse: 代表升序或者降序預設是升序( 從小到大排序) reverse=False
返回值: 排序後的結果
# 1.預設是從小到大排序 lst = [1,2,3,4,5,-90,-4,-1,100] res = sorted(lst) print(res) # 2.reverse 從大到小排序 res = sorted(lst,reverse=True) print(res) # 3.指定函式進行排序 # 按照絕對值排序 abs lst = [-10,-1,3,5] res = sorted(lst,key=abs) """ -1 => abs(-1) => 1 3 => abs(3) => 3 5 => abs(5) => 5 -10 => abs(-10) => 10 [-1, 3, 5, -10] """ print(res) # 4.使用自定義函式進行排序 lst = [19,21,38,43,55] def func(n): return n % 10 lst = sorted(lst,key=func) print(lst) """ 21 => n % 10 => 1 43 => n % 10 => 3 55 => n % 10 => 5 38 => n % 10 => 8 19 => n % 10 => 9 21 43 55 38 19 """ # ### sorted 和 sort 之間的區別 #字串 container = "eadc" #列表 container = [19,21,38,43,55] #元組 container = (19,21,38,43,55) #集合 container = {19,21,38,43,55} #字典 (排序的是字典的鍵) container = {"c":3,"a":1,"b":2} container = {"王聞":3,"高雲峰":2} print("<===>") res = sorted(container) print(res) #(1) sorted可以排序一切容器型別資料, sort只能排列表 #(2) sorted返回的是新列表,sort是基於原有的列表進行修改 #(3) 推薦使用sorted
推導式
語法:
val for val in iterable
三種推導式方式
[val for val initerable]
{val for val in iterable}
{ k:v for k,v in iterable }
1. 基礎語法:
推導式的語法: val for val in Iterable 三種方式: [val for val in Iterable] {val for val in Iterable} {k:v for k,v in Iterable} # 改寫成推導式 lst = [i*3 for i in lst] print(lst)
2. 帶有判斷條件的單迴圈推導式
# (2) 帶有判斷條件的單迴圈推導式 (只能是單項分支,接在for後面) lst = [1,2,3,4,5,6,7,8] lst_new = [] for i in lst: if i % 2 == 1: lst_new.append(i) print(lst_new) # 改寫成推導式 lst = [i for i in lst if i % 2 == 1] print(lst)
3. 雙迴圈推導式
# (3) 雙迴圈推導式 lst1 = ["李博倫","高雲峰","孫致和","葛龍"] lst2 = ["李亞","劉彩霞","劉子豪","劉昕"] # "誰"❤"誰" lst_new = [] for i in lst1: for j in lst2: strvar = i + "❤" + j lst_new.append(strvar) print(lst_new) # 改寫成推導式 lst = [i + "❤" + j for i in lst1 for j in lst2] print(lst)
4. 帶有判斷條件的雙迴圈推導式
# (4) 帶有判斷條件的多迴圈推導式 lst_new = [] for i in lst1: for j in lst2: if lst1.index(i) == lst2.index(j): strvar = i + "❤" + j lst_new.append(strvar) print(lst_new) # 改寫成推導式 lst = [ i + "❤" + j for i in lst1 for j in lst2 if lst1.index(i) == lst2.index(j) ] print(lst)
集合推導式
常規寫法
# 常規寫法 setvar = set() for i in listvar: if 18 <= i["age"] <= 21 and 5000 <= i["money"] <= 5500: res = "尊貴VIP卡老" + i["name"][0] else: res = "摳腳大漢卡老" + i["name"][0] setvar.add(res) print(setvar) # 改寫成集合推導式 # {三元運算子 + 推導式} setvar = { "尊貴VIP卡老" + i["name"][0] if 18 <= i["age"] <= 21 and 5000 <= i["money"] <= 5500 else "摳腳大漢卡老" + i["name"][0] for i in listvar } print(setvar)
字典推導式
enumerate
enumerate(iterable,[start=0]) 功能:列舉 ; 將索引號和iterable中的值,一個一個拿出來配對組成元組放入迭代器中 引數: iterable: 可迭代性資料 (常用:迭代器,容器型別資料,可迭代物件range) start: 可以選擇開始的索引號(預設從0開始索引) 返回值:迭代器 """ from collections import Iterator lst = ["東邪","西毒","南帝","北丐"] # 基本使用 it = enumerate(lst) print(isinstance(it,Iterator)) # for + next for i in range(4): print(next(it)) # list """start可以指定開始值,預設是0""" it = enumerate(lst,start=1) print(list(it)) # enumerate 形成字典推導式 變成字典 dic = { k:v for k,v in enumerate(lst,start=1) } print(dic) # dict 強制變成字典 dic = dict(enumerate(lst,start=1)) print(dic)
zip
""" zip(iterable, ... ...) 功能: 將多個iterable中的值,一個一個拿出來配對組成元組放入迭代器中 iterable: 可迭代性資料 (常用:迭代器,容器型別資料,可迭代物件range) 返回: 迭代器 特徵: 如果找不到對應配對的元素,當前元素會被捨棄 """ # 基本使用 lst1 = ["晏國彰","劉子濤","郭凱","宋雲傑"] lst2 = ["劉有右柳翔","馮雍","孫志新"] lst3 = ["周鵬飛","袁偉倬"] # it = zip(lst1,lst2) it = zip(lst1,lst2,lst3) print(isinstance(it,Iterator)) print(list(it)) """ [('晏國彰', '劉有右柳翔'), ('劉子濤', '馮雍'), ('郭凱', '孫志新')] [('晏國彰', '劉有右柳翔', '周鵬飛'), ('劉子濤', '馮雍', '袁偉倬')] """ # zip 形成字典推導式 變成字典 lst1 = ["晏國彰","劉子濤","郭凱","宋雲傑"] lst2 = ["劉有右柳翔","馮雍","孫志新"] dic = { k:v for k,v in zip(lst1,lst2) } print(dic) # dict 強制變成字典 dic = dict(zip(lst1,lst2)) print(dic)
生成器表示式
生成器本質就是迭代器, 允許自定義邏輯的迭代器
迭代器與生成器的區別:
迭代器本身是系統內建的,重寫不了,而生成器是使用者自定義的,可以重寫迭代邏輯
生成器的倆種建立方式
1. 生成器表示式( 裡面是推導式,外面用圓括號)
2. 生成器函式( 用def定義 ,裡面含有yield)
from collections import Iterator,Iterable # 生成器表示式 gen = (i*2 for i in range(1,11)) print(isinstance(gen,Iterator)) # next res = next(gen) print(res) # for for i in gen: print(i) # for + next gen = (i*2 for i in range(1,11)) for i in range(3): res = next(gen) print(res) # list print("<=====>") res = list(gen) print(res)
生成器函式
yield 類似於 return
共同點: 執行到該點時,都會把值返回出去.
不同點: yield 每次返回時 ,會記住上次離開時執行的位置,下次在呼叫生成器,會從上次執行的位置繼續向下執行,而return,會直接終止函式,不可以重頭呼叫.
yield 6 和yield(6) 倆種寫法都可以 yield 6 更像 return 6 的寫法 ( 推薦使用)
生成器函式的基本用法
定義一個生成器函式 def mygen(): print(111) yield 1 print(222) yield 2 print(333) yield 3 #初始化生成器函式,返回生成器物件,簡稱生成器 gen = mygen() print(isinstance(gen,Iterator)) #使用next呼叫 res = next(gen) print(res) res = next(gen) print(res) res = next(gen) print(res) #res = next(gen) error #print(res) """ 程式碼解析: 初始化生成器函式 -> 生成器(通過next呼叫) 第一次呼叫生成器 res = next(gen) => print(111) yield 1 儲存當前程式碼狀態14行,並將1這個值返回 print(1) ,等待下一次呼叫 第二次呼叫生成器 res = next(gen) => 從上一次儲存的狀態14行繼續向下執行 print(222) yield 2 儲存當前程式碼狀態17行,並將2這個值返回 print(2) ,等待下一次呼叫 第三次呼叫生成器 res = next(gen) => 從上一次儲存的狀態17行繼續向下執行 print(333) yield 3 儲存當前程式碼狀態20行,並將3這個值返回 print(3) ,等待下一次呼叫 第四次呼叫生成器 因為沒有更多yield返回資料了,所以直接報錯. """
優化程式碼
def mygen(): for i in range(1,101): yield “該球衣號碼是{}”.format(i) #初始化生成器函式 -> 生成器 gen = mygen() #for + next 呼叫資料 for i in range(50): res = next(gen) print(res) print("<====>") for i in range(30): res = next(gen) print(res)
send 的用法
# 3.send 用法 """ ### send # next和send區別: next 只能取值 send 不但能取值,還能傳送值 # send注意點: 第一個 send 不能給 yield 傳值 預設只能寫None 最後一個yield 接受不到send的傳送值 send 是給上一個yield傳送值 """ def mygen(): print("process start") res = yield 100 print(res,"內部列印1") res = yield 200 print(res,"內部列印2") res = yield 300 print(res,"內部列印3") print("process end") # 初始化生成器函式 -> 生成器 gen = mygen() # 在使用send時,第一次呼叫必須傳遞的引數是None(硬性語法),因為第一次還沒有遇到上一個yield '''第一次呼叫''' res = gen.send(None) #<=> next(gen) print(res) '''第二次呼叫''' res = gen.send(101) #<=> next(gen) print(res) '''第三次呼叫''' res = gen.send(201) #<=> next(gen) print(res) '''第四次呼叫, 因為沒有更多的yield返回資料了,所以StopIteration''' """ res = gen.send(301) #<=> next(gen) print(res) """ """ # 程式碼解析: 初始化生成器函式,返回生成器物件 第一次呼叫時, print("process start") res = yield 100 記錄當前程式碼狀態81行,返回100,等待下一次呼叫 res = 100 print(100) 第二次呼叫時, 把101 傳送給上一個yield儲存的狀態81行 res = 101 從81行繼續往下走 print(101,"內部列印1") res = yield 200 記錄當前程式碼狀態84行,返回200,等待下一次呼叫 res = 200 print(200) 第三次呼叫時, 把201 傳送給上一個yield儲存的狀態84行 res = 201 從84行繼續往下走 print(201,"內部列印2") res = yield 300 記錄當前程式碼狀態87行,返回300,等待下一次呼叫 res = 300 print(300) """
yield from : 將一個迭代器物件變成一個迭代器返回
# 4.yield from : 將一個可迭代物件變成一個迭代器返回 def mygen(): yield from ["馬生平","劉彩霞","餘銳","晏國彰"] gen = mygen() print(next(gen)) print(next(gen)) print(next(gen)) print(next(gen)) # 5.用生成器描述斐波那契數列 """1 1 2 3 5 8 13 21 34 ... """ """ yield 1 a,b = b,a+b = 1,1 yield 1 a,b = b,a+b = 1,2 yield 2 a,b = b,a+b = 2,3 yield 3 a,b = b,a+b = 3,5 yield 5 .... """ def mygen(maxlen): a,b = 0,1 i = 0 while i < maxlen: yield b a,b = b,a+b i+=1 # 初始化生成器函式 -> 生成器 gen = mygen(10) for i in range(3): print(next(gen))
遞迴函式
遞迴函式: 自己呼叫自己的函式就是遞迴函式( 遞: 就是去 歸: 就是回 一去一回就是遞迴)
def digui(n): print(n,"<====1===>") if n > 0: digui(n-1) print(n,"<====2===>") digui(5) """ # 程式碼解析: 去的過程: n = 5 print(5,"<====1===>") 5>0 條件成立-> digui(5-1) => digui(4) 程式碼阻塞在第13行 n = 4 print(4,"<====1===>") 4>0 條件成立-> digui(4-1) => digui(3) 程式碼阻塞在第13行 n = 3 print(3,"<====1===>") 3>0 條件成立-> digui(3-1) => digui(2) 程式碼阻塞在第13行 n = 2 print(2,"<====1===>") 2>0 條件成立-> digui(2-1) => digui(1) 程式碼阻塞在第13行 n = 1 print(1,"<====1===>") 1>0 條件成立-> digui(1-1) => digui(0) 程式碼阻塞在第13行 n = 0 print(0,"<====1===>") 0>0 條件不成立 print(0,"<====2===>") 當前這層空間程式碼已經執行結束 此刻觸發回的過程 n = 1 從上一次13行的程式碼阻塞位置,繼續向下執行 print(1,"<====2===>") n = 2 從上一次13行的程式碼阻塞位置,繼續向下執行 print(2,"<====2===>") n = 3 從上一次13行的程式碼阻塞位置,繼續向下執行 print(3,"<====2===>") n = 4 從上一次13行的程式碼阻塞位置,繼續向下執行 print(4,"<====2===>") n = 5 從上一次13行的程式碼阻塞位置,繼續向下執行 print(5,"<====2===>") 到此,遞迴函式徹底執行結束. 5 4 3 2 1 0 0 """
每一次呼叫函式時 ,在記憶體中都會單獨開闢一片空間,配合函式執行,這個空間叫做棧幀空間
1).遞迴是一去一回的過程, 呼叫函式時,會開闢棧幀空間,函式執行結束之後,會釋放棧幀空間 遞迴實際上就是不停的開闢和釋放棧幀空間的過程 每次開闢棧幀空間,都是獨立的一份,其中的資源不共享 (2).觸發回的過程 1.當最後一層棧幀空間全部執行結束的時候,會觸底反彈,回到上一層空間的呼叫處 2.遇到return,會觸底反彈,回到上一層空間的呼叫處, (3).寫遞迴時,必須給與遞迴跳出的條件,否則會發生記憶體溢位,藍屏宕機的情況. 如果遞迴層數過多,不推薦使用遞迴 """ # 官方說法:預設1000層,實際996層左右 """ def func(): func() func() """
遞迴階乘
# (1) 用遞迴計算n的階乘 # 常規方法 # 5! = 5*4*3*2*1 def func(n): total = 1 for i in range(n,0,-1): total *= i return total res = func(5) print(res) # 遞迴寫法 def jiecheng(n): if n <= 1: return 1 return n*jiecheng(n-1) res = jiecheng(5) print(res) """ return 後面的表示式,一定是先計算完在返回 # 程式碼解析: # 去的過程: n = 5 return 5*jiecheng(5-1) => 5 * jiecheng(4) n = 4 return 4*jiecheng(4-1) => 4 * jiecheng(3) n = 3 return 3*jiecheng(3-1) => 3 * jiecheng(2) n = 2 return 2*jiecheng(2-1) => 2 * jiecheng(1) n = 1 return 1 # 回的過程: n = 2 return 2*jiecheng(2-1) => 2 * jiecheng(1) => 2 * 1 n = 3 return 3*jiecheng(3-1) => 3 * jiecheng(2) => 3 * 2 * 1 n = 4 return 4*jiecheng(4-1) => 4 * jiecheng(3) => 4 * 3 * 2 * 1 n = 5 return 5*jiecheng(5-1) => 5 * jiecheng(4) => 5 * 4 * 3 * 2 * 1 return 5 * 4 * 3 * 2 * 1 => return 120 額外解析: jiecheng(1) => 1 jiecheng(2) => 2*jiecheng(1) => 2*1 jiecheng(3) => 3*jiecheng(2) => 3*2*1 jiecheng(4) => 4*jiecheng(3) => 4*3*2*1 jiecheng(5) => 5*jiecheng(4) => 5* 4*3*2*1 """ print(jiecheng(1))
尾遞迴
""" 自己呼叫自己,並且非表示式 計算的結果要在引數當中完成. 尾遞迴無論呼叫多少次函式,都只佔用一份空間,但是目前cpython不支援. """ def jiecheng(n,endval): if n <= 1: return endval return jiecheng(n-1,endval*n) res = jiecheng(5,1) print(res) """ # 程式碼解析: 去的過程 n=5 , endval=1 return jiecheng(5-1,endval*5) => jiecheng(4,1*5) n=4 , endval=1*5 return jiecheng(4-1,endval*4) => jiecheng(3,1*5*4) n=3 , endval=1*5*4 return jiecheng(3-1,endval*3) => jiecheng(2,1*5*4*3) n=2 , endval=1*5*4*3 return jiecheng(2-1,endval*2) => jiecheng(1,1*5*4*3*2) n=1 , endval=1*5*4*3*2 return 120 回的過程: n=2 return 120 n=3 return 120 n=4 return 120 n=5 return 120 因為最後一層空間的返回值就是第一層空間的返回值,所有在使用尾遞迴的時候 不需要考慮回的邏輯過程,就能解決問題.推薦使用. """ # 優化1 def jiecheng(n,endval=1): if n <= 1: return endval return jiecheng(n-1,endval*n) res = jiecheng(5) print(res,"<111>") # 優化2 """為了避免使用者亂傳引數,把endval這個引數隱藏起來""" def outer(n): def jiecheng(n,endval=1): if n <= 1: return endval return jiecheng(n-1,endval*n) return jiecheng(n) # jiecheng(n-1,endval*n) res = outer(5) print(res)
遞迴斐波那契數列
# 遞迴計算斐波那契數列 """ 1,1,2,3,5,8,13 .... n = 3 => 2 n = 5 => 5 n = 6 => 8 """ # 上一個 n-1 上上個 n-2 def feb(n): # 遞迴跳出的條件 if n <= 2: # n == 1 or n == 2 => 1 return 1 return feb(n-1) + feb(n-2) res = feb(5) print(res) """ 程式碼解析: n = 5 return feb(5-1) + feb(5-2) => feb(4) + feb(3) feb(4)->3 + feb(3)->2 => 5 feb(3) + feb(2) feb(2) + feb(1) feb(2)+feb(1) | 1 1 + 1 2 + 1 """