From b8036394cd3c012c21d377f379acfc23e9a3a57a Mon Sep 17 00:00:00 2001 From: Alicja Miloszewska Date: Fri, 21 Feb 2025 11:39:45 +0100 Subject: [PATCH] [Py OV] Change approach to openvino.runtime lazy loading (#29053) ### Details: - In current approach for lazy import, we use `importlib.util.LazyLoader`. This approach first creates a module, adds it to python `sys.module`s, and then LazyLoader executes the module when sb tries to access e.g. `openvino.runtime` attribute. - The issue is that in this approach `openvino.runtime` might be initialized too early, e.g. by [`inspect` module](https://github.com/python/cpython/blob/140e69c4a878d7a0a26d4406bdad55e56ddae0b7/Lib/inspect.py#L915) which iterates over modules present in `sys.modules`, e.g.: ![torch_issue_25_0](https://github.com/user-attachments/assets/19d9e2e5-858a-4917-b9a4-c1ff1ede203e) Proposal: - Change the lazy loading approach. Import `openvino.runtime` and add it to `sys.modules` only when an attribute is accessed. Possible thanks to overloading `__getattr__` and `__dir__` methods. ![torch_issue_25_1](https://github.com/user-attachments/assets/561121f5-2f22-4408-82c3-269dd31d2a79) ### Tickets: - *ticket-id* --------- Signed-off-by: Alicja Miloszewska --- src/bindings/python/src/openvino/__init__.py | 4 +- .../python/src/openvino/package_utils.py | 37 ++++++++++++------- tools/benchmark_tool/openvino/__init__.py | 4 +- tools/ovc/openvino/__init__.py | 4 +- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/bindings/python/src/openvino/__init__.py b/src/bindings/python/src/openvino/__init__.py index b916a6e1c9bfc5..3c6866d998bad0 100644 --- a/src/bindings/python/src/openvino/__init__.py +++ b/src/bindings/python/src/openvino/__init__.py @@ -60,8 +60,8 @@ from openvino._ov_api import Op # Import all public modules -from openvino.package_utils import lazy_import -runtime = lazy_import("openvino.runtime") +from openvino.package_utils import LazyLoader +runtime = LazyLoader("openvino.runtime") from openvino import frontend as frontend from openvino import helpers as helpers from openvino import experimental as experimental diff --git a/src/bindings/python/src/openvino/package_utils.py b/src/bindings/python/src/openvino/package_utils.py index feafc35c5ae50e..13262a09372a4d 100644 --- a/src/bindings/python/src/openvino/package_utils.py +++ b/src/bindings/python/src/openvino/package_utils.py @@ -5,7 +5,7 @@ import os import sys from functools import wraps -from typing import Callable, Any +from typing import Callable, Any, Optional from pathlib import Path import importlib.util from types import ModuleType @@ -117,17 +117,28 @@ def __get__(self, obj: Any, cls: Any = None) -> Any: return decorator -def lazy_import(module_name: str) -> ModuleType: - spec = importlib.util.find_spec(module_name) - if spec is None or spec.loader is None: - raise ImportError(f"Module {module_name} not found") +class LazyLoader: + """A class to lazily load a module, importing it only when an attribute is accessed.""" - loader = importlib.util.LazyLoader(spec.loader) - module = importlib.util.module_from_spec(spec) - sys.modules[module_name] = module + def __init__(self, module_name: str): + self.module_name = module_name + self._module: Optional[ModuleType] = None - try: - loader.exec_module(module) - except Exception as e: - raise ImportError(f"Failed to load module {module_name}") from e - return module + def _load_module(self) -> None: + if self._module is None: + # Import the module and update sys.modules with the loaded module + self._module = importlib.import_module(self.module_name) + # Update the LazyLoader instance's __dict__ with the module's __dict__ + # This ensures that subsequent attribute accesses use the module's attributes directly (by __getattribute__() ) + self.__dict__.update(self._module.__dict__) + + def __getattr__(self, item: str) -> Any: + self._load_module() + return getattr(self._module, item) + + def __dir__(self) -> list: + self._load_module() + return dir(self._module) + + def __repr__(self) -> str: + return f"" diff --git a/tools/benchmark_tool/openvino/__init__.py b/tools/benchmark_tool/openvino/__init__.py index b916a6e1c9bfc5..3c6866d998bad0 100644 --- a/tools/benchmark_tool/openvino/__init__.py +++ b/tools/benchmark_tool/openvino/__init__.py @@ -60,8 +60,8 @@ from openvino._ov_api import Op # Import all public modules -from openvino.package_utils import lazy_import -runtime = lazy_import("openvino.runtime") +from openvino.package_utils import LazyLoader +runtime = LazyLoader("openvino.runtime") from openvino import frontend as frontend from openvino import helpers as helpers from openvino import experimental as experimental diff --git a/tools/ovc/openvino/__init__.py b/tools/ovc/openvino/__init__.py index b916a6e1c9bfc5..3c6866d998bad0 100644 --- a/tools/ovc/openvino/__init__.py +++ b/tools/ovc/openvino/__init__.py @@ -60,8 +60,8 @@ from openvino._ov_api import Op # Import all public modules -from openvino.package_utils import lazy_import -runtime = lazy_import("openvino.runtime") +from openvino.package_utils import LazyLoader +runtime = LazyLoader("openvino.runtime") from openvino import frontend as frontend from openvino import helpers as helpers from openvino import experimental as experimental