在定义类时自动注册一个类(但不在任何地方导入它)

时间:2011-08-31 20:39:15

标签: python oop class metaclass

我想在创建时注册一个类(不是实例)但不导入它。

基本上,我想做这里描述的内容:

How to auto register a class when it's defined

...但无需在任何地方导入已注册的课程。

问题出在这里:在我的应用程序的某个时刻,我需要运行某些系统命令(让我们坚持使用reboot,停止......例如)。我已经创建了一个“base”(抽象-mmnnno-not-really look)类“Command”和一个“CommandManager”,它将接收一个包含要运行的命令的字符串,为所述命令创建适当的实例,对其进行排队并调度需要时(或可能的话)命令。 “Command”类的所有实例都将覆盖实际执行命令的“execute”方法。如果我想“重启”,我会在“RebootCommand”类上扩展“Command”类,并覆盖“执行”,调用外部应用程序“shutdown -r”或“rebo​​ot”。

最终目标是能够做到这样的事情:

CommandManager.execute("reboot")

系统将重启。

为此,必须在CommandManager内的字典中注册“ reboot ”字符串。该字典将“ 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类对象,因此它将执行注册发生的Command MetaClass的__new__方法(详见上述其他问题的答案)。

有什么办法吗?我已经尝试在__init__.py中导入实际命令(继承自Command的类),但是,如果我创建一个新命令,我必须将它添加到__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__

函数运行,“rebo​​ot”注册为RebootCommand类,每个人都很高兴)

但是如果可能的话,我想避免这样做(触摸__init__.py

我尝试使用*通配符导入但没有运气。

总而言之,我希望另一个想要实现新命令的程序员,只需要创建一个继承自Command的新类,而不必将其添加到__init__.py

我知道用Command.py文件中的注释无法解决这个问题,说“必须在__init__.py中添加任何继承此类的类”,但我很想知道如果我能以更“优雅”的方式做到这一点。

1 个答案:

答案 0 :(得分:2)

我觉得你的方法实现你想要的功能有点复杂。我的建议是你使用这样的策略:

  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()
    
  4. 编辑:似乎我混淆了imp模块的用法。更新了代码以使用__import__