Python 3.x 注册所有子类的最佳方法
我目前正在开发一个软件,其中我的类instamce是由字典生成的。这些字典构成文件的方式如下:Python 3.x 注册所有子类的最佳方法,python-3.x,class,subclass,decorator,metaclass,Python 3.x,Class,Subclass,Decorator,Metaclass,我目前正在开发一个软件,其中我的类instamce是由字典生成的。这些字典构成文件的方式如下: 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": "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
类)。我已经想到了一些方法来做到这一点,我想我会问哪种方法最好,为什么,因为我没有太多的经验开发软件(完成理学硕士学位)
方法1:元类
我想到的第一种方法是让一个元类在dict中注册BaseLayer
的每个子类,并对这个dict进行简单的查找,而不是使用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:遍历
\uuuuu子类
树
我想到的第二种方法是使用BaseLayer
的\uuuuu subclass\uuuu
函数来获取所有子类的列表,然后创建一个dict,其中Layer.\uuuu name\uuuu
作为键,而Layer
作为值。请参见下面的示例代码:
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\u 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
事实上,这就是Metaclas设计的目的。从上面提到的选项中可以看出,它是更简单、更直接的设计 他们有时会因为两件事而“皱眉”:(1)当时人们不理解,也不关心理解;(2) 人们在实际不需要的时候滥用;(3) 它们很难组合——因此,如果您的任何类要与具有不同元类(比如abc.abc)的mixn一起使用,您还必须生成一个组合元类 方法4:
\uuuu init\u子类
这就是说,Python 3.6中有一个新特性可以覆盖您的用例,而不需要元类:
当创建基类的子类时,它作为基类上的classmethod被调用
你只需要在你的
BaseLayer
类上编写一个合适的\uuuuu init\u subclass\uuuuu
方法,并从元类的实现中获得所有好处,没有任何缺点我的问题是,要得到一个层,我需要写层。注册层。层[layer\u dict['layer\u type']
每当我创建一个新层时,我并不特别喜欢这种方法。但是,我可能会创建一个带有我使用的调用函数的类。我想你可能在这里有一个输入错误,意思是键入注册层。层[layer\u dict['layer\u type']]
而不是层…
。我没有使用\u调用
方法,因为我认为访问寄存器(register)层
非常简单。但是如果你喜欢getter函数,那么你肯定可以用\u调用(代码)方法代替我的\u新(代码)方法。