Skip to content

Lazy loading of modules and exposed utility functions#246

Open
jlaehne wants to merge 9 commits intoLumiSpy:mainfrom
jlaehne:lazy-import
Open

Lazy loading of modules and exposed utility functions#246
jlaehne wants to merge 9 commits intoLumiSpy:mainfrom
jlaehne:lazy-import

Conversation

@jlaehne
Copy link
Copy Markdown
Contributor

@jlaehne jlaehne commented Jan 4, 2026

Description of the change

Import of LumiSpy is rather slow (1.6 s on linux) mainly due to the direct loading of Signal1D from HyperSpy. This PR uses __getattr__ as proposed by GitHub Copilot lazy-loader to load both the sub-modules and utility functions exposed at the top level of LumiSpy using a lazy approach.

Splits off lazy signal modules in line with hyperspy/hyperspy#3569.

Progress of the PR

  • Change implemented (can be split into several points),
  • [n/a] docstring updated (if appropriate),
  • [n/a] update user guide (if appropriate),
  • added tests -> existing tests run through,
  • add a changelog entry in the upcoming_changes folder (see upcoming_changes/README.rst),
  • Check formatting of the changelog entry (and eventual user guide changes) in the docs/readthedocs.org:lumispy build of this PR (link in github checks),
  • ready for review.

Minimal example of the bug fix or the new feature

import time, importlib

t0 = time.perf_counter()
importlib.import_module("lumispy")
print("import time:", time.perf_counter()-t0)

@jlaehne
Copy link
Copy Markdown
Contributor Author

jlaehne commented Jan 4, 2026

Failing tests should be fixed in #247 -- EDIT: fixed by merging in upstream including #247

Copy link
Copy Markdown
Contributor

@ericpre ericpre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth adding test like in https://github.com/hyperspy/rosettasciio/blob/main/rsciio/tests/test_import.py?

Should lazy-loader be used to support typing and auto-completion in some development environment? This would add one more library but it is already a dependency of scikit-image and I also think that it will be good to use it in HyperSpy.

After hyperspy/hyperspy#3569 is merged, it will be possible to speed up signals import even more to 0.2-0.3s.

@ericpre
Copy link
Copy Markdown
Contributor

ericpre commented Jan 15, 2026

%time from lumispy.signals import CLSpectrum

takes 1.5 s on my laptop, while it should be possible to speed it up to ~0.3s.

@jlaehne
Copy link
Copy Markdown
Contributor Author

jlaehne commented Jan 15, 2026

takes 1.5 s on my laptop, while it should be possible to speed it up to ~0.3s.

With or without hyperspy/hyperspy#3569 ? Maybe we first see what that brings before we go too far in the optimization here. We should though maybe also split out the lazy signal versions like you just did in HyperSpy.

@ericpre
Copy link
Copy Markdown
Contributor

ericpre commented Jan 23, 2026

With or without hyperspy/hyperspy#3569 ? Maybe we first see what that brings before we go too far in the optimization here. We should though maybe also split out the lazy signal versions like you just did in HyperSpy.

This needs hyperspy/hyperspy#3569. I think that splitting the lazy signal is useful to avoid importing dask.

@ericpre
Copy link
Copy Markdown
Contributor

ericpre commented Jan 25, 2026

What do you think about using lazy-loader? See example: hyperspy/exspy@7e4c228#diff-0bd73c0ba37a66df9347cc8e2b3bf2b84e5e803c7dd0b5ee37fe79d7b45de8c5

The main advantage is to support static type checkers.

@jlaehne
Copy link
Copy Markdown
Contributor Author

jlaehne commented Jan 25, 2026

What do you think about using lazy-loader?

I was actually working on it already, but forgot to commit before pushing.

@jlaehne
Copy link
Copy Markdown
Contributor Author

jlaehne commented Jan 25, 2026

Should now mirror approach from hyperspy/exspy#160

import lumispy is <0.01 s with this PR,
from lumispy.signals import LumiSpectrum is 0.4 s with this PR
Both were about 1.6 s before (on my linux)

Copy link
Copy Markdown
Contributor

@ericpre ericpre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See jlaehne#20 for suggestions.

Comment thread lumispy/__init__.pyi
"join_spectra",
"to_array",
"savetxt",
"crop_edges",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This are now moved from lumispy.utils to lumispy, would it better to keep these in lumispy.utils?

>>> import lumispy
>>> dir(lumispy)
['__version__',
 'components',
 'crop_edges',
 'eV2nm',
 'invcm2nm',
 'join_spectra',
 'nm2eV',
 'nm2invcm',
 'savetxt',
 'signals',
 'to_array',
 'utils']

While we are at it, it may be worth tidy up lumispy.utils too by defined __all__ and __dir__, currently it shows:

>>> dir(lumispy.utils)
['__builtins__',
 '__cached__',
 '__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__path__',
 '__spec__',
 'axes',
 'axis2eV',
 'axis2invcm',
 'com',
 'crop_edges',
 'data2eV',
 'data2invcm',
 'signals',
 'solve_grating_equation',
 'var2eV',
 'var2invcm']

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

join_spectra etc. are currently imported in the __init__.py of the main module to make them directly available from there - though they are not part of the dir

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I see, then should it be tidied up? Currently, these can be imported from two different modules, should only one import be kept?

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.

2 participants