Yield From
Chain iterable using yield from
No argument that generators are crazy useful.
However, writing generator functions can get messy beyond yielding one sequence.
Consider this function:
def range_then_exp(N):
for i in range(N):
yield i
for i in (x**2 for x in range(N)):
yield i
For a given N
, this yields the numbers 0 to N, then their squares.
list(range_then_exp(5))
[0, 1, 2, 3, 4, 0, 1, 4, 9, 16]
However, a much cleaner way to write that is using yield from
.
def range_then_exp(N):
yield from range(N)
yield from (x**2 for x in range(N))
list(range_then_exp(5))
[0, 1, 2, 3, 4, 0, 1, 4, 9, 16]
More practical Example
Say you’re playing some table-top game and find yourself doing a lot of dice rolling.
We could use this to make generic functions.
import numpy as np
def roll_die(numFaces):
return np.random.randint(1, numFaces+1)
def multiple_dice(numDice, diceFaces):
'''Takes a sequence of [numDice]d20 rolls'''
yield from (roll_die(diceFaces) for _ in range(numDice))
def attack_rolls(numEnemies):
return [val for val in multiple_dice(numEnemies, diceFaces=20)]
def damage_rolls(numEnemies):
'''Assuming a greataxe and +2 str!'''
return [val+2 for val in multiple_dice(numEnemies, diceFaces=12)]
attack_rolls(5)
[13, 5, 9, 17, 4]
damage_rolls(5)
[10, 4, 4, 6, 6]
Neat!
And if you’re feeling especally lazy…
def roll_for_me(numEnemies):
return [(atk, dmg) for (atk, dmg) in zip(attack_rolls(numEnemies), damage_rolls(numEnemies))]
roll_for_me(5)
[(3, 10), (13, 4), (2, 9), (7, 4), (9, 10)]