Skip to content

Commit 38d752b

Browse files
committed
[melobot] Continuously improve importing system
1 parent 8bcffaa commit 38d752b

File tree

1 file changed

+88
-67
lines changed

1 file changed

+88
-67
lines changed

src/melobot/_imp.py

Lines changed: 88 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,20 @@
2020
from .utils import singleton
2121

2222
ALL_EXTS = tuple(all_suffixes())
23-
NAMESPACE_PKG_TAG = "__melobot_namespace__"
23+
EMPTY_PKG_TAG = "__melobot_namespace_pkg__"
24+
ZIP_MODULE_TAG = "__melobot_zip_module__"
2425

2526

2627
class _NestedQuickExit(BaseException): ...
2728

2829

2930
@singleton
3031
class SpecFinder(MetaPathFinder):
31-
3232
def find_spec(
3333
self,
3434
fullname: str,
3535
paths: Sequence[str] | None,
36-
target: ModuleType | None = None, # pylint: disable=unused-argument
36+
target: ModuleType | None = None,
3737
sys_cache: bool = True,
3838
load_cache: bool = True,
3939
pre_sys_len: int = -1,
@@ -48,43 +48,50 @@ def find_spec(
4848

4949
mod_path: Path | None = None
5050
submod_locs: list[str] | None = None
51-
# The spec finding according PEP420: https://peps.python.org/pep-0420/#specification
51+
52+
# 模块查找的优先级,遵循 PEP420: https://peps.python.org/pep-0420/#specification
5253
try:
5354
for entry in paths:
5455
entry_path = Path(entry)
55-
zip_file_path = entry_path.joinpath(f"{name}.zip")
5656
dir_path = entry_path.joinpath(name)
5757
pkg_init_path = dir_path.joinpath("__init__.py")
5858

59+
# 带有 __init__.py 的包优先
5960
if pkg_init_path.exists():
6061
mod_path = pkg_init_path
6162
submod_locs = [str(dir_path.resolve())]
6263
raise _NestedQuickExit
6364

65+
# 其次是各种可加载的文件
6466
for ext in ALL_EXTS:
6567
_mod_path = entry_path.joinpath(f"{name}{ext}")
6668
if _mod_path.exists():
6769
mod_path = _mod_path
6870
submod_locs = None
6971
raise _NestedQuickExit
7072

71-
if zip_file_path.exists():
72-
spec = zipimport.zipimporter(str(zip_file_path)).find_spec(
73-
fullname, target
74-
)
75-
assert spec is not None
76-
assert spec.loader is not None
77-
spec.loader = ModuleLoader(
78-
fullname,
79-
zip_file_path,
80-
sys_cache,
81-
load_cache,
82-
pre_sys_len,
83-
pre_cache_len,
84-
spec.loader,
73+
# 再次是 zip 文件导入
74+
if entry_path.suffix == ".zip" and entry_path.exists():
75+
zip_importer = zipimport.zipimporter( # pylint: disable=no-member
76+
str(entry_path)
8577
)
86-
return spec
78+
spec = zip_importer.find_spec(fullname, target)
79+
if spec is not None:
80+
assert spec.origin is not None and spec.origin != ""
81+
assert spec.loader is not None
82+
spec.loader = ModuleLoader(
83+
fullname,
84+
Path(spec.origin).resolve(),
85+
sys_cache,
86+
load_cache,
87+
pre_sys_len,
88+
pre_cache_len,
89+
spec.loader,
90+
)
91+
setattr(spec, ZIP_MODULE_TAG, True)
92+
return spec
8793

94+
# 没有 __init__.py 的包最后查找,spec 设置为与内置导入兼容的命名空间包格式
8895
if dir_path.exists() and dir_path.is_dir():
8996
dir_path_str = str(dir_path.resolve())
9097
submod_locs = _NamespacePath(
@@ -109,7 +116,7 @@ def find_spec(
109116
assert spec is not None
110117
spec.has_location = False
111118
spec.origin = None
112-
setattr(spec, NAMESPACE_PKG_TAG, True)
119+
setattr(spec, EMPTY_PKG_TAG, True)
113120
return spec
114121

115122
except _NestedQuickExit:
@@ -155,7 +162,7 @@ def set_cache(self, name: str, mod: ModuleType) -> None:
155162
):
156163
return
157164

158-
# __file__ 存在且不为空,可能包或任意可被加载的文件,包对应 __init__.py,应该转换为不包含 __init__.py 后缀的形式
165+
# __file__ 存在且不为空,可能包或任意可被加载的文件
159166
if mod.__file__ is not None:
160167
fp = Path(mod.__file__)
161168
if fp.parts[-1] == "__init__.py":
@@ -192,61 +199,66 @@ def __init__(
192199
inner_loader: Loader | None = None,
193200
) -> None:
194201
super().__init__()
195-
self.cacher = ModuleCacher()
196-
self.fullname = fullname
197-
self.fp = fp
198-
self.use_sys_cache = sys_cache
199-
self.use_load_cache = load_cache
200-
self.pre_sys_len = pre_sys_len
201-
self.pre_cache_len = pre_cache_len
202-
203-
self.inner_loader: Loader | None = inner_loader
202+
# 避免在 self.__getattr__() 运行反射时重名
203+
self.melobot_cacher = ModuleCacher()
204+
self.melobot_fullname = fullname
205+
self.melobot_fp = fp
206+
self.melobot_sys_cache = sys_cache
207+
self.melobot_load_cache = load_cache
208+
self.melobot_pre_sys_len = pre_sys_len
209+
self.melobot_pre_cache_len = pre_cache_len
210+
211+
self.melobot_inner_loader: Loader | None = inner_loader
204212
if inner_loader is not None:
205213
return
206214

207215
for loader_class, suffixes in _get_supported_file_loaders():
208216
if str(fp).endswith(tuple(suffixes)):
209217
loader = loader_class(fullname, str(fp)) # pylint: disable=not-callable
210-
self.inner_loader = loader
218+
self.melobot_inner_loader = loader
211219
break
212220

213221
def create_module(self, spec: ModuleSpec) -> ModuleType | None:
214222
mod = None
215-
if self.use_load_cache:
216-
mod = self.cacher.get_cache(self.fp)
217-
if mod is None and self.inner_loader is not None:
218-
mod = self.inner_loader.create_module(spec)
223+
if self.melobot_load_cache:
224+
mod = self.melobot_cacher.get_cache(self.melobot_fp)
225+
if mod is None and self.melobot_inner_loader is not None:
226+
mod = self.melobot_inner_loader.create_module(spec)
219227
return mod
220228

221229
def exec_module(self, mod: ModuleType) -> None:
222-
if self.cacher.has_cache(mod):
223-
pass
224-
else:
225-
if self.inner_loader is not None:
226-
if self.use_sys_cache:
227-
sys.modules[self.fullname] = mod
230+
if (
231+
not self.melobot_cacher.has_cache(mod)
232+
and self.melobot_inner_loader is not None
233+
):
234+
# 遵循先记录原则,防止 exec_module 发起的某些递归导入出现错误
235+
if self.melobot_sys_cache:
236+
sys.modules[self.melobot_fullname] = mod
228237

238+
try:
239+
self.melobot_inner_loader.exec_module(mod)
240+
# 设置为与内置导入机制兼容的模式
241+
if hasattr(mod.__spec__, EMPTY_PKG_TAG):
242+
mod.__file__ = None
243+
except BaseException:
229244
try:
230-
self.inner_loader.exec_module(mod)
231-
if hasattr(mod.__spec__, NAMESPACE_PKG_TAG):
232-
mod.__file__ = None
233-
except BaseException:
234-
try:
235-
del sys.modules[self.fullname]
236-
except KeyError:
237-
pass
238-
raise
239-
240-
if self.use_load_cache:
241-
self.cacher.set_cache(self.fullname, mod)
242-
243-
if not self.use_load_cache:
244-
diff = self.cacher.get_len() - self.pre_cache_len
245+
del sys.modules[self.melobot_fullname]
246+
except KeyError:
247+
pass
248+
raise
249+
# 若 inner_loader 为空,则是纯粹的命名空间包(没有 __init__.py 的包模块)
250+
# 也就不需要任何实质性的 exec_module 过程
251+
252+
if self.melobot_load_cache:
253+
self.melobot_cacher.set_cache(self.melobot_fullname, mod)
254+
255+
if not self.melobot_load_cache:
256+
diff = self.melobot_cacher.get_len() - self.melobot_pre_cache_len
245257
if diff > 0:
246-
self.cacher.rm_lastn(diff)
258+
self.melobot_cacher.rm_lastn(diff)
247259

248-
if not self.use_sys_cache:
249-
diff = len(sys.modules) - self.pre_sys_len
260+
if not self.melobot_sys_cache:
261+
diff = len(sys.modules) - self.melobot_pre_sys_len
250262
if diff > 0:
251263
iter = reversed(sys.modules.keys())
252264
rm_names: list[str] = []
@@ -256,8 +268,10 @@ def exec_module(self, mod: ModuleType) -> None:
256268
sys.modules.pop(name)
257269

258270
def __getattr__(self, name: str) -> Any:
259-
if self.inner_loader is not None:
260-
return getattr(self.inner_loader, name)
271+
# 直接使用反射,而不是更复杂的继承方案
272+
# inner_loader 实现了必要的 内省接口、importlib.resources 接口
273+
if self.melobot_inner_loader is not None:
274+
return getattr(self.melobot_inner_loader, name)
261275
raise AttributeError(
262276
f"'{self.__class__.__name__}' object has no attribute '{name}'"
263277
)
@@ -271,13 +285,13 @@ def import_mod(
271285
sys_cache: bool = True,
272286
load_cache: bool = True,
273287
) -> ModuleType:
288+
# 必须先获取,后续可能运行的递归将会影响序列长度
274289
pre_sys_len = len(sys.modules)
275290
pre_cache_len = ModuleCacher().get_len()
276291

277292
if path is not None:
278-
if load_cache:
279-
if (mod_cache := Importer.get_cache(Path(path))) is not None:
280-
return mod_cache
293+
if load_cache and (mod_cache := Importer.get_cache(Path(path))) is not None:
294+
return mod_cache
281295

282296
try:
283297
sep = name.rindex(".")
@@ -302,6 +316,7 @@ def import_mod(
302316
name=name,
303317
path=str(path),
304318
)
319+
305320
mod = module_from_spec(spec)
306321
assert spec.loader is not None
307322
spec.loader.exec_module(mod)
@@ -316,7 +331,13 @@ def get_cache(path: Path) -> ModuleType | None:
316331
return ModuleCacher().get_cache(path)
317332

318333

334+
def _union_provider(mod: Any) -> pkg_resources.NullProvider:
335+
if hasattr(mod.__spec__, ZIP_MODULE_TAG):
336+
return pkg_resources.ZipProvider(mod)
337+
return pkg_resources.DefaultProvider(mod)
338+
339+
319340
sys.meta_path.insert(0, SpecFinder())
320-
pkg_resources.register_finder(SpecFinder, pkg_resources.find_on_path)
321-
pkg_resources.register_loader_type(ModuleLoader, pkg_resources.DefaultProvider)
341+
# 兼容 pkg_resources 的资源获取操作
342+
pkg_resources.register_loader_type(ModuleLoader, cast(type, _union_provider))
322343
pkg_resources.register_namespace_handler(SpecFinder, pkg_resources.file_ns_handler)

0 commit comments

Comments
 (0)