python之路-----迭代器 和生成器

迭代器

迭代器 和 可迭代对象

for 循环的本质就是一个python给我们提供的一个便捷迭代器

for循环的本质就是在内部调用了__next__方法才能取到一个一个的值。

#for 循环的对象可以是一个迭代对象 也可以是一个迭代器
#for[1,2,3]
# [1,2,3].__iter__()
# __next__()
for

能被for循环的就是可迭代的

可迭代协议

 --  可迭代协议
就是数据类型和python解释器定下来的协议
 '.__iter__'这个方法导致了一个数据类型的可迭代
只要包含了'双下iter'方法的数据类型就是可迭代的

在python里  你学过的所有的可以被for循环的 基本数据类型 都是可迭代的  而不是迭代器

迭代器----iterator

 

 

 

 迭代器协议

 迭代器必须要满足两点才可以叫做可迭代器

 :  内部实现了__iter__和__next__方法

 

迭代器的本质   就是for循环调用的底层内置函数的运用

可迭代对象:字符串、列表、元组、字典、集合都是可迭代的对象

 .............................

Iterable -- 可迭代的 形容词
iterator -- 迭代器 名词
....................................
迭代器包含可迭代对象
迭代器=可迭代对象.__iter__()

执行可迭代的Iterable方法就得到一个iterator迭代器

为什么要用迭代器?

第一个功能,能够对python中的基本数据类型进行统一的遍历,不需要关心每一个值是什么
第二个功能,他可以节省内存
第三,思想--惰性运算 只有在你要的时候__next__时才会执行调用

重要点''''只能迭代出当前和下一个数据 不能反向迭代 ,
文件句柄就是一个迭代器  f=open('','')
range 就是一个可迭代的对象 他也是为了节省内存存在的
 

 

通过代码来理解

'''
dir([1,2].__iter__())是列表迭代器中实现的所有方法,dir([1,2])是列表中实现的所有方法,都是以列表的形式返回给我们的,为了看的更清楚,我们分别把他们转换成集合,
然后取差集。
'''
#print(dir([1,2].__iter__()))
#print(dir([1,2]))
print(set(dir([1,2].__iter__()))-set(dir([1,2])))

结果:
{'__length_hint__', '__next__', '__setstate__'}
迭代器方法

迭代器中的三个方法的作用

iter_l = [1,2,3,4,5,6].__iter__()
#获取迭代器中元素的长度
print(iter_l.__length_hint__())
#根据索引值指定从哪里开始迭代
print('*',iter_l.__setstate__(4))
#一个一个的取值
print('**',iter_l.__next__())
print('***',iter_l.__next__())
方法的作用

在for循环中,就是在内部调用了__next__方法才能取到一个一个的值,,但是取不到值的时候会报错

经典  一眼就能看出来
l = [1,2,3,4]
l_iter = l.__iter__()
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)经典
经典

内置函数:next和iter方法

print('__next__' in dir(range(12)))  #查看'__next__'是不是在range()方法执行之后内部是否有__next__
print('__iter__' in dir(range(12)))  #查看'__next__'是不是在range()方法执行之后内部是否有__next__

from collections import Iterator
print(isinstance(range(100000000),Iterator))  #验证range执行之后得到的结果不是一个迭代器

range函数的返回值是一个可迭代对象
range函数的返回值是一个可迭代对象

 

生成器--本质上就是一个'自己写的可实现迭代器功能的东西'

 

目的是为了节省内存 ,不会在内存中生成太多数据

生成器 里面的值是有限的 只能被顺序取用一次, 取完就没有了
生成器里面的值只在调用的时候才取值

  

1.生成器函数:只要包含yield关键字的函数就是一个生成器函数

使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

 

import time
def genrator_fun1():
    a = 1
    print('现在定义了a变量')
    yield a
    b = 2
    print('现在又定义了b变量')
    yield b

g1 = genrator_fun1()
print('g1 : ',g1)       #打印g1可以发现g1就是一个生成器
print('-'*20)   #我是华丽的分割线
print(next(g1))
time.sleep(1)   #sleep一秒看清执行过程
print(next(g1))

初识生成器函数
初始生成器函数

 

#初识生成器二

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件衣服。
#剩下的还有很多衣服,我们可以一直拿,也可以放着等想拿的时候再拿

初识生成器二
生成器2

 

 

1.3 生成器Generator的本质:开发者自定义的迭代器   ..惰性运算 ...节省内存..

1.4生成器函数: 一个包含yield关键字的函数就是一个 生成器函数, yield可以为我们从函数中返回值,但其又不同于return,return意味着程序结束    而其是程序停在那里    需要调用的时候再调用 就可以继续运行

代码体现生成器不占内存

import time
def genrator_fun1():
    a = 1
    print('现在定义了a变量')
    yield a
    b = 2
    print('现在又定义了b变量')
    yield b

g1 = genrator_fun1()
print('g1 : ',g1)       #打印g1可以发现g1就是一个生成器
print('-'*20)   #我是华丽的分割线
print(next(g1))
time.sleep(1)   #sleep一秒看清执行过程
print(next(g1))

初识生成器函数
体现生成器不占内存

监控文件这点自己不是太懂记得问一下

import time


def tail(filename):
    f = open(filename)
    f.seek(0, 2) #从文件末尾算起
    while True:
        line = f.readline()  # 读取文件中新的文本行
        if not line:
            time.sleep(0.1)
            continue
        yield line

tail_g = tail('tmp')
for line in tail_g:
    print(line)

生成器监听文件输入的例子
监控文件

 

小结和代码练习

#可迭代对象   内部有__iter__
#迭代器    内部有__iter__ __next__
#生成器    就是迭代器
    #自己写的迭代器
    #生成器函数   yield /yield from
    #生成器表达式
def generator_func():  #生成器函数
    print(123)
    yield 'aaa'   #return
    print(456)
    yield 'bbb'

def get_clothing():
    for cloth in range(1,2000000):
        yield '第%s件衣服'%cloth

import time
def tail(filename):
    f = open(filename,encoding='utf-8')
    f.seek(0,2)   #将文件的光标移到最后
    while True:
        line = f.readline()
        if not line:
            time.sleep(0.1)
            continue
        yield line

tail_g = tail('demo')
for line in tail_g:
    print(line,end='')


yield form 就相当于for i in 然后yield 返回值
def func2(): yield from [1,2,3] yield from 'ABC' g = func2() for i in g: print(i) # 生成器 # 处理文件,从一个文件中找到包含自己要查找的值的那一行,并打印出来 # python

 

生成器函数进阶与关键字send的用法

生成器函数 用代码 来具体表现一下  他的惰性  和 取值

# def func():
#     yield 1
#     yield 2
#     yield 3
#
# g=func()
# for i in g:
#     print(i)
#     print(list(g))
yield和list

send :文字面意思就是发送 , 在生成器函数中表达的就是传值     

send(进去的值 )=yield+返回值

代码表达就是:

    def func():
      print(123)
      value=yield 1
      print(value)
      print(456)
      yield '***'+value+'***'

g=func()
print(g.__next__())
print(g.send('aaa'))
 

#执行结果会拿到 123,1,aaa,456,***aaa***

send 存在的作用就是 往生成器函数中发送一个值

 

第一小段练习

def func():
    print(123)
    value=yield 1
    yield value

g=func()
g.__next__()
print(g.send('aaa'))

第二小段练习
def func():
    print('*')
    value=yield 1
    print('**',value)
    yield 2

g=func()
print(g.__next__())
print(g.send('aaa'))

第三小段练习
def func():
    print('*')
    value=yield 1
    print('**',value)
    v=yield 2
    print(v)
    yield 3

g=func()
print(g.__next__())
print(g.send('aaa'))
print(g.send('bbb'))


第四小段练习
def func():
    print(1)
    yield 2
    print(3)
    value=yield 4
    print(5)
    yield value

g=func()
print(g.__next__())
print(g.send('100'))
print(g.__next__())


虽然在调用阶段 send进去一个值   但是send进去的值没有yield来接收  所以返回的是一个空
第四小段结果是 1 2 3 4 5 none



第五小段练习
  def func():
  print(1)
  a=yield 2
  yield a
  print(3)
  value=yield 4
  print(5)
  yield value

g=func()
print(g.__next__())
a=g.send('100')
print(a)
print(g.__next__())


得到的结果是 1 2 100 3 4
如果注释掉 a=yield 2 那就是返回 1 2 3 4 5 none


 

 

代码体现send关键字在实际中的书写:

  计算移动平均值:总值/个数  (他们不是一个固定值)

 

def averager():
    total = 0.0
    count = 0
    average = 0
    while True:
        term = yield average  #term依次=10=30=5 
        total += term
        count += 1
        average = total/count


g_avg = averager()
next(g_avg)
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))

计算移动平均值(1)

加装饰器的计算平均值(2)_预激活

def init(func):  #在调用被装饰生成器函数的时候首先用next激活生成器
    def inner(*args,**kwargs):
        g = func(*args,**kwargs)
        next(g)
        return g
    return inner

@init
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count


g_avg = averager()
# next(g_avg)   在装饰器中执行了next方法
print(g_avg.send(10))
print(g_avg.send(30))
print(g_avg.send(5))

计算移动平均值(2)_预激协程的装饰器
装饰器激活生成器

 

 

 生成器进阶小结

send :首先send和next工作的起止位置是完全相同的
send可以把一个值作为信号量传递到函数中去
在生成器伊始,只能用next
只要用send传递参数的时候,必须在生成器中还有一个未被返回的yield
在平时生活当中 没有必须的点 面试时用到

列表推导式和生成器表达式

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

 

#***由于*哥的强势加盟很快走上了上市之路,***思来想去决定下几个鸡蛋来报答峰哥

egg_list=['鸡蛋%s' %i for i in range(10)] #列表解析

#*哥瞅着alex下的一筐鸡蛋,捂住了鼻子,说了句:哥,你还是给我只母鸡吧,我自己回家下

laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式
print(laomuji)
print(next(laomuji)) #next本质就是调用__next__
print(laomuji.__next__())
print(next(laomuji))

*哥与****的故事

(总结的知识点  希望大家能看懂  )

#列表解析
sum([i for i in range(100000000)])#内存占用大,机器容易卡死
 
#生成器表达式
sum(i for i in range(100000000))#几乎不占内存

 列表推导式的目的是为了 : 简化代码

列表推导式: print([i*i for i in[1,3,9]] )#结果必须是列表[1,9,81]
      for前面就是新列表里面的值 和 一些附加条件 , 也可以在末尾加一些if条件判断

 

问1:用range取7以内能被2整除的数字   ,   一列表推导式表现出来

      print([i//2 for i in range(0,7,2)])

问2:加上字符串拼接 :   print(['egg%s'%i for i in range(10)])

问3:加上一些条件判断: print([i for i in range(30) if i % 3 is 0])

 问4:  找到嵌套列表中名字含有两个‘e’的所有名字

names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],
         ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]

print([name for lst in names for name in lst if name.count('e') >= 2])  # 注意遍历顺序,这是实现的关键

for i in (name for lst in names for name in lst if name.count('e') >= 2):
     print(i)

 

小结: 写代码时注意点:

    写代码尽量多的让列表推导式[]--默认变成生成式表达式()
    让推导式简化你的操作,增强你的代码可读性
    如果推导式过于复杂,应该转换成普通的python代码
    所有的列表推导式都可以转化成生成器表达式 ,在你的代码中尽量多的用到生成器表达式

禁忌:  在代码里多层嵌套的for循环是禁忌--会大幅度增加代码复杂程度

 

 

生成器表达式 -- 节省内存 , 简化代码  就是把 列表推导式的[]去掉

laomuji=('egg%d'%i for i in range(10))
print(laomuji)
1.__next__()
2.for
for i in laomuji:
print(i)

laomuji 是一个可迭代对象  不是一个迭代器/生成器

总结:

1.把列表解析的[]换成()得到的就是生成器表达式

2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存

3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:

sum(x ** 2 for x in xrange(4))

而不用多此一举的先构造一个列表:

sum([x ** 2 for x in xrange(4)]) 

 

字典推导式

例一:将一个字典的key和value对调

mcase = {'a': 10, 'b': 34}
mcase_frequency = {mcase[k]: k for k in mcase}
print(mcase_frequency)
字典推导

例二:合并大小写对应的value值,将k统一成小写

mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}
mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys()}
print(mcase_frequency)
字典推导2

 

集合推导式

例:计算列表中每个值的平方,自带去重功能

squared = {x**2 for x in [1, -1, 2]}
print(squared)
# Output: set([1, 4])
集合推导

 

练习题:

例1:  过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母

例2:  求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表

例3:  求M中3,6,9组成的列表M = [[1,2,3],[4,5,6],[7,8,9]]

1.[name.upper() for name in names if len(name)>3] 
2.[(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1] 
3. [row[2] for row in M] 
答案

 

             

 

posted @ 2017-08-02 19:18  太上老君门下一只虾  阅读(163)  评论(0编辑  收藏  举报