找回密码
 立即注册
首页 业界区 业界 [python]动态实例化

[python]动态实例化

讣丢 2025-9-25 10:54:21
前言

最近在查一个服务的问题时,看到有一段代码if .. elif ... 写了近百行,类似
  1. if command == "xxx":
  2.         obj = CommandX()
  3.         obj.run()
  4.         # ...
  5. elif command == "yyy":
  6.         obj = CommandY()
  7.         obj.run()
  8.     # ...
  9. elif command == "zzz":
  10.         obj = CommandZ()
  11.         obj.run()
  12.     # ...
  13. # ...
复制代码
翻了下git记录,最开始其实只有两三个条件判断,后来command越加越多,就这么延续下来了。
代码逻辑其实没什么问题,也很简单明了,就是看起来有点丑,而且我还开了比较高的桌面缩放,导致一屏幕几乎都是这段if ... elif
看来看去越发觉得丑,先写个demo看看能不能跑通代码。
方式1, 字典映射

如果需要判断的条件比较少,用字典做映射还是挺方便的,但如果条件多,看起来还是挺丑的。
  1. from abc import ABC, abstractmethod
  2. class AbstractCommand(ABC):
  3.     @abstractmethod
  4.     def run(self):
  5.         pass
  6. class CommandA(AbstractCommand):
  7.     def run(self):
  8.         return "this is command A"
  9.    
  10. class CommandB(AbstractCommand):
  11.     def run(self):
  12.         return "this is command B"
  13.    
  14. class CommandC(AbstractCommand):
  15.     def run(self):
  16.         return "this is command C"
  17.    
  18. class CommandFactory:
  19.     @staticmethod
  20.     def create_command(command_type: str) -> AbstractCommand:
  21.         command_mapping = {
  22.             "cmda": CommandA,
  23.             "cmdb": CommandB,
  24.             "cmdc": CommandC
  25.         }
  26.         cls = command_mapping.get(command_type.lower())
  27.         if not cls:
  28.             raise ValueError(f"Unknown command type: {command_type}")
  29.         return cls()
  30.    
  31. if __name__ == "__main__":
  32.     cmd = CommandFactory.create_command("cmda")
  33.     assert cmd.run() == "this is command A"
  34.     cmd = CommandFactory.create_command("cmdb")
  35.     assert cmd.run() == "this is command B"
  36.     cmd = CommandFactory.create_command("cmdc")
  37.     assert cmd.run() == "this is command CD"  # should be exception
  38.     cmd = CommandFactory.create_command("cmdd")  # should be exception
  39.     assert cmd.run() == "this is command D"
复制代码
方式2, __init_subclass__

《流畅的Python(第2版)》的最后一章提到了这个__init__subclass__,根据python官方文档:
当所在类派生子类时此方法就会被调用。cls 将指向新的子类。如果定义为一个普通实例方法,此方法将被隐式地转换为类方法。传给一个新类的关键字参数会被传给上级类的 __init_subclass__。 为了与其他使用 __init_subclass__ 的类兼容,应当去掉需要的关键字参数再将其他参数传给基类。
借助这个机制,可以在实现抽象基类时自动注册子类,避免手动维护注册表。
  1. from abc import ABCMeta, abstractmethod
  2. from threading import Lock
  3. from collections import UserDict
  4. class ThreadSafeDict(UserDict):
  5.     """线程安全的字典"""
  6.     def __init__(self):
  7.         super().__init__()
  8.         self._lock = Lock()
  9.    
  10.     def __setitem__(self, key, item):
  11.         with self._lock:
  12.             super().__setitem__(key, item)
  13. class Command(metaclass=ABCMeta):
  14.     registry = ThreadSafeDict()
  15.     def __init__(self):
  16.         pass
  17.     @abstractmethod
  18.     def run(self):
  19.         pass
  20.     def __init_subclass__(cls, **kwargs):
  21.         super().__init_subclass__(**kwargs)
  22.         cls.registry[cls.__name__.lower()] = cls  # 自动注册子类
  23. # 子类定义即自动注册
  24. class CommandA(Command):
  25.     def run(self):
  26.         return "this is command a!"
  27. class CommandB(Command):
  28.     def run(self):
  29.         return "this is command b!"
  30. class CommandC(Command):
  31.     def run(self):
  32.         return "this is command b!"
  33.    
  34. def create_command(command_type: str) -> Command:
  35.     """工厂函数"""
  36.     cls = Command.registry.get(command_type.lower())
  37.     if not cls:
  38.         raise ValueError(f"Unknown command type: {command_type}")
  39.     return cls()
  40.    
  41. if __name__ == "__main__":
  42.     cmd = create_command("CommandA")
  43.     assert cmd.run() == "this is command a!"
  44.     cmd = create_command("CommandB")
  45.     assert cmd.run() == "this is command b!"
  46.     cmd = create_command("CommandC")
  47.     assert cmd.run() == "this is command cc!"  # should be exception
  48.     cmd = create_command("CommandD")
  49.     assert cmd.run() == "this is command b!"  # should be exception
复制代码
乍一看还是挺不错的,但是也有个缺点,那就是如果各个类分散在不同模块中,那么工厂函数所在的模块就要写一堆from xxx import ...
如果module和类命名比较规范,也可以这么动态加载类
  1. import importlib
  2. def create_class(module_name, class_name):
  3.     module = importlib.import_module(module_name)
  4.     cls = getattr(module, class_name)
  5.     return cls()
复制代码
补充

自动注册类看起来炫,但是对代码阅读来说不是很直观。易读还是美观?这是一个问题。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

2025-11-2 19:40:51

举报

您需要登录后才可以回帖 登录 | 立即注册