196 lines
6.1 KiB
Python
196 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import functools
|
|
import importlib
|
|
import logging
|
|
import threading
|
|
from collections import OrderedDict
|
|
from typing import Any, Callable, Generic, TypeVar
|
|
|
|
T = TypeVar("T")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Lazy(Generic[T]):
|
|
"""延迟初始化包装器 — 线程安全,支持自定义工厂函数。"""
|
|
|
|
def __init__(self, factory: Callable[[], T]):
|
|
self._factory = factory
|
|
self._value: T | None = None
|
|
self._lock = threading.Lock()
|
|
self._initialized = False
|
|
|
|
@property
|
|
def value(self) -> T:
|
|
if not self._initialized:
|
|
with self._lock:
|
|
if not self._initialized:
|
|
self._value = self._factory()
|
|
self._initialized = True
|
|
return self._value
|
|
|
|
def reset(self) -> None:
|
|
with self._lock:
|
|
self._value = None
|
|
self._initialized = False
|
|
|
|
def is_initialized(self) -> bool:
|
|
return self._initialized
|
|
|
|
|
|
def lazy_property(factory: Callable[[], T]) -> property:
|
|
"""装饰器:将方法转换为延迟加载属性。"""
|
|
_lazy = Lazy(factory)
|
|
|
|
@property
|
|
def prop(self) -> T:
|
|
return _lazy.value
|
|
|
|
return prop
|
|
|
|
|
|
class LazyModule:
|
|
"""延迟导入模块 — 避免循环导入与启动时性能开销。"""
|
|
|
|
def __init__(self, module_name: str):
|
|
self._module_name = module_name
|
|
self._module: Any = None
|
|
self._lock = threading.Lock()
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
if self._module is None:
|
|
with self._lock:
|
|
if self._module is None:
|
|
self._module = importlib.import_module(self._module_name)
|
|
return getattr(self._module, name)
|
|
|
|
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
return self.__getattr__("__call__")(*args, **kwargs)
|
|
|
|
def __dir__(self) -> list[str]:
|
|
return dir(self._module if self._module is not None else importlib.import_module(self._module_name))
|
|
|
|
def __repr__(self) -> str:
|
|
status = "loaded" if self._module is not None else "not loaded"
|
|
return f"LazyModule({self._module_name!r}, {status})"
|
|
|
|
|
|
class LazyClass:
|
|
"""延迟导入类 — 按需解析模块中的类,支持实例化和属性访问。"""
|
|
|
|
def __init__(self, module_name: str, class_name: str):
|
|
self._module_name = module_name
|
|
self._class_name = class_name
|
|
self._cls: type | None = None
|
|
self._lock = threading.Lock()
|
|
|
|
def _resolve(self) -> type:
|
|
if self._cls is None:
|
|
with self._lock:
|
|
if self._cls is None:
|
|
module = importlib.import_module(self._module_name)
|
|
self._cls = getattr(module, self._class_name)
|
|
return self._cls
|
|
|
|
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
return self._resolve()(*args, **kwargs)
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
return getattr(self._resolve(), name)
|
|
|
|
def __repr__(self) -> str:
|
|
status = "resolved" if self._cls is not None else "not resolved"
|
|
return f"LazyClass({self._module_name!r}, {self._class_name!r}, {status})"
|
|
|
|
|
|
async def run_sync(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
|
|
"""在默认线程池中运行同步函数,返回协程。"""
|
|
return await asyncio.get_running_loop().run_in_executor(
|
|
None, functools.partial(func, *args, **kwargs)
|
|
)
|
|
|
|
|
|
async def run_sync_partial(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
|
|
"""带偏应用的 run_sync — 将 *args/**kwargs 与 func 绑定后在线程池执行。"""
|
|
bound = functools.partial(func, *args, **kwargs)
|
|
return await asyncio.get_running_loop().run_in_executor(None, bound)
|
|
|
|
|
|
class LRULazyModule:
|
|
"""LRU 缓存的延迟模块加载器 — 线程安全,支持异步 get/invalidate/clear。"""
|
|
|
|
def __init__(self, maxsize: int):
|
|
if not isinstance(maxsize, int) or maxsize <= 0:
|
|
raise ValueError("maxsize must be a positive integer")
|
|
self._maxsize = maxsize
|
|
self._cache: OrderedDict[str, Any] = OrderedDict()
|
|
self._lock = threading.Lock()
|
|
self._pending: dict[str, asyncio.Event] = {}
|
|
|
|
@property
|
|
def size(self) -> int:
|
|
with self._lock:
|
|
return len(self._cache)
|
|
|
|
def __len__(self) -> int:
|
|
return self.size
|
|
|
|
def __contains__(self, key: str) -> bool:
|
|
with self._lock:
|
|
return key in self._cache
|
|
|
|
def __getitem__(self, key: str) -> Any:
|
|
with self._lock:
|
|
if key not in self._cache:
|
|
raise KeyError(key)
|
|
self._cache.move_to_end(key)
|
|
return self._cache[key]
|
|
|
|
async def get(self, module_name: str) -> Any:
|
|
with self._lock:
|
|
if module_name in self._cache:
|
|
self._cache.move_to_end(module_name)
|
|
return self._cache[module_name]
|
|
|
|
if module_name in self._pending:
|
|
event = self._pending[module_name]
|
|
is_loader = False
|
|
else:
|
|
event = asyncio.Event()
|
|
self._pending[module_name] = event
|
|
is_loader = True
|
|
|
|
if not is_loader:
|
|
await event.wait()
|
|
with self._lock:
|
|
return self._cache[module_name]
|
|
|
|
try:
|
|
module = importlib.import_module(module_name)
|
|
except Exception:
|
|
with self._lock:
|
|
self._pending.pop(module_name, None)
|
|
event.set()
|
|
raise
|
|
|
|
with self._lock:
|
|
self._pending.pop(module_name, None)
|
|
self._cache[module_name] = module
|
|
self._cache.move_to_end(module_name)
|
|
if len(self._cache) > self._maxsize:
|
|
self._cache.popitem(last=False)
|
|
event.set()
|
|
return module
|
|
|
|
async def invalidate(self, module_name: str) -> None:
|
|
with self._lock:
|
|
self._cache.pop(module_name, None)
|
|
|
|
async def clear(self) -> None:
|
|
with self._lock:
|
|
self._cache.clear()
|
|
|
|
def __repr__(self) -> str:
|
|
return f"LRULazyModule(maxsize={self._maxsize}, size={self.size})"
|