【问题标题】:Python — Init class reflectivelyPython——反射式地初始化类
【发布时间】:2021-03-10 04:54:48
【问题描述】:

我正在用 Python 创建一个命令系统。我有一个模块vkcommands,它有一个处理来自聊天的命令的类(这是一个聊天机器人),在其中,我还有一个类VKCommand,其属性如nameusagemin_rank、等等。然后我有模块 vkcmds 和实现这些命令的子模块:

...
vkcommands.py
vkcmds
    |- __init__.py  # empty
    |- add_group.py
    |- another_cmd.py
    |- ...

命令的实现(例如add_group)如下所示:

import ranks
import vkcommands
from vkcommands import VKCommand


class AddGroup(VKCommand):
    def __init__(self, kristy):
        VKCommand.__init__(self, kristy,
                           label='create',
                           # ... (other attributes)
                           min_rank=ranks.Rank.USER)

    def execute(self, chat, peer, sender, args=None, attachments=None):
        # implementation (called from vkcommands.py)

当用户在聊天中发送消息时,命令管理器会对其进行分析并查看已注册的commands 列表,以确定这是普通消息还是机器人命令。目前我像这样手动注册commands列表中的所有命令:

class VKCommandsManager:
    def __init__(self, kristy):
        from vkcmds import (
            add_group,
            next_class
        )

        self.kristy = kristy
        self.commands = (
            add_group.AddGroup(kristy),
            next_class.NextClass(kristy)
        )

现在我希望使用反射自动注册所有命令。在 Java 中,我会遍历命令包中的所有类,反射性地为每个类 getConstructor,调用它来检索VKCommand 对象,并将其添加到命令列表中。

如何在 Python 中做到这一点? 同样,我需要:

  1. 遍历模块(文件夹)vkcmds/中的所有子模块;
  2. 对于每个子模块,检查内部是否有一些类X 扩展了VKCommand
  3. 如果 (2) 是 true,则使用一个参数调用该类的构造函数(保证所有命令的构造函数只有一个已知类型的参数(我的机器人主类));
  4. 将 (3) 中构造的对象 (? extends VKCommand) 添加到 commands 列表中,以便稍后进行迭代。

【问题讨论】:

  • 我认为您可以在__init__.py 中创建所有命令的列表,然后遍历所有命令并构建它们。
  • 整个问题是我如何构建它们。我已经能够弄清楚如何列出模块的所有子模块,但是如何检查它们是否有特定的类,以及如何实际初始化该类?
  • 像调用函数一样实例化一个类:obj = AddGroup(arg1, arg2, ...)
  • 我想反思一下。无需自己为每个命令键入类或构造函数的名称。这样我就可以创建一个新的子模块,比如vkcmds/new_cmd.py,在其中创建一个扩展VKCommand 的类,执行命令,然后什么都不做——该命令应该自动注册,而无需我编写“import new_cmd / // commands.append(NewCmd(...))"

标签: python python-3.x reflection chatbot python-class


【解决方案1】:

使用此文件结构:

- Project
   ├─ commands
   |   ├─ base.py
   |   ├─ baz.py
   |   └─ foo_bar.py
   |
   └─ main.py

以及commands目录里面的以下文件:

  • base.py

    class VKCommand:
        """ We will inherit from this class if we want to include the class in commands.  """
    
  • baz.py

    from commands.base import VKCommand
    
    class Baz(VKCommand):
        pass
    
    
    def baz():
        """ Random function we do not want to retrieve.  
    
  • foo_bar.py

    from .base import VKCommand
    
    
    class Foo(VKCommand):
        """ We only want to retrieve this command.  """
        pass
    
    
    class Bar:
        """ We want to ignore this class.  """
        pass
    
    
    def fizz():
        """  Random function we do not want to retrieve. """
    

我们可以使用以下代码直接检索类实例和名称:

  • main.py

    """
      Dynamically load all commands located in submodules.
      This file is assumed to be at most 1 level higher than the
      specified folder.
    """
    
    import pyclbr
    import glob
    import os
    
    def filter_class(classes):
        inherit_from = 'VKCommand'
        classes = {name: info for name, info in classes.items() if inherit_from in info.super}
        return classes
    
    # Locate all submodules and classes that it contains without importing it.
    folder = 'commands'  # `vkcmds`.
    submodules = dict()
    absolute_search_path = os.path.join(os.path.dirname(__file__), folder, '*.py')
    for path in glob.glob(absolute_search_path):
        submodule_name = os.path.basename(path)[:-3]
        all_classes = pyclbr.readmodule(f"commands.{submodule_name}")
        command_classes = filter_class(all_classes)
        if command_classes:
            submodules[submodule_name] = command_classes
    
    # import the class and store an instance of the class into the command list
    class_instances = dict()
    for submodule_name, class_names in submodules.items():
        module = __import__(f"{folder}.{submodule_name}")
        submodule = getattr(module, submodule_name)
        for class_name in class_names:
            class_instance = getattr(submodule, class_name)
            class_instances[class_name] = class_instance
    
    print(class_instances)
    

说明

解决方案是双重的。它首先定位具有从VKCommand 继承的类并位于文件夹“commands”中的所有子模块。这导致以下输出包含必须分别导入和实例化的模块和类:

{'baz': {'Baz': <pyclbr.Class object at 0x000002BF886357F0>}, 'foo_bar': {'Foo': <pyclbr.Class object at 0x000002BF88660668>}}

代码的第二部分在运行时导入了正确的模块和类名。变量class_instance 包含类名和对可用于实例化它的类的引用。最终输出将是:

{'Baz': <class 'commands.baz.Baz'>, 'Foo': <class 'commands.foo_bar.Foo'>}

重要提示:

  1. 该代码仅在导入比字典更深 1 的模块时有效。如果您想递归使用它,您必须找到 relative path difference 并使用正确的(完整)相对导入路径更新 pyclbr.readmodule__import__

  2. 只有包含从VKCommand 继承的类的模块才会被加载。所有其他模块导入,必须手动导入。

【讨论】:

    【解决方案2】:

    我相信您可以将文件夹中的所有命令组成一个数组,然后遍历它们并实例化对象。

    __init__.py

    all_commands = [AddGroup, AnotherCmd, ...]
    

    像这样实例化它们:

    objects = [Cmd(arg1, arg2, ...) for Cmd in all_commands]
    

    编辑: 您还可以使用您所说的获取文件夹中所有类名的方法来检索类名。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-20
      相关资源
      最近更新 更多