Python開發【第五篇】迭代器、生成器、遞歸函數、二分法

閱讀目錄

一.迭代器

1. 迭代的概念python

#迭代器即迭代的工具(自定義的函數),那什麼是迭代呢?
#迭代:指一個重複的過程,每次重複均可以稱之爲一次迭代,而且每一次重複的結果是下一個迭代的初始值(例如:罰寫做業100遍)

while True: #只是單純地重複,於是不是迭代 print('===>') l=[1,2,3] count=0 while count < len(l): #迭代 print(l[count]) count+=1

2.爲什麼要有迭代器? 什麼是可迭代對象? 什麼是迭代器對象?算法

#一、爲什麼要有迭代器?
對於序列類型:字符串、列表、元組,咱們可使用索引的方式迭代取出其包含的元素。但對於字典、集合、文件等類型是沒有索引的,若還想取出其內部包含的元素,則必須找出一種不依賴於索引的迭代方式,這就是迭代器

#二、什麼是可迭代對象?
可迭代對象指的是內置有__iter__方法的對象,即obj.__iter__,以下
'hello'.__iter__
(1,2,3).__iter__
[1,2,3].__iter__
{'a':1}.__iter__
{'a','b'}.__iter__
open('a.txt').__iter__

#三、什麼是迭代器對象?
可迭代對象執行obj.__iter__()獲得的結果就是迭代器對象
而迭代器對象指的是即內置有__iter__又內置有__next__方法的對象

文件類型是迭代器對象
open('a.txt').__iter__()
open('a.txt').__next__()


#四、注意:
迭代器對象必定是可迭代對象,而可迭代對象不必定是迭代器對象
爲什麼要有迭代器?什麼是可迭代對象?什麼是迭代器對象?

 3.迭代器對象的使用express

dic={'a':1,'b':2,'c':3}
iter_dic=dic.__iter__() #獲得迭代器對象,迭代器對象即有__iter__又有__next__,可是:迭代器.__iter__()獲得的仍然是迭代器自己
iter_dic.__iter__() is iter_dic #True

print(iter_dic.__next__()) #等同於next(iter_dic)
print(iter_dic.__next__()) #等同於next(iter_dic)
print(iter_dic.__next__()) #等同於next(iter_dic)
# print(iter_dic.__next__()) #拋出異常StopIteration,或者說結束標誌

#有了迭代器,咱們就能夠不依賴索引迭代取值了
iter_dic=dic.__iter__()
while 1:
    try:
        k=next(iter_dic)
        print(dic[k])
    except StopIteration:
        break
        
#這麼寫太醜陋了,須要咱們本身捕捉異常,控制next,python這麼牛逼,能不能幫我解決呢?能,請看for循環
迭代器對象的使用

4. for循環原理編程

#基於for循環,咱們能夠徹底再也不依賴索引去取值了
dic={'a':1,'b':2,'c':3}
for k in dic:
    print(dic[k])

#for循環的工做原理
#1:執行in後對象的dic.__iter__()方法,獲得一個迭代器對象iter_dic
#2: 執行next(iter_dic),將獲得的值賦值給k,而後執行循環體代碼
#3: 重複過程2,直到捕捉到異常StopIteration,結束循環

5. 迭代器的優缺點數據結構

#優勢:
  - 提供一種統一的、不依賴於索引的迭代方式
  - 惰性計算,節省內存
#缺點:
  - 沒法獲取長度(只有在next完畢才知道到底有幾個值)
  - 一次性的,只能日後走,不能往前退

二. 生成器

  1. 什麼是生成器app

   生成器:常規函數定義,可是,使用yield語句而不是return語句返回結果。yield語句一次返回一個結果,在每一個結果中間,掛起函數的狀態,以便下次重它離開的地方繼續執行ide

   生成器表達式:相似於列表推導,可是,生成器返回按需產生結果的一個對象,而不是一次構建一個結果列表函數

#只要函數內部包含有yield關鍵字,那麼函數名()的到的結果就是生成器,而且不會執行函數內部代碼
def func():
    print('====>first')
    yield 1
    print('====>second')
    yield 2
    print('====>third')
    yield 3
    print('====>end')
g=func()
print(g) #<generator object func at 0x0000000002184360>

  2.生成器就是迭代器工具

g.__iter__
g.__next__
#因此生成器就是迭代器,所以能夠這麼取值
res=next(g)
print(res)

  3.生成器Generator總結:性能

    本質:迭代器(因此自帶了__iter__方法和__next__方法,不須要咱們去實現)

    特色:惰性運算,開發者自定義

  4.生成器函數

    一個包含yield關鍵字的函數就是一個生成器函數。yield能夠爲咱們從函數中返回值,可是yield又不一樣於return,return的執行意味着程序的結束,調用生成器函數不會獲得返回的具體的值,而是獲得一個可迭代的對象。每一次獲取這個可迭代對象的值,就能推進函數的執行,獲取新的返回值。直到函數執行結束。

import time
def generator_fun1():
    a = 1
    print('如今定義了a變量')
    yield a
    b = 2
    print('如今又定義了b變量')
    yield b

g1 = generator_fun1()
print('g1 : ',g1)       #打印g1能夠發現g1就是一個生成器
print('-'*20)   #我是華麗的分割線
print(next(g1))
time.sleep(1)   #sleep一秒看清執行過程
print(next(g1))

  5.生成器有什麼好處呢?

    1.延遲計算,一次返回一個結果。也就是說,它不會一次生成全部的結果,這對於大數據量處理,將會很是有用。

# 假如我想讓工廠給學生作校服,生產2000000件衣服,我和工廠一說,工廠應該是先答應下來,而後再去生產,我能夠一件一件的要,也能夠根據學生一批一批的找工廠拿。
# 而不能是一說要生產2000000件衣服,工廠就先去作生產2000000件衣服,等回來作好了,學生都畢業了。。。

def produce():
    """生產衣服"""
    for i in range(2000000):
        yield "生產了第%s件衣服"%i

product_g = produce()
print(product_g.__next__()) #要一件衣服
print(product_g.__next__()) #再要一件衣服
print(product_g.__next__()) #再要一件衣服
num = 0
for i in product_g:         #要一批衣服,好比5件
    print(i)
    num +=1
    if num == 5:
        break

#到這裏咱們找工廠拿了8件衣服,我一共讓生產函數(也就是produce生成器函數)生產2000000件衣服。
#剩下的還有不少衣服,咱們能夠一直拿,也能夠放着等想拿的時候再拿
示例

  6.練習

#一、自定義函數模擬range(1,7,2)

#二、模擬管道,實現時時獲取文件中最新內容
# 一、自定義函數模擬range(1,7,2)
def myRange(start,stop,step=1):
    while start < stop:
        yield start
        start += step

obj = myRange(1,7,2)
print(next(obj))
print(next(obj))
print(next(obj))
print(next(obj)) #StopIteration

#二、模擬管道,實現時時獲取文件中最新內容
import time
def tail(filename):
    with open(filename,'r',encoding='utf-8')as f:
        f.seek(0,2) #從文件末尾開始讀取
        while True:
            line = f.readline()
            if not line:
                time.sleep(0.5)
                continue
            yield line

obj = tail('a.txt')
for i in obj:
    print(i)
代碼示例

    7.協程函數

  什麼是協程:

  協程是一個無優先級的子程序調度組件,容許子程序在特色的地方掛起恢復。(相似於看電影時的暫停播放)

  線程包含於進程,協程包含於線程。只要內存足夠,一個線程中能夠有任意多個協程,但某一時刻只能有一個協程在運行,多個協程分享該線程分配到的計算機資源。

#yield關鍵字的另一種使用形式:表達式形式的yield
def eater(name):
    print('%s 準備開始吃飯啦' % name)
    food_list=[]
    while True:
        food = yield food_list
        print('%s 吃了 %s' % (name, food))
        food_list.append(food)

e=eater('鋼蛋')
print(e.send(None))  #初始化,對於表達式形式的yield,在使用時,第一次必須傳None,e.send(None)等同於next(e)
print(e.send('包子'))  
print(e.send('韭菜餡包子'))
print(e.send('大蒜包子'))
e.close() #關閉
print(e.send('大麻花'))
#send 獲取下一個值的效果和next基本一致 #只是在獲取下一個值的時候,給上一yield的位置傳遞一個數據 #使用send的注意事項 # 第一次使用生成器的時候 是用send(None)或者 next進行初始化

 8.練習:

  一、編寫裝飾器,實現初始化協程函數的功能

def init(func):
    def inner(*args,**kwargs):
        res = func(*args,**kwargs)
        next(res) #在裝飾器中執行初始化方法
        return res
    return  inner

@init
def eater(name):
    print('%s 準備開始吃飯啦' % name)
    food_list=[]
    while True:
        food = yield food_list
        print('%s 吃了 %s' % (name, food))
        food_list.append(food)

e=eater('鋼蛋')
# e.send(None)
print(e.send('包子'))
print(e.send('韭菜餡包子'))
print(e.send('大蒜包子'))
裝飾器實現初始化協程方法

 9. yield 關鍵字 總結

一、把函數作成迭代器
二、對比return,能夠返回屢次值,能夠掛起/保存函數的運行狀態

三. 列表推導式、生成器表達式

  1.列表推導式

#一、示例
egg_list=[]
for i in range(10):
    egg_list.append('雞蛋%s' %i)

print(egg_list)

#列表推導式1
egg_list = ['臭雞蛋%s' %i for i in range(0,10) ]
print(egg_list)

#列表推導式2
egg_list = ['臭雞蛋%s' %i for i in range(0,10) if i>6]
print(egg_list)

#二、語法
[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
...
for itemN in iterableN if conditionN
]
相似於
res=[]
for item1 in iterable1:
    if condition1:
        for item2 in iterable2:
            if condition2
                ...
                for itemN in iterableN:
                    if conditionN:
                        res.append(expression)

#三、優勢:方便,改變了編程習慣,可稱之爲聲明式編程

  2.生成器表達式

#生成器表達式
#一、把列表推導式的[]換成()就是生成器表達式

#二、示例:生一筐雞蛋變成給你一隻老母雞,用的時候就下蛋,這也是生成器的特性
chicken=('雞蛋%s' %i for i in range(5))

print(chicken) # generator object <genexpr> at 0x10143f200>

print(next(chicken)) #'雞蛋0'

print(list(chicken)) #因chicken可迭代,於是能夠轉成列表 ['雞蛋1', '雞蛋2', '雞蛋3', '雞蛋4',]


#三、優勢:省內存,一次只產生一個值在內存中

  總結:

    1.把列表解析的[]換成()獲得的就是生成器表達式

    2.列表解析與生成器表達式都是一種便利的編程方式,只不過生成器表達式更節省內存

 4.聲明式編程練習題

一、將names=['egon','alex_sb','wupeiqi','yuanhao']中的名字所有變大寫

二、將names=['egon','alex_sb','wupeiqi','yuanhao']中以sb結尾的名字過濾掉,而後保存剩下的名字長度

3、求文件a.txt中最長的行的長度(長度按字符個數算,須要使用max函數)

4、求文件a.txt中總共包含的字符個數?思考爲什麼在第一次以後的n次sum求和獲得的結果爲0?(須要使用sum函數)

5、思考題
with open('a.txt') as f:
    g=(len(line) for line in f)
print(sum(g)) #爲什麼報錯?
# 一、將names=['egon','alex_sb','wupeiqi','yuanhao']中的名字所有變大寫
names=['egon','alex_sb','wupeiqi','yuanhao']
names = [name.upper() for name in names]
print([name.upper() for name in names])

# 二、將names=['egon','alex_sb','wupeiqi','yuanhao']中以sb結尾的名字過濾掉,而後保存剩下的名字長度
names=['egon','alex_sb','wupeiqi','yuanhao']
names = [len(name) for name in names if not name.endswith("sb") ]
print(names)
# 三、求文件a.txt中最長的行的長度(長度按字符個數算,須要使用max函數)
with open('a.txt','r',encoding='utf-8')as f:
    print(max(len(i) for i in f)) #最後有一個換行符

# 四、求文件a.txt中總共包含的字符個數?思考爲什麼在第一次以後的n次sum求和獲得的結果爲0?(須要使用sum函數)
with open('a.txt','r',encoding='utf-8') as f:
    print(sum(len(i) for i in f))
    print(sum(len(i) for i in f))  #緣由讀取文件的指針已經到文件的末尾了
# 五、思考題
 with open('a.txt') as f:
     g=(len(line) for line in f)
 print(sum(g)) #爲什麼報錯?
#答: with open 在執行完文件操做後,會自動關閉掉此文件,因此再使用文件內容時會報錯
代碼示例

 四.遞歸函數

  1. 遞歸調用的定義

#遞歸調用是函數嵌套調用的一種特殊形式,函數在調用時,直接或間接調用了自身,就是遞歸調用

  2.遞歸分爲兩個階段:遞推,回溯

#問年齡遊戲
#圖解。。。
# age(4) = age(3) + 2
# age(3) = age(2) + 2
# age(2) = age(1) + 2
# age(1) = 40
 
def age(n):
    if n == 1:
        return 40
    else:
        return age(n-1)+2
 
print(age(4))

  3.遞歸的使用

#總結遞歸的使用:
1. 必須有一個明確的結束條件

2. 每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減小

3. 遞歸效率不高,遞歸層次過多會致使棧溢出(在計算機中,函數調用是經過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,
  每當函數返回,棧就會減一層棧幀。因爲棧的大小不是無限的,因此,遞歸調用的次數過多,會致使棧溢出)

4.遞歸默認調用的最大深度爲 ---997

 4.設置遞歸最大深度

#設置遞歸最大深度
#注意:實際上能夠達到的深度 取決於計算機的性能了
import sys
sys.setrecursionlimit(10000) #遞歸最大深度

def func1(n):
    print(">>>>>>",n)
    n+=1
    func1(n)

func1(0)

  5.練習題 

  1.使用遞歸函數完成三級菜單

menu = {
    '北京': {
        '海淀': {
            '五道口': {
                'soho': {},
                '網易': {},
                'google': {}
            },
            '中關村': {
                '愛奇藝': {},
                '汽車之家': {},
                'youku': {},
            },
            '上地': {
                '百度': {},
            },
        },
        '昌平': {
            '沙河': {
                '老男孩': {},
                '北航': {},
            },
            '天通苑': {},
            '回龍觀': {},
        },
        '朝陽': {},
        '東城': {},
    },
    '上海': {
        '閔行': {
            "人民廣場": {
                '炸雞店': {}
            }
        },
        '閘北': {
            '火車戰': {
                '攜程': {}
            }
        },
        '浦東': {},
    },
    '山東': {},
}
menu
def threeLM(dic):
    while True:
        for k in dic:print(k)

        key = input('input>>').strip()

        if key == 'b' or key == 'q':return key

        elif key in dic.keys() and dic[key]:
            ret = threeLM(dic[key])
            if ret == 'q': return 'q'

threeLM(menu)
#遞歸函數實現三級菜單
遞歸函數實現三級菜單

 五. 二分查找法

  想從一個按照從小到大排列的數字列表中找到指定的數字,遍歷的效率過低,用二分法(算法的一種,算法是解決問題的方法)能夠極大低縮小問題規模

  二分查找法 簡單版
#二分查找法
l=[1,2,10,30,33,99,101,200,301,402] #從小到大排列的數字列表
count = 0 #
def search(num,l):
    global count
    count+=1
    if l:
        print(l)  #查看列表的變化
        mid = (len(l)-1)//2
        if num > l[mid]:
            l=l[mid+1:] # 取列表右邊數據
        elif num < l[mid]:
            l =l[:mid]  # 取列表左邊數據
        else:
            print(l[mid])
            return  #經過return 終止遞歸
        search(num,l)   #遞歸循環調用
    else:
        print("沒有找到指定數字")

search(10,l)
print(count)

 二分查找法升級版  

#二分查找法 升級版
l=[1,2,10,30,33,99,101,200,301,402]

def search(num,l,start=0,stop=len(l)-1):
    if start <= stop:
        mid=start+(stop-start)//2
        print('start:[%s] stop:[%s] mid:[%s] mid_val:[%s]' %(start,stop,mid,l[mid]))
        if num > l[mid]:
            start=mid+1
        elif num < l[mid]:
            stop=mid-1
        else:
            print('find it',mid)
            return
        search(num,l,start,stop)
    else: #若是stop > start則意味着列表實際上已經所有切完,即切爲空
        print('not exists')
        return

search(101,l)

 

六 練習 

#1.使用遞歸打印斐波那契數列(前兩個數的和獲得第三個數,如:0 1 1 2 3 4 7...)

#2.一個嵌套不少層的列表,如l=[1,2,[3,[4,5,6,[7,8,[9,10,[11,12,13,[14,15]]]]]]],用遞歸取出全部的值
#1.使用遞歸打印斐波那契數列(前兩個數的和獲得第三個數,如:0 1 1 2 3 4 7...)

#非遞歸
def fib(n):
    a,b=0,1
    while a < n:
        print(a,end=' ')
        a,b=b,a+b
    print()

fib(10)
#遞歸
def fib(a,b,stop):
    if  a > stop:
        return
    print(a,end=' ')
    fib(b,a+b,stop)

fib(0,1,10)

#2. 一個嵌套不少層的列表,如l=[1,2,[3,[4,5,6,[7,8,[9,10,[11,12,13,[14,15]]]]]]],用遞歸取出全部的值

l=[1,2,[3,[4,5,6,[7,8,[9,10,[11,12,13,[14,15]]]]]]]

def get(seq):
    for item in seq:
        if type(item) is list:
            get(item)
        else:
            print(item)
get(l)
代碼示例
相關文章
相關標籤/搜索