Python迭代器和生成器(改编自知乎相关文章)

news/2024/7/4 1:03:56

Python迭代器和生成器(改编自知乎相关文章)

1.迭代器

有一些Python对象,我们可以从中按一定次序提取出其中的元素。这些对象称之为可迭代对象。比如,字符串、列表、元组都是可迭代对象。

我们回忆一下从可迭代对象中提取元素的过程。这次,我们显式的使用列表的下标

my_str = 'abc'
for i,_ in enumerate(my_str):
    print my_str[i]

同样的,以下是通过下标对可迭代对象的元素进行改写(用下标数字访问可迭代对象的方法):

my_list = [1,2,3]
for i,_ in enumerate(my_list):
    my_list[i] = my_list[i] + 1
print my_list

我们知道,在以上两个例子中,读取和写入元素,是通过可迭代对象的操作符[]实现的。而下标只是作为参数出现。我们不妨把这种模式,称作"可迭代对象是一等公民"。

与之相对的,有没有可能将下标作为一等公民?换句话说,元素的提取只和下标打交道,而和可迭代对象无关。答案是有的。这样的一种设计模式,就是迭代器模式。那个升级版的下标,称之为迭代器。

下面的代码,说明了如何显式的使用迭代器。利用Python内建函数iter,可以得到一个可迭代对象的迭代器。

my_list = [1,2,3]
i = iter(my_list)
while True:
    try:
        print next(i)
    except StopIteration:
        break

一旦迭代器建立起来,提取元素的过程就不再依赖于原始的可迭代对象,而是仅仅依赖于迭代器本身。Python内建的next函数作用于迭代器上,会执行三个操作:

  1. 返回当前『位置』的元素,第一次next调用,当前位置是可迭代对象的起始位置
  2. 将『位置』向后递增
  3. 如果到达可迭代对象的末尾,即没有元素可以提取,则抛出StopIteration异常

实际上,你并不需要这么麻烦的方法来使用迭代器,Python中的循环语句会自动进行迭代器的建立、next的调用和StopIteration的处理。换句话说,遍历一个可迭代对象的元素,这样写就对了:

my_list = [1,2,3]
for v in my_list:
    print v

换句话说,Python的for...in语句,隐藏了大量的细节。

所以,Python 2.X 的 range和xrange有何区别?答案是,range的返回值就是一个list,在你调用range的时候,Python会产生所有的元素。而xrange是一个特别设计的可迭代对象,它在建立的时候仅仅保存终止值。你可比较以下两种写法的实际运行结果:

for v in range(1000000000000): #possible Memory Error
    if v == 2:
        break
    
for v in xrange(1000000000000): #fine
    if v == 2:
        break

在Python 3.X 中,不再有内建的xrange,其range等效于Python 2.X 的xrange。

2.自定义迭代器

那么Python的iter函数和next函数都做了什么呢?答案是,基本上什么都没做!它们的内部实现是(逻辑上)这样的:

def iter(obj):
    return obj.__iter__()

#Python 2.X
def next(obj):
    return obj.next()

#Python 3.X
def next(obj):
    return obj.__next__()

所以利用这一点,我们很容易写一个自己的可迭代对象和迭代器。下面就是一个例子,这个迭代器有随机的长度。测试它的方法就用for...in...

import random

class demo_iterator(object):
    def __next__(self):
        return self.next()
    
    def next(self):
        v = random.randint(0,10)
        if v < 5:
            raise StopIteration()
        else:
            return v
    
class demo_iterable(object):
    def __iter__(self):
        return demo_iterator()
    
for v in demo_iterable():
    print(v)

这个故事告诉我们,若想让你自己的对象支持for...in...,你可以实现它的迭代器接口。

使用迭代器有何好处?用默认的列表不也很好吗?实际上,使用迭代器最大的优点是,能够及时处理『未知』的事件(例如,用户的输入,网络上的信号),并在迭代推进的过程中随时可以终止迭代。 就拿range为例,我们如果想尽可能多的利用系统内存产生尽可能多的数据。那么使用迭代器的方法,可以在每一次迭代的时候都检查一下剩余可用的内存,从而决定要不要进行下一次迭代。而使用list的方法,过程中使用了多少内存,是很难预见的。

4.生成器

生成器是创建迭代器的一种简便的方法。生成器是一个特殊的函数。我们可以从静态和动态两个角度理解生成器函数。

首先,从静态的角度,生成器函数在代码中表现为:

  • 含有yield语句(无论yield是否可能会被执行)
  • 无return或者仅有无值return(一旦函数里存在yield语句,有值return会视为语法错误)

其次,从动态的角度,生成器函数在运行过程中:

  • 当生成器函数被调用的时候,生成器函数不执行内部的任何代码,直接立即返回一个迭代器。
  • 当所返回的迭代器第一次调用next的时候,生成器函数从头开始执行,如果遇到了执行yield x,next会立即返回yeild值x(进入挂起状态)
  • 当所返回的迭代器继续调用next的时候,生成器函数从上次yield语句的下一句开始执行,直到遇到下一次执行yield(从挂起状态中恢复)
  • 任何时候遇到函数结尾,或者return语句,抛出StopIteration异常

特别的,对于该生成器所返回的迭代器,其__iter__返回其自身。

def g():
    print 'L1'
    yield 1
    print 'L2'
    yield 2
    print 'L3'
    yield 3
    print 'L4'

    
it = iter(g())
v = next(it);print v
v = next(it);print v
v = next(it);print v
v = next(it);print v

g()已经返回了一个迭代器,iter这个迭代器将得到迭代器自身。所以it依然是生成器函数g返回的迭代器。

it = g()
print id(it)
print id(iter(it)) #same value as previous line

在执行g()的时候,并没有输出L1。而是第一次调用next的时候,L1输出,next返回1。以此类推,之后L4被打印出来,最后一个next抛出SropIteration异常。

利用生成器,我们可以重写之前的随机序列,看起来简单多了。

import random
    
def demo_generator(n):
    while True:
        v = random.randint(0,n)
        if v > 5:
            yield v
        else:
            break
        
for i in demo_generator(10):
    print i
5.应用、itertools、以及其他

迭代器由于其不定长的特性,特别适合表达数学中的"无穷序列"。

比如说,我们要寻找前10组(边长全为整数的)直角三角形的三边长度。算法是暴力枚举每一边的长度。然而,我们并不知道边长的搜索边界,用列表做循环显然不合适。

所以,我们首先产生一个正整数无穷序列:

def pint():
    i = 1
    while True:
        yield i
        i += 1

然后进行穷举。因为直角三角形的直角边总小于斜边,所以直角边的范围不用取无穷序列。另外,为了避免对称重复,最内层循环的直角边只取比另一个直角边小的值。

def tri(): 
    for h in pint(): #hypotenuse 
        for c1 in range(1,h): #cathetus1 
            for c2 in range(1,c1): #cathetus2 
                if c1 * c1 + c2 * c2 == h * h: 
                    yield (c1,c2,h)

tri也是个生成器,得到的迭代器也是个无穷序列。取前10个,用for循环就好:

for i,v in enumerate(tri()):
    if i == 10:
        break
    print v

实际上想取多少就取多少。这种无穷序列在解决数学问题的时候特别方便。

Python内建库itertools有很多很方便的函数,大部分函数都支持无穷序列的运算。若自己写一些迭代器(无论用类的方法,还是用生成器),就可以很方便的利用这些itertools函数了。具体的计算功能可见相关文档。

Python中除了可迭代对象,还有"容器"对象的概念。尽管很多内建对象即是容器又是可迭代对象,但这两个概念是相互独立的。容器对象无非是实现了__contains__成员,使得能够接受in操作符的运算。一个对象是不是容器,和它是不是可迭代,没有任何关系。

class cont(object):
    def __contains__(self,x):
        return True
    
if 2 in cont():
    print 'Here'
    
for x in cont(): #TypeError: 'cont' object is not iterable
    print x

转载于:https://www.cnblogs.com/Khannia/p/6213438.html


http://www.niftyadmin.cn/n/3628390.html

相关文章

第15周阅读程序(3)

/* *Copyright (c) 2016,烟台大学计算机学院 *All rights reserved. *文件名称 : *作 者 : 刘云 *完成日期 : 2016年6月7号 *版 本 号 : v6.0 * *问题描述 : 阅读程序 *输入描述 :无 *程序输出 : */#include<algorithm> #include<functional> #include<vector&g…

第15周阅读程序(4)

/* *Copyright (c) 2016,烟台大学计算机学院 *All rights reserved. *文件名称 : *作 者 : 刘云 *完成日期 : 2016年6月7号 *版 本 号 : v6.0 * *问题描述 : 阅读程序 *输入描述 :无 *程序输出 : */#include<algorithm> #include<functional> #include<iostream…

lightoj 1140 - How Many Zeroes?(数位DP)

1140 - How Many Zeroes? PDF (English)StatisticsForum Time Limit: 2 second(s)Memory Limit: 32 MB Jimmy writes down the decimal representations of all natural numbers between and including m and n, (m ≤ n). How many zeroes will he write down? Input Inp…

JS对象继承(6种)

原型链(父类的实例)借用构造函数(调用超类构造函数)组合继承(原型链和借用构造函数)原型式继承(借助原型基于已有的对象上创建新对象)寄生式继承(创建一个仅用于封装继承过程的函数)寄生组合式继承(解决父类属性重写)ECMAScript无法实现接口继承&#xff0c;只支持实现继承&…

第15周阅读程序(5)

/* *Copyright (c) 2016,烟台大学计算机学院 *All rights reserved. *文件名称 : *作 者 : 刘云 *完成日期 : 2016年6月7号 *版 本 号 : v6.0 * *问题描述 : 阅读程序 *输入描述 :无 *程序输出 : */#include<iterator> #include<algorithm> #include<functional…

Java的String字符串内容总结

String--字符串 获取字符串的长度 使用Sring类的length()方法可获取字符串对象的长度&#xff0c;例&#xff1a; str.length(); str代表指定的字符串对象;返回值为返回指定字符串的长度。例&#xff1a; 获取字符串中指定字符的索引位置 String类提供了indexOf()和lastIndexOf…

第15周阅读程序(6)

/* *Copyright (c) 2016,烟台大学计算机学院 *All rights reserved. *文件名称 : *作 者 : 刘云 *完成日期 : 2016年6月7号 *版 本 号 : v6.0 * *问题描述 : 阅读程序 *输入描述 :无 *程序输出 : */#include<string> #include<iostream> #include<map> using…

poj 3208 Apocalypse Someday 数位dp+二分答案

Apocalypse Someday Time Limit: 1000MS Memory Limit: 131072KTotal Submissions: 2203 Accepted: 1110Description The number 666 is considered to be the occult “number of the beast” and is a well used number in all major apocalypse themed blockbuster movies. …