Лучший способ зарегистрировать все подклассы - PullRequest
0 голосов
/ 24 октября 2018

В настоящее время я занимаюсь разработкой программного обеспечения, в котором у меня есть классовые инстагменты, сгенерированные из словарей.Структура этого файла словаря выглядит следующим образом:

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: Метаклассы Первый метод, о котором я подумал, состоял в том, чтобы иметь метакласс, который регистрирует каждый подкласс 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, чтобы получить список всех подклассов, а затем создать dict с Layer.__name__ в качестве ключей.и 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}

Преимущество: Легко объяснить, как это работает.
Недостаток: Мы можем получить два слоя с одинаковымиname.

Метод 3: Использование декоратора класса Последний метод - мой любимый, так как он не скрывает никаких деталей реализации в метаклассе, и все же удается предотвратить несколько классов с одним и тем жеname.

Здесь модуль layer имеет глобальную переменную с именем layers и декоратор с именем register_layer, который просто добавляет декорированные классы к диктанту layers.См. Код ниже.

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

Преимущество: Нет метаклассов и нет возможности иметь два слоя с одинаковым именем.
Недостаток: Требуется глобальная переменная, что часто осуждается.

Итак, мой вопрос, какой метод предпочтительнее?И что более важно, почему?

Ответы [ 2 ]

0 голосов
/ 01 ноября 2018

На самом деле - это то, для чего предназначены метаклазы.Как видно из вариантов, которые вы указали выше, это более простой и понятный дизайн.

Их иногда "осуждают" из-за двух вещей: (1) люди тогда не понимают и не заботятся о понимании;(2) люди злоупотребляют тогда, когда они на самом деле не нужны;(3) их трудно комбинировать - поэтому, если какой-либо из ваших классов будет использоваться со смесью, имеющей другой метакласс (скажем, abc.ABC), вы также должны создать метакласс объединения.

Метод 4: __init_subclass__

Теперь, как уже говорилось, в Python 3.6 появилась новая функция, которая может охватывать ваш сценарий использования без необходимости метаклассов: класс __init_subclass__ method : он вызывается как метод класса в base классе, когда создаются его подклассы.

Все, что вам нужно, - это написать правильный __init_subclass__ метод на вашем BaseLayer и получите все преимущества, которые вы получили бы от реализации в метаклассах, и ни одного из недостатков

0 голосов
/ 24 октября 2018

Как и вы, мне нравится подход декоратора классов, так как он более читабелен.

Вы можете избежать использования глобальной переменной, сделав сам декоратор класса классом, и вместо этого сделав layers переменную класса.Вы также можете избежать возможного конфликта имен, соединив имя целевого класса с именем модуля:

class register_layer:
    layers = {}
    def __new__(cls, target):
        cls.layers['.'.join((target.__module__, target.__name__))] = target
        return target
...