Hi Michael,
On Excel interop, where I’ve landed so far is doing the cheap half of compatibility: reading / writing the on-disk format, and the expensive half is copying their PY scheduler into Calc.
I was mistaken earlier, what Excel saves after edit is closer to the current LibrePy design than I initially realized: static xl(“A1:B10”) gets rewritten to placeholders in pythonScripts.xml, and the cell formula is _xlws.PY(index, returnType, …deps) with the ranges as real trailing formula arguments.
LibrePy now has a bidirectional OOXML rewriter that maps the code into cells and converts native =PY(code; ranges…) to the MS format (and back on export). It seems true dynamic xl(variable) is not a Microsoft product path yet so we can ignore for now.
I don’t have torture sheets, just a few samples I found on GitHub (GitHub - dbogt/PythonExcel: Demos of Python in Excel · GitHub) to test the round-trip script. My current proposal is about a 4000 line patch to lokit and online that calls into Python /LibrePy for everything including conversion.
On calculation order / “depend on everything above and to the left”: it’s a small Core idea, but it under-approximates Excel and over-taxes Calc. Excel’s co-volatility re-runs all PY cells when any PY is dirty (row-major), plus the Excel↔Python flip-flop.
Flip-flop is the extra scheduler needed when normal Excel formulas and PY cells depend on each other in a chain. Excel and Python use incompatible calc models, so Excel does not fold PY into one dependency pass. It alternates:
- Run an Excel pass on dirty non-PY cells (dependency order, partial).
- When a cell needs a PY result that is not ready yet, defer that cell (push it later in the chain).
- Run a Python pass: because of co-volatility, that means all PY cells (LTR / TTB).
- Resume Excel for cells that were waiting on those PY results.
- If another PY still depends on a newly computed Excel cell, defer again and do another full PY pass.
- Repeat until nothing dirty remains.
Synthetic above/left deps would force ordering among cells that recalculate, but they also create a lot of false dependencies, and on Online that multiplies remote POSTs to the compute service. In spite of the bad performance, it still wouldn’t be fully compatible.
Regarding the Microsoft backend: Excel also runs scientific Python out of the spreadsheet process: hypervisor-isolated Azure containers with a curated Anaconda image, reached over Microsoft’s OfficePy HTTPS control plane (Bearer, runtime setup, then code + cell data).
Public reverse-engineering and process lists point at a Jupyter-like kernel per workbook. Behaviorally it looks like a shared kernel with sequential cell execs into globals (plus co-volatility). Our compute_service (stdlib HTTP, POST /v1/execute, optional shared session) seems the Collabora-shaped version of that split.
On the “we can call Python from formulae / nicer UDF UI” idea: that path is real and worth improving, but it solves a different problem than =PY().
Stock Calc can reach Python only as named functions. That’s a catalog API. A COOL sidebar that helps create and insert those names would be lovely, and Classic LibrePy has Monaco + a Python sidebar for editing, but the execution surface is still “call one of the known functions.”
Excel Python (and what data analysts paste into cells) is ad-hoc pipelines, not a fixed API. A cell might be “load this range as a DataFrame → dropna → groupby Region → mean of Sales → return,” and the next cell a completely different pandas/numpy/matplotlib story. Those scripts live as snippets.
Without a generic Add-In that accepts code plus explicit data ranges (=PY(code, data…)), you’d need either a new Calc function per cell or you’d reinvent “run this string” under another name.
I’d treat nicer UDF/editor UX as complementary for curated helpers, and keep =PY(code, data…) as the surface that can import Excel sheets and run real scientific pipelines.
BTW, besides =PY / xl() / co-volatility, Excel Python workbooks also can use package and formula features Calc can’t evaluate: Table[#All], ANCHORARRAY/spill parents, engine spill, returnType object cards, and long scripts vs Calc’s ~1024 formula MAXSTRLEN.
We already treat those on file rewrite: on open we snapshot tables/spill parents to A1, store long code on py_code sheets, map to DAG =PY, and strip Excel Python package parts; on save we restore pythonScripts.xml, _xlws.PY, and the original Table/ANCHORARRAY tokens from side meta. That’s round-trip fidelity, not live Calc Table/spill support. Growing tables and live spill parents won’t update until the engine adds those features.
It seems Calc’s strength is the dependency DAG and partial recalc. For the optional shared kernel mode, LibrePy requires user pass upstream cells as data so the DAG orders precedents, rather than re-running the world.
It’s gray area to talk about how well import / export works. It could be possible to say for v1, that “well-designed” scripts that respect Calc’s dependency order (and engine features) run.
Update: One more point I forgot to go into: right now my patch doesn’t start the compute service yet. I’ll research the forkit code and try to figure out how to use that.
Thank you again for the advice.
Keith