from functools import lru_cache@lru_cache()deffib_recursive(n):if n <=1:return1else:returnfib_recursive(n-1)+fib_recursive(n-2)
let's create an iterator approach so we can iterate over the sequence, but without materializing it
deffib_gen(n): fib_0 =1yield fib_0 fib_1 =1yield fib_1for i inrange(n-1): fib_0, fib_1 = fib_1, fib_0 + fib_1yield fib_1 [num for num infib_gen(7)]Out:[1,1,2,3,5,8,13,21]
Making an Iterable from a Generator
As we now know, generators are iterators.
This means that they become exhausted - so sometimes we want to create an iterable instead.
There's no magic here, we simply have to implement a class that implements the iterable protocol:
defsquares_gen(n):for i inrange(n):yield i **2sq =squares_gen(5)for num in sq:print(num)014916next(sq)StopIteration
But, sq was an iterator - so now it's been exhausted, To restart the iteration we have to create a new instance of the generator,let's wrap this in an iterable:
classSquares:def__init__(self,n): self.n = n@staticmethoddefsquares_gen(n):for i inrange(n):yield i **2def__iter__(self):return Squares.squares_gen(self.n)
Card Deck
from collections import namedtupleCard =namedtuple('Card', 'rank, suit')SUITS = ('Spades','Hearts','Diamonds','Clubs')RANKS =tuple(range(2, 11))+tuple('JQKA')defcard_gen():for suit in SUITS:for rank in RANKS: card =Card(rank, suit)yield cardfor card incard_gen():print(card)Card(rank=2, suit='Spades')Card(rank=3, suit='Spades')Card(rank=4, suit='Spades')Card(rank=5, suit='Spades')Card(rank=6, suit='Spades')Card(rank=7, suit='Spades')......
We can now make it into an iterable:
deck =CardDeck()print(len[card for card in deck])54
One thing we don't have here is the support for reversed,but we can add it in by implementing the __reversed__ method and returning an iterator that iterates the deck in reverse order.
Here's an example where using yield from can be quite effective.
In this example we need to read car brands from multiple files to get it as a single collection.
We might do it this way:
brands = []withopen('car-brands-1.txt')as f:for brand in f: brands.append(brand.strip('\n'))withopen('car-brands-2.txt')as f:for brand in f: brands.append(brand.strip('\n'))withopen('car-brands-3.txt')as f:for brand in f: brands.append(brand.strip('\n'))for brand in brands:print(brand, end=', ')
We can simplify our function by using yield from:
defbrands(*files):for f_name in files:withopen(f_name)as f:yield from ffor brand inbrands(*files):print(brand, end=', ')Alfa Romeo, Aston Martin, Audi, Bentley, Benz, BMW, Bugatti, Cadillac, Chevrolet, Chrysler, Citroën, Corvette, DAF, Dacia, Daewoo, Daihatsu......
So, we are going to create generators that can read each line of the file, and yield a clean result, and we'll yield from that generator:
defgen_clean_read(file):withopen(file)as f:for line in f:yield line.strip('\n')
As you can see, this generator function will clean each line of the file before yielding it. Let's try it with a single file and make sure it works:
f1 =gen_clean_read('car-brands-1.txt')for line in f1:print(line, end=', ')Alfa Romeo, Aston Martin, Audi, Bentley, Benz, BMW,Bugatti...
Ok, that works. So now, we can proceed with our overarching generator function as before, except we'll yield from our generators, instead of directly from the file iterator:
files ='car-brands-1.txt','car-brands-2.txt','car-brands-3.txt'defbrands(*files):for file in files:yield fromgen_clean_read(file)for brand inbrands(*files):print(brand, end=', ')Alfa Romeo, Aston Martin, Audi, Bentley, Benz, BMW,Bugatti...
Other examples
# 注:为了加强示例代码的说明性,本文中的部分代码片段使用了Python 3.5# 版本添加的 Type Hinting 特性defadd_ellipsis(comments: typing.List[str],max_length:int=12):"""如果评论列表里的内容超过 max_length,剩下的字符用省略号代替 """ index =0for comment in comments: comment = comment.strip()iflen(comment)> max_length: comments[index]= comment[:max_length]+'...' index +=1return commentscomments = ["Implementation note","Changed","ABC for generator",]print("\n".join(add_ellipsis(comments)))Implementati...ChangedABC for gene...============================================================defadd_ellipsis_gen(comments: typing.Iterable[str], max_length: int =12):"""如果可迭代评论里的内容超过 max_length,剩下的字符用省略号代替 """for comment in comments: comment = comment.strip()iflen(comment)> max_length:yield comment[:max_length]+'...'else:yield commentprint("\n".join(add_ellipsis_gen(comments)))Implementati...ChangedABC for gene...