| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- """
- ultralytics 导入兼容层。
- 背景:本框架根目录下有一个同名的 `ultralytics/` 目录(YOLO 源码仓库根,
- 顶层无真正可 import 的包语义,或嵌套 `ultralytics/ultralytics/`)。
- 运行脚本通常把框架根插到 sys.path 首位(为 `import config` / `from modules import ...`),
- 这会让该目录以"命名空间包"形式遮蔽已(pip -e)安装、真正的 ultralytics 包,
- 导致 `from ultralytics import YOLO` 取到空包而报 ImportError。
- 另外:若进程 cwd 恰好是框架根,sys.path[0] 常为 ''(空串),旧逻辑
- `if not p: return False` 会漏判遮蔽,必须把 '' 解析成真实 cwd。
- 解决:导入前,把 sys.path 中"含同名 ultralytics/ 子目录但没有 __init__.py"的遮蔽条目
- 临时移除;若 sys.modules 里已有半残的 ultralytics 命名空间包也一并清掉再 import;
- 导入后再把移除的条目放回 sys.path 末尾。
- """
- from __future__ import annotations
- import os
- import sys
- def _framework_root():
- return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- def _resolve_path_entry(p: str) -> str:
- """把 sys.path 条目解析成绝对目录。'' / '.' → cwd。"""
- if p is None:
- return ""
- if p == "" or p == ".":
- return os.path.abspath(os.getcwd())
- return os.path.abspath(p)
- def _is_shadowing(p: str) -> bool:
- """该 path 项下是否有同名的、非真正包的 ultralytics/ 目录(命名空间遮蔽源)。"""
- root = _resolve_path_entry(p)
- if not root:
- return False
- d = os.path.join(root, "ultralytics")
- # 顶层 ultralytics/ 无 __init__.py → 命名空间包遮蔽
- if os.path.isdir(d) and not os.path.exists(os.path.join(d, "__init__.py")):
- return True
- return False
- def import_yolo():
- """返回 ultralytics.YOLO,规避框架根下同名目录的命名空间包遮蔽。"""
- # 1) 去掉遮蔽 path
- shadow = [p for p in list(sys.path) if _is_shadowing(p)]
- for p in shadow:
- try:
- sys.path.remove(p)
- except ValueError:
- pass
- # 2) 清掉可能已缓存的半残 ultralytics 模块(命名空间包 / 空包)
- doomed = [k for k in list(sys.modules) if k == "ultralytics" or k.startswith("ultralytics.")]
- # 仅当当前 ultralytics 看起来不可用(无 YOLO)时才清
- need_clear = False
- mod = sys.modules.get("ultralytics")
- if mod is None:
- need_clear = False
- else:
- if not hasattr(mod, "YOLO"):
- need_clear = True
- else:
- # 即便有 YOLO,若 __file__ 指向框架根下命名空间也不可靠——有 YOLO 就信
- need_clear = False
- if need_clear:
- for k in doomed:
- sys.modules.pop(k, None)
- try:
- from ultralytics import YOLO # noqa: WPS433
- if not hasattr(YOLO, "__call__") and not callable(YOLO):
- # 极端兜底:再清一次强刷
- for k in list(sys.modules):
- if k == "ultralytics" or k.startswith("ultralytics."):
- sys.modules.pop(k, None)
- from ultralytics import YOLO # noqa: WPS433
- return YOLO
- finally:
- for p in shadow:
- if p not in sys.path:
- sys.path.append(p)
|