秃头的一天

  1. 有yield和没有yield的情况会对生成器了解多点:

    第一种:使用 yield

    #!/usr/bin/python3
    
    import sys
    
    def fibonacci(n,w=0): # 生成器函数 - 斐波那契
        a, b, counter = 0, 1, 0
        while True:
            if (counter > n): 
                return
            yield a
            a, b = b, a + b
            print('%d,%d' % (a,b))
            counter += 1
    f = fibonacci(10,0) # f 是一个迭代器,由生成器返回生成
    
    while True:
        try:
            print (next(f), end=" ")
        except :
            sys.exit()

    输出结果:

    0 1,1
    1 1,2
    1 2,3
    2 3,5
    3 5,8
    5 8,13
    8 13,21
    13 21,34
    21 34,55
    34 55,89
    55 89,144

    第二种:不使用 yield

    #!/usr/bin/python3
    
    import sys
    
    def fibonacci(n,w=0): # 生成器函数 - 斐波那契
        a, b, counter = 0, 1, 0
        while True:
            if (counter > n): 
                return
            #yield a
            a, b = b, a + b
            print('%d,%d' % (a,b))
            counter += 1
    f = fibonacci(10,0) # f 是一个迭代器,由生成器返回生成
    
    while True:
        try:
            print (next(f), end=" ")
        except :
            sys.exit()

    输出结果:

    1,1
    1,2
    2,3
    3,5
    5,8
    8,13
    13,21
    21,34
    34,55
    55,89
    89,144

    第二种没有yield时,函数只是简单执行,没有返回迭代器f。这里的迭代器可以用生成l列表来理解一下:

    >>> l = [i for i in range(0,15)]
    >>> print(l)
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
    >>> m = (i for i in range(0,15))
    >>> print(m)
    <generator object <genexpr> at 0x104b6f258>
    >>> for g in m:
    ...     print(g,end=', ')
    ... 
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,

    这里的m就像上面的f一样,是迭代器。

  2. 什么情况下需要使用 yield?

    一个函数 f,f 返回一个 list,这个 list 是动态计算出来的(不管是数学上的计算还是逻辑上的读取格式化),并且这个 list 会很大(无论是固定很大还是随着输入参数的增大而增大),这个时候,我们希望每次调用这个函数并使用迭代器进行循环的时候一个一个的得到每个 list 元素而不是直接得到一个完整的 list 来节省内存,这个时候 yield 就很有用。

    具体怎么使用 yield 参考:

    以斐波那契函数为例,我们一般希望从 n 返回一个 n 个数的 list:

    def fab(max): 
       n, a, b = 0, 0, 1 
       L = [] 
       while n < max: 
           L.append(b) 
           a, b = b, a + b 
           n = n + 1 
       return L

    上面那个 fab 函数从参数 max 返回一个有 max 个元素的 list,当这个 max 很大的时候,会非常的占用内存。

    一般我们使用的时候都是这个样子的,比如:

    f = iter(fab(1000))
    while True:
        try:
            print (next(f), end=" ")
        except StopIteration:
            sys.exit()

    这样我们实际上是先生成了一个 1000 个元素的 list:f,然后我们再去使用这个 f。

    现在,我们换一个方法:

    因为我们实际使用的是 list 的遍历,也就是 list 的迭代器。那么我们可以让这个函数 fab 每次只返回一个迭代器——一个计算结果,而不是一个完整的 list:

    def fab(max): 
        n, a, b = 0, 0, 1 
        while n < max: 
            yield b 
            # print b 
            a, b = b, a + b 
            n = n + 1

    这样,我们每次调用fab函数,比如这样:

    for x in fab(1000):
        print(x)

    或者 next 函数之类的,实际上的运行方式是每次的调用都在 yield 处中断并返回一个结果,然后再次调用的时候再恢复中断继续运行。

  3. 对yield的测试结果:

    1. 打个比方的话,yield有点像断点。     加了yield的函数,每次执行到有yield的时候,会返回yield后面的值 并且函数会暂停,直到下次调用或迭代终止;
    2. yield后面可以加多个数值(可以是任意类型),但返回的值是元组类型的。
    def get():
        m = 0
        n = 2
        l = ['s',1,3]
        k = {1:1,2:2}
        p = ('2','s','t')
        while True:
            m += 1
            yield m
            yield m ,n ,l ,k ,p
            
    it = get()
    print(next(it)) #1
    print(next(it)) #(1, 2, ['s', 1, 3], {1: 1, 2: 2}, ('2', 's', 't'))
    
    print(next(it)) #2
    print(type(next(it))) #<class 'tuple'>
  4. def get():
        m = 0
        n = 2
        l = ['s',1,3]
        k = {1:1,2:2}
        p = ('2','s','t')
        while True:
            m += 1
            yield m
            yield m ,n ,l ,k ,p
            
    it = get()
    print(next(it)) #1
    print(next(it)) #(1, 2, ['s', 1, 3], {1: 1, 2: 2}, ('2', 's', 't'))
    
    print(next(it)) #2
    print(type(next(it))) #<class 'tuple'>

    如果再加一句:

    print(type(next(it))) #<class 'int'>  #返回的是整形

    所以返回值的类型,应该是当前调用时,yield 返回值的类型。

  5. def myYield_1():
        a, i = 'yield', 0
        while True:
            print('before #%d' % i, end=", ")
            yield a, i
            print('after #%d' % i, end=", ")
            i += 1
    
    def myYield_2():
        a, i = 'yield_a', 0
        b, i = 'yield_b', 0
        while True:
            print('before #%d' % i, end=", ")
            yield a, i
            yield b, i
            print('after #%d' % i, end=", ")
            i += 1
    
    it1 = iter(myYield_1())
    it2 = iter(myYield_2())
    
    for i in range(10):
        print("next #%d" % i, end=": ")
        print(next(it1))
    print('\n')
    for i in range(10):
        print("next #%d" % i, end=": ")
        print(next(it2))

    输出是这样的:

    next #0: before #0, ('yield', 0)
    next #1: after #0, before #1, ('yield', 1)
    next #2: after #1, before #2, ('yield', 2)
    next #3: after #2, before #3, ('yield', 3)
    next #4: after #3, before #4, ('yield', 4)
    next #5: after #4, before #5, ('yield', 5)
    next #6: after #5, before #6, ('yield', 6)
    next #7: after #6, before #7, ('yield', 7)
    next #8: after #7, before #8, ('yield', 8)
    next #9: after #8, before #9, ('yield', 9)
    
    
    next #0: before #0, ('yield_a', 0)
    next #1: ('yield_b', 0)
    next #2: after #0, before #1, ('yield_a', 1)
    next #3: ('yield_b', 1)
    next #4: after #1, before #2, ('yield_a', 2)
    next #5: ('yield_b', 2)
    next #6: after #2, before #3, ('yield_a', 3)
    next #7: ('yield_b', 3)
    next #8: after #3, before #4, ('yield_a', 4)
    next #9: ('yield_b', 4)
  6. 字符串,列表或元组对象都可用于创建迭代器。

    字符串(Strings):

    普通的旧字符串也是可迭代的。

    for s in "hello":
        print s

    输出结果为:

    h
    e
    l
    l
    o

    列表(Lists):

    这些可能是最明显的迭代。

    for x in [None,3,4.5,"foo",lambda : "moo",object,object()]:
        print "{0}  ({1})".format(x,type(x))

    输出结果为:

    None  (<type 'NoneType'>)
    3  (<type 'int'>)
    4.5  (<type 'float'>)
    foo  (<type 'str'>)
    <function <lambda> at 0x7feec7fa7578>  (<type 'function'>)
    <type 'object'>  (<type 'type'>)
    <object object at 0x7feec7fcc090>  (<type 'object'>)

    元组(Tuples):

    元组在某些基本方面与列表不同,注意到以下示例中的可迭代对象使用圆括号而不是方括号,但输出与上面列表示例的输出相同。

    for x in (None,3,4.5,"foo",lambda : "moo",object,object()):
        print "{0}  ({1})".format(x,type(x))

    输出结果为:

    None  (<type 'NoneType'>)
    3  (<type 'int'>)
    4.5  (<type 'float'>)
    foo  (<type 'str'>)
    <function <lambda> at 0x7feec7fa7578>  (<type 'function'>)
    <type 'object'>  (<type 'type'>)
    <object object at 0x7feec7fcc090>  (<type 'object'>)

    字典(Dictionaries):

    字典是键值对的无序列表。当您使用for循环遍历字典时,您的虚拟变量将使用各种键填充。

    d = {
      'apples' : 'tasty',
      'bananas' : 'the best',
      'brussel sprouts' : 'evil',
      'cauliflower' : 'pretty good'
    }
    
    for sKey in d:
      print "{0} are {1}".format(sKey,d[sKey])

    输出结果为:

    brussel sprouts are evil
    apples are tasty
    cauliflower are pretty good
    bananas are the best

    也许不是这个顺序,字典是无序的!!!

  7. 使用自定义迭代器实现斐波那契数列

    class Fibonacci:
      def __init__(self, count):
        self.count = count
    
      def __iter__(self):
        self.i = 0
        self.a, self.b = 0, 1
        return self
    
      def __next__(self):
      if self.i < self.count:
        self.i += 1
        a_old = self.a
        self.a, self.b = self.b, self.a + self.b
        return a_old
      else:
        raise StopIteration
    
    for i in Fibonacci(10):
      print(i, end=" ")
  8. 迭代器和生成器算是 Python 一大特色,其核心是基于迭代器协议来的。

    而平时我们经常使用的 for in 循环体,本质就是迭代器协议的一大应用。

    同时 Python 内置的集合类型(字符、列表、元组、字典)都已经实现了迭代器协议,所以才能使用 for in 语句进行迭代遍历。for in 循环体在遇到 StopIteration 异常时,便终止迭代和遍历。

    再说下可迭代、迭代器、生成器三个概念的联系和区别。

    1、可迭代概念范围最大,生成器和迭代器肯定都可迭代,但可迭代不一定都是迭代器和生成器,比如上面说到的内置集合类数据类型。可以认为,在 Python 中,只要有集合特性的,都可迭代。

    2、迭代器,迭代器特点是,均可以使用 for in 和 next 逐一遍历。

    3、生成器,生成器一定是迭代器,也一定可迭代。

    至于 Python 中为何要引入迭代器和生成器,除了节省内存空间外,也可以显著提升代码运行速度。

    自定义迭代器类示例和说明如下:

    class MyIter():
      def __init__(self):
        #为了示例,用一个简单的列表作为需迭代的数据集合,并且私有化可视情况变为其他类型集合
        self.__list1=[1,2,3,4]
        self.__index=0
    
      def __iter__(self):
        #该魔法方法,必须返回一个迭代器对象,如果self已经定义了__next__()魔法方法,则只需要返回self即可
        #因为如上面所述,生成器一定是迭代器
        return iter(self.list1)    
    
      def __next__(self):
        #此处的魔法函数,python会自动记忆每次迭代的位置,无需再使用yield来处理
        #在使用next(obj)时,会自动调用该魔法方法
        res=self.__list1[self.__index]
        self.__index+=1
        return res

    以上为自定义迭代器类的机制。

    下面再示例说明下,如何自定义生成器函数,因为大多数实战场景中,使用生成器函数可能会更多一些:

    def my_gene_func():
      index=0
      li=[1,2,3,4,5]
      yield li[index]
      index+=1

    调用以上函数时,会返回一个生成器对象,然后对该生成器对象,使用 next() 逐一返回:

    gene=my_gene_func()
    next(gene)

    其实核心的概念还是记忆上次迭代的位置,类中直接使用 __next__ 魔法方法实现,函数中使用 yield 实现。且怀疑,类中的 __next__ 魔法方法底层也是使用 yield 来实现的。

    迭代器和生成器具体应用场景,就凡是需要提升运行效率或节约内存资源,且遍历的数据是集合形式的,都可以考虑。

    另外一个小众的使用场景,是变相实现协程的效果,即在同一个线程内,实现不同任务交替执行

    def mytask1():
      print('task1 开始执行')
      '''
      task code
      '''
      yield
    
    def mytask2():
      print('task2 开始执行')
      '''
      task code
      '''
      yield
    
    gene1=mytask1()
    gene2=mytask2()
    
    for i in range(100):
      next(gene1)
      next(gene2)
posted @ 2020-12-07 21:49  亓浩  阅读(197)  评论(0)    收藏  举报