This section covers Python language features that may be new to some readers. They are used by bluesky but not unique Wherever possible, we to bluesky.

A Primer on yield and yield from

This is a very brief primer on the Python syntax yield and yield from, a feature of the core language that we will use extensively.

A Python function returns once:

In [1]: def f():
   ...:     return 1

In [2]: f()
Out[2]: 1

A Python generator is like a function with multiple exit points. Calling a generator produces an iterator that yields one value at a time. After each yield statement, its execution is suspended.

In [3]: def f():
   ...:     yield 1
   ...:     yield 2

We can exhaust the generator (i.e., get all its values) by calling list().

In [4]: list(f())
Out[4]: [1, 2]

We can get one value at a time by calling next()

In [5]: it = f()

In [6]: next(it)
Out[6]: 1

In [7]: next(it)
Out[7]: 2

or by looping through the values.

In [8]: for val in f():
   ...:     print(val)

To examine what is happening when, we can add prints.

In [9]: def verbose_f():
   ...:     print("before 1")
   ...:     yield 1
   ...:     print("before 2")
   ...:     yield 2
In [10]: it = verbose_f()

In [11]: next(it)
before 1
Out[11]: 1

In [12]: next(it)
before 2
Out[12]: 2

Notice that execution is suspended after the first yield statement. The second print is not run until we resume execution by requesting a second value. This is a useful feature of generators: they can express “lazy” execution.

Generators can delegate to other generators using yield from. This is syntax we commonly use to combine plans.

In [13]: def double_f():
   ....:     yield from f()
   ....:     yield from f()

The above is equivalent to:

In [14]: def double_f():
   ....:     for val in f():
   ....:         yield val
   ....:     for val in f():
   ....:         yield val

The yield from syntax is just more succinct.

In [15]: list(double_f())
Out[15]: [1, 2, 1, 2]