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



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

我注册这些类的方式是通过一个decorator。我的代码看起来很像我们在这里看到的:

  • 用不同文件中的类向工厂注册类(我的问题与此重复,但将此问题突出(
  • https://medium.com/@geoffreykoh/实现工厂模式-via-动态-注册-和ython-decorators-479fc1537bbe

我想知道:

  • 为什么把它们放在同一个文件中,而把它们分开不起作用
  • 如何使单独的文件方法发挥作用

希望文章中的代码示例能澄清我正在尝试做什么以及正在努力做什么。

我目前正在探索一个类似的问题,我想我可能已经找到了解决方案。不过,这有点像"黑客",所以要谨慎对待。

为什么把它们放在同一个文件中,同时把它们分开

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

如何使单独的文件方法发挥作用?

在我的例子中,我在尝试实现"简单工厂"时遇到了这个问题,使用自注册子类来避免修改工厂的get()方法中典型的"if/else"习惯用法。

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

装饰器示例

假设我们有一个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中自注册,同时将其定义保留在单个.py文件中,我们必须以某种方式强制在.py文件中加载Sandal类。

我分三步完成:

  1. 将所有类实现保存在特定文件夹中,例如,按如下方式构建文件:
.
└- shoe.py     # file with the ShoeFactory class
└─ shoes/
└- __init__.py
└- croc.py
└- sandal.py
  1. 将以下语句添加到shoe.py文件的末尾,该语句将负责加载和注册每个单独的类:
from shoes import *
  1. shoes/foder中的__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 *
  1. 将混凝土鞋类更改为从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")

最新更新