python - Python——反射式地初始化类
问题描述
我正在用 Python 创建一个命令系统。我有一个模块vkcommands
,它有一个处理来自聊天的命令的类(这是一个聊天机器人),在它里面,我也有一个VKCommand
具有属性的类name
,如usage
, min_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 中做到这一点?同样,我需要的是:
- 遍历模块(文件夹)中的所有子模块
vkcmds/
; - 对于每个子模块,检查是否有一些类在里面
X
扩展VKCommand
; - 如果 (2) is
true
,则使用一个参数调用该类的构造函数(保证所有命令的构造函数只有一个已知类型的参数(我的机器人的主类)); ? extends VKCommand
将(3) 中构造的对象 ( ) 添加到commands
我以后可以迭代的列表中。
解决方案
使用此文件结构:
- Project
├─ commands
| ├─ base.py
| ├─ baz.py
| └─ foo_bar.py
|
└─ main.py
commands
目录文件中的以下内容:
基础.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. """
我们可以使用以下代码直接检索类实例和名称:
主文件
""" 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 个字典的模块时才有效。如果要递归使用它,则必须找到相对路径差异并使用正确的(完整)相对导入路径更新
pyclbr.readmodule
and 。__import__
只有包含继承自的类的模块
VKCommand
才会被加载。所有其他模块均未导入,必须手动导入。
推荐阅读
- javascript - 使用 array.reduce 将字符串分解为具有条件的数组
- google-cloud-platform - 如何在没有 CLI 的情况下使用控制台设置和使用 Cloud Storage 的 Pub/Sub 通知?
- java - 在tomcat的java方法中查找分配的内存
- r - pivot_wider 按出现次数动态变化
- r - regsubsets 正在从因子水平生成虚拟变量 - 我们可以让它不这样做吗?
- spring-boot - 如何评估在应用程序中是使用spring batch还是scheduler?
- python - 如何安排函数在 discord.py 中的特定时间每天运行?
- c# - PostSharp MulticastAttributeUsage 不起作用
- mongodb - 在 SpEL Bean 参考的集成测试期间注册 Mock Bean
- python - 在这里使用嵌套的 try-except 可接受的 Python 样式