Python之迭代器、生成器

1、列表生成式

# 普通生成列表
ab=[]
for x in range(10):
    ab.append(x*x)
print(ab)

# 列表表达式
a=[x*x for x in range(10)] # 这就叫列表生成式,也叫声明式编程
'''
1:先取元素
2:把取出来的元素进行一个处理 x*x
3:把处理完的元素依次存放到列表里
print(a)
'''

egg=[x for x in range(100) if x<50]    # 如果 x 小于  50,就加进去 egg 的列表
print(egg)

缺点:
如果可迭代的对象很大,会把可迭代的内容全放到内存处理,占满内存,解决这个问题用生成器表达式

2、生成器表达式

#生成器表达式
egg = (x for x in range(100) if x < 50) # 这就叫生成器表达式,与列表生成式的区别就是用小括号

print(egg)  # <generator object <genexpr> at 0x000001FCDB352E60>  生成器对象

# 如果想要拿里面的值通过 for 循环,或者 next 方法
for i in egg:
    print(i) 

print(next(egg))
print(next(egg))
print(next(egg))
print(next(egg))

'''
跟列表生成式的区别
列表生成式有索引,取值灵活,能获取长度
生成器表达式省内存,用一个取一个,不能获取长度,没有索引
'''

 

3、迭代器

'''
迭代器协议满足两个条件
1:对象可以被__iter__方法转换成迭代器的,或 iter(obj)
2:对象可以通过__next__方法取值的,或 next(obj)
'''

l=[1,2,3,5]  # 列表是一个可迭代对象,不是迭代器,有 iter 方法

d=iter(l)  # 或者  l.__iter__(),把列表用 iter 函数或__iter__()方法,转换为一个迭代器
print(d)  # <list_iterator object at 0x0000000000B16B38>


# 优点:
'''
1:迭代器提供了一种不依赖索引的取值方式,这样就可以遍历那种没有索引的可迭代对象(字典,集合,文件)

2:迭代器与列表比较,迭代器是惰性计算的,更节省内存,什么叫惰性计算,列表可迭代对象,和迭代器比较,可以知道可迭代对象的长度len方法,但无法知道迭代器的长度,迭代器每next 只拿一次数据
'''

# 缺点:
'''
一次性的,只能往后取值,不能倒着取值
'''

取迭代器里的内容

print(d.__length_hint__())  # 获取迭代器元素的长度
d.__setstate__(2)   # 设置迭代器索引值的开始
print(next(d))  # 用 next 方法取迭代器对象里的值 
print(next(d))
print(next(d))  # 报错,为什么,看下面 while 和 for


while True:
    try:
        print(next(d))      # 循环取迭代器里的内容
    except StopIteration:   # 去捕捉 StopIteration 的错误
        break   # 一旦捕捉到,就退出


for i in d:
    print(i)
'''
for 循环内部一共做了三件事
1:调用可迭代对象(d)的 __iter__ 方法返回一个迭代器对象
2:不断调用迭代器对象的 next 方法,去取值
3:处理 stopiteration 异常,原理跟上面的 while 差不多
'''


from collections import Iterator,Iterable
print(isinstance(l,Iterable))  判断是否为可迭代对象
iterator 迭代器
iterable 可迭代
# 格式 isinstance(对象,类型)
isinstance方法:判断对象是否为指定的类型,是返回 True,否返回 False

4、生成器

注意:生成器都是迭代器,迭代器不一定是生成器

# 创建生成器:

a = (x*2 for x in range(5))    # 通过生成器表达式创建生成器


# 函数里面有 yield 关键字表示这个函数是一个生成器,其内部是把函数做成迭代器
def foo():
    print('ok')
    yield 1   # 返回值,类似 return,返回给 next()
    print('ok2')
    yield 2   # 返回值,类似 return

g=foo()  # foo() 返回一个生成器对象
print(g)  #<generator object foo at 0x000000000083A518>
next(g) # 显示的是 ok:返回值是 1,生成器也是迭代器,通过 next 方法取值
next(g) # 显示的是 ok2:返回值 2
# a=next(g)   # 1  ,屏幕会输出 ok
# b=next(g)   # 2  ,屏幕会输出 ok2
# print(a,b)


for 循环过程
for i in foo(): 
    while True:
        i=next(foo()) #先执行 next(foo())返回 print(ok),然后把 yield 的值返回给 i,print(i)=1
ok
1
ok2
2

 

# 生成器就是一个可迭代对象(iterable)

生成器自动实现了迭代器协议

for i in a: 
    print(i)
# for 会对 a 对象进行一个 next 的方法调用,循环取一个值,取第二个值,第一个值没有变量引用,会被内存垃圾回收了,达到节省内存的目的

# 生成器 yield 与 函数 return有何区别?

return只能返回一次函数就彻底结束了,而yield能返回多次值

 

# yield到底干了什么事情:

yield 把 __iter__ 和 __next__ 方法封装到函数内部使其变成生成器-->迭代器

用return返回值能返回一次,而yield返回多次

函数在暂停以及继续下一次运行时的状态是由yield保存

 

send 方法

def bar():
    print('ok')
    count=yield 1
    print(count)
    print('ok2')
    yield 2
b=bar()  #生成一个生成器对象
ret=b.send(None)  #b.send(None)=next(b)  第一次 send 前如果没有 next,只能传一个 None,并返回 yield 的值;这里赋给 ret,或者先 next(b) 执行一下,也可以写一个初始化生成器的装饰器
print(ret)  # ret 的值为 yield 的值:1
ret2=b.send('eee') #把 eee 赋值给 count,并返回 count 的值 eee
print(ret2)

send() 与 next() 的区别

1、如果函数内yield是表达式形式,那么必须先next(e),可以为生成器造一个装饰器让其不用每次都next一遍
2、二者的共同之处是都可以让函数在上次暂停的位置继续运行,不一样的地方在于send在触发下一次代码的执行时,会顺便给yield传一个值
3、send 传多个值得时候要以元祖的形式传过去

 

生成器例子

菲波那切数列

# def fib(max):
#     n,before,after=0,0,1
#     while n < max:
#         print(before)
#         before,after=after,before+after
#         n+=1
#
# fib(2)


def fiber(max):
    n,before,after=0,0,1
    while n<max:
        yield before
        before,after=after,before+after
        n+=1
g=fiber(8)   # 生成器对象
# print(g)
# print(next(g))   # 返回生成器对象的 yield 值
# print(next(g))
View Code

 

生成器练习

通过生成器写一个日志调用方法,支持以下功能

根据指令向屏幕输出日志

根据指令向文件输出日志

根据指令同时向文件&屏幕输出日志

以上日志格式如下

2017-10-19 22:07:38 [1] test log db backup 3

2017-10-19 22:07:40 [2] user alex login success

#注意:其中[1],[2]是指自日志方法第几次调用,每调用一次输出一条日志

def init(func):
    def warpper(*args, **kwargs):
        res = func(*args, **kwargs)
        next(res)
        return res
    return warpper


@init
def logger(filename, channel='terminal'):

    count = 1

    while 1:

        c = yield 1
        current_time = time.strftime('%Y-%m-%d %H:%M:%S')
        info = '%s [%s] %s' % (current_time, count, c,)

        if channel == 'terminal':
            print(info)
        elif channel == 'file':
            with open('example.ini', 'a', encoding='utf8') as f:
                f.write('\n%s' % (info,))
        elif channel == 'both':
            print(info)
            with open('example.ini', 'a', encoding='utf8') as f:
                f.write('\n%s' % (info,))
        count += 1


lo = logger('abc', channel='both')
next(lo)
lo.send('aaa')
time.sleep(2)
lo.send('ccc')
View Code

模拟 linux tail 命令

#!/usr/local/python3.5.2/bin/python3.5
def tail_f(file_path):
    import time
    with open(file_path,'r') as f:
        f.seek(0,2)   # 光标始终置尾部
        while True:
            for line in f:
                if not line:  # 如果为空就停住
                   time.sleep(0.3)
                   continue
                else:
                    yield line.strip()

g=tail_f('a.txt')
for i in g:
    print(i)
View Code

模拟 linux 的 tail + grep 命令

def tail_f(file_path):
    import time
    with open(file_path,'r') as f:
        f.seek(0,2)
        while True:
            for line in f:
                if not line:
                   time.sleep(0.3)
                   continue
                else:
                    yield line


def grep(partten,lines):
    for line in lines:
        if partten in line:
            yield line

g1=tail_f('a.txt')
g2=grep('error',g1)
for i in g2:
    print(i.strip())
View Code

.

posted @ 2018-05-19 18:39  H-JIACHENG  阅读(207)  评论(0编辑  收藏  举报