我目前正在开发一个软件,其中我有从字典生成的类instamces。这些dictionariea文件的结构方式如下:
layer_dict = {
"layer_type": "Conv2D",
"name": "conv1",
"kernel_size": 3,
...
}
然后,运行以下代码
def create_layer(layer_dict):
LayerType = getattr(layers, layer_dict['layer_type']
del layer_dict['layer_type']
return LayerType(**layer_dict)
现在,我想支持创建新的图层类型(通过继承BaseLayer
类)。我想过几种方法可以做到这一点,并认为我会问哪种方式最好,为什么我没有太多开发软件的经验(在comp生物中完成一个MSc)。
方法1:元类我想到的第一个方法是有一个元类,它在一个字典中注册BaseLayer
的每个子类,并对这个字典进行简单的查找,而不是使用getattr。
class MetaLayer(type)
layers = {}
def __init__(cls, name, bases, dct):
if name in MetaLayer.layers:
raise ValueError('Cannot have more than one layer with the same name')
MetaLayer.layers[name] = cls
好处:元类可以确保没有两个类具有相同的名称。在创建新图层时,用户不需要考虑任何事情,只需进行子类化。 缺点:元类很难理解,经常不赞成
方法2:遍历__subclasses__
树
我想到的第二种方法是使用__subclassess__
的BaseLayer
函数来获取所有子类的列表,然后创建一个以Layer.__name__
为键,Layer
为值的dict。请参阅以下示例代码:
def get_subclasses(cls):
"""Returns all classes that inherit from `cls`
"""
subclasses = {
sub.__name__: sub for sub in cls.__subclasses__()
}
subsubclasses = (
get_subclasses(sub) for sub in subclasses.values()
)
subsubclasses = {
name: sub for subs in subsubclasses for name, sub in subs.items()
}
return {**subclasses, ** subsubclasses}
好处:易于解释这是如何工作的。 缺点:我们可能最终得到两个具有相同名称的图层。
方法3:使用类装饰器最后一个方法是我最喜欢的,因为它不会隐藏元类中的任何实现细节,并且仍设法阻止具有相同名称的多个类。
这里的layers模块有一个名为layers
的全局变量和一个名为register_layer
的装饰器,它只是将装饰的类添加到layers
dict中。见下面的代码。
layers = {}
def register_layer(cls):
if cls.__name__ in layers:
raise ValueError('Cannot have two layers with the same name')
layers[cls.__name__] = cls
return cls
好处:没有元类,也没有两个同名的图层。 缺点:需要一个全局变量,这通常是不受欢迎的。
所以,我的问题是,哪种方法更可取?更重要的是,为什么?
像你一样,我喜欢类装饰器方法,因为它更具可读性。
您可以通过使类装饰器本身成为类来避免使用全局变量,并使layers
成为类变量。您还可以通过将目标类的名称与其模块名称连接来避免可能的名称冲突:
class register_layer:
layers = {}
def __new__(cls, target):
cls.layers['.'.join((target.__module__, target.__name__))] = target
return target
实际上 - 这就是metaclases设计的东西。从上面提到的选项中可以看出,它是更简单,更直接的设计。
他们有时因为两件事而“不赞成”:(1)人们当时并不理解,也不关心理解; (2)当人们实际上不需要时,人们会滥用; (3)它们很难组合 - 所以如果你的任何类与一个具有不同元类的mixn(比如abc.ABC)一起使用,你还需要生成一个组合元类。
__init_subclass__
现在,从Python 3.6开始,有一个新功能可以覆盖你的用例,而不需要元类:class __init_subclass__
method:当创建它的子类时,它被称为基类的类方法。
你需要的只是在你的__init_subclass__
类上编写一个合适的BaseLayer
方法,并获得你在元类中实现的所有好处,没有任何缺点