迭代器详解
分类:面向对象

Python函数式编程指南(三):迭代器详解,python编程指南

3. 迭代器

3.1. 迭代器(Iterator)概述

迭代器是访问集合内元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素都被访问一遍后结束。

迭代器不能回退,只能往前进行迭代。这并不是什么很大的缺点,因为人们几乎不需要在迭代途中进行回退操作。

迭代器也不是线程安全的,在多线程环境中对可变集合使用迭代器是一个危险的操作。但如果小心谨慎,或者干脆贯彻函数式思想坚持使用不可变的集合,那这也不是什么大问题。

对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值,这是后话)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。

迭代器的另一个优点就是它不要求你事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代至某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。这个特点被称为延迟计算或惰性求值(Lazy evaluation)。

迭代器更大的功劳是提供了一个统一的访问集合的接口。只要是实现了__iter__()方法的对象,就可以使用迭代器进行访问。

3.2. 使用迭代器

使用内建的工厂函数iter(iterable)可以获取迭代器对象:
复制代码 代码如下:
>>> lst = range(2)
>>> it = iter(lst)
>>> it
<listiterator object at 0x00BB62F0>

使用迭代器的next()方法可以访问下一个元素:
复制代码 代码如下:
>>> it.next()
0

如果是Python 2.6+,还有内建函数next(iterator)可以完成这一功能:
复制代码 代码如下:
>>> next(it)
1

如何判断迭代器还有更多的元素可以访问呢?Python里的迭代器并没有提供类似has_next()这样的方法。
那么在这个例子中,我们已经访问到了最后一个元素1,再使用next()方法会怎样呢?
复制代码 代码如下:
>>> it.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Python遇到这样的情况时将会抛出StopIteration异常。事实上,Python正是根据是否检查到这个异常来决定是否停止迭代的。
这种做法与迭代前手动检查是否越界相比各有优点。但Python的做法总有一些利用异常进行流程控制的嫌疑。

了解了这些情况以后,我们就能使用迭代器进行遍历了。

复制代码 代码如下:
it = iter(lst)
try:
    while True:
        val = it.next()
        print val
except StopIteration:
    pass

实际上,因为迭代操作如此普遍,Python专门将关键字for用作了迭代器的语法糖。在for循环中,Python将自动调用工厂函数iter()获得迭代器,自动调用next()获取元素,还完成了检查StopIteration异常的工作。上述代码可以写成如下的形式,你一定非常熟悉:

复制代码 代码如下:
for val in lst:
    print val

首先Python将对关键字in后的对象调用iter函数获取迭代器,然后调用迭代器的next方法获取元素,直到抛出StopIteration异常。对迭代器调用iter函数时将返回迭代器自身,所以迭代器也可以用于for语句中,不需要特殊处理。

常用的几个内建数据结构tuple、list、set、dict都支持迭代器,字符串也可以使用迭代操作。你也可以自己实现一个迭代器,如上所述,只需要在类的__iter__方法中返回一个对象,这个对象拥有一个next()方法,这个方法能在恰当的时候抛出StopIteration异常即可。但是需要自己实现迭代器的时候不多,即使需要,使用生成器会更轻松。下一篇我们将讨论生成器的部分。

*异常并不是非抛出不可的,不抛出该异常的迭代器将进行无限迭代,某些情况下这样的迭代器很有用。这种情况下,你需要自己判断元素并中止,否则就死循环了!

使用迭代器的循环可以避开索引,但有时候我们还是需要索引来进行一些操作的。这时候内建函数enumerate就派上用场咯,它能在iter函数的结果前加上索引,以元组返回,用起来就像这样:
复制代码 代码如下:
for idx, ele in enumerate(lst):
    print idx, ele

3.3. 生成器表达式(Generator expression)和列表解析(List Comprehension)
绝大多数情况下,遍历一个集合都是为了对元素应用某个动作或是进行筛选。如果看过本文的第二部分,你应该还记得有内建函数map和filter提供了这些功能,但Python仍然为这些操作提供了语言级的支持。

复制代码 代码如下:
(x+1 for x in lst) #生成器表达式,返回迭代器。外部的括号可在用于参数时省略。
[x+1 for x in lst] #列表解析,返回list

如你所见,生成器表达式和列表解析(注:这里的翻译有很多种,比如列表展开、列表推导等等,指的是同一个意思)的区别很小,所以人们提到这个特性时,简单起见往往只描述成列表解析。然而由于返回迭代器时,并不是在一开始就计算所有的元素,这样能得到更多的灵活性并且可以避开很多不必要的计算,所以除非你明确希望返回列表,否则应该始终使用生成器表达式。接下来的文字里我就不区分这两种形式了:)

你也可以为列表解析提供if子句进行筛选:

复制代码 代码如下:
(x+1 for x in lst if x!=0)

或者提供多条for子句进行嵌套循环,嵌套次序就是for子句的顺序:

复制代码 代码如下:
((x, y) for x in range(3) for y in range(x))

列表解析就是鲜明的Pythonic。我常遇到两个使用列表解析的问题,本应归属于最佳实践,但这两个问题非常典型,所以不妨在这里提一下:

第一个问题是,因为对元素应用的动作太复杂,不能用一个表达式写出来,所以不使用列表解析。这是典型的思想没有转变的例子,如果我们将动作封装成函数,那不就是一个表达式了么?

第二个问题是,因为if子句里的条件需要计算,同时结果也需要进行同样的计算,不希望计算两遍,就像这样:

复制代码 代码如下:
(x.doSomething() for x in lst if x.doSomething()>0)

这样写确实很糟糕,但组合一下列表解析即可解决:

复制代码 代码如下:
(x for x in (y.doSomething() for y in lst) if x>0)

内部的列表解析变量其实也可以用x,但为清晰起见我们改成了y。或者更清楚的,可以写成两个表达式:

复制代码 代码如下:
tmp = (x.doSomething() for x in lst)
(x for x in tmp if x > 0)

列表解析可以替代绝大多数需要用到map和filter的场合,可能正因为此,著名的静态检查工具pylint将map和filter的使用列为了警告。

3.4. 相关的库

Python内置了一个模块itertools,包含了很多函数用于creating iterators for efficient looping(创建更有效率的循环迭代器),这说明很是霸气,这一小节就来浏览一遍这些函数并留下印象吧,需要这些功能的时候隐约记得这里面有就好。这一小节的内容翻译自itertools模块官方文档。

3.4.1. 无限迭代

count(start, [step])

从start开始,以后每个元素都加上step。step默认值为1。
count(10) --> 10 11 12 13 14 ...

cycle(p)

迭代至序列p的最后一个元素后,从p的第一个元素重新开始。
cycle('ABCD') --> A B C D A B C D ...

repeat(elem [,n])

将elem重复n次。如果不指定n,则无限重复。
repeat(10, 3) --> 10 10 10

3.4.2. 在最短的序列参数终止时停止迭代
chain(p, q, ...)
迭代至序列p的最后一个元素后,从q的第一个元素开始,直到所有序列终止。
chain('ABC', 'DEF') --> A B C D E F
compress(data, selectors)
如果bool(selectors[n])为True,则next()返回data[n],否则跳过data[n]。
compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F
dropwhile(pred, seq)
当pred对seq[n]的调用返回False时才开始迭代。
dropwhile(lambda x: x<5, [1,4,6,4,1]) --> 6 4 1
takewhile(pred, seq)
dropwhile的相反版本。
takewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4
ifilter(pred, seq)
内建函数filter的迭代器版本。
ifilter(lambda x: x%2, range(10)) --> 1 3 5 7 9
ifilterfalse(pred, seq)
ifilter的相反版本。
ifilterfalse(lambda x: x%2, range(10)) --> 0 2 4 6 8
imap(func, p, q, ...)
内建函数map的迭代器版本。
imap(pow, (2,3,10), (5,2,3)) --> 32 9 1000
starmap(func, seq)
将seq的每个元素以变长参数(*args)的形式调用func。
starmap(pow, [(2,5), (3,2), (10,3)]) --> 32 9 1000
izip(p, q, ...)
内建函数zip的迭代器版本。
izip('ABCD', 'xy') --> Ax By
izip_longest(p, q, ..., fillvalue=None)
izip的取最长序列的版本,短序列将填入fillvalue。
izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
tee(it, n)
返回n个迭代器it的复制迭代器。
groupby(iterable[, keyfunc])
这个函数功能类似于SQL的分组。使用groupby前,首先需要使用相同的keyfunc对iterable进行排序,比如调用内建的sorted函数。然后,groupby返回迭代器,每次迭代的元素是元组(key值, iterable中具有相同key值的元素的集合的子迭代器)。或许看看Python的排序指南对理解这个函数有帮助。
groupby([0, 0, 0, 1, 1, 1, 2, 2, 2]) --> (0, (0 0 0)) (1, (1 1 1)) (2, (2 2 2))

3.4.3. 组合迭代器
product(p, q, ... [repeat=1])
笛卡尔积。
product('ABCD', repeat=2) --> AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
permutations(p[, r])
去除重复的元素。
permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
combinations(p, r)
排序后去除重复的元素。
combinations('ABCD', 2) --> AB AC AD BC BD CD
combinations_with_replacement()
排序后,包含重复元素。
combinations_with_replacement('ABCD', 2) --> AA AB AC AD BB BC BD CC CD DD
此篇结束。

  1. 迭代器 3.1. 迭代器(Iterator)概述 迭代器是访问集合内元素的一种方式。迭代器对...

python 迭代器 生成器 1. 迭代器

 

      迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。

 

1.1 使用迭代器的优点

 

      对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。

 

      另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。

 

      迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。

 

迭代器有两个基本的方法

 

next方法:返回迭代器的下一个元素

__iter__方法:返回迭代器对象本身

下面用生成斐波那契数列为例子,说明为何用迭代器

 

代码1

 

复制代码

 def fab(max): 

    n, a, b = 0, 0, 1 

    while n < max: 

        print b 

        a, b = b, a + b 

        n = n + 1

复制代码

直接在函数fab(max)中用print打印会导致函数的可复用性变差,因为fab返回None。其他函数无法获得fab函数返回的数列。

 

代码2

 

复制代码

 def fab(max): 

    L = []

    n, a, b = 0, 0, 1 

    while n < max: 

        L.append(b) 

        a, b = b, a + b 

        n = n + 1

    return L

复制代码

代码2满足了可复用性的需求,但是占用了内存空间,最好不要。

 

代码3

 

对比

 

 for i in range(1000): pass

 for i in xrange(1000): pass

前一个返回1000个元素的列表,而后一个在每次迭代中返回一个元素,因此可以使用迭代器来解决复用可占空间的问题

 

复制代码

 class Fab(object): 

    def __init__(self, max): 

        self.max = max 

        self.n, self.a, self.b = 0, 0, 1 

 

    def __iter__(self): 

        return self 

 

    def next(self): 

        if self.n < self.max: 

            r = self.b 

            self.a, self.b = self.b, self.a + self.b 

            self.n = self.n + 1 

            return r 

        raise StopIteration()

 

执行

 

 

>>> for key in Fabs(5):

    print key

 

     

 

Fabs 类通过 next() 不断返回数列的下一个数,内存占用始终为常数  

 

1.2 使用迭代器

 

使用内建的工厂函数iter(iterable)可以获取迭代器对象:

 

 

>>> lst = range(5)

>>> it = iter(lst)

>>> it

<listiterator object at 0x01A63110>

使用next()方法可以访问下一个元素:

 

>>> it.next()

 

>>> it.next()

 

>>> it.next()

 

python处理迭代器越界是抛出StopIteration异常

 

>>> it.next()

 

>>> it.next

<method-wrapper 'next' of listiterator object at 0x01A63110>

>>> it.next()

 

>>> it.next()

 

Traceback (most recent call last):

  File "<pyshell#27>", line 1, in <module>

    it.next()

StopIteration

了解了StopIteration,可以使用迭代器进行遍历了

 

复制代码

lst = range(5)

it = iter(lst)

try:

    while True:

        val = it.next()

        print val

except StopIteration:

    pass

复制代码

结果

 

 

>>> 

 

事实上,因为迭代器如此普遍,python专门为for关键字做了迭代器的语法糖。在for循环中,Python将自动调用工厂函数iter()获得迭代器,自动调用next()获取元素,还完成了检查StopIteration异常的工作。如下

 

复制代码

>>> a = (1, 2, 3, 4)

>>> for key in a:

    print key

 

    

 

复制代码

首先python对关键字in后的对象调用iter函数迭代器,然后调用迭代器的next方法获得元素,直到抛出StopIteration异常。

 

1.3 定义迭代器

 

下面一个例子——斐波那契数列

 

复制代码

# -*- coding: cp936 -*-

class Fabs(object):

    def __init__(self,max):

        self.max = max

        self.n, self.a, self.b = 0, 0, 1  #特别指出:第0项是0,第1项是第一个1.整个数列从1开始

    def __iter__(self):

        return self

    def next(self):

        if self.n < self.max:

            r = self.b

            self.a, self.b = self.b, self.a + self.b

            self.n = self.n + 1

            return r

        raise StopIteration()

 

print Fabs(5)

for key in Fabs(5):

    print key

    

复制代码

结果

 

 

<__main__.Fabs object at 0x01A63090>

 

 

 

  1. 迭代器

 

      带有 yield 的函数在 Python 中被称之为 generator(生成器),几个例子说明下(还是用生成斐波那契数列说明)

 

可以看出代码3远没有代码1简洁,生成器(yield)既可以保持代码1的简洁性,又可以保持代码3的效果

 

代码4 

 

def fab(max):

    n, a, b = 0, 0, 1

    while n < max:

        yield b

        a, b = b, a + b

        n = n = 1

执行

 

>>> for n in fab(5):

    print n

 

     

 

      简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

 

也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:

 

 

>>> f = fab(3)

>>> f.next()

1

>>> f.next()

1

>>> f.next()

2

>>> f.next()

 

Traceback (most recent call last):

  File "<pyshell#62>", line 1, in <module>

    f.next()

StopIteration

return作用

 

在一个生成器中,如果没有return,则默认执行到函数完毕;如果遇到return,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。例如

 

>>> s = fab(5)

>>> s.next()

1

>>> s.next()

 

Traceback (most recent call last):

  File "<pyshell#66>", line 1, in <module>

    s.next()

StopIteration

代码5  文件读取

 

复制代码

 def read_file(fpath): 

    BLOCK_SIZE = 1024 

    with open(fpath, 'rb') as f: 

        while True: 

            block = f.read(BLOCK_SIZE) 

            if block: 

                yield block 

            else: 

                return

复制代码

如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。

迭代器 生成器 1. 迭代器 迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束...

本文由10bet手机官网发布于面向对象,转载请注明出处:迭代器详解

上一篇:没有了 下一篇:linux系统学习,linux常用命令
猜你喜欢
热门排行
精彩图文