Управление порядком аргументов в init производного класса, определенного с помощью attrs - PullRequest
0 голосов
/ 20 сентября 2018

В attrs порядок аргументов сгенерированного метода init определяется порядком определения атрибута в классе + MRO (стандартный способ определения общего порядка на основе множественных отношений наследования).Это не очень хорошо для моего варианта использования, но, похоже, нет никакой гибкости.Вот пример использования:

Я использую attrs для определения некоторых классов, моделирующих графические примитивы.Эти примитивы связаны с тем, что им всем нужны данные для работы и создания графики с заданными высотой и шириной, которые имеют значение по умолчанию.Таким образом, на верхнем уровне есть класс

@attr.s
class BaseGraphics:
    data = attr.ib()
    height = attr.ib(default=300)
    width = attr.ib(default=400)

. Производные от этого есть три класса, UnivariateGraphics, BivariateGraphics и MultivariateGraphics, которые используют один, два или более столбцов в data соответственно.Позвольте мне показать один из них:

@attr.s
class BivariateGraphics(BaseGraphics):
    x = attr.ib()
    y = attr.ib()

Одномерный регистр имеет только x, а многомерный регистр - как один columns атрибут.Это терпит неудачу, потому что в MRO x и y идут после height и width, но x и y являются обязательными, тогда как height и width не обязательны.Точная ошибка:

ValueError: No mandatory attributes allowed after an attribute with 
a default value or factory.  Attribute in question: 
Attribute(name='x', default=NOTHING, validator=None, repr=True, 
cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, 
converter=None, kw_only=False)

. Я могу установить значение по умолчанию для x и y, как в первом и втором столбце, но все равно порядок будет неправильным.Например, если вы хотите написать что-то вроде

BivariateGraphics(iris_data, "petalWidth", "sepalWidth")

, второй и третий аргументы будут интерпретироваться как height и width, а не x и y.Я могу предотвратить эту ошибку, создав все атрибуты, кроме data только для ключевых слов, но я не могу поддерживать этот синтаксис.Из прочтения нескольких связанных вопросов, например, № 38, кажется, что это рекомендуемый подход.Близко, но без сигары.

Другим обходным решением будет добавление height и width к каждому производному классу независимо.Это будет нарушением принципа СУХОЙ и не сможет выразить и усилить эту общность между классами.Есть более трех классов, и это будет довольно неприятно.

Это не просто "академический" вопрос.Я использую attrs в пакете, autosig, чтобы помочь определить API согласованным образом.Это, в свою очередь, используется в пакете статистической графики altair_recipes, где на самом деле возникает описанная выше ситуация (ну, в следующем выпуске, когда мне нужно добавить height и width ко всем графическим примитивам).

Я мог бы подать проблему разработчикам, но поскольку главный разработчик в шутку (?) Угрожал людям, которые занимаются электрошоком подкласса, я чувствую, что это будет бесполезно.Я был бы заинтересован в решении, не основанном на наследовании, которое не требует нарушений СУХОЙ или сковороды.Спасибо

Ответы [ 2 ]

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

В подобных случаях, когда вы хотите иметь возможность полагаться на стабильные API (особенно при создании подклассов, когда порядок не всегда понятен с первого взгляда), мы настоятельно рекомендуем использовать фабрики методов класса (и это тожесклонны использовать в моем собственном коде).Всякий раз, когда вы начинаете что-то менять в своих классах, все может запутаться, особенно если вам приходится перемещаться по нескольким иерархиям.Поэтому лучше создавать API на ваших собственных условиях и использовать атрибуты только для слесарного дела.

, но так как главный разработчик в шутку (?) Угрожал людям, которые подклассом электрошока

Хотя у меня довольно сильная позиция в отношении подклассов, у меня есть проблемы с представлением о том, что я могу угрожать электрошоком никому, кроме меня, если только это не было подшучиванием под влиянием друзей-единомышленников.Если у меня действительно была такая ошибка, я прошу прощения.

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

Я решил не использовать наследование и заменить его комбинатором, который действует до генерации классов.Для этого вместо синтаксиса класса я использовал attr.make_class, который принимает атрибуты как OrderdDict и соблюдает порядок.Следовательно, вышеупомянутый комбинатор - это просто способ объединить OrderedDicts с некоторым соглашением для определения окончательного порядка.Не уверен, что это проходит обходной путь для достижения статуса ответа, но это заставило меня двигаться.

...