pythoncookbook 第4章 生成器与迭代器

[toc]

4 迭代器与生成器

for xxx in xxx理解

__iter__几乎是for xx in xx 此设计的
  • for xxx in xxx在迭代器中应用
  1. 调用iter()方法,返回一个对象 itmes = iter(object)
  2. 调用此对象 items.next(), 捕捉到StopIteration后 结束
########for xxx in xxx理解
###########1
it = iter(lines) # it = lines.__iter__()
    while True:
        try:
            next(it)
        except StopIteration:
            print 'finish iteration'
            break
################2
class ABC(object):
    def __iter__(self):
        return ABC2()

class ABC2(object):
    num = 100
    def next(self):
        self.num -= 1
        if self.num < 0:
            raise StopIteration()
        return self.num

for i in ABC():
    print i
       
  • for xxx in xxx在生成器
    运行生成器内部代码,将yield的逐个值取出
####for xxx in xxx理解
def abc(x):
    print 123
    yield x
 
for i in abc('hello world')
    print i
#abc('hello world')内置__iter__,next,send,throw方法
 
##类的实现
class ABC(object):
    def __iter__(self):
        return self.abc('hello world')
    def abc(self, x):
        print 123
        yield x
 
for i in ABC():
    print i
#和下面的等同
items = iter(ABC())
while True:
    try:
        print items.next()
    except StopIteration:
        break

迭代器

  • 在python中实现了__iter__方法的对象可以迭代的,即可以调用内置函数iter()方法,return对象.
  • 实现了next()方法的对象实迭代器,得到iter()返回的对象,不断调用next()方法
class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1
 
    def __iter__(self):
        return self
 
    def next(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > 100:
            raise StopIteration()
        return self.a
 
fetch = iter(Fib()) #获取用于迭代的对象
while True:
    #不断调用next(),捕捉到StopIteration结束循环
    try:
        i = fetch.next()
        print i
    except StopIteration:
        break
 
#等同for xxx in XXX      
for i in Fib():
    print i

生成器

生成器是对象.保留栈帧的上下文

def abc(x):
    print 123
    yield x
abc("hello world")  # 此时并不会打印出123, 这一步执行产生一个生成器对象
abc("hello world").next() #此时才去执行生成器内的代码

4.4 实现深度优先的遍历树形节点的生成器

  • for 循环的理解
  • yield 其他对象的 yield
class Node(object):
    def __init__(self, value):
        self._value = value
        self._children =[]
 
    def __repr__(self):
        return "Node{!r}".format(self._value)
 
    def __iter__(self):
        return iter(self._children)
 
    def add_child(self, node):
        return self._children.append(node)
 
    def depth_first(self):
    """
    yield 相于return, 再次调用的时候,从原先的地方执行
    将其他的对象的yield后 再次yield出来,分为两次
    """
        yield self
        for c in self:
            #下面的代码相当于yield from c.depth_first()
            for items in c.depth_first():
                yield items
 
if __name__ == '__main__':
 
    root = Node(0)
    child1 = Node(1)
    child2 = Node(2)
 
    child11 = Node(11)
    child21 = Node(21)
 
    root.add_child(child1)
    root.add_child(child2)
 
    child1.add_child(child11)
    child2.add_child(child21)
 
    child11.add_child(Node(100))
    child21.add_child(Node(200))
    child21.add_child(Node(201))
 
"""
对于for循环的理解:
ch 为形参,接受 root.depth_first() 返还的实参(相当于return)
root.depth_first()必须实现迭代协议,可以为生成器
"""
    for ch in root.depth_first():
        print ch

depth_first() 方法首先返回(yield)本身 并迭代每一个节点的depth_first()方法,并返回(yield)对应元素

  • 传统方法的实现,缺点繁琐
class Node2(object):
    def __init__(self, value):
        self._value = value
        self._children = []
 
    def __repr__(self):
        return "Node{!r}".format(self._value)
 
    def __iter__(self):
        return iter(self._children)
 
    def add_child(self, node):
        return self._children.append(node)
 
    def depth_first(self):
        return DepthFirstIterator(self)
 
 
class DepthFirstIterator(object):
    def __init__(self, start_node):
        self._node = start_node
        self._children_iter = None
        self._child_iter = None
 
    def __iter__(self):
        return self
 
    def __next__(self):
        if self._children_iter is None:
            self._children_iter = iter(self._node)
            return self._node
 
        elif self._child_iter:
            try:
                nextchild = next(self._child_iter)
                return nextchild
            except StopIteration:
                self._child_iter = None
                return next(self)
 
        else:
            self._child_iter = next(self._children_iter).depth_first()
            return next(self)

4.5 反向迭代

  • 采用内置的函数 reversed()
  • 必须实现内置的reversed()方法
class Countdown(object):
    def __init__(self, start):
        self.start = start
 
    def __iter__(self):
        n = self.start
        while n > 0:
            yield n
            n -= 1
 
    def __reversed__(self):
        n = 1
        while n <= self.start:
            yield n
            n += 1
 
 
if __name__ == '__main__':
    for rr in reversed(Countdown(30)):
        print rr
    for rr in Countdown(30):
        print rr

4.6 带有外部参数生成器函数

from collections import deque
 
class LineHistory:
    def __init__(self, lines, hislen=3):
        self.lines = lines
        self.history = deque(maxlen=hislen)
 
    def __iter__(self):
        for lineno, line in enumerate(self.lines, 1):
            self.history.append((lineno, line))
            yield line
 
    def clear(self):
        self.history.clear()

4.7 迭代器切片

得到迭代器生成的切片对象

import itertools
def count(n):
    while True:
        yield n
        n += 1
c = count(0)
# c[10:20]  >>>TypeError: 'generator' object has no attribute '__getitem__'
for items in itertools.islice(c, 10, 21):
    print items

函数 islice() 返回一个可以生成指定元素的迭代器,它通过遍
历并丢弃直到切片开始索引位置的所有元素。然后才开始一个个的返回元素,并直到切片结束索引位置。缺点不能重复使用迭代器里面的数据

4.8 跳过不需要的迭代部分

???跳过一个可迭代对象的开始部分,对后面的不影响?
创建一个迭代器,
只要函数predicate(item)为True,就丢弃iterable中的项,
如果predicate返回False,就会生成iterable中的项和所有后续项。

from itertools import dropwhile
with open('manage.py') as f:
    for line in dropwhile(lambda line: line.startwith("#"), f):
        print line

4.9 排列组合实现

比如排列A23 ,组合 C23等

from itertools import permutations,combinations, combinations_with_replacement
items = ['a', 'b', 'c']
for c in permutations(items) # 排列A33
for c in permutations(items, 2) # 排列A33
for c in combinations(items, 3) # 组合 C23
for c in combinations_with_replacement(items, 3) # 同一元素重复使用 3*3*3

4.10 序列上索引迭代

my_list = ['a', 'b', 'c']
for idx, val in enumerate(my_list, 1):
print(idx, val)

这种情况在你遍历文件时想在错误消息中使用行号定位时候非常有用:

def parse_data(filename):
    with open(filename, 'rt') as f:
        for lineno, line in enumerate(f, 1):
            fields = line.split()
            try:
                count = int(fields[1])
                ...
            except ValueError as e:
                print('Line {}: Parse error: {}'.format(lineno, e))
data = [ (1, 2), (3, 4), (5, 6), (7, 8) ]
for n, (x, y) in enumerate(data):

4.11 迭代多个序列 zip()

zip() 会创建一个迭代器来作为结果返回

  • 基本用法 压缩
a = [1, 2, 3]
b = ['w', 'x', 'y', 'z']
for i in zip(a,b):
    print(i)
>>>(1,'w')
>>>(2,'x')
>>>(3,'y')
 
from itertools import zip_longest
for i in zip_longest(a,b,fillvalue=None):
print(i)
>>>(1, 'w')
>>>(2, 'x')
>>>(3, 'y')
>>>(None, 'z')
  • 打包字典,变为列表
headers = ['name', 'shares', 'price']
values = ['ACME', 100, 490.1]
s = dict(zip(headers,values))
 
list(zip(headers, values))
  • zip() 可以接受多于两个的序列的参数 zip(a, b, c)

4.12 不同集合上元素的迭代 chain()

from itertools import chain
a = [1, 2, 3, 4]
b = ['x', 'y', 'z']
for x in chain(a, b):
    print(x)

a,b可以为不同的类型 chain(set,list)甚至是chain(dict,list)

# Inefficent
for x in a + b:
# Better
for x in chain(a, b):

第一种方案中, a + b 操作会创建一个全新的序列并要求 a 和 b 的类型一致
chian() 不会有这一步,所以如果输入序列非常大的时候会很省内存。并且当可迭代
对象类型不一样的时候 chain() 同样可以很好的工作。

4.13 创建数据管道

os.walk 从文件夹某个位置开始遍历

# x为当前的目录 y为当前目录下包含的文件夹 z 为当前目录下的文件
for x, y, z, in os.walk(r"D:\Workspace\sell"):
    for zpieces in z :
        print '{}{}'.format(x,zpieces)

fnmatch.filter(filellist, filepat)
filelist为list则返回符合filepart的文件
filelist为str 则返回布尔值

# encoding:utf-8
import os
import fnmatch
import gzip
import bz2
import re
 
 
def gen_find(filepat, top):
    """
    根据filepat的文件类型,查找当前目录下的文件
    """
    for path, dirlist, filelist in os.walk(top):
            # 过滤符合格式的地址并返回
        for name in fnmatch.filter(filelist, filepat):
            yield os.path.join(path, name)  # 文件的绝对地址的生成器
 
 
def gen_opener(filenames):
    """
    打开文件,yield文件,并关闭
    """
    for filename in filenames:  # 从生成器中取出绝对地址 filename为地址  filenames为含地址的生成器
        if filename.endswith('.gz'):
            f = gzip.open(filename, 'rt')
        elif filename.endswith('.bz2'):
            f = bz2.open(filename,"rt")
            ##todo 可能有问题
        else:
            f = open(filename, "r") 
        yield f  # 文件对象的生成器 
        f.close()
 
 
def gen_concatenate(iterators):
    for it in iterators: #it为文件对象,iterators是文件对象生成器
        for items in it: # items 句子 it文件对象
            yield items # 抛出句子生成器 在外部用for xx in xx得到
 
 
def gen_grep(pattern, lines):
    """
    匹配文中的语句
    """
    pat = re.compile(pattern)
    for line in lines:
        if pat.search(line):
            yield line
 
 
lognames = gen_find("*.py", r"D:\Workspace\sell")
files = gen_opener(lognames)
lines = gen_concatenate(files)
 
pylines = gen_grep(r'^class ', lines)  # 打印类名
for line in pylines:
    print line
#todo 不太懂
#bytecolumn = (line.rsplit(None,1)[1] for line in pylines)
#bytes = (int(x) for x in bytecolumn if x != '-')
#print('Total', sum(bytes))

看不懂嵌套的生成器,请看下面的例子

def gen1 ():
    for i in [[1,2,3,4,5],[6,7,8,9,0]]:
        yield i
def gen2 (i):
    for j in i:
        for k in j:
            yield k
g1 = gen1()
g2 = gen2(g1)
for x in g2:
    print x

不太靠谱的理解 ,for xx in xx 可以解开生成器,要想得到生成器里的内容,for xx in xx 层数大于生成器嵌套的层数。

4.14 递归生成器展开嵌套的序列

原代码采用 yield from 实现python2 不支持可用 for i in xx : yield i 代替

# encoding:utf-8
from collections import Iterable
def flatten(items, ignore_types=(str, bytes)):
    for x in items:
    #isinstance(x, Iterable) 判断是否可以迭代 ,可以则继续递归
    #not isinstance(x, ignore_types),排除字符串,字节,这两者也可以迭代
        if isinstance(x, Iterable) and not isinstance(x, ignore_types):
            for i in flatten(x):
                yield i
        else:
            yield x
items1 = [1, 2, [3, 4, [5, 6], 7], 8]
items2 = ['Dave', 'Paula', ['Thomas', 'Lewis']]
l1 = [x for x in flatten(items1)]
l2 = [x for x in flatten(items2)]

4.15 有序对象合并再排序

heapq.merge()
heapq.merge 生成器迭代特性意味着它不会立马读取所有序列。这就意味着你可以在非
常长的序列中使用它,而不会有太大的开销

import heapq
a = [1, 4, 7, 10]
b = [2, 5, 6, 11]
l = [x for x in heapq.merge(a, b)] ##heapq.merge(a, b)是生成器
>>>[1, 2, 4, 5, 6, 7, 10, 11]

4.16 迭代器代替while循环

其实就是用遍历代替while.
途径:iter(functiong, status)能够迭代,具体参考本节末尾
常见的IO程序,伪代码

CHUNKSIZE = 8192
def reader(s):
    while True:
        data = s.recv(CHUNKSIZE)
        if data == b'':
            break
        process_data(data)
 
f = open("views.py", "r")
reader(f)
 
#用iter()循环代替
def reader2(s):
    for chunk in iter(lambda : s.recv(CHUNKSIZE),b""):
        pass
        #process_data(data)
 

实例代码

import sys
f = open("views.py","r")
for chunk in iter(lambda: f.read(10), ""):
    n = sys.stdout.write(chunk)
 

iter()内置函数:
单参数时Iter(func),fun对象支持迭代协议,不然报错
两个参数时Iter(func,arg),它接受一个可选的 callable 对象和一个标记 (结
尾) 值作为输入参数,不断调用next(),func返回值和标记一样时,抛出StopIteration

x = 0
def func():
    global x
    x +=1
    print x
    return x

while True:
    i = iter(func,100)
    try:
        i.next() #renturn值为100的时候抛出StopIteration
    except StopIteration:
        print '停止迭代'
        break

本章总结

  • 迭代器
    迭代器协议几乎是为 for xx inxx设计的,
    什么是迭代器呢?遵循__iter__,next()这两个协议的对象。即为__iter__指向迭代的某个对象,这个对象有next()方法。不断的调用next
    (),抛出Stopiteration迭代结束。
  • 生成器
    涉及太多不在本章展开
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容