函数进阶之生成器和迭代器

前提:python

列表生成式算法

给列表a里的大于5的每个值加10函数

a = [1, 2, 5, 6, 7, 8]
a = [i + 10 if i > 5 else i for i in a]  # 能够循环任何可循环的东西,不过只能写到列表或元组里。
print(a)  # [1, 2, 5, 16, 17, 18]

复杂东西列表生成式写不出来,最多到三元运算了。性能

 

正文:大数据

生成器是为了省内存,不一次释放,须要一个取一个。spa

g = (i for i in range(5))
print(g)  # <generator object <genexpr> at 0x101fba048>
print(next(g))  # 0
print(next(g))  # 1
print(next(g))  # 2
print(next(g))  # 3
print(next(g))  # 4
print(next(g))  # 到最后没有了,会报错

生成器只能往前走,不能后退。code

咱们建立了一个generator后,基本上永远不会调用next(),而是经过for循环来迭代它,而且不须要关心StopIteration的错误,for循环会把它自动消化。对象

g2 = (i for i in range(5))

for i in g2:
    print(i)

generator很是强大。若是推算的算法比较复杂,用相似列表生成式的for循环没法实现的时候,还能够用函数来实现。blog

 

生成器和range内存

python2里range是直接建立列表。
python3里的range是用生成器作的,是循环到了才建立。
python2里有个xrange,其实就是等于python3里的range。python3只有range,等因而把python2里的那个range去掉了。

 

用生成器实现斐波那契

def fib(max):
    n, a, b = 10, 0, 1
    while n < max:
        print('before yield')yield n  # 把函数的执行过程冻结在这一步,而且把b的值,返回给外面的next()
        print(b)
        a, b = b, a + b
        n += 1
    return 'done'


print(fib(15))  # <generator object fib at 0x101fba048> 里面有yield,函数名加括号,内部代码根本不执行,只是生成一个生成器对象。

f = fib(15)  # return function into a generator

next(f)  # before yield  # first time call next()
n = next(f)  # 1 # before yield
next(f)  # 1 # before yield
next(f)  # 2 # before yield

print(n)  # 11

yield的做用是把函数里面的值返回出来到外部

解析:第一次print出 before yield以后,遇到yield,程序终止,再次执行next(f),程序继续运行print出b的值,而后直到再次走到print('before yield')后,
程序遇到yield n,程序终止,而后再次执行next(f),以此循环......

 

生成器的建立方式

1. "列表"生成式(),最多支持三元表达式。 例:g2 = (i for i in range(5))
2. 函数

 

用生成器实现range方法

def range2(n):
    count = 0
    while count < n:
        print('count', count)
        yield count
        count += 1


new_range = range2(10)
r1 = next(new_range)
print(r1)  # 0
r2 = new_range.__next__()  # 和 next(new_range)是同样的
print(r2)  # 1

 

yield vs return

return 返回并终止函数
yield 返回数据,并冻结当前的执行过程

next 唤醒冻结的函数执行过程,继续执行,直到遇到下一个yield

 

函数有了yield以后
1.函数名加()就变成了一个生成器
2.return在生成器里,表明生成器的终止,直接报错

 

生成器send方法

def range2(n):
    count = 0
    while count < n:
        print('count', count)
        sign = yield count
        count += 1
        if sign == 'stop':
            print('----sign ', sign)
            break


new_range = range2(10)
n1 = next(new_range)  # count 0

n2 = new_range.__next__()  # count 1

n3 = new_range.send(None)  # count 2 这条语句等于next,由于next(或__next__)方法就是默认往生成器里面发送了一个None

n4 = new_range.send('stop')  # 终止,程序报错,打印----sign  stop

 

send的做用:
1.唤醒并继续执行
2. 发送一个信息到生成器的内部

next()默认往生成器里发送一个None

 

迭代器

能够被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

可使用isinstance()判断一个对象是不是Iterable对象

from collections import Iterable

print(isinstance(123, Iterable))  # False
print(isinstance('abc', Iterable))  # True

只有生成器是迭代器。

from collections import Iterator

li = [i for i in range(10)]
print(isinstance(li, Iterator))  # False

可是能够把列表、字符串等变成迭代器

li = iter(li)

print(li.__next__())  # 0
print(li.__next__())  # 1

Python的Iterator对象表示的是一个数据流,Iterator对象能够被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。
能够把这个数据流看作是一个有序序列,但咱们却不能提早知道序列的长度,只能不断经过next()函数实现按需计算下一个数据,因此Iterator的计算是惰性的,
只有在须要返回下一个数据时它才会计算。Iterator甚至能够表示一个无限大的数据流,例如全体天然数。而使用list是永远不可能存储全体天然数的。

 

须要注意的是:

  1.列表用len能知道长度,可是迭代器不能知道
  2.迭代器比生成器的范围要大一些,学了面向对象后不用生成器也能next。

 

凡是可做用于for循环的对象都是Iterable类型;
凡是可做用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过能够经过iter()函数得到一个Iterator对象。
Python3的for循环本质上就是经过不断调用next()函数实现的

 

生成器算得上是Python语言中最吸引人的特性之一,生成器实际上是一种特殊的迭代器,不过这种迭代器更加优雅。
它不须要再像上面的类同样写__iter__()和__next__()方法了,只须要一个yiled关键字。
生成器必定是迭代器(反之不成立),所以任何生成器也是以一种懒加载的模式生成值。

 

生成器 VS 迭代器 (补充)

 

1. 迭代器(Iterator)

这里的迭代能够指for循环,在Python中,对于像list,dict和文件等而言,均可以使用for循环,可是它们并非迭代器,它们属于可迭代对象。
1.1 什么可迭代对象
最简单的解释:可使用for...in...语句进行循环的对象,就是可迭代对象(Iterable),可使用isinstance()方法进行判断。

from collections import Iterable 
type = isinstance('python', Iterable)
print type

1.2 什么是迭代器
迭代器指的是可使用next()方法来回调的对象,能够对可迭代对象使用iter()方法,将其转换为迭代器。

temp = iter([1, 2, 3])
print type(temp)
print next(temp)

此时temp就是一个迭代器。因此说,迭代器基于两个方法:

  • next:返回下一个项目
  • _iter_ 返回迭代器自己
    可理解为可被next()函数调用并不断返回下一个值的对象就是迭代器,在定义一个装饰器时将须要同时定义这两个方法。
    迭代器的优点
    在构建迭代器时,不是将全部的元素一次性的加载,而是等调用next方法时返回元素,因此不须要考虑内存的问题。
    迭代器应用场景
    那么,具体在什么场景下可使用迭代器呢?
  • 数列的数据规模巨大
  • 数列有规律,可是不能使用列表推导式描述。

1.3 文件对象

f = open('myfile.txt')
print(next(f))
print(next(f))
print(next(f))
print(next(f))

hello text file
goodbyt text file
hahahahah
Traceback (most recent call last):
 File "E:/12homework/12homework.py", line 5, in <module>
print(next(f))
StopIteration

 文件对象的迭代器就是他本身。即文件对象既是迭代器,又是可迭代对象.

2. 生成器

生成器是一种高级迭代器,使得须要返回一系列元素的函数所需的代码更加的简单和高效(不像建立迭代器代码那般冗长)。
2.1 生成器函数
生成器函数和常规函数都采用def语句进行定义,可是基于yield指令返回一个值,能够暂停一个函数并返回中间结果。当须要一个将返回一个序列或在循环中执行的函数时,就可使用生成器,由于当这些元素被传递到另外一个函数中进行后续处理时,一次返回一个元素能够有效的提高总体性能。
常见的应用场景是使用生成器的流数据缓冲区。

2.2 生成器表达式
生成式表达式是一种实现生成器的便捷方式,将列表推导式的中括号替换为圆括号。
和列表推导式的区别:列表生成式能够直接建立一个表,可是生成器表达式是一种边循环边计算,使得列表的元素能够在循环过程当中一个个的推算出来,不须要建立完整的列表,从而节省了大量的空间。


g = (x * x for x in range(10))

总结:生成器是一种高级迭代器。生成器的优势是延迟计算,一次返回一个结果,这样很是适用于大数据量的计算。可是,使用生成器必需要注意的一点是:生成器只能遍历一次。

相关文章
相关标签/搜索