1. 程式人生 > 實用技巧 >Python (進階 第二部)

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關鍵字在函式內部修改一個全域性變數

函式名的使用

    1. 函式名是個特殊的變數,可以當做變數賦值
      a = “你好”
      print(a)
      a = func
      a()

      函式可以像變數一樣銷燬

    2. 函式名可以作為容器型別資料的元素

    3. 函式名可以作為函式的引數

    4. 函式名可作為函式的返回值

閉包

如果內函式使用了外函式的區域性變數,並且外函式把內函式返回的過程,叫做閉包.

被外函式返回出的函式就是閉包函式

基礎語法

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()

匿名函式

用一句話來表達只有返回值的函式

  1. 語法: lambda 引數: 返回zhi
  2. 追求程式碼簡潔高效
  3. 無參的lambda表示式
    1. def func():
       return "123"
      func = lambda : "123"
      res = func()
      print(res)

  4. 有參的lambda表示式
    1. def func(n):
       return type(n)
      
      func = lambda n : type(n)
      print(  func([1,2,3])  )

  5. 帶有判斷條件的lambda表示式
    1. def func(n):
       if n % 2 == 0:
        return "偶數"
       else:
        return "奇數"
      
      func = lambda n : "偶數" if n % 2 == 0 else "奇數"
      res = func(17)
      print(res)

  6. 三元運算
    1. 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
"""