[python]动态实例化

前言

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

python 复制代码
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, 字典映射

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

python 复制代码
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__ 的类兼容,应当去掉需要的关键字参数再将其他参数传给基类。

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

python 复制代码
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和类命名比较规范,也可以这么动态加载类

python 复制代码
import importlib

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

补充

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

相关推荐
兵慌码乱7 小时前
基于Python+PyQt5+SQLite的药房管理系统实现:事务一致性与界面解耦全流程解析
python·sqlite·信号与槽·pyqt5·数据库设计·桌面应用开发·事务处理
金銀銅鐵9 小时前
[Python] 体验用欧几里得算法计算最大公约数的过程
python·数学
FreakStudio13 小时前
W55MH32L-EVB 上手测评:硬件 TCP/IP 加持的以太网单片机,MicroPython 零门槛开发
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
用户03321266636714 小时前
使用 Python 从零创建 Word 文档
python
Csvn19 小时前
Python 两大经典坑点 —— 可变默认参数 & 闭包延迟绑定
后端·python
曲幽20 小时前
别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了
python·docker·web·pot·translate·libretranslate·arogstranslate
用户5569188175321 小时前
#从脚本到独立程序:Python + Playwright 批量抓取的完整踩坑记录
python·自动化运维
兵慌码乱1 天前
基于 MediaPipe 与 PySide2 的手势交互音乐控制系统实现:轻量化视觉交互全流程解析
python·opencv·计算机视觉·人机交互·手势识别·mediapipe·pyside2
luckdewei2 天前
FastAPI 资产管理系统实战:复杂 ORM 关联、Alembic 迁移与 N+1 查询优化
python