Python @autowired装饰器延迟初始化实现及优化需求问询
Nice work getting that @autowired decorator and lazy initialization function up and running—keeping IDE code completion intact is a huge win, so kudos on that detail! Below are practical, actionable optimization directions to polish your implementation, covering type safety, flexibility, debuggability, and extensibility:
1. Boost Type Safety & Generic Support
Your current lazy function likely handles basic types and string references well, but it might struggle with generic type annotations (like list[User] or dict[str, Order]). Extend it to resolve generics, cache resolved types, and preserve type hints for IDEs:
from typing import Type, TypeVar, Generic, get_args, get_origin import importlib T = TypeVar('T') class Lazy(Generic[T]): def __init__(self, type_ref: Type[T] | str): self._type_ref = type_ref self._resolved_type: Type[T] | None = None self._instance: T | None = None def _resolve_type(self) -> Type[T]: if isinstance(self._type_ref, str): # Cache resolved types to avoid repeated imports if not self._resolved_type: try: module_name, class_name = self._type_ref.rsplit('.', 1) module = importlib.import_module(module_name) self._resolved_type = getattr(module, class_name) except (ValueError, ImportError, AttributeError) as e: raise ValueError(f"Failed to resolve type '{self._type_ref}': {str(e)}") from e else: self._resolved_type = self._type_ref # Handle generics (extract the actual type if needed) origin = get_origin(self._resolved_type) if origin is not None: self._resolved_type = get_args(self._resolved_type)[0] return self._resolved_type def __call__(self) -> T: if self._instance is None: type_ = self._resolve_type() self._instance = type_() return self._instance def lazy(type_annotation: Type[T] | str) -> Lazy[T]: return Lazy(type_annotation)
This implementation uses Generic[T] to preserve IDE type inference, caches resolved types for better performance, and adds clear error handling for invalid type references.
2. Configurable Instance Scopes
Add support for different instance lifecycles (singleton, transient) to make your tool more flexible for different use cases:
from enum import Enum class Scope(Enum): SINGLETON = "singleton" # Reuse one instance (default) TRANSIENT = "transient" # Create a new instance every time class Lazy(Generic[T]): def __init__(self, type_ref: Type[T] | str, scope: Scope = Scope.SINGLETON, **init_kwargs): self._type_ref = type_ref self._scope = scope self._init_kwargs = init_kwargs self._resolved_type: Type[T] | None = None self._instance: T | None = None def __call__(self) -> T: type_ = self._resolve_type() if self._scope == Scope.SINGLETON: if self._instance is None: self._instance = type_(**self._init_kwargs) return self._instance else: return type_(**self._init_kwargs) def lazy(type_annotation: Type[T] | str, scope: Scope = Scope.SINGLETON, **init_kwargs) -> Lazy[T]: return Lazy(type_annotation, scope, **init_kwargs)
Now users can choose whether to reuse a single instance or create new ones on demand, and even pass initialization arguments directly to lazy.
3. IDE-Friendly Explicit Annotations
Use typing.Annotated to mark lazy-injected fields, making intent clearer for both IDEs and static type checkers like mypy:
from typing import Annotated import inspect # Define a marker for lazy initialization LazyInit = object() def autowired(cls): # Handle class attributes for name, annotation in cls.__annotations__.items(): if get_origin(annotation) is Annotated: type_, *metadata = get_args(annotation) if LazyInit in metadata: setattr(cls, name, lazy(type_)()) # Handle constructor parameters original_init = cls.__init__ def new_init(self, *args, **kwargs): sig = inspect.signature(original_init) for param_name, param in sig.parameters.items(): if get_origin(param.annotation) is Annotated: type_, *metadata = get_args(param.annotation) if LazyInit in metadata and param_name not in kwargs: kwargs[param_name] = lazy(type_)() original_init(self, *args, **kwargs) cls.__init__ = new_init return cls # Usage example class Service: def process(self): pass @autowired class App: # Lazy-injected class attribute service: Annotated[Service, LazyInit] def __init__(self, db: Annotated[Database, LazyInit]): # Lazy-injected constructor parameter self.db = db
This makes your code more self-documenting and ensures tools like PyCharm or mypy correctly recognize the field types.
4. Enhanced Debugging & Error Handling
Add logging and better error messages to simplify debugging dependency issues:
import logging logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger(__name__) class Lazy(Generic[T]): # ... (previous code) def __call__(self) -> T: if self._instance is None: type_ = self._resolve_type() logger.debug(f"Lazy initializing {type_.__name__} (scope: {self._scope.value})") try: self._instance = type_(**self._init_kwargs) except Exception as e: raise RuntimeError(f"Failed to initialize {type_.__name__}: {str(e)}") from e return self._instance
Now you’ll get clear logs when instances are initialized, and more context when initialization fails.
These optimizations can be rolled out incrementally based on your needs—start with type safety and scopes if you’re building a general-purpose tool, or focus on constructor injection if you’re targeting complex applications.
内容的提问来源于stack exchange,提问作者Rodrigo Oliveira

