wdm0006/python-skills/lazy-dependencies
Claude Code and agent skill by wdm0006/python-skills
- community
Checking that the SKILL.md file is still in the repository.
Keep expensive resources — ML models, spellcheck dictionaries, embedded databases, large third-party imports — off code paths that never use them. Covers constructor work paid by every caller, one-size factories that load the heaviest member for the cheapest request, load-and-unload-per-call masquerading as a cache, measuring per-call cost in a fresh process, telling a one-time import from a repeated model load, and the test fallout (patches that only now take effect, autouse fixtures that hide…
Install
npx vskill@latest install wdm0006/python-skills/lazy-dependenciesSKILL.md
Deferring Heavy Dependencies
A word count should not load a language model. When it does, the cause is almost never the algorithm — it is that the cheap function shares a constructor, a factory, or an import with an expensive one. Profiling the function body finds nothing, because the time is spent before the body runs.
The cost is in construction, not in the call
character_count profiles as free and still takes 80 ms. Defer the field:
functools.cached_property covers the common case with less code, but it is not equivalent: since 3.12 it has no lock around the first call (so concurrent first access can compute twice), and its reset/pre-populate idiom (del obj.x, obj.x = val) is easy to get wrong — deleting a never-computed attribute raises AttributeError. Use the explicit form when you need controlled locking or a guaranteed-safe reset path.
Deferring is only safe when construction has no side effect the caller depends on ordering-wise — opening a file, binding a port, registering a signal handler. If it does, it isn't initialization, it's an operation: give it a method name.
One factory for everything makes cheap paths pay for the expensive one
The pattern that produces the worst numbers is a single accessor that builds the whole family:
Every entry point calls get_analyzers(), so character counts, word counts, and reading-time estimates each pay a full model load and unload. Measured in a fresh process, that is on the order of half a second per call for work that is otherwise microseconds.
Split by what the path actually needs. Either give each analyzer its own accessor, or make the model itself a lazy handle the analyzers that need it pull from. Then state the boundary explicitly — which helpers are pure (regex, string, third-party parsers) and which genuinely require the initialized model — because that boundary is what the next change will violate.
Load-and-unload per call is not a cache
A factory that loads a model, uses it, and unloads it at the end of the request has the worst of both: the cost of loading, and none of the benefit of keeping it. Cache the handle for the process lifetime.
That choice has a consequence worth stating in the code: a cache that is never cleared makes every constructor cost permanent, so anything expensive inside the cached objects must be lazy too. The two techniques are a pair — process-long caching is what makes eager __init__ work unrecoverable, and laziness is what makes process-long caching affordable.
Measure per call, in a fresh process
In-process timing after the first call measures nothing; the model is already resident. Time the first call of a cold process, one entry point at a time:
Distinguish a one-time import from a repeated load. After the fix, one entry point often still shows a few hundred milliseconds on its first call — that is the third-party package importing, paid once per process, not a model load returning. Confirm which you are looking at before reading it as the fix failing:
Then re-time the same call a second time in the same process: a one-time import drops to near zero, a per-call load does not.
For the memory half, tracemalloc around a single construction gives the retained size that argues for laziness far better than a wall-clock number does.
An ambient fixture hides the dependency you are trying to remove
The test that should catch a re-introduced model load usually cannot, because a session-scoped autouse fixture already loaded the real model for the whole run. Everything passes whether or not the cheap path touched it.
Assert on the loader, not on the outcome:
Failing loudly from the mock is deliberate: a bare assert_not_called() at the end still passes if the production code catches the exception it provoked.
Making a field lazy changes when patches bind
This is the surprise that follows the refactor. A test like:
did nothing while the field was eager and the instance was built in setup_method — the real object was already bound before the patch applied, so the test exercised the real dictionary. Once the field is lazy, the patch is consulted at first use and finally takes effect. The tests keep passing, but they now assert against a mock. Re-read every test that patches the deferred constructor and decide, per test, whether the mock is what you wanted.
The same shift is useful on purpose: to exercise code behind a lazy handle without loading anything, populate the private slot directly.
That runs in milliseconds and needs no fixtures — but it reaches past the public API, so keep it to tests whose subject is the surrounding logic, not the loading.
Checklist
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/python/lazy-dependencies/SKILL.md
Popularity
- Repository stars
- 109
- Repository forks
- 17
- 7-day trend score
- 23
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/wdm0006/python-skills/lazy-dependencies)