我正在尝试将不同文件中的类注册到工厂类。工厂类有一个名为“registry”的字典,它保存/映射用户定义的名称到注册类。我的问题是,如果我的工厂类和注册类位于同一个 .py 文件中,一切都会按预期工作,但是当我将注册类移动到它们自己的 .py 文件中并导入工厂类以应用寄存器装饰器时(如中所述)下面的问题和文章)“registry”字典保持为空,这意味着类没有被注册。
我注册这些类的方式是通过装饰器。我的代码看起来非常像我们在这里看到的:
- Registering classes to factory with classes in different files (我的问题与此重复,但将这个问题放在首位)
- https://medium.com/@geoffreykoh/implementing-the-factory-pattern-via-dynamic-registry-and-python-decorators-479fc1537bbe
我想知道:
- 为什么将它们保留在同一个文件中而将它们分开却不起作用
- 如何使单独文件方法发挥作用?
希望文章中的代码示例能够阐明我正在尝试做的事情和正在努力解决的问题。
最佳答案
我目前正在探索类似的问题,我想我可能已经找到了解决方案。不过,这有点“黑客”,所以请持保留态度。
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")
为了使 Sandal
在 ShoeFactory
中 self 注册,同时将其定义保留在单个 .py
文件中,我们必须以某种方式强制加载 .py
文件中的 Sandal
类。
我通过 3 个步骤完成了此操作:
- 将所有类实现保存在特定文件夹中,例如按如下方式构建文件:
.
└- 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__()
方法,该方法又将子类注册到工厂。
与上面的示例相比,此方法需要进行以下更改:
- 向
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/