Рубрики
Без рубрики

TypedDict против обработки данных в Python – Epic Typing Battle!

Почему мы мигрировали из TypedDict в DataTypes в нашем последнем проекте. Tagged с питоном, типами, новичками.

Недавно мы мигрировали наш Мишкан Продукт от Python TypedDict к DataClasses Анкет Эта статья объясняет почему. Мы начнем с общего обзора типов в Python. Затем мы проведем разницу между двумя стратегиями печати с примерами. В конце концов, у вас должна быть информация, необходимая для выбора той, которая лучше всего подходит для вашего проекта Python.

Оглавление

  • Типы в Python
  • Классы и обработки данных
    • Настройка нашего примера
    • Добавление определений типа
    • Работа с DataClasses
  • TypedDict
    • Краткое представление о наборе утки
    • Работа с TypedDict
  • Мигрируя из TypedDict к DataClasses
    • Соответствующий
    • Проверка
  • Вывод

Типы в Python

PEP 484 , в соавторстве с создателем Python Гвидо Ван Россум, дает обоснование для типов в Python. Он предлагает:

Стандартный синтаксис для аннотаций типа, открытие кода Python для более легкого статического анализа и рефакторинга, потенциальная проверка типа выполнения и (Возможно, в некоторых контекстах) генерация кода с использованием информации типа.

Для меня Статический анализ является самым сильным преимуществом типов в Python.

Требуется так:

# exp.py
def exp(a, b):
 return a ** b

exp(1, "result")

Что повышает эту ошибку во время выполнения:

$ python exp.py
  File "./exp.py", line 4, in 
    exp(1, "result")
  File "./exp.py", line 2, in exp
    return a ** b
TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'str'

И позволяет вам сделать это:

# exp.py
def exp(a: int, b: int) -> int:
  return a ** b

exp(1, "result")

Который повышает эту ошибку в Время компиляции :

$ mypy exp.py # pip install mypy to install mypy
exp.py:4: error: Argument 2 to "exp" has incompatible type "str"; expected "int"
Found 1 error in 1 file (checked 1 source file)

Типы помогают нам поймать ошибки раньше и уменьшают количество модульных тестов для поддержания.

Классы и обработки данных

Python Typing также работает и для занятий. Давайте посмотрим, как статическое наборы с классами может перемещать две ошибки со времени выполнения в состав времени.

Настройка нашего примера

Следующее Area.py Файл содержит функцию, которая вычисляет область формы, используя данные, предоставленные двумя классами:

# area.py
class RangeX:
  left: float
  right: float

class RangeY:
  up: float
  down: float

def area(x, y):
  return (x.right - x.lefft) * (y.right- y.left)

x = RangeX(); x.left = 1; x.right = 4
y = RangeY(); y.down = -3; y.up = 6
print(area(x, y))

Первая ошибка во время выполнения, которую создает:

$ python area.py
Traceback (most recent call last):
  File "./area.py", line 14, in 
    print(area(x, y))
  File "./area.py", line 10, in area
    return (x.right - x.lefft) * (y.right- y.left)
AttributeError: 'RangeX' object has no attribute 'lefft'

Икес! Укушена орфографической ошибкой в область функция Давайте исправим это, изменив Леффт к слева Анкет

Мы снова бегаем и:

$ python area.py
Traceback (most recent call last):
  File "./area.py", line 14, in 
    print(area(x, y))
  File "./area.py", line 10, in area
    return (x.right - x.left) * (y.right- y.left)
AttributeError: 'RangeY' object has no attribute 'right'

О, нет! В определении область , мы использовали справа и слева для y вместо вверх и вниз Анкет Это общая ошибка копирования и вставки.

Давайте изменим область функционируйте снова, чтобы окончательная функция читалась:

def area(x, y):
  return (x.right - x.left) * (y.up - y.down)

После того, как мы снова запустили наш код, мы получаем результат 27 Анкет Это то, что мы ожидаем, чтобы быть площадь прямоугольника 9×3.

Добавление определений типа

Теперь давайте посмотрим, как Python поймал бы оба из этих ошибок с использованием типов в Время компиляции Анкет

Сначала добавляем определения типа в область Функция:

# area.py
class RangeX:
 left: float
 right: float

class RangeY:
 up: float
 down: float

def area(x: RangeX, y: RangeY) -> float:
 return (x.right - x.lefft) * (y.right - y.left)

x = RangeX(); x.left = 1; x.right = 4
y = RangeY(); y.down = -3; y.up = 6
print(area(x, y))

Тогда мы можем запустить наш Area.py Файл с использованием mypy , Статический тип проверки для Python:

$ mypy area.py
area.py:10: error: "RangeX" has no attribute "lefft"; maybe "left"?
area.py:10: error: "RangeY" has no attribute "right"
area.py:10: error: "RangeY" has no attribute "left"
Found 3 errors in 1 file (checked 1 source file)

Это замечает те же три ошибки, прежде чем мы даже запустим наш код.

Работа с DataClasses

В нашем предыдущем примере вы заметите, что назначение атрибутов, таких как X.left и x.right неуклюже. Вместо этого мы хотели бы сделать Rangex (слева) Анкет DataClass Декоратор делает это возможным. Это требует класс и турботорж его конструктором и несколькими другими полезными методами.

Давайте возьмем наше Area.py файл и используйте DataClass декоратор.

# area.py
from dataclasses import dataclass

@dataclass # <----- check this out
class RangeX:
  left: float
  right: float

@dataclass # <----- and this
class RangeY:
  up: float
  down: float

def area(x: RangeX, y: RangeY) -> float:
  return (x.right - x.left) * (y.up - y.down)

x = RangeX(left = 1, right = 4)
y = RangeY(down = -3, up = 6)

print(area(x, y))

Согласно mypy , наш файл теперь без ошибок:

$ mypy area.py
Success: no issues found in 1 source file

И это дает нам ожидаемый результат 27 :

$ python area.py
27

класс и DataClass хорошие способы представлять объекты как типы. Они страдают от нескольких ограничений, однако, что TypedDict Решает.

TypedDict

Но сначала…

Краткое введение в набор уток

В мире типов есть понятие, называемое Утка печатает Анкет Вот идея: если объект выглядит как утка и кряки, как утка, это утка.

Например, возьмите следующий JSON:

{
  "name": "Stacey O'Hara",
  "age": 42,
}

На языке с набором утки мы определили бы тип с атрибутами название и возраст Анкет Тогда, любой Объект с этими атрибутами будет соответствовать типу.

В Python классы не напечатаны утки, что приводит к следующей ситуации:

# person_vs_comet.py
from dataclasses import dataclass

@dataclass
class Person:
  name: str
  age: int

@dataclass
class Comet:
  name: str
  age: int

Person(name="Haley", age=42000) == Comet(name="Haley", age=42000) # False

Этот пример должен вернуть Ложный Анкет Но без набора утки, json или дикта Версии Комета и Человек будет таким же.

Мы можем увидеть это, когда мы проверяем наш пример с помощью удильник :

from dataclass import asdict

asdict(Person(name="Haley", age=42000)) == asdict(Comet(name="Haley", age=42000)) # True

Утка Typing помогает нам кодировать классы в другой формат, не теряя информации. То есть мы можем создать поле под названием Тип это представляет "Человек" или "Комета" Анкет

Работа с TypedDict

TypedDict Приносит набор уток в Python, позволив диктат S действовать как типы.

# person_vs_comet.py
from typing import TypedDict

class Person(TypedDict):
  name: str
  age: int

class Comet(TypedDict):
  name: str
  age: int

Person(name="Haley", age=42000) == Comet(name="Haley", age=42000) # True

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

Давайте представляем, например, что мы расширили Человек вот так:

# person.py
from dataclasses import dataclass
from typing import Optional

@dataclass
class Person:
  name: str
  age: int
  car: Optional[str] = None
  bike: Optional[str] = None
  bank: Optional[str] = None
  console: Optional[str] = None

larry = Person(name="Larry", age=25, car="Kia Spectra")
print(larry)

Если мы распечатаем Человек , мы увидим, что Нет Значения все еще присутствуют:

Person(name='Larry', age=25, car='Kia Spectra', bike=None, bank=None, console=None)

Это чувствует себя немного не так – у него много явного Нет Поля и получают многословные, добавляя больше дополнительных полей. Утка набирает это только путем добавления только существующих полей в объект.

Итак, давайте переписать наши Person.py Файл для использования TypedDict :

# person.py
from typing import TypedDict

class _Person(TypedDict, total=False):
  car: str
  bike: str
  bank: str
  console: str

class Person(_Person):
  name: str
  age: int

larry: Person = dict(name="Larry", age=25, car="Kia Spectra")
print(larry)

Теперь, когда мы печатаем наш Человек , мы видим только существующие поля:

Person(name='Larry', age=25, car='Kia Spectra')

Переход от TypedDict в DataClasses

Возможно, вы уже догадались, но, как правило, мы предпочитаем печатать уток по сравнению с классами. По этой причине мы с энтузиазмом о TypedDict Анкет Тем не менее, в Мишкан , мы мигрировали из TypedDict к DataClasses по нескольким причинам. На протяжении всей остальной части этой статьи мы объясним, почему мы сделали этот шаг.

Две причины, по которым мы мигрировали из TypedDict к DataClasses соответствуют и валидации:

  • Сопоставление означает определение класса объекта, когда есть союз нескольких классов.
  • Валидация означает убедиться, что неизвестные структуры данных, такие как JSON, будут отображаться в классе.

Соответствующий

Давайте использовать Person_vs_comet.py Пример из ранее, чтобы понять, почему класс лучше в сопоставлении в Python.

# person_vs_comet.py
from dataclasses import dataclass
from typing import Union

@dataclass
class Person:
 name: str
 age: int

@dataclass
class Comet:
 name: str
 age: int

def i_am_old(obj: Union[Person, Commet]) -> bool:
  return obj.age > 120 if isinstance(obj, Person) else obj.age > 1000000000

print(i_am_old(Person(name="Spacey", age=1000))) # True
print(i_am_old(Comet(name="Spacey", age=1000))) # False

В Python, Isinstance может различать типы союза. Это важно для большинства реальных программ, которые поддерживают несколько типов.

В Мишкан , мы работаем с типами союза все время в Openapi . Например, большинство спецификаций объектов могут быть Схема или A Ссылка к схеме. По всей нашей кодовой базе вы увидите ISinstance (r, ссылка) Чтобы провести это различие.

TypedDict не работает с Isinstance – И не зря. Под капюшоном, Изинист Посмотрите имя класса объекта Python. Это очень быстрая операция. С набором утки вам придется осмотреть весь объект, чтобы увидеть, «это утка. ” Хотя это быстро для небольших объектов, это слишком медленно для больших объектов, таких как спецификации OpenAPI. Isinstance Паттерн ускорил наш код много Анкет

Проверка

Большая часть кода получает ввод от внешнего источника, например, файл или API. В этих случаях важно убедиться, что ввод можно использовать программой. Это часто требует отображения ввода во внутренний класс. При наборе утки, после шага проверки, это требует вызова актеры Анкет

Проблема с актеры это то, что он позволяет неверно пройти код проверки. В следующем Person.py Пример, есть преднамеренная ошибка. Он спрашивает, есть ли ISinstance (D ['age'], str) хотя возраст это int Анкет актеры , потому что это так разрешительно, не поймет эту ошибку:

# person.py
from typing import cast, TypedDict, Optional

class Person(TypedDict):
  name: str
  age: Optional[int]

def to_person(d: dict) -> Person:
  if ('name' in d) and isinstance(d['name'], str) and (('age' not in d) or (( 'age' in d) and (isinstance(d['age'], str))):
    return cast(d, Person) # this will work at runtime even though it shouldn't
  raise ValueError('d is not a Person')

Однако класс будет работать только с конструктором. Так что это поймает ошибку в момент строительства:

# person.py
from typing import Optional
from dataclasses import dataclass

@dataclass
class Person:
 name: str
 age: Optional[int] = None

def to_person(d: dict) -> Person:
  if ('name' in d) and isinstance(d['name'], str) and (('age' not in d) or (( 'age' in d) and (isinstance(d['age'], str))):
   # will raise a runtime error for age when age is a str
   # because it is `int` in `Person` 
   return Person(**to_person)

 raise ValueError('d is not a Person')

Вышеупомянутое to_person вынесет ошибку, тогда как TypedDict Версия не будет. Это означает, что когда возникнет ошибка, это произойдет позже в будущем. Эти типы ошибок гораздо сложнее отладить.

Когда мы изменились с TypedDict к DataClasses в Мишкан , некоторые тесты начали терпеть неудачу. Оглядываясь на них, мы поняли, что они никогда не должны были преуспеть. Их успех был связан с использованием актеры , в то время как класс Подход всплыл на несколько ошибок.

Вывод

Пока нам нравится идея TypedDict И набрав утки, это практические ограничения в Python. Это делает его плохим выбором для большинства крупномасштабных приложений. Мы бы порекомендовали использовать TypedDict В ситуациях, когда вы уже используете DICTS Анкет В этих случаях TypedDict Можно добавить степень безопасности типа, не переписывая ваш код. Для нового проекта, я бы порекомендовал использовать DataClasses Анкет Это работает лучше с системой типа Python и приведет к более устойчивому коду.

Не согласны с нами? Существуют ли какие -либо сильные или слабые стороны любого подхода, которые нам не хватает? Оставьте нам комментарий!

Оригинал: “https://dev.to/meeshkan/typeddict-vs-dataclasses-in-python-epic-typing-battle-onb”