Подтвердить что ты не робот

Python: Быстрые и грязные типы данных (DTO)

Очень часто я обнаруживаю, что кодирую тривиальные типы данных, такие как

def Pruefer:
    def __init__(self, ident, maxNum=float('inf'), name=""):
        self.ident  = ident
        self.maxNum = maxNum
        self.name   = name

Хотя это очень полезно (ясно, что я не хочу заменять вышеприведенное анонимным 3-кортежем), оно также очень типично.

Теперь, например, когда я хочу использовать класс в dict, я должен добавить больше шаблонов, как

    def __hash__(self):
        return hash(self.ident, self.maxNum, self.name)

Я признаю, что может быть трудно распознать общую закономерность среди всех моих шаблонных классов, но тем не менее я хотел бы ответить на этот вопрос:

  • Существуют ли в Python идиомы для быстрого и грязного получения типов данных с именованными аксессорами?

  • Или, может быть, если нет, может быть, гуру Python, возможно, захочет показать какой-нибудь взлом метакласса или фабрику классов, чтобы облегчить мою жизнь?

4b9b3361

Ответ 1

>>> from collections import namedtuple
>>> Pruefer = namedtuple("Pruefer", "ident maxNum name")
>>> pr = Pruefer(1,2,3)
>>> pr.ident
1
>>> pr.maxNum
2
>>> pr.name
3
>>> hash(pr)
2528502973977326415

Чтобы предоставить значения по умолчанию, вам нужно сделать немного больше... Простое решение - написать подкласс с переопределением для метода __new__:

>>> class Pruefer(namedtuple("Pruefer", "ident maxNum name")):
...     def __new__(cls, ident, maxNum=float('inf'), name=""):
...         return super(Pruefer, cls).__new__(cls, ident, maxNum, name)
... 
>>> Pruefer(1)
Pruefer(ident=1, maxNum=inf, name='')

Ответ 2

Одна из наиболее многообещающих вещей в Python 3.6 - это переменные аннотации. Они позволяют определить namedtuple как класс следующим образом:

In [1]: from typing import NamedTuple

In [2]: class Pruefer(NamedTuple):
   ...:     ident: int
   ...:     max_num: int
   ...:     name: str
   ...:     

In [3]: Pruefer(1,4,"name")
Out[3]: Pruefer(ident=1, max_num=4, name='name')

Это то же самое, что и namedtuple, но сохраняет аннотации и позволяет проверять тип с помощью статического анализатора типов, такого как mypy.

Обновление: 15.05.2018

Теперь в Python 3.7 представлены классы данных, так что это предпочтительный способ определения DTO, также для обратной совместимости вы можете использовать библиотеку attrs.

Ответ 3

Мне нечего добавить к уже отличному ответу Алексея Качаева. Однако одна вещь, которая может быть полезна, следующая:

Pruefer.__new__.func_defaults = (1,float('inf'),"")

Это позволит вам создать функцию factory, которая возвращает новый named-tuple, который может иметь аргументы по умолчанию:

def default_named_tuple(name,args,defaults=None):
    named_tuple = collections.namedtuple(name,args)
    if defaults is not None:
        named_tuple.__new__.func_defaults = defaults
    return named_tuple

Это может показаться черной магией. Сначала это было для меня, но все это задокументировано в Data Model и обсуждено в этот пост.

В действии:

>>> default_named_tuple("Pruefer", "ident maxNum name",(1,float('inf'),''))
<class '__main__.Pruefer'>
>>> Pruefer = default_named_tuple("Pruefer", "ident maxNum name",(1,float('inf'),''))
>>> Pruefer()
Pruefer(ident=1, maxNum=inf, name='')
>>> Pruefer(3)
Pruefer(ident=3, maxNum=inf, name='')
>>> Pruefer(3,10050)
Pruefer(ident=3, maxNum=10050, name='')
>>> Pruefer(3,10050,"cowhide")
Pruefer(ident=3, maxNum=10050, name='cowhide')
>>> Pruefer(maxNum=12)
Pruefer(ident=1, maxNum=12, name='')

И только указание некоторых аргументов по умолчанию:

>>> Pruefer = default_named_tuple("Pruefer", "ident maxNum name",(float('inf'),''))
>>> Pruefer(maxNum=12)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __new__() takes at least 2 arguments (2 given)
>>> Pruefer(1,maxNum=12)
Pruefer(ident=1, maxNum=12, name='')

Обратите внимание, что как написано, возможно, безопасно передать tuple как defaults. Тем не менее, вы можете легко получить больше фантазии, гарантируя, что у вас есть разумный объект tuple внутри функции.

Ответ 4

Альтернативный подход, который может помочь вам сделать код вашей котельной плиты более универсальным, - это итерация по (местной) переменной dicts. Это позволяет помещать переменные в список и обрабатывать их в цикле. Например:

class Pruefer:
     def __init__(self, ident, maxNum=float('inf'), name=""):
         for n in "ident maxNum name".split():
             v = locals()[n]  # extract value from local variables
             setattr(self, n, v)  # set member variable

     def printMemberVars(self):
         print("Member variables are:")
         for k,v in vars(self).items():
             print("  {}: '{}'".format(k, v))


P = Pruefer("Id", 100, "John")
P.printMemberVars()

дает:

Member Variables are:
  ident: 'Id'
  maxNum: '100'
  name: 'John'

С точки зрения эффективного использования ресурсов этот подход, конечно, субоптимален.