python - 将不同文件中的类注册到类工厂

标签 python oop factory-pattern

我正在尝试将不同文件中的类注册到工厂类。工厂类有一个名为“registry”的字典,它保存/映射用户定义的名称到注册类。我的问题是,如果我的工厂类和注册类位于同一个 .py 文件中,一切都会按预期工作,但是当我将注册类移动到它们自己的 .py 文件中并导入工厂类以应用寄存器装饰器时(如中所述)下面的问题和文章)“registry”字典保持为空,这意味着类没有被注册。

我注册这些类的方式是通过装饰器。我的代码看起来非常像我们在这里看到的:

我想知道:

  • 为什么将它们保留在同一个文件中而将它们分开却不起作用
  • 如何使单独文件方法发挥作用?

希望文章中的代码示例能够阐明我正在尝试做的事情和正在努力解决的问题。

最佳答案

我目前正在探索类似的问题,我想我可能已经找到了解决方案。不过,这有点“黑客”,所以请持保留态度。

What why keeping them in the same file work while splitting them out doest

为了使您的类在工厂中自行注册,同时将其定义保留在单个 .py 文件中,我们必须以某种方式强制加载 .py 文件。

How can I make the separate file approach work?

就我而言,我在尝试实现 'Simple Factory' 时遇到了这个问题。 ,使用自注册子类以避免修改工厂的 get() 方法中典型的“if/else”习惯用法。

我将使用一个简单的示例,从您提到的装饰器方法开始。

装饰器示例

假设我们有一个如下所示的 ShoeFactory,我们在其中注册不同的鞋子“类别”:

# file shoe.py

class ShoeFactory:
    _shoe_classes = {}

    @classmethod
    def get(cls, shoe_type:str):
        try:
            return cls._shoe_classes[shoe_type]()
        except KeyError:
            raise ValueError(f"unknown product type : {shoe_type}")

    @classmethod
    def register(cls, shoe_type:str):
        def inner_wrapper(wrapped_class):
            cls._shoe_classes[shoe_type] = wrapped_class
            return wrapped_class
        return inner_wrapper

鞋类示例:

# file sandal.py

from shoe import ShoeFactory

@ShoeFactory.register('Sandal')
class Sandal:
    def __init__(self):
        print("i'm a sandal")
# file croc.py

from shoe import ShoeFactory

@ShoeFactory.register('Croc')
class Croc:
    def __init__(self):
        print("i'm a croc")

为了使 SandalShoeFactory 中 self 注册,同时将其定义保留在单个 .py 文件中,我们必须以某种方式强制加载 .py 文件中的 Sandal 类。

我通过 3 个步骤完成了此操作:

  1. 将所有类实现保存在特定文件夹中,例如按如下方式构建文件:
.
└- shoe.py     # file with the ShoeFactory class
└─ shoes/
  └- __init__.py
  └- croc.py
  └- sandal.py
  • 将以下语句添加到 shoe.py 文件末尾,该文件将负责加载和注册每个单独的类:
  • from shoes import *
    
  • 将如下代码片段添加到 shoes/ 文件夹中的 __init__.py 中,以便动态加载所有类 [1] :
  • from inspect import isclass
    from pkgutil import iter_modules
    from pathlib import Path
    from importlib import import_module
    
    # iterate through the modules in the current package
    package_dir = Path(__file__).resolve().parent
    for (_, module_name, _) in iter_modules([package_dir]):
    
        # import the module and iterate through its attributes
        module = import_module(f"{__name__}.{module_name}")
        for attribute_name in dir(module):
            attribute = getattr(module, attribute_name)
    
            if isclass(attribute):            
                # Add the class to this package's variables
                globals()[attribute_name] = attribute
    
    

    如果我们遵循这种方法,我在运行一些测试代码时会得到以下结果,如下所示:

    # file shoe_test.py
    
    from shoe import ShoeFactory
    
    if __name__ == "__main__":
        croc = ShoeFactory.get('Croc')
        sandal = ShoeFactory.get('Sandal')
    
    
    $ python shoe_test.py
    i'm a croc
    i'm a sandal
    

    使用 __init_subclass__() 的示例

    我个人对我的简单工厂设计采用了稍微不同的方法,它不使用装饰器。

    我定义了一个 RegistrableShoe 基类,然后使用 __init_subclass__() 方法进行自注册([1] 第 49 项,[2] )。

    我认为这个想法是,当Python找到RegistrableShoe的子类的定义时,会运行__init_subclass__()方法,该方法又将子类注册到工厂。

    与上面的示例相比,此方法需要进行以下更改:

    1. shoe.py 文件添加了 RegistrableShoe 基类,并对 ShoeFactory 进行了一些重构:
    # file shoe.py
    
    
    class RegistrableShoe():
        def __init_subclass__(cls, shoe_type:str):
            ShoeFactory.register(shoe_type, shoe_class=cls)
    
    
    class ShoeFactory:
        _shoe_classes = {}
    
        @classmethod
        def get(cls, shoe_type:str):
            try:
                return cls._shoe_classes[shoe_type]()
            except KeyError:
                raise ValueError(f"unknown product type : {shoe_type}")
    
        @classmethod
        def register(cls, shoe_type:str, shoe_class:RegistrableShoe):
            cls._shoe_classes[shoe_type] = shoe_class
    
    from shoes import *
    
    
  • 将具体鞋类更改为从 RegistrableShoe 基类派生并传递 shoe_type 参数:
  • # file croc.py
    
    from shoe import RegistrableShoe
    
    class Croc(RegistrableShoe, shoe_type='Croc'):
        def __init__(self):
            print("i'm a croc")
    
    # file sandal.py
    
    from shoe import RegistrableShoe
    
    class Sandal(RegistrableShoe, shoe_type='Sandal'):
        def __init__(self):
            print("i'm a sandal")
    

    关于python - 将不同文件中的类注册到类工厂,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73829483/

    相关文章:

    python - 使用多重继承调用父类 __init__,正确的方法是什么?

    python - 实现顺序 try-excepts 的 pythonic 方法是什么?

    python - 在 python 中使用 iGraph 进行社区检测并将每个节点的社区编号写入 CSV

    Python 类输入参数

    python - Bokeh 将图例添加到步骤图

    java - 关联关系中调用父方法

    java - 如何使用反射来构建通用子类型?

    vba - 如何借助 VBA 中初始化对象的属性来初始化对象

    python - 装箱 Pandas value_counts

    python - 关于多线程,Python 中的计时器如何工作?