# 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)]