python-lazy-object-proxy

A fast, dependency-free lazy object proxy for Python that defers construction until first use.

Library
PyPI
v1.12.0
262stars
BSD-2-Clause

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
38/100Needs Attention
Development Activity4
Maintenance0
Community68
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
74/100Good
Architecture80
Code Quality78
Innovation68
Learning Curve70

lazy-object-proxy is a small, dependency-free Python library that defers the creation of an object until it’s actually accessed. You wrap a zero-argument factory function in a Proxy, and the target object is only constructed the first time an attribute, method call, or operator touches the proxy — after which the resolved value is cached and reused for the life of the proxy instance.

The library ships three interchangeable backends behind one public API: a compiled C extension for maximum speed, a slots-based pure-Python implementation, and a plain dict-based pure-Python fallback for interpreters (like PyPy and GraalPy) where the C extension can’t be built. It’s a direct descendant of GrahamDumpleton’s wrapt.ObjectProxy, forked specifically to add true lazy evaluation, and its exhaustive dunder-method coverage (including async iteration, awaiting, and context-manager protocols) makes proxied objects behave indistinguishably from the real thing in almost every situation.

What You Get

  • A Proxy(factory) class that transparently forwards nearly every Python operation to a lazily-constructed target object.
  • Three interchangeable backends — a compiled C extension, a slots-based pure-Python implementation, and a plain-Python fallback — selected automatically at import time.
  • Full async support, including __await__, __aiter__/__anext__, and __aenter__/__aexit__, so proxies work transparently with coroutines and async context managers.
  • Zero runtime dependencies and wheels built for CPython 3.9-3.14 plus PyPy and GraalPy.

Common Use Cases

  • Deferring expensive imports or computations in a Django/Flask settings module until the value is actually read.
  • Building placeholder objects for dependency injection where the real service isn’t available until later in the request lifecycle.
  • Wrapping thread-local or request-scoped objects that shouldn’t be resolved until they’re used inside the current context.
  • Implementing lazy configuration or connection objects that shouldn’t touch the network or filesystem at import time.

Under The Hood

Architecture The core abstraction is the Proxy(factory) object exposed from src/lazy_object_proxy/__init__.py, which stores a callable and lazily resolves __wrapped__ on first attribute access, caching the result thereafter; virtually every dunder method is forwarded to __wrapped__ through wrapper methods generated in simple.py’s make_proxy_method. The package implements this abstraction three separate times behind one public name: a compiled C extension (cext.c, ~1,450 lines), a slots-based pure-Python implementation (slots.py, using a _ProxyMetaType metaclass to inject _ProxyMethods properties for __module__/__doc__/__dict__/__weakref__), and a plain dict-based implementation (simple.py). __init__.py tries the C extension first and falls back to simple.py if the import fails, while shared async/utility helpers (await_, cached_property, identity, and the async dunder implementations) live in utils.py and small compatibility shims in compat.py. This tiered, swappable-backend design means removing or failing to build the C extension degrades performance but never breaks functionality.

Tech Stack Pure Python (3.9+) with an optional CPython C extension, built via setuptools + setuptools_scm (pyproject.toml, build-backend setuptools.build_meta) and zero runtime dependencies (dependencies = []). Dev/test tooling includes pytest and pytest-benchmark, with Django, objproxies, and wrapt pulled in only for benchmark comparisons. Linting and formatting run through ruff/ruff-format and taplo via pre-commit, tox drives multi-environment test matrices, and GitHub Actions builds wheels across CPython 3.9-3.14 plus PyPy/GraalPy (where the C extension is disabled). Docs are built with Sphinx and hosted on Read the Docs.

Code Quality Two large, dunder-by-dunder test suites — tests/test_lazy_object_proxy.py (1,894 lines) and tests/test_async_py3.py (1,752 lines) — exercise the proxy’s full special-method surface and async behavior via pytest, parametrized across all three backends, plus dedicated pytest-benchmark performance tests. Coverage is tracked through Coveralls and Codecov and enforced in CI on every push, and pre-commit hooks run ruff, taplo, trailing-whitespace, end-of-file, and debug-statement checks before merge. There is no static type-checking configuration (no mypy/pyright setup), so type safety relies on the extensive test suite rather than type annotations.

API Design The public surface is deliberately tiny — a single Proxy class imported as from lazy_object_proxy import Proxy and constructed from a zero-argument factory, with no subclassing or configuration required to get started, exactly as shown in the one-line README example. It positions itself precisely against wrapt.ObjectProxy (which it forks from and credits): where wrapt.ObjectProxy forwards to an already-resolved target, this library evaluates the factory once, lazily, on first use — a narrow but clearly documented behavioral distinction. Backend selection (C extension vs. slots vs. simple) is fully transparent to callers, so the fast path never requires different calling code. The scope is intentionally narrow, so there’s little surface area to document beyond the README and changelog, and there are no dedicated async-usage docs beyond what the test suite demonstrates.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search