Ваш первоначальный подход с ast
- хорошее начало, но вы можете в конечном итоге пропустить некоторые крайние случаи (например, те, которые я уже упоминал в комментариях), это большая работа, которую можно избежать.
Если вы не возражаете против импорта анализируемого вами модуля (и мы уверены, что присутствуют все подмодули, такие как django
), то мы можем сделать это «грязным» способом:
- Модуль импорта с
TYPE_CHECKING
, установленным на False
(по умолчанию). - Модуль импорта с
TYPE_CHECKING
, установленным на True
. - Сравните 2 модуля и получите «дополнительные»"имена подмодулей.
Прежде всего давайте определим вспомогательную функцию для исправления обезьян
from contextlib import contextmanager
@contextmanager
def patch(object_, attribute_name, value):
old_value = getattr(object_, attribute_name)
try:
setattr(object_, attribute_name, value)
yield
finally:
setattr(object_, attribute_name, old_value)
, после этого наша функция может быть написана с использованием модуля importlib
(для динамического импорта и перезагрузки).) и inspect
module (для проверки, является ли объект модулем) из stdlib, например
import importlib
import inspect
import typing
def detect_type_checking_mode_modules_names(module_name):
module = importlib.import_module(module_name)
default_module_names = set(vars(module))
with patch(typing, 'TYPE_CHECKING', True):
# reloading since ``importlib.import_module``
# will return previously cached entry
importlib.reload(module)
type_checked_module_namespace = dict(vars(module))
# resetting to "default" mode
importlib.reload(module)
return {name
for name, content in type_checked_module_namespace.items()
if name not in default_module_names
and inspect.ismodule(content)}
Test
Для test.py
модуля с содержимым
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import abc
from django.utils import timezone
if 'aaa':
print('bbb')
print('hello world')
дает нам
>>> detect_type_checking_mode_modules_names('test')
{'abc', 'timezone'}