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, возможно, захочет показать какой-нибудь взлом метакласса или фабрику классов, чтобы облегчить мою жизнь?
Ответы
Ответ 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'
С точки зрения эффективного использования ресурсов этот подход, конечно, субоптимален.
Ответ 5
если вы используете Python 3.7, вы можете использовать классы данных; Классы данных можно рассматривать как "изменяемые именованные кортежи со значениями по умолчанию"
https://docs.python.org/3/library/dataclasses.html https://www.python.org/dev/peps/pep-0557/