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