[python]动态实例化

前言

最近在查一个服务的问题时,看到有一段代码if .. elif ... 写了近百行,类似

if command == "xxx": 	obj = CommandX() 	obj.run() 	# ... elif command == "yyy": 	obj = CommandY() 	obj.run()     # ... elif command == "zzz": 	obj = CommandZ() 	obj.run()     # ...  # ... 

翻了下git记录,最开始其实只有两三个条件判断,后来command越加越多,就这么延续下来了。

代码逻辑其实没什么问题,也很简单明了,就是看起来有点丑,而且我还开了比较高的桌面缩放,导致一屏幕几乎都是这段if ... elif

看来看去越发觉得丑,先写个demo看看能不能跑通代码。

方式1, 字典映射

如果需要判断的条件比较少,用字典做映射还是挺方便的,但如果条件多,看起来还是挺丑的。

from abc import ABC, abstractmethod  class AbstractCommand(ABC):     @abstractmethod     def run(self):         pass  class CommandA(AbstractCommand):     def run(self):         return "this is command A"      class CommandB(AbstractCommand):     def run(self):         return "this is command B"      class CommandC(AbstractCommand):     def run(self):         return "this is command C"      class CommandFactory:     @staticmethod     def create_command(command_type: str) -> AbstractCommand:         command_mapping = {             "cmda": CommandA,             "cmdb": CommandB,             "cmdc": CommandC         }         cls = command_mapping.get(command_type.lower())         if not cls:             raise ValueError(f"Unknown command type: {command_type}")         return cls()      if __name__ == "__main__":     cmd = CommandFactory.create_command("cmda")     assert cmd.run() == "this is command A"      cmd = CommandFactory.create_command("cmdb")     assert cmd.run() == "this is command B"      cmd = CommandFactory.create_command("cmdc")     assert cmd.run() == "this is command CD"  # should be exception      cmd = CommandFactory.create_command("cmdd")  # should be exception     assert cmd.run() == "this is command D" 

方式2, __init_subclass__

《流畅的Python(第2版)》的最后一章提到了这个__init__subclass__,根据python官方文档:

当所在类派生子类时此方法就会被调用。cls 将指向新的子类。如果定义为一个普通实例方法,此方法将被隐式地转换为类方法。传给一个新类的关键字参数会被传给上级类的 __init_subclass__。 为了与其他使用 __init_subclass__ 的类兼容,应当去掉需要的关键字参数再将其他参数传给基类。

借助这个机制,可以在实现抽象基类时自动注册子类,避免手动维护注册表。

from abc import ABCMeta, abstractmethod from threading import Lock from collections import UserDict  class ThreadSafeDict(UserDict):     """线程安全的字典"""     def __init__(self):         super().__init__()         self._lock = Lock()          def __setitem__(self, key, item):         with self._lock:             super().__setitem__(key, item)  class Command(metaclass=ABCMeta):     registry = ThreadSafeDict()      def __init__(self):         pass      @abstractmethod     def run(self):         pass      def __init_subclass__(cls, **kwargs):         super().__init_subclass__(**kwargs)         cls.registry[cls.__name__.lower()] = cls  # 自动注册子类  # 子类定义即自动注册 class CommandA(Command):     def run(self):         return "this is command a!"  class CommandB(Command):     def run(self):         return "this is command b!"  class CommandC(Command):     def run(self):         return "this is command b!"      def create_command(command_type: str) -> Command:     """工厂函数"""     cls = Command.registry.get(command_type.lower())     if not cls:         raise ValueError(f"Unknown command type: {command_type}")     return cls()      if __name__ == "__main__":     cmd = create_command("CommandA")     assert cmd.run() == "this is command a!"     cmd = create_command("CommandB")     assert cmd.run() == "this is command b!"     cmd = create_command("CommandC")     assert cmd.run() == "this is command cc!"  # should be exception     cmd = create_command("CommandD")     assert cmd.run() == "this is command b!"  # should be exception 

乍一看还是挺不错的,但是也有个缺点,那就是如果各个类分散在不同模块中,那么工厂函数所在的模块就要写一堆from xxx import ...

如果module和类命名比较规范,也可以这么动态加载类

import importlib  def create_class(module_name, class_name):     module = importlib.import_module(module_name)     cls = getattr(module, class_name)     return cls() 

补充

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

发表评论

评论已关闭。

相关文章