python生成器和迭代器及相关概念

文章目录

  • 前言
  • 一、迭代器
    • 可迭代对象
    • 迭代器
      • 使用
        • 迭代器的自动访问
        • 委托迭代
        • 对迭代器做切片操作
        • 跳过可迭代对象中的一部分元素
        • 迭代所有可能的组合
        • 以索引-值对的形式迭代序列
        • 迭代多个序列
        • 用迭代器取代while循环
        • 实现反向迭代
  • 二、生成器【generator数据未生成】
    • 一个重要特性
  • 上下文管理器——with
    • 多个上下文管理器的使用
    • contextlib
  • 概念补充
    • yield、yield from
    • 迭代协议

前言

在一种语言中有些语法元素属于高级语法元素,有着一些有趣或者难以理解的特性,其中最常见的是迭代器、生成器、装饰器、上下文管理器本文将介绍迭代器、生成器、上下文管理器。


一、迭代器

可迭代对象

内部持有__iter__()方法的对象。

迭代器

迭代是Python最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。任何实现了__iter__和__next__()方法即迭代器协议的对象都是迭代器。__iter__返回迭代器自身【对应iter()方法】,__next__返回容器中的下一个值【next(generator),只能依次正序生成,生成数据不能再次生成。所有生成器不能超过迭代次数
】。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。字符串,列表或元组对象都可用于创建迭代器。

使用

迭代器的自动访问

通过上面我们可以知道next()可以返回迭代器的下一个值,但有时候我们需要它自动访问。我们可以用for循环。

with open("exhibitionism.txt") as f:
try:
        while True:
            line=next(f)
            print(line,end=' ')
    except StopIteration:
        pass
#----等价于
with open("exhibitionism.txt") as f:
    while True:
        line=next(f,None)
        if line is None:
            break
        print(line,end='')

注:使用next函数可以了解底层迭代器精细控制的情况。
for循环内部三件事:

  1. 调用可迭代对象的iter方法返回一个迭代器对象。
  2. 不断调用迭代器对象的next方法。
  3. 处理stopIteration

委托迭代

让自己的新容器对象【内部包含持有一个可迭代对象】能够完成迭代操作。一般来说我们要定义一个__iter__()方法,将迭代请求委托到对象内部持有的容器上。

class Node:
    def __init__(self,value):
        self._value=value
        self._children=[]
    def __repr__(self):
        return 'Node({!r})'.format(self._value)
    def add_child(self,node):

        self._children.append(node)
        print(self._children)
    def __iter__(self):
        return iter(self._children)
root=Node(0)
child1=Node(1)
child2=Node(2)
root.add_child(child1)
root.add_child(child2)
for ch in root:
    print(ch)
'''
结果:
[Node(1)]
[Node(1), Node(2)]
Node(1)
Node(2)
'''

对迭代器做切片操作

切片我们不陌生,普通的切片操作不能对迭代器产生的数据做切片处理,itertools.islice()函数是完美的选择。

def Cutdown(n):
    while n>0:
        yield n
        n-=1
#Cutdown(6)[1:2]#'generator' object is not subscriptable
import itertools
s=itertools.islice(Cutdown(6),1,3)#

注:islice()函数产生的数据和迭代器一样,只能访问一次,访问多次还需要转换成列表。

跳过可迭代对象中的一部分元素

我们已经知道了普通切片操作无法对生成器使用,如果我需要跳过一部分元素,那应该如何操作呢?需求简单如跳过前N个元素可以使用islice(iter,N,None)的方法。需求复杂你可能会考虑过滤函数filter,其实我们还可以使用itertools.dropwhile(Lf,iterable)【函数参数前者为过滤函数,后者为可迭代对象】。

import itertools
def Cutdown(n):
    while n>0:
        yield str(n)
        n-=1
def L(x):
    if x>'2':
        return x
for i in itertools.dropwhile(L ,Cutdown(8) ):#跳过x>2的数输出
    print(i,end=',')#2,1

迭代所有可能的组合

我们想对一系列元素所有可能的组合或排列进行迭代
以元组序列返回,元素全排列itertools.permutations(iterable,N)
元素的全部组合【不重复】itertools.combinations()

import itertools
items=['a','b','c']
for p in itertools.permutations(items,3):
    print(p,end='')
#('a', 'b', 'c')('a', 'c', 'b')('b', 'a', 'c')('b', 'c', 'a')('c', 'a', 'b')('c', 'b', 'a')
for i in itertools.combinations(items,3):
    print(i)#('a', 'b', 'c')

以索引-值对的形式迭代序列

迭代序列但想记录序列中当前处理到的元素索引
使用enumerate(iter,start=0)

迭代序列但想记录序列中当前处理到的元素索引
	使用enumerate(iter,start=0)

讨论:这种情况特别适合于跟踪记录文件中的行号。
补充:对于enumerate的使用

my_list1=[['a','b'],['c','d'],['e','f']]
for n ,[x,y] in enumerate(my_list1):
    print(n,x,y)
'''
0 a b
1 c d
2 e f
'''

迭代多个序列

  • 同时迭代zip函数

  • itertools.chain()依次迭代

  • heapq.merge()依次迭代但会进行排序输出

a=[1,2,3,4]
b=['one','two','three','four']
for Int,Str in zip(a,b):
    print(Int,Str)
'''
1 one
2 two
3 three
4 four
'''
for i in itertools.chain(a,b):
    print(i,end='')
'''1234onetwothreefour'''

用迭代器取代while循环

内建函数iter()可以接受一个无参的可调用对象,以及一个哨兵值(结束值)作为输入。iter创建一个迭代器,重复调用用户提供可调用对象,直到它返回哨兵值为止。

with open('exhibitionism.txt','r') as f:
    while True:
        q=f.read(10)
        if not q:
            break
        print(q)
#------------替换
with open('exhibitionism.txt','r') as f:
    for chunk in iter(lambda:f.read(10),''):
        print(chunk)

常对read\recv使用。

实现反向迭代

我们已经知道了next只能正序获取值,但是如果我们想要反向迭代的话,可利用reversed()函数实现反向迭代,但这个方法只有待处理的对象有可确定的大小或实现__reversed__()特殊方法时才能奏效。否则必须先将这个对象转换成列表,但当对象比较大时无疑会消耗大量的内存。

with open('exhibitionism.txt','r') as f:
    for line in reversed(list(f)):#不使用list会TypeError: '_io.TextIOWrapper' object is not reversible
        print(line)

下面实现__reversed__()方法的示例:

class Countdown():
    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
for i in Countdown(10):
    print(i,end='')#10987654321
for i in reversed(Countdown(10)):
    print(i,end='')#12345678910

二、生成器【generator数据未生成】

生成器是一种特殊的迭代器,它的返回值不是通过return而是用yield
生成器算得上是Python语言中最吸引人的特性之一,生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。它不需要再像上面的类一样写__iter__()和__next__()方法了,只需要一个yiled关键字。 生成器一定是迭代器(反之不成立)。
创建:

  • .(里面格式如列表推导式)
  • yield关键字
def countdown(n):
    while n>0:
        yield n
        n-=1
    print('Done')
q=countdown(2)
print(next(q))
print(q.__next__())
print(next(q))
'''
2
1
Done#StopIteration
'''

示例二

class NodeNoOne(Node):
    def depth_first(self):
        print('self:',self)
        yield self

        for c in self:
            yield from c.depth_first()
root=NodeNoOne(0)
child1=NodeNoOne(1)
child2=NodeNoOne(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(NodeNoOne(3))
child1.add_child(NodeNoOne(4))
child2.add_child(NodeNoOne(5))
for ch in root.depth_first():
    print(ch)
'''

self: Node(0)
Node(0)
self: Node(1)
Node(1)
self: Node(3)
Node(3)
self: Node(4)
Node(4)
self: Node(2)
Node(2)
self: Node(5)
Node(5)
'''

一个重要特性

python生成器的一个重要特性就是利用next可以实现交互,即yield变成一个表达式,而值可以通过名为send的方法来传递值。

def psychologist():
print('Could you say something that make you unhappy to make us happy?')
    while True:
        answer=(yield )
        if answer is not None:
            if answer.endswith("?"):
                print("不要问我,我只是可爱的兔兔")
            elif answer.endswith("。"):
                print("就这?就这?我还以为有多好笑呢?")
                print("我来讲个给你听一听")
                print("LSP是啥意思?答:Lovely Special Person 可爱又独特的人")
                print('LSP,很好笑吧')
            else:
                print('大声点,听不见')
        else:
            print('额,没有吗?真是很好(违心的说)')
>>>free=psychologist()
>>>next(free)
Could you say something that make you unhappy to make us happy?
>>>free.send("I free bad 。")
就这?就这?我还以为有多好笑呢?
我来讲个给你听一听
LSP是啥意思?答:Lovely Special Person 可爱又独特的人
LSP,很好笑吧

说明:send的作用和next类似,但会将函数定义内部传入的值变成yield的返回值。

上下文管理器——with

ContextManager ,上下文是 context 直译的叫法,在程序中用来表示代码执行过程中所处的前后环境。上下文管理器中有 enterexit 两个方法。enter 方法会在执行 with 后面的语句时执行,一般用来处理操作前的内容。比如一些创建对象,初始化等;exit 方法会在 with 内的代码执行完毕后执行,一般用来处理一些善后收尾工作,比如文件的关闭,数据库的关闭。
关于上下文管理器的大部分内容,都在类、并发中都有讲到,如类中的实现上下文管理器协议,并发中的显式上锁。这里只补充为讲到的知识。

多个上下文管理器的使用

with B() as A ,C() as D:
    pass
#等价于
with B() as A:
    with C() as D:
        pass

contextlib

使用类似乎是实现python语言提供的任何协议最灵活的方式,但对于许多场景并不是最好的方式。标准库中新增了contextlib模块,提供与上下文管理器一起使用的辅助函数。它最有用的部分contextmanager装饰器。

contextmanager
def tag(name):
    print("<%s>" % name)
    yield
print("" % name)

with tag("h1"):
    print("hello")
    print("world")
#上述代码执行结果为:
<h1>
hello
world
</h1>

代码的执行顺序是:

with语句首先执行yield之前的语句,因此打印出<h1>yield调用会执行with语句内部的所有语句,因此打印出hello和world;
最后执行yield之后的语句,打印出</h1>

如果一个对象没有实现上下文,我们就不能把它用于with语句。这个时候,可以用closing()来把该对象变为上下文对象。例如,用with语句使用urlopen()

from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('https://www.python.org')) as page:
    for line in page:
        print(line)

概念补充

yield、yield from

概念了解:
1、调用方:调用委派生成器的客户端(调用方)代码
2、委托生成器:包含yield from表达式的生成器函数
3、子生成器:yield from后面加的生成器函数
yield from 是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰. yield from 后面加上一个生成器后,就实现了生成的嵌套。当然实现生成器的嵌套,并不是一定必须要使用yield from,而是使用yield from可以让我们避免让我们自己处理各种料想不到的异常,而让我们专注于业务代码的实现。如果自己用yield去实现,那只会加大代码的编写难度,降低开发效率,降低代码的可读性。

# 子生成器
def average_gen():
    total = 0
    count = 0
    average = 0
    while True:
        new_num = yield average
        if new_num is None:
            break
        count += 1
        total += new_num
        average = total/count

    # 每一次return,都意味着当前协程结束。
    return total,count,average

# 委托生成器
def proxy_gen():
    while True:
        # 只有子生成器要结束(return)了,yield from左边的变量才会被赋值,后面的代码才会执行。
        total, count, average = yield from average_gen()
        print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))

# 调用方
def main():
    calc_average = proxy_gen()
    next(calc_average)            # 预激协程
    print(calc_average.send(10))  # 打印:10.0
    print(calc_average.send(20))  # 打印:15.0
    print(calc_average.send(30))  # 打印:20.0
    calc_average.send(None)      # 结束协程
    # 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程
if __name__ == '__main__':
    main()

yield from它可以帮我们处理异常StopIteration

迭代协议

迭代协议:迭代协议具体分为两个协议:可迭代协议和迭代器协议。可迭代协议允许对象定义或定制它们的迭代行为,例如,在一个对象中,哪些值可以被遍历到。一些内置类型同时是内置可迭代对象,并且有默认的迭代行为,迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。

你可能感兴趣的:(python基础)