How marimo notebooks run
Reactive execution is based on a single rule: when a cell is run, all other cells that reference any of the global variables it defines run automatically.
To provide reactive execution, marimo creates a dataflow graph out of your cells. ## References and definitions
A marimo notebook is a directed acyclic graph in which nodes represent cells and edges represent data dependencies. marimo creates this graph by analyzing each cell (without running it) to determine its
- references (“refs*), the global variables it reads but doesn’t define;
- definitions (“defs”), the global variables it defines.
There is an edge from one cell to another if the latter cell references any global variables defined by the former cell.
The rule for reactive execution can be restated in terms of the graph: when a cell is run, its descendants are run automatically. ### Example
The next four cells plot a sine wave with a given period and amplitude. Each cell is labeled with its refs and defs.
Use mo.refs()
and mo.defs()
to inspect the refs and defs of any
given cell. This can help with debugging complex notebooks.
For example, here are the refs and defs of this cell:
mo.accordion(%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22Tip%3A%20inspecting%20refs%20and%20defs%22%3A%20f%22%22%22%0A%20%20%20%20%20%20%20%20Use%20%60mo.refs()%60%20and%20%60mo.defs()%60%20to%20inspect%20the%20refs%20and%20defs%20of%20any%0A%20%20%20%20%20%20%20%20given%20cell.%20This%20can%20help%20with%20debugging%20complex%20notebooks.%0A%0A%20%20%20%20%20%20%20%20For%20example%2C%20here%20are%20the%20refs%20and%20defs%20of%20this%20cell%3A%0A%0A%20%20%20%20%20%20%20%20%7Bmo.as_html(%7B%22refs%22%3A%20mo.refs()%2C%20%22defs%22%3A%20mo.defs()%7D)%7D%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%7D%0A)
refs: ('amplitude', 'mo', 'period', 'plot_wave')
defs: ()
mo.md(%0A%20%20%20%20f%22%22%22%0A%20%20%20%20%7Bmo.as_html(plot_wave(amplitude%2C%20period))%7D%0A%0A%20%20%20%20-%20%60refs%3A%20%7Bmo.refs()%7D%60%0A%20%20%20%20-%20%60defs%3A%20%7Bmo.defs()%7D%60%0A%20%20%20%20%22%22%22%0A)
refs: ('mo',)
defs: ('period',)
period%20%3D%202%20*%203.14159%0A%0Amo.md(%0A%20%20%20%20f%22%22%22%0A%20%20%20%20-%20%60refs%3A%20%7Bmo.refs()%7D%60%0A%20%20%20%20-%20%60defs%3A%20%7Bmo.defs()%7D%60%0A%20%20%20%20%22%22%22%0A)
refs: ('mo',)
defs: ('amplitude',)
amplitude%20%3D%201%0A%0Amo.md(%0A%20%20%20%20f%22%22%22%0A%20%20%20%20-%20%60refs%3A%20%7Bmo.refs()%7D%60%0A%20%20%20%20-%20%60defs%3A%20%7Bmo.defs()%7D%60%0A%20%20%20%20%22%22%22%0A)
refs: ('matplotlib_installed', 'mo', 'np', 'numpy_installed', 'plt')
defs: ('plot_wave',)
def%20plot_wave(amplitude%2C%20period)%3A%0A%20%20%20%20if%20not%20numpy_installed%3A%0A%20%20%20%20%20%20%20%20return%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%3E%20Oops!%20It%20looks%20like%20you%20don't%20have%20%60numpy%60%20installed.%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20if%20not%20matplotlib_installed%3A%0A%20%20%20%20%20%20%20%20return%20mo.md(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%3E%20Oops!%20It%20looks%20like%20you%20don't%20have%20%60matplotlib%60%20installed.%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20x%20%3D%20np.linspace(0%2C%202%20*%20np.pi%2C%20256)%0A%20%20%20%20plt.plot(x%2C%20amplitude%20*%20np.sin(2%20*%20np.pi%20%2F%20period%20*%20x))%0A%20%20%20%20plt.xlim(0%2C%202%20*%20np.pi)%0A%20%20%20%20plt.ylim(-2%2C%202)%0A%20%20%20%20plt.xticks(%0A%20%20%20%20%20%20%20%20%5B0%2C%20np.pi%20%2F%202%2C%20np.pi%2C%203%20*%20np.pi%20%2F%202%2C%202%20*%20np.pi%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%20r%22%24%5Cpi%2F2%24%22%2C%20r%22%24%5Cpi%24%22%2C%20r%22%243%5Cpi%2F2%24%22%2C%20r%22%242%5Cpi%24%22%5D%2C%0A%20%20%20%20)%0A%20%20%20%20plt.yticks(%5B-2%2C%20-1%2C%200%2C%201%2C%202%5D)%0A%20%20%20%20plt.gcf().set_size_inches(6.5%2C%202.4)%0A%20%20%20%20return%20plt.gca()%0A%0Amo.md(%0A%20%20%20%20f%22%22%22%0A%20%20%20%20-%20%60refs%3A%20%7Bmo.refs()%7D%60%0A%20%20%20%20-%20%60defs%3A%20%7Bmo.defs()%7D%60%0A%20%20%20%20%22%22%22%0A)
🌊 Try it! In the above cells, try changing the value period
or ampltitude
, then click the run button ( ▷ ) to register your changes. See what happens to the sine wave. Here is the dataflow graph for the cells that make the sine wave plot, plus the cells that import libraries. Each cell is labeled with its defs.
+------+ +-----------+
+-----------| {mo} |-----------+ | {np, plt} |
| +---+--+ | +----+------+
| | | |
| | | |
v v v v
+----------+ +-------------+ +--+----------+
| {period} | | {amplitude} | | {plot_wave} |
+---+------+ +-----+-------+ +------+------+
| | |
| v |
| +----+ |
+------------> | {} | <-------------+
+----+
The last cell, which doesn’t define anything, produces the plot. ## Dataflow programming
marimo’s runtime rule has some important consequences that may seem surprising if you are not used to dataflow programming. We list these below. ### Execution order is not cell order
The order in which cells are executed is determined entirely by the dataflow graph. This makes marimo notebooks more reproducible than traditional notebooks. It also lets you place boilerplate, like imports or long markdown strings, at the bottom of the editor. ### Global variable names must be unique
Every global variable can be defined by only one cell. Without this constraint, there would be no way for marimo to know which order to execute cells in.
If you violate this constraint, marimo provides a helpful error message, like below:
({'name': 'planet', 'cells': ('Xref',), 'type': 'multiple-defs'},)
planet%20%3D%20%22Mars%22%0Aplanet
({'name': 'planet', 'cells': ('PKri',), 'type': 'multiple-defs'},)
planet%20%3D%20%22Earth%22%0Aplanet
🌊 Try it! In the previous cell, change the name planet
to home
, then run the cell. Because defs must be unique, global variables cannot be modified with operators like +=
or -=
in cells other than the one that created them; these operators count as redefinitions of a name.
🌊 Try it! Get rid of the following errors by merging the next two cells into a single cell.
({'name': 'count', 'cells': ('BYtC',), 'type': 'multiple-defs'},)
count%20%3D%200
({'name': 'count', 'cells': ('SFPL',), 'type': 'multiple-defs'},)
count%20%2B%3D%201
Underscore-prefixed variables are local to cells
Global variables prefixed with an underscore are “private” to the cells that define them. This means that multiple cells can define the same underscore-prefixed name, and one cell’s private variables won’t be made available to other cells.
Example.
_private_variable%2C%20_%20%3D%201%2C%202%0A_private_variable%2C%20_
_private_variable%2C%20_%20%3D%203%2C%204%0A_private_variable%2C%20_
[{'msg': "This cell raised an exception: NameError('name '_private_variable' is not defined')", 'exception_type': 'NameError', 'raising_cell': None, 'type': 'exception'}]
%23%20%60_private_variable%60%20and%20%60_%60%20are%20not%20defined%20in%20this%20cell%0A_private_variable%2C%20_
Deleting a cell deletes its variables
Deleting a cell deletes its global variables and then runs all cells that reference them. This prevents severe bugs that can arise when state has been deleted from the editor but not from the program memory.
🌊 Try it!
Delete this cell by clicking the trash bin icon.
to_be_deleted%20%3D%20%22variable%20still%20exists%22%0A%0Amo.md(%0A%20%20%20%20%22%22%22%0A%20%20%20%20%F0%9F%8C%8A%20**Try%20it!**%0A%0A%20%20%20%20Delete%20this%20cell%20by%20clicking%20the%20trash%20bin%20icon.%0A%20%20%20%20%22%22%22%0A)
variable still exists
to_be_deleted
Cycles are not allowed
Cycles among cells are not allowed. For example:
({'edges_with_vars': (('iLit', ['one'], 'ZHCJ'), ('ZHCJ', ['two'], 'iLit')), 'type': 'cycle'},)
one%20%3D%20two%20-%201
({'edges_with_vars': (('iLit', ['one'], 'ZHCJ'), ('ZHCJ', ['two'], 'iLit')), 'type': 'cycle'},)
two%20%3D%20one%20%2B%201
marimo doesn’t track attributes
marimo only tracks global variables. Writing object attributes does not trigger reactive execution.
🌊 Example. Change the value of state.number
in the next cell, then run the cell. Notice how the subsequent cell isn’t updated.
state.number%20%3D%201
0
state.number
class%20namespace%3A%0A%20%20%20%20pass%0A%0Astate%20%3D%20namespace()%0Astate.number%20%3D%200
marimo can't reliably trace attributes
to cells that define them. For example, attributes are routinely
created or modified by library code.
mo.accordion(%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22Why%20not%20track%20attributes%3F%22%3A%20%22%22%22%0A%20%20%20%20%20%20%20%20marimo%20can't%20reliably%20trace%20attributes%0A%20%20%20%20%20%20%20%20to%20cells%20that%20define%20them.%20For%20example%2C%20attributes%20are%20routinely%0A%20%20%20%20%20%20%20%20created%20or%20modified%20by%20library%20code.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%7D%0A)
marimo doesn’t track mutations
In Python, it’s impossible to know whether code will mutate an object without running it. So: mutations (such as appending to a list) will not trigger reactive execution.
You can use the fact that marimo does not track attributes or
mutations to implement mutable state in marimo. An example of
this is shown in the ui
tutorial.
mo.accordion(%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%22Tip%20(advanced)%3A%20mutable%20state%22%3A%20(%0A%20%20%20%20%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20You%20can%20use%20the%20fact%20that%20marimo%20does%20not%20track%20attributes%20or%0A%20%20%20%20%20%20%20%20mutations%20to%20implement%20mutable%20state%20in%20marimo.%20An%20example%20of%0A%20%20%20%20%20%20%20%20this%20is%20shown%20in%20the%20%60ui%60%20tutorial.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20)%0A%20%20%20%20%7D%0A)
Best practices
The constraints marimo puts on your notebooks are all natural consequences of the fact that marimo programs are directed acyclic graphs. As long as you keep this fact in mind, you’ll quickly adapt to the marimo way of writing notebooks.
Ultimately, these constraints will enable you to create powerful notebooks and apps, and they’ll encourage you to write clean, reproducible code.
Follow these tips to stay on the marimo way:
Keep the number of global variables in your program small to avoid
name collisions across cells. Keep the number of global variables
defined by any one cell small to make sure that the units of
reactive execution are small.
Use descriptive variable names, especially for global variables.
This will help you minimize name clashes, and will also result in
better code.
Encapsulate logic into functions to avoid polluting the global
namespace with temporary or intermediate variables.
We saw earlier that marimo cannot track object mutations. So try
to only mutate an object in the cell that creates it, or create
new objects instead of mutating existing ones.
For example, don't do this:
# a cell
numbers = [1, 2, 3]
# another cell
numbers.append(4)
Instead, prefer
# a cell
numbers = [1, 2, 3]
numbers.append(4)
or
# a cell
numbers = [1, 2, 3]
# another cell
more_numbers = numbers + [4]
Write cells whose outputs and behavior are the same when given
the same inputs (refs); such cells are called idempotent. This will
help you avoid bugs, and let you cache expensive intermediate
computations (see the next tip).
Use Python's builtin functools
library to cache expensive
intermediate computations. You can do this if you abstract complex
logic into idempotent functions, following earlier tips.
For example:
import functools
@functools.cache
def compute_prediction(problem_parameters):
...
Whenever compute_predictions
is called with a value of
problem_parameters
it has not seen, it will compute the predictions
and store them in a cache. The next time it is called with the same
parameters, instead of recomputing the predictions, it will just
fetch the previously computed ones from the cache.
mo.accordion(tips)
What’s next?
Check out the tutorial on interactivity for a tour of UI elements:
marimo tutorial ui
matplotlib_installed%20%3D%20False%0Anumpy_installed%20%3D%20False%0A%0Atry%3A%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%0A%20%20%20%20matplotlib_installed%20%3D%20True%0Aexcept%20ModuleNotFoundError%3A%0A%20%20%20%20pass%0A%0Atry%3A%0A%20%20%20%20import%20numpy%20as%20np%0A%0A%20%20%20%20numpy_installed%20%3D%20True%0Aexcept%20ModuleNotFoundError%3A%0A%20%20%20%20pass
tips%20%3D%20%7B%0A%20%20%20%20%22Use%20global%20variables%20sparingly%22%3A%20(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Keep%20the%20number%20of%20global%20variables%20in%20your%20program%20small%20to%20avoid%0A%20%20%20%20%20%20%20%20name%20collisions%20across%20cells.%20Keep%20the%20number%20of%20global%20variables%0A%20%20%20%20%20%20%20%20defined%20by%20any%20one%20cell%20small%20to%20make%20sure%20that%20the%20units%20of%0A%20%20%20%20%20%20%20%20reactive%20execution%20are%20small.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%2C%0A%20%20%20%20%22Use%20descriptive%20names%22%3A%20(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Use%20descriptive%20variable%20names%2C%20especially%20for%20global%20variables.%0A%20%20%20%20%20%20%20%20This%20will%20help%20you%20minimize%20name%20clashes%2C%20and%20will%20also%20result%20in%0A%20%20%20%20%20%20%20%20better%20code.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%2C%0A%20%20%20%20%22Use%20functions%22%3A%20(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Encapsulate%20logic%20into%20functions%20to%20avoid%20polluting%20the%20global%0A%20%20%20%20%20%20%20%20namespace%20with%20temporary%20or%20intermediate%20variables.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%2C%0A%20%20%20%20%22Minimize%20mutations%22%3A%20(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20We%20saw%20earlier%20that%20marimo%20cannot%20track%20object%20mutations.%20So%20try%0A%20%20%20%20%20%20%20%20to%20only%20mutate%20an%20object%20in%20the%20cell%20that%20creates%20it%2C%20or%20create%0A%20%20%20%20%20%20%20%20new%20objects%20instead%20of%20mutating%20existing%20ones.%0A%0A%20%20%20%20%20%20%20%20For%20example%2C%20don't%20do%20this%3A%0A%0A%20%20%20%20%20%20%20%20%60%60%60python3%0A%20%20%20%20%20%20%20%20%23%20a%20cell%0A%20%20%20%20%20%20%20%20numbers%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20%20%20%20%20%60%60%60%0A%0A%20%20%20%20%20%20%20%20%60%60%60python3%0A%20%20%20%20%20%20%20%20%23%20another%20cell%0A%20%20%20%20%20%20%20%20numbers.append(4)%0A%20%20%20%20%20%20%20%20%60%60%60%0A%0A%20%20%20%20%20%20%20%20Instead%2C%20prefer%0A%0A%20%20%20%20%20%20%20%20%60%60%60python3%0A%20%20%20%20%20%20%20%20%23%20a%20cell%0A%20%20%20%20%20%20%20%20numbers%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20%20%20%20%20numbers.append(4)%0A%20%20%20%20%20%20%20%20%60%60%60%0A%0A%20%20%20%20%20%20%20%20or%0A%0A%20%20%20%20%20%20%20%20%60%60%60python3%0A%20%20%20%20%20%20%20%20%23%20a%20cell%0A%20%20%20%20%20%20%20%20numbers%20%3D%20%5B1%2C%202%2C%203%5D%0A%20%20%20%20%20%20%20%20%60%60%60%0A%0A%20%20%20%20%20%20%20%20%60%60%60python3%0A%20%20%20%20%20%20%20%20%23%20another%20cell%0A%20%20%20%20%20%20%20%20more_numbers%20%3D%20numbers%20%2B%20%5B4%5D%0A%20%20%20%20%20%20%20%20%60%60%60%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%2C%0A%20%20%20%20%22Write%20idempotent%20cells%22%3A%20(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Write%20cells%20whose%20outputs%20and%20behavior%20are%20the%20same%20when%20given%0A%20%20%20%20%20%20%20%20the%20same%20inputs%20(refs)%3B%20such%20cells%20are%20called%20_idempotent_.%20This%20will%0A%20%20%20%20%20%20%20%20help%20you%20avoid%20bugs%2C%20and%20let%20you%20cache%20expensive%20intermediate%0A%20%20%20%20%20%20%20%20computations%20(see%20the%20next%20tip).%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%2C%0A%20%20%20%20%22Cache%20intermediate%20computations%20with%20%60%40functools.cache%60%22%3A%20(%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20%20%20%20%20Use%20Python's%20builtin%20%60functools%60%20library%20to%20cache%20expensive%0A%20%20%20%20%20%20%20%20intermediate%20computations.%20You%20can%20do%20this%20if%20you%20abstract%20complex%0A%20%20%20%20%20%20%20%20logic%20into%20idempotent%20functions%2C%20following%20earlier%20tips.%0A%0A%20%20%20%20%20%20%20%20For%20example%3A%0A%0A%20%20%20%20%20%20%20%20%60%60%60python3%0A%20%20%20%20%20%20%20%20import%20functools%0A%0A%20%20%20%20%20%20%20%20%40functools.cache%0A%20%20%20%20%20%20%20%20def%20compute_prediction(problem_parameters)%3A%0A%20%20%20%20%20%20%20%20%20%20...%0A%20%20%20%20%20%20%20%20%60%60%60%0A%0A%20%20%20%20%20%20%20%20Whenever%20%60compute_predictions%60%20is%20called%20with%20a%20value%20of%0A%20%20%20%20%20%20%20%20%60problem_parameters%60%20it%20has%20not%20seen%2C%20it%20will%20compute%20the%20predictions%0A%20%20%20%20%20%20%20%20and%20store%20them%20in%20a%20cache.%20The%20next%20time%20it%20is%20called%20with%20the%20same%0A%20%20%20%20%20%20%20%20parameters%2C%20instead%20of%20recomputing%20the%20predictions%2C%20it%20will%20just%0A%20%20%20%20%20%20%20%20fetch%20the%20previously%20computed%20ones%20from%20the%20cache.%0A%20%20%20%20%20%20%20%20%22%22%22%0A%20%20%20%20)%2C%0A%7D
import%20marimo%20as%20mo