Автоматическая регистрация класса, когда он определен (но не импортируется никуда) - PullRequest
2 голосов
/ 01 сентября 2011

Я хотел бы зарегистрировать класс (не экземпляр) при его создании ... но без его импорта.

В основном, я хочу сделать то, что описано здесь:

Как автоматически зарегистрировать класс, когда он определен

... но без необходимости импортировать зарегистрированный класс в любом месте.

Вот проблема: в определенный момент моего приложения мне нужно запустить определенные системные команды (для примера давайте придерживаться перезагрузки, остановки ...). Я создал «базовый» (абстрактно-ммннно-не очень выглядящий) класс «Command» и «CommandManager», который получит строку с командой для запуска, создаст соответствующий экземпляр для указанной команды, поставит ее в очередь и отправит Команды, когда это необходимо (или когда это возможно). Все экземпляры класса «Command» будут перезаписывать метод «execute», который фактически выполняет команду. Если я хочу выполнить «перезагрузку», я бы расширил класс «Command» класса «RebootCommand» и перезаписал это «execute», сделав вызов внешнему приложению «shutdown -r» или «reboot».

Конечная цель - сделать что-то вроде:

CommandManager.execute("reboot")

И система перезагрузится.

Для этого строка " reboot " должна быть зарегистрирована в словаре внутри CommandManager. Этот словарь будет иметь значение « reboot » (строка) в качестве ключа и класс RebootCommand в качестве значения. CommandManager получит строку « reboot », перейдет к его «commandDictionary», создаст экземпляр класса RebootCommand и вызовет его метод execute () (таким образом, перезагрузив систему).

Что-то вроде:

#------------- CommandManager.py -------------
@classmethod
def execute(cls, parameter):
    if parameter in cls.validCommands:
        tmpCommand = cls.validCommands[parameter]()
        tmpCommand.execute()
#---------------------------------------------

Чтобы выполнить регистрацию, должен быть создан объект класса RebootCommand, и для этого я должен импортировать его куда-нибудь. Но я не хочу. Мне не нужно Мне просто нужно создать объект класса RebootCommand, чтобы он выполнял метод __new__ для командного метакласса, в котором происходит регистрация (как подробно описано в ответе на другой вопрос выше).

Есть ли способ сделать это? Я попытался импортировать фактические команды (классы, унаследованные от Command) в __init__.py, но затем, если я создаю новую команду, мне нужно добавить ее в __init__.py, и я хотел бы избежать этого ( так что другой программист может создать новую команду, не беспокоясь о том, чтобы забыть добавить класс в __init__.py)

Вот моя структура каталогов:

commands/
    __init__.py
    CommandManager.py
    Command.py
    RebootCommand.py

И вот, содержимое Command и RebootCommand, которые делают регистрацию:

#--------------- Command.py -------------------
def register(newCommand):
    if newCommand and newCommand._command:
        import CommandManager
        CommandManager.CommandManager.registerCommand(newCommand._command, newCommand)


# Fancy registering! https://stackoverflow.com/questions/5189232/how-to-auto-register-a-class-when-its-defined
class CommandMetaClass(type):
    def __new__(cls, clsname, bases, attrs):
        newCommand = super(cls, CommandMetaClass).__new__(cls, clsname, bases, attrs)
        register(newCommand)  # here is your register function
        return newCommand

class Command(object):
    __metaclass__ = CommandMetaClass
    _command = None
#-----------------------------------------------------

и ...

#--------------- RebootCommand.py -------------------
class RebootCommand(Command.Command):
    _command = "reboot"
#---------------------------------------------------

Процесс регистрации выполняется правильно, если я открываю __init__.py и пишу:

import RebootCommand

(на этом этапе создается объект класса RebootCommand,

CommandMetaClass.__new__

функция запущена, "перезагрузка" зарегистрирована как класс RebootCommand, и все довольны)

но я бы хотел избежать этого (касаясь __init__.py), если возможно

Я попытался импортировать с подстановочным знаком * без удачи.

Подводя итог, я хотел бы, чтобы другой программист, который хочет реализовать новую Команду, просто должен создать новый класс, унаследованный от Command, но без добавления его в __init__.py

Я знаю, что ничего нельзя исправить с помощью комментария в файле Command.py, говорящего что-то вроде "любой класс, унаследованный от этого, должен быть добавлен в __init__.py", но мне любопытно узнать, если я может сделать это более «элегантно».

1 Ответ

2 голосов
/ 01 сентября 2011

Мне кажется, что ваш подход немного сложен для реализации желаемой функциональности. Я предлагаю вам использовать такую ​​стратегию:

  1. Назовите ваши модули так, чтобы классы команд были определены в файлах, названных в честь команд, которые они реализуют. Например, класс RebootCommand входит в файл commands/reboot.py.
  2. Каждый командный модуль определяет переменную верхнего уровня command_cls, которая содержит ссылку на класс, реализующий команду. Следовательно, commands/reboot.py будет содержать такой код:

    class RebootCommand:
      def execute(self):
        # [...]
    
    command_cls = RebootCommand
    
  3. Чтобы выполнить команду, вы используете функцию __import__ для динамической загрузки вашего модуля. Ваш CommandManager.py будет логически находиться в файле вне каталога commands (который зарезервирован для командных модулей) и будет реализован следующим образом:

    class CommandManager:
      @classmethod
      def execute(cls, command, *args, **kw):
        # import the command module
        commands_mod = __import__("commands", globals(), locals(), [command])
        mod = getattr(commands_mod, command)
        return mod.command_cls(*args, **kw).execute()
    

EDIT : Кажется, я запутался в использовании модуля imp. Обновлен код для использования __import__.

...