Skip to content

perf: import awkward/vector/graphviz/particle lazily#382

Open
henryiii wants to merge 1 commit into
mainfrom
perf-lazy-imports
Open

perf: import awkward/vector/graphviz/particle lazily#382
henryiii wants to merge 1 commit into
mainfrom
perf-lazy-imports

Conversation

@henryiii

Copy link
Copy Markdown
Member

🤖 AI text below 🤖

Summary

import pylhe previously eagerly imported four heavy dependencies that the core reader/writer never needs:

  • awkward and vector — only needed for to_awkward (pulled in by the module-bottom from .awkward import to_awkward, which also runs vector.register_awkward() and several ak.mixin_class registrations at import time).
  • graphviz and particle — only needed for the LHEEvent.graph visualization (plus the module-level _PDGID2LaTeXNameMap = DirectionalMaps(...) construction).

This PR makes all four lazy so the core parse path imports nearly instantly, with no public API changes.

Changes

  • Removed the bottom-of-module from .awkward import to_awkward; to_awkward is now exposed via a module-level __getattr__ that imports it from .awkward on first access (raising the standard AttributeError for unknown names). "to_awkward" stays in __all__/__dir__. from pylhe import to_awkward, pylhe.to_awkward, and import pylhe.awkward all still work, and the vector/awkward behavior registrations now happen only when the awkward bridge is actually used.
  • Moved the graphviz/particle imports and the DirectionalMaps construction into LHEEvent._build_graph. The PDGID→LaTeX map is built once and cached via functools.cache. Type annotations are quoted with a TYPE_CHECKING-guarded import graphviz.
  • mypy stays green under strict (the # type: ignore[import-untyped] for untyped graphviz now lives on the TYPE_CHECKING import; no stale ignores).

Laziness verification

python -c "import sys, pylhe; assert not any(m in sys.modules for m in (\"awkward\",\"vector\",\"graphviz\",\"particle\"))"

passes after the change. A new tests/test_api.py::test_lazy_heavy_imports asserts this in a fresh interpreter (via subprocess.run) so other tests' imports do not pollute sys.modules. pylhe.to_awkward and LHEEvent.graph were verified end-to-end (covered by tests/test_awkward.py and tests/test_visualize.py).

Notes

  • Dependencies are intentionally not changed: graphviz/particle/awkward/vector remain required deps. Making them optional extras is a possible follow-up, deliberately out of scope here.

Part of #378

🤖 Generated with Claude Code

Importing pylhe previously eagerly imported graphviz, particle (plus
building the PDGID->LaTeX DirectionalMaps at module load) and, via the
bottom-of-module ``from .awkward import to_awkward``, awkward and vector
(which register vector/awkward behaviors at import time). None of these
are needed for the core LHE reader/writer.

After this change ``import pylhe`` imports none of those four modules:

- ``to_awkward`` is exposed lazily through a module-level ``__getattr__``
  that imports it from ``.awkward`` on first access. ``from pylhe import
  to_awkward``, ``pylhe.to_awkward`` and ``import pylhe.awkward`` all
  still work, and the vector/awkward behavior registrations happen only
  when the awkward bridge is actually used.
- graphviz and particle (and the DirectionalMaps construction, now
  cached via ``functools.cache``) are imported inside
  ``LHEEvent._build_graph``, so they load only when the ``graph``
  property is used. Annotations are quoted with a ``TYPE_CHECKING``
  guarded import.

Public API is unchanged; dependencies remain required (making them
optional extras is a possible follow-up). Adds a test that asserts the
four heavy modules are not imported by ``import pylhe`` in a fresh
interpreter.

Assisted-by: ClaudeCode:claude-opus-4-8
@henryiii henryiii marked this pull request as ready for review June 12, 2026 17:57
@github-actions

Copy link
Copy Markdown

🏎️ Benchmark Comparison


--------------------------------------------------------------------------------------------------- benchmark: 4 tests --------------------------------------------------------------------------------------------------
Name (time in ms)                                   Min                   Max                  Mean             StdDev                Median                IQR            Outliers     OPS            Rounds  Iterations
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_count_events_benchmark (0002_mr)          420.2988 (1.0)        429.8500 (1.0)        424.6970 (1.0)       2.8136 (1.32)       425.1568 (1.0)       4.1999 (1.65)          2;0  2.3546 (1.0)          10           1
test_count_events_benchmark (0001_base)        423.8510 (1.01)       431.1994 (1.00)       427.5154 (1.01)      2.1313 (1.0)        428.0011 (1.01)      2.5497 (1.0)           3;0  2.3391 (0.99)         10           1
test_fromfile_and_to_awkward (0001_base)     3,493.5209 (8.31)     3,543.3831 (8.24)     3,519.7872 (8.29)     20.3815 (9.56)     3,519.4007 (8.28)     40.6146 (15.93)         5;0  0.2841 (0.12)         10           1
test_fromfile_and_to_awkward (0002_mr)       3,494.8875 (8.32)     3,524.7333 (8.20)     3,510.5020 (8.27)     10.5849 (4.97)     3,508.8153 (8.25)     20.8205 (8.17)          5;0  0.2849 (0.12)         10           1
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Legend:
  Outliers: 1 Standard Deviation from Mean; 1.5 IQR (InterQuartile Range) from 1st Quartile and 3rd Quartile.
  OPS: Operations Per Second, computed as 1 / Mean

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant