Сравните Dictionary, NamedTuple, класс Data и модель Pydantic в Python
Python - это объектно-ориентированный язык программирования, и существуют различные способы представления данных в виде объектов. Простые объекты могут быть представлены словарями с парами ключ-значение. А более сложные могут быть представлены некоторыми специальными классами данных, включая namedtuple, data class и Pedantic models.
В этом посте мы познакомим вас с этими типами данных на простых примерах. Затем мы можем выбрать тот, который наиболее подходит для решения конкретных задач.
Словари
Первый тип данных, который нужно представить, — это dict
, который представляет собой встроенный контейнерный тип данных общего назначения в Python. Словари называются объектами или хэш-картами в других языках программирования и используются для хранения пар ключ-значение. Например, в этом dict
хранится запись об ученике с данными об имени, возрасте и баллах:
student = {"name": "John", "age": 15, "scores": ["A", "B", "B", "C", "C", "C"]}
dict
— это просто базовый тип данных, который мы собираемся представить, и который большинство из нас уже должно знать. Однако позже будут представлены более интересные и полезные, которые могут быть новыми для многих читателей.
Существует специальный подкласс dict
, называемый defaultdict
, который обычно используется для применения фабричной функции для предоставления отсутствующих значений. Фабричная функция, такая как list
или int
, обычно используется для предоставления начального значения, когда значение ключа еще не присвоено. Например, list
сгенерирует пустой список, а int
вернет 0.
Давайте рассмотрим простой пример для подсчета вхождений каждой оценки:
from collections import defaultdict
scores = ["A", "B", "B", "C", "C", "C"]
score_counts = defaultdict(int)
for score in scores:
# The initial count for each score will be set to be 0.
score_counts[score] += 1
print(score_counts)
# defaultdict(int, {'A': 1, 'B': 2, 'C': 3})
defaultdict
полезен, когда мы хотим установить начальное значение для ключа в dict
только тогда, когда это необходимо, и не хотим заранее явно инициализировать все ключи dict
некоторыми начальными значениями.
NamedTuple
namedtuple
— это подкласс кортежа, который можно использовать для создания объектов, похожих на кортежи, которые ведут себя как кортежи и как объекты. В частности, вы можете получить доступ к значениям как по атрибутам, так и по индексам. Однако у него также есть ограничение кортежей, заключающееся в том, что значения не могут быть изменены.
Все это можно продемонстрировать на нашем простом примере:
from collections import namedtuple
Student = namedtuple("Student", field_names=["name", "age", "scores"])
# Create an instance of the one-line namedtuple class.
student = Student(name="John", age=15, scores=["A", "B", "B", "C", "C", "C"])
# The type is a class.
type(student)
# __main__.Student
# We can access values by indexes like a tuple.
student[0] # John
# We can also access values by attributes like an object.
student.name # John
# However, we cannot access by keys like a dict.
student["name"]
# TypeError: tuple indices must be integers or slices, not str
# And we cannot change the value because it's a tuple.
student.age = 16
# AttributeError: can't set attribute
Мы можем преобразовать namedtuple
в dict
с помощью метода _asdict()
.
student._asdict()
# {'name': 'John', 'age': 15, 'scores': ['A', 'B', 'B', 'C', 'C', 'C']}
Обратите внимание, что префикс подчеркивания используется для предотвращения конфликтов с именами полей. Это не означает, что метод является приватным методом класса namedtuple
, который не следует использовать.
Собственный data class
В Python есть специальный декоратор dataclasses.dataclass
, который может пометить обычный класс как так называемый класс данных. Классы данных — это обычные классы Python с некоторыми специальными методами, такими как __init__()
и __repr__()
, автоматически добавляемыми декоратором класса данных. В них удобно хранить и сравнивать данные.
Давайте используем класс данных для наших данных о студентах выше:
from dataclasses import dataclass
@dataclass
class Student:
name: str
age: int
scores: list[str]
# Normal usage:
student = Student(name="John", age=15, scores=["A", "B", "B", "C", "C", "C"])
print(student)
# Generated by the __repr__ magical method added by the dataclass decorator.
# Student(name="John", age=15, scores=["A", "B", "B", "C", "C", "C"])
Однако классы данных просто хранят данные как есть и не выполняют никакого преобразования или проверки данных:
# The data types are not converted.
student = Student(name="John", age="15", scores=[60, 70])
print(student)
# Student(name='John', age='15', scores=[60, 70])
# And no error will be raised when the data types are not coercible.
student = Student(name="John", age="young", scores="A")
print(student)
# Student(name='John', age='young', scores='A')
Ограничения собственных классов данных могут быть решены с помощью классов данных Pydantic.
Pydantic класс данных
pydantic.dataclasses.dataclass
— это замена dataclasses.dataclass
с преобразованием и проверкой данных. Давайте посмотрим, как это работает на нашем простом примере:
from pydantic.dataclasses import dataclass
@dataclass
class Student:
name: str
age: int
scores: list[str]
# Normal usage:
student = Student(name="John", age=15, scores=["A", "B", "B", "C", "C", "C"])
# Coercible data types are converted automatically.
student = Student(name="John", age="15", scores=[60, 70])
print(student)
# Student(name="John", age=15, scores=["60", "70"])
# An error will be raised if the data is not coercible.
student = Student(name="John", age="young", scores="A")
# ValidationError: 2 validation errors for Student
# age
# value is not a valid integer (type=type_error.integer)
# scores
# value is not a valid list (type=type_error.list)
Как показано выше, Pydantic dataclass
создается так же, как и собственный dataclass
, с дополнительными преимуществами преобразования и проверки данных.
Pydantic модель
Наш любимый тип данных — BaseModel
от Pydantic, который обладает всеми преимуществами dataclass
, а также тем преимуществом, что его легче наследовать и настраивать.
from pydantic import BaseModel
class Student(BaseModel):
name: str
age: int
scores: list[str]
# Normal usage:
student = Student(name="John", age=15, scores=["A", "B", "B", "C", "C", "C"])
# coercible data types are converted automatically.
student = Student(name="John", age="15", scores=[60, 70])
print(student)
# name='John' age=15 scores=['60', '70']
# An error will be raised if the data is not coercible.
student = Student(name="John", age="young", scores="A")
# ValidationError: 2 validation errors for Student
# age
# value is not a valid integer (type=type_error.integer)
# scores
# value is not a valid list (type=type_error.list)
Многие из нас могут спросить, поскольку BaseModel
лучше, чем dataclass
, почему нам все еще нужно использовать dataclass
. Если у вас такая же путаница, то эта проблема GitHub прояснит ее для вас. По сути, основная причина использования dataclasses
состоит в том, чтобы упростить работу с унаследованным кодом, не изменяя слишком много кода.
Мы только что коснулись моделей Pydantic, и нам предстоит еще многое изучить. Pydantic — действительно мощный инструмент, который можно использовать для очень гибкой проверки ваших данных с помощью настраиваемых валидаторов. Во многих случаях он может даже заменить схему JSON для проверки данных.
Помимо проверки данных, Pydantic можно использовать для чтения переменных среды, что является действительно классной и удобной функцией.
Вывод
В этом посте мы рассмотрели некоторые распространенные структуры данных, включая модели dict
, namedtuple
, dataclass
и Pydantic. Для очень простых пар ключ-значение лучше всего подходит dict
, потому что для него не нужно создавать класс. Если нам нужно получить доступ к некоторым данным как по индексу, так и по атрибутам, тогда namedtuple
— лучший выбор. А если нам нужно преобразование и проверка данных, то лучше всего подойдет Pydanic BaseModel. dataclass
используется не так часто, и большинство его функций могут быть реализованы в моделях Pydantic.