Как сделать класс attrs с распаковкой кортежей и dict, но без дополнительных методов

Я только начал использовать attrs модуль для Python, который довольно гладкий (или аналогично мы могли бы использовать Python 3.7 DataClasses). Обычный шаблон использования, который у меня есть, заключается в том, чтобы класс был контейнером для значений параметров. Мне нравится маркировка, когда я назначаю параметры, и более чистая ссылка на значения атрибутов в стиле атрибутов, но мне также нравится иметь несколько функций, которые хороши при хранении значений в чем-то вроде упорядоченного dict:

  1. * распаковка как tuple или list вводить в аргументы функции
  2. ** распаковка, когда необходимо или желательно передать ключевое слово.

Я могу добиться всего этого, добавив три метода в класс

@attr.s
class DataParameters:
    A: float = attr.ib()
    alpha: float = attr.ib()
    c: float = attr.ib()
    k: float = attr.ib()
    M_s: float = attr.ib()

    def keys(self):
        return 'A', 'alpha', 'c', 'k', 'M_s'

    def __getitem__(self, key):
        return getattr(self, key)

    def __iter__(self):
        return (getattr(self, x) for x in self.keys())

Тогда я могу использовать классы так:

params = DataParameters(1, 2, 3, 4, 5)
result1 = function1(100, 200, *params, 300)
result2 = function2(x=1, y=2, **params)

Мотивация здесь заключается в том, что классы данных обеспечивают удобство и ясность. Однако есть причины, по которым я не знаю, какой модуль я пишу для использования класса данных. Желательно, чтобы вызовы функций принимали простые аргументы, а не сложные классы данных.

Вышеприведенный код в порядке, но мне интересно, если я что-то упустил, что позволило бы мне вообще пропустить написание функций, поскольку шаблон довольно ясен. Атрибуты добавляются в том порядке, в котором я хотел бы их распаковывать, и могут быть прочитаны как пары ключ-значение на основе имени атрибута для аргументов ключевого слова.

Может быть, это что-то вроде:

@addtupleanddictunpacking
@attr.s
class DataParameters:
    A: float = attr.ib()
    alpha: float = attr.ib()
    c: float = attr.ib()
    k: float = attr.ib()
    M_s: float = attr.ib()

но я не уверен, есть ли что-то в attrs Сам, который делает это, что я не нашел. Кроме того, я не уверен, как бы сохранить порядок атрибутов при их добавлении и перевести это в метод ключей как таковой.

2 ответа

Решение

Это не интегрировано непосредственно в класс, но asdict а также astuple вспомогательные функции предназначены для выполнения такого рода преобразования.

params = DataParameters(1, 2, 3, 4, 5)
result1 = function1(100, 200, *attr.astuple(params), 300)
result2 = function2(x=1, y=2, **attr.asdict(params))

Они не интегрированы в сам класс, потому что это заставит класс вести себя как последовательность или отображение повсюду, что может привести к бесшумному поведению, когда TypeError / AttributeError будет ожидаемым. С точки зрения производительности, это должно быть хорошо; распаковка конвертируется в tuple / dict в любом случае (он не может передать то, что не является tuple или же dict напрямую, так как API C ожидают, что смогут использовать API-интерфейсы конкретных типов в своих аргументах).

Если вы действительно хотите, чтобы класс действовал как последовательность или отображение, вам в основном нужно делать то, что вы сделали, хотя вы могли бы использовать вспомогательные функции для сокращения пользовательского кода и повторяющихся имен переменных, например:

@classmethod
def keys(cls):
    return attr.fields_dict(cls).keys()

def __getitem__(self, key):
    return getattr(self, key)

def __iter__(self):
    return iter(attr.astuple(self, recurse=False))  

Расширяя идеи @ShadowRanger, можно создать собственный декоратор, который включает в себя attr.s и attr.ib, для более краткого решения, которое в основном добавляет дополнительную обработку.

import attr
field = attr.ib  # alias because I like it

def parameterset(cls):
    cls = attr.s(cls)

    # we can use a local variable to store the keys in a tuple
    # for a faster keys() method
    _keys = tuple(attr.fields_dict(cls).keys())
    @classmethod
    def keys(cls):
    #     return attr.fields_dict(cls).keys()
        return (key for key in _keys)

    def __getitem__(self, key):
        return getattr(self, key)

    def __iter__(self):
        return iter(attr.astuple(self, recurse=False))

    cls.keys = keys
    cls.__getitem__ = __getitem__
    cls.__iter__ = __iter__

    return cls

@parameterset
class DataParam:
    a: float = field()
    b: float = field()

dat = DataParam(a=1, b=2)
print(dat)
print(tuple(dat))
print(dict(**dat))

дает вывод

DataParam(a=1, b=2)
(1, 2)
{'a': 1, 'b': 2}
Другие вопросы по тегам