Python компонентное сканирование - PullRequest
0 голосов
/ 25 марта 2020

У меня есть декоратор, скажем, @find_me. Я хочу найти все классы, которые украшены им.

Или у меня есть класс, скажем, FindMe, и я хочу найти все его подклассы.

Почему? потому что я хочу что-то сделать с этими классами до того, как они будут import -ед.

Так что я читал о __subclasses()__ и о поиске декораторов. Проблема с решениями, которые я нашел, заключается в том, что класс должен быть import -ed перед выполнением кода.

Другими словами, если у меня есть:

  • в модуле ${proj_root}/some_path/FindMe.py a class FindMe(object):,
  • и в модуле ${proj_root}/some_other_path/NeedsToBeFound.py, a class NeedsToBeFound(FindMe):,
  • и другом модуле ${proj_root}/yet_another_path/some_module.py,
  • и если some_module.py выглядит что-то вроде:
import ... FindMe

...

subclasses_of_FindMe = FindMe.__subclasses()__

тогда ожидаемый класс NeedsToBeFound не будет в результате (при условии, что его не было import где-то по пути).

Итак, я думаю, что я ищу способ выполнить какое-то сканирование компонентов по всем python классам (которые находятся в поддереве ${proj_root}).

Как сделать это проще: найти декораторов или найти подклассы? И как я могу это сделать? ...

Заранее спасибо!

Ответы [ 2 ]

0 голосов
/ 02 апреля 2020

ОК, поэтому я придумал что-то такое:

# /{some_proj_home_dir_1}/{some_path}/.../FindMe.py
class FindMe(object):
    pass



# /{some_proj_home_dir_1}/{some_path}/.../A.py

class A(object):

    ...

    def foo(self, project_home_dir):
        # going to mess with sys.path, so preserve current length of it, later to be restored
        sys_path_length = _append_dir_to_sys_path(project_home_dir)

        try:
            # dynamically load all modules in "the other" project
            self._load_all_modules(project_home_dir)
            # find all classes that needs to be found
            all_subclasses = self._find_all_needs_to_be_found()

            # do something with all_classes
            ...

        finally:
            # restore sys.path back to what it was
            sys.path = sys.path[:sys_path_length]


    def _load_all_modules(self, project_home_dir, subpackages=None):
        subdir = project_home_dir if subpackages is None else "/".join([project_home_dir] + subpackages)
        for (module_loader, name, ispkg) in pkgutil.iter_modules([subdir]):
            if ispkg:
                self._load_all_modules(project_home_dir, [name] if subpackages is None else subpackages + [name])
                return

            module_name = ".".join(subpackages)
            importlib.import_module("." + name, module_name)


    def _find_all_needs_to_be_found():
        return [FindMe] + FindMe.__subclasses__()


def _append_dir_to_sys_path(project_dir):
    path_length = len(sys.path)
    path = os.path.abspath(project_dir)

    while _is_part_of_package(path):
        sys.path.append(path)
        path = os.path.abspath(os.path.join(path, ".."))

    sys.path.append(path)

    return path_length


def _is_part_of_package(path):
    return os.path.isfile(os.path.join(path, "__init__.py"))



# /{some_different_proj_home_dir_2}/{some_path}/.../NeedsToBeFound.py
class NeedsToBeFound(FindMe):
    pass

Теперь вызов A().foo({some_different_proj_home_dir_2}), кажется, работает:)

0 голосов
/ 25 марта 2020

Рассматривали ли вы помещение классов / модулей, для которых вы «хотите что-то сделать, прежде чем они будут импортированы», в общий пакет и использование __init__.py? Вот для чего этот файл на самом деле. ;)

Если все пакеты находятся в одном модуле, вы также можете просто поместить код инициализатора в этот модуль (вне всякого определения).

См. Также, например,

...