迭代器 生成器

一 迭代器

一 迭代的概念

#迭代器即迭代的工具,那什么是迭代呢?
#迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值(每次循环依赖上一次循环的结果) while True: #只是单纯地重复,因而不是迭代 print('===>') l=[1,2,3] count=0 while count < len(l): #迭代 print(l[count]) count+=1

二 为何要有迭代器?什么是可迭代对象?什么是迭代器对象?

#1、为何要有迭代器?
对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。
但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器。
利用for循环来遍历
字典、集合、文件。
#2、什么是可迭代对象? 可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,如下 'hello'.__iter__ (1,2,3).__iter__ [1,2,3].__iter__ {'a':1}.__iter__ {'a','b'}.__iter__ open('a.txt').__iter__ #3、什么是迭代器对象?遵循可迭代协议: 可迭代对象执行obj.__iter__()得到的结果就是迭代器对象 而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象 文件类型是迭代器对象 open('a.txt').__iter__() open('a.txt').__next__() #4、注意: 迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象。
如字典列表等,for循环可以执行

三 迭代器对象的使用

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循环
四 for循环:提供统一的迭代。都转化为可迭代对象,不区分原来有没有索引
能被for循环的全都要有iter方法。对于字典调用next方法循环的就是key,所以字典执行for循环得到的默认为key
可以遍历序列和非序列类型。
#基于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,结束循环

但while不能

五 迭代器的优缺点

#优点:
  - 提供一种统一的、不依赖于索引的迭代方式
  - 惰性计算,节省内存
#缺点:
  - 无法获取长度(只有在next完毕才知道到底有几个值)
  - 一次性的,只能往后走,不能往前退
l=['die','erzi','sunzi','chongsunzi']  #全放内存

iter_l=l.__iter__()            #只得到迭代器对象,指向内存地址,便于传输中
print(iter_l)
# 方法一,直接用iter_l.__next__()
print(iter_l.__next__())
print(iter_l.__next__())
print(iter_l.__next__())
print(iter_l.__next__())
print(iter_l.__next__())

# 方法二:用next()来取值
# next()为python中内置的函数,就是在调用iter_l.__next__()为数据类型中内置的
print(next(iter_l)) #next()---->iter_l.__next__()

# 1. 迭代器就是可迭代对象

二 生成器(是数据类型,是可迭代对象)

一 什么是生成器:python不同方式的生成器

1.生成器函数:常规函数定义。但使用yield语句而不是return来返回结果。yield一次返回一个结果,在每个结果中间挂起函数状态。以便下次从他离开的地方继续执行。

2.生成器表达式:类似于列表推导。但生成器返回产生结果的一个对象,而不是一次构建一个结果列表

# 自动实现了迭代器协议,生成器自动就具有next,
# 1.生成器函数形式:
#只要函数内部包含有yield关键字,那么函数名()的到的结果就是生成器,并且不会执行函数内部代码 def func(): print('====>first') yield 1          # 相当于了return,但可以执行多次;而且yield可以保存状态。 print('====>second') yield 2 print('====>third') yield 3 print('====>end') g=func()        ##得到生成器对象 print(g)     #<generator object func at 0x0000000002184360> 
# 此时函数还没有运行,并没有返回值,只是得到了生成器
print(g._next_())    #====>first 1 调用next执行生成器函数
print(g._next_())    #2     从第一次执行完的位置继续,直到碰到yield

#生成器调用next函数的原理:执行函数,直到碰到一个yield,就返回一个值。
#紧接着再执行一次,就在上一次的基础上继续执行直到遇到下一个yield。

二 生成器就是迭代器

# 因此生成器包含以下两功能:封装起来
g.__iter__ g.__next__ #2、所以生成器就是迭代器,因此可以这么取值 res=next(g) print(res)

1. 生成器表达式就是将列表解析中的[ ]变成()

2. 生成器表达式和列表解析都是一种便利的编程方式,只不过生成器更省内存。因为是基于next一个个取。

3. python中不但使用迭代器协议,让for循环变得更加通用,大部分内置函数,也是使用迭代器协议访问对象。

  基于迭代器:for、map、reduce、filter、sum等

#列表解析表达式:
g_l=[i for i in the range(10)]   #中括号
#生成器表达式:
g_l=(i for i in the range(10)) #圆括号
l=['a' for i in range(10)] #表示循环十次,每次都放入一个a到列表当中


#
python的内置函数sum()使用迭代器访问对象,而生成器实现迭代器协议。 sum(x **2 for x in xrange(4)) #而不用先构造一个列表,占内存:
sum[1,2,4,5,6,7]
#如果列表很大,必定占内存

生成器特点:1、语法上和函数类似:几乎一样,除了函数用return,生成器用yield

      2、自动实现迭代器协议:从而可以调用next方法,并且没有值返回时,生成器自动产生Stopiteration异常

      3、自动挂起:生成器使用yield返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行

      4、生成器只能遍历一次

生成器优点:

#####例1:生孩子
import time
def test():
    print('开始生孩子啦。。。。。。')
    print('开始生孩子啦。。。。。。')
    print('开始生孩子啦。。。。。。')
    yield '' #return
    time.sleep(3)
    print('开始生儿子啦')
    yield '儿子'

    time.sleep(3)
    print('开始生孙子啦')
    yield '孙子'

res=test()
print(res)
print(res.__next__()) #test()
print(res.__next__()) #test()
print(res.__next__()) #test()

########### 例2:卖包子
# 传统方法
def product_baozi():
    ret=[]
    for i in range(100):
        ret.append('一屉包子%s' %i)
    return ret
baozi_list=product_baozi()
print(baozi_list)

# 利用生成器
def product_baozi():
    for i in range(100):
        print('正在生产包子')
        yield '一屉包子%s' %i #i=1        #做好一个,有人要就直接给他了
        print('开始卖包子')
pro_g=product_baozi()

baozi1=pro_g.__next__()    # 正在生产包子 (光标停在yield那一行)
#加代码
baozi2=pro_g.__next__()    # 开始卖包子    正在生产包子  (从yield后面一行开始,然后进入下一轮循环,第二次遇到yield循环)


########## 例3:下蛋
def xiadan():
    ret=[]
    for i in range(10000):             #缺点1:列表要足够大
        ret.append('鸡蛋%s' %i)
    return ret

print(xiadan())
#缺点1:占空间大
#缺点2:效率低,需要执行完,才能取给别人

####改进:
def xiadan():
    for i in range(5):
        yield '鸡蛋%s' %i

alex_lmj=xiadan()
print(alex_lmj.__next__())
print(alex_lmj.__next__())
print(alex_lmj.__next__())
print(alex_lmj.__next__())
print(alex_lmj.__next__())
print(alex_lmj.__next__())
# 优点1:延迟计算。一次返回一个结果。不会一次性生成所有的结果。有利于大数据量处理。
######### 不用一次性全部弄出来,节省内存。来一个给一个,提高效率
# 优点2:生成器还能有效提高代码可读性
# 优点3:更加简洁,保证可读性的前提下尽可能减少代码

for jidan in alex_lmj:        #for循环了生成器。python中的for才能实现把类似函数的循环了
    print(jidan)

jidan=alex_lmj.__next__()
jidan=alex_lmj.__next__()
jidan=alex_lmj.__next__()
jidan=alex_lmj.__next__()
jidan=alex_lmj.__next__()
jidan=alex_lmj.__next__()

print('zhoushaochen 取鸡蛋',jidan)
人口问题:
#
统计出五个省人口分别占全体的比例,'人口普查'文件内容为: {'name':'北京','population':10} {'name':'山东','population':1000000} {'name':'山西','population':31} {'name':'河北','population':3110330000} {'name':'台湾','population':311030330} def get_polulation(): with open('人口普查', 'r', encoding='utf-8') as f: for i in f: yield i #函数的方法是(效率低): ret.append(i) return ret g=get_polulation() # g.__next__() # g.__next__() # g.__next__() # g.__next__() # print(g.__next__()['population']) #取出文件的记录,全是字符串,没办法操作 # s1=eval(g.__next__()) #利用eval将字符串中类型取出来生成一个字典 # print(type(s1)) #检查发现为<class 'dict'> # print(s1['population']) #10 res=0 # for p in g: # p_dic=eval(p) # print(p_dic['population']) # res+=p_dic['population'] # print(res) #上述步骤可以直接用sum来替代 all_pop=sum(eval(i)['population'] for i in g) #前面不能有调用next,否则这里不能取全部的 print(all_pop) # for p in g: #此g在sum循环的时候就已经迭代完了,此步骤无法执行了 # print('%s %%' %eval(p)['population']/all_pop) #获取每个省的人口信息
例:生产者—消费者模型
#_*_coding:utf-8_*_
__author__ = 'Alex Li'
#通过生成器实现协程并行运算,单线程实现了并发
# 生产过程和消费过程是并发的,同时运行
import time
def consumer(name):                        #生成器函数
    print("%s 准备吃包子啦!" %name)
    while True:
       baozi = yield 1

       print("包子[%s]来了,被[%s]吃了!" %(baozi,name))

def producer(name):
    c = consumer('A')            
    c2 = consumer('B')                #添加用户B
    print(c.__next__())                #此处第一次调用函数停在yield1的位置
    print(c2.__next__())
    print("老板开始准备做包子啦!")
    for i in range(10):                #能多次选择你想要的事物,利用for循环和send
        time.sleep(1)                #模拟生产包子需要的时间
        print("做了2个包子!")
        c.send(i)                    #此处第二次调用函数执行yield1下一行的打印操作
        c2.send(i)

producer("alex")

#####补充知识:send可以触发函数运行,并传值给yield
def test()
    print('开始了')
    yield 1
    print('第一次')
    yield 2
    print('第二次')

t=test()
print(t._next_())       #或者print(next(t))
# 开始了
# 1

t.send(None)                #send里一定要传值,哪怕写None。又在上一次next的基础上执行了一次函数
# 第一次
# yield 第一个特性:类似return
# t.send()执行效果和t._next_()一样。但此时None将会替代原来的yield返回值1,
def test()
    print('开始了')
    f = yield 1                #yield后面的1作为函数返回值,但给f的是send传给yield的值
    print('第一次',f)
    yield 2
    print('第二次')

# yield另外一个特性:f= yield接收send传过来的一个值
t=test()
print(t._next_())   #停在yield 1  
t.send(None)        #从yield继续执行,send中的传给yield,然后传给f。
# 开始了
# None
print(t._next_())        #2

三 练习

1、自定义函数模拟range(1,7,2)

2、模拟管道,实现功能:tail -f access.log | grep '404'

复制代码
#题目一:
def my_range(start,stop,step=1):
    while start < stop:
        yield start
        start+=step

#执行函数得到生成器,本质就是迭代器
obj=my_range(1,7,2) #1  3  5
print(next(obj))
print(next(obj))
print(next(obj))
print(next(obj)) #StopIteration

#应用于for循环
for i in my_range(1,7,2):
    print(i)

#题目二
import time
def tail(filepath):
    with open(filepath,'rb') as f:
        f.seek(0,2)
        while True:
            line=f.readline()
            if line:
                yield line
            else:
                time.sleep(0.2)

def grep(pattern,lines):
    for line in lines:
        line=line.decode('utf-8')
        if pattern in line:
            yield line

for line in grep('404',tail('access.log')):
    print(line,end='')

#测试
with open('access.log','a',encoding='utf-8') as f:
    f.write('出错啦404\n')
复制代码

四 协程函数

复制代码
#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)

g=eater('egon')
g.send(None) #对于表达式形式的yield,在使用时,第一次必须传None,g.send(None)等同于next(g)
g.send('蒸羊羔')
g.send('蒸鹿茸')
g.send('蒸熊掌')
g.send('烧素鸭')
g.close()
g.send('烧素鹅')
g.send('烧鹿尾')
复制代码

五 练习
1、编写装饰器,实现初始化协程函数的功能

2、实现功能:grep  -rl  'python'  /etc

复制代码
#题目一:
def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper
@init
def eater(name):
    print('%s 准备开始吃饭啦' %name)
    food_list=[]
    while True:
        food=yield food_list
        print('%s 吃了 %s' % (name,food))
        food_list.append(food)

g=eater('egon')
g.send('蒸羊羔')

#题目二:
#注意:target.send(...)在拿到target的返回值后才算执行结束
import os
def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper

@init
def search(target):
    while True:
        filepath=yield
        g=os.walk(filepath)
        for dirname,_,files in g:
            for file in files:
                abs_path=r'%s\%s' %(dirname,file)
                target.send(abs_path)
@init
def opener(target):
    while True:
        abs_path=yield
        with open(abs_path,'rb') as f:
            target.send((f,abs_path))
@init
def cat(target):
    while True:
        f,abs_path=yield
        for line in f:
            res=target.send((line,abs_path))
            if res:
                break
@init
def grep(pattern,target):
    tag=False
    while True:
        line,abs_path=yield tag
        tag=False
        if pattern.encode('utf-8') in line:
            target.send(abs_path)
            tag=True
@init
def printer():
    while True:
        abs_path=yield
        print(abs_path)


g=search(opener(cat(grep('你好',printer()))))
# g.send(r'E:\CMS\aaa\db')
g=search(opener(cat(grep('python',printer()))))
g.send(r'E:\CMS\aaa\db')
复制代码

六 yield总结

#1、把函数做成迭代器
#2、对比return,可以返回多次值,可以挂起/保存函数的运行状态
# 生成器只能迭代一次
问题1:
def test()
    for i in range(4):
        yield i
    
t=test()
#for i in t
#    print(i)          #得到0    1      2     3  

t1=(i for i in t)        # t1也是一个生成器
#for i in t1
#    print(i)          #得到0    1      2     3  

print(list(t1))        #遍历生成器,放入到一个列表中[0,1,2,3]

#情况2:
t=test()
for i in t
    print(i)  
t1=(i for i in t)       #由于t只能被遍历一遍,此处t1什么都读不到
print(list(t1))    
#0
#1
#2
#3
#[]    

#情况3:
t=test()                #此时t中什么都没有
t1=(i for i in t)            #取出没有遍历过的t
t2=(i for i in t)  
print(list(t1))             #0   1   2   3  此时把t1当中的值被取完了,
print(list(t2))             #[]    t2就什么都取不到了
#生成器在产生的时候,实质t中什么都没有

三 面向过程编程

复制代码
#1、首先强调:面向过程编程绝对不是用函数编程这么简单,面向过程是一种编程思路、思想,而编程思路是不依赖于具体的语言或语法的。言外之意是即使我们不依赖于函数,也可以基于面向过程的思想编写程序

#2、定义
面向过程的核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么

基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式

#3、优点:复杂的问题流程化,进而简单化

#4、缺点:可扩展性差,修改流水线的任意一个阶段,都会牵一发而动全身

#5、应用:扩展性要求不高的场景,典型案例如linux内核,git,httpd

#6、举例
流水线1:
用户输入用户名、密码--->用户验证--->欢迎界面

流水线2:
用户输入sql--->sql解析--->执行功能
复制代码

 ps:函数的参数传入,是函数吃进去的食物,而函数return的返回值,是函数拉出来的结果,面向过程的思路就是,把程序的执行当做一串首尾相连的功能,该功能可以是函数的形式,然后一个函数吃,拉出的东西给另外一个函数吃,另外一个函数吃了再继续拉给下一个函数吃。。。

posted on 2018-04-02 23:14  Josie_chen  阅读(109)  评论(0编辑  收藏  举报

导航