как сделать класс attrs с распаковкой tuple и dict, но без дополнительных методов - PullRequest
0 голосов
/ 29 августа 2018

Я только начал использовать модуль 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 ]

0 голосов
/ 30 августа 2018

Расширяя идеи @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}
0 голосов
/ 29 августа 2018

Он не интегрирован напрямую в класс, но вспомогательные функции 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-интерфейсы на их аргументы).

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

@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))  
...