Статистика использования. Отключена, пока вы её не разрешите.
Реклама и измерение рекламы. Отключены, пока вы их не разрешите.
О САЙТЕ
Все открытые вкладки сайта
Python · Синтаксис · Промежуточный
dataclasses
10 задач
Научитесь использовать датаклассы Python для определения чистых контейнеров данных без шаблонного кода с автоматической генерацией __init__, __repr__ и __eq__.
Краткое введение в тему и пояснения перед упражнениями (упражнения ниже):
До датаклассов создание простого контейнера данных в Python требовало ручного написания `__init__`, `__repr__` и `__eq__` — много повторяющегося кода. Декоратор `@dataclass` (Python 3.7+) генерирует всё это автоматически.
```python
from dataclasses import dataclass, field
```
## Проблема без датаклассов
```python
# Старый способ: много шаблонного кода
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y
```
## Решение с dataclass
```python
@dataclass
class Point:
x: float
y: float
p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)
print(p1) # Point(x=1.0, y=2.0) — __repr__ авто-генерируется
print(p1 == p2) # True — __eq__ авто-генерируется
print(p1.x) # 1.0
```
Декоратор генерирует `__init__`, `__repr__` и `__eq__` на основе аннотаций класса.
## Значения по умолчанию
```python
@dataclass
class Config:
host: str = "localhost"
port: int = 8080
debug: bool = False
c = Config() # все по умолчанию
print(c) # Config(host='localhost', port=8080, debug=False)
c2 = Config(host="0.0.0.0", port=443)
print(c2) # Config(host='0.0.0.0', port=443, debug=False)
```
**Правило**: поля со значениями по умолчанию должны идти после полей без них (то же правило, что и для параметров функции).
## field() для изменяемых значений по умолчанию
Нельзя использовать `default=[]` напрямую — Python совместно использовал бы тот же список для всех экземпляров. Вместо этого используйте `field(default_factory=list)`:
```python
@dataclass
class Student:
name: str
grades: list = field(default_factory=list) # каждый экземпляр получает свой список
active: bool = True
s1 = Student("Alice")
s2 = Student("Bob")
s1.grades.append(95)
print(s1.grades) # [95]
print(s2.grades) # [] — независимый!
```
## __post_init__: вычисляемые поля и валидация
```python
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False) # исключено из __init__
def __post_init__(self):
if self.width <= 0 or self.height <= 0:
raise ValueError("Ширина и высота должны быть положительными")
self.area = self.width * self.height
r = Rectangle(4.0, 5.0)
print(r) # Rectangle(width=4.0, height=5.0, area=20.0)
print(r.area) # 20.0
```
`__post_init__` выполняется автоматически после `__init__`.
## Параметры @dataclass
```python
@dataclass(
init=True, # генерировать __init__
repr=True, # генерировать __repr__
eq=True, # генерировать __eq__ и __ne__
order=False, # генерировать __lt__, __le__, __gt__, __ge__
unsafe_hash=False, # генерировать __hash__ даже если eq=True
frozen=False, # сделать экземпляры неизменяемыми
slots=False, # использовать __slots__ (Python 3.10+)
kw_only=False, # все поля keyword-only в __init__ (Python 3.10+)
)
class MyClass:
...
```
## Параметры field()
```python
from dataclasses import field
@dataclass
class Example:
name: str # обязательное поле
score: float = 0.0 # простое значение по умолчанию
tags: list = field(default_factory=list) # изменяемое значение по умолчанию
_id: int = field(init=False, repr=False) # исключено из init и repr
label: str = field(default="n/a", compare=False) # исключено из eq
```
| Параметр | По умолч. | Значение |
|---|---|---|
| `default` | MISSING | Значение по умолчанию |
| `default_factory` | MISSING | Callable возвращающий значение по умолч. |
| `init` | True | Включать в `__init__` |
| `repr` | True | Включать в `__repr__` |
| `hash` | None | Включать в `__hash__` |
| `compare` | True | Включать в `__eq__` и упорядочение |
| `metadata` | None | Произвольный словарь метаданных |
## Вспомогательные функции
| Функция | Что делает |
|---|---|
| `asdict(obj)` | Конвертировать во вложенный словарь |
| `astuple(obj)` | Конвертировать во вложенный кортеж |
| `fields(class_or_instance)` | Вернуть кортеж объектов Field |
| `replace(obj, **changes)` | Вернуть новый экземпляр с некоторыми изменениями |
## dataclass vs обычный класс vs namedtuple
| Функция | обычный класс | namedtuple | dataclass |
|---|---|---|---|
| Авто `__init__` | Нет | Да | Да |
| Авто `__repr__` | Нет | Да | Да |
| Авто `__eq__` | Нет | Да | Да |
| Мутабельный | Да | Нет | Да (по умолч.) |
| Хешируемый | Нет | Да | С frozen=True |
| Упорядочение | Вручную | Да | С order=True |
| Методы | Да | Ограничено | Да |
Создайте датакласс `Book` с полями: `title: str`, `author: str`, `year: int`, `rating: float = 0.0`. Затем создайте два экземпляра: `Book("1984", "Orwell", 1949, 9.5)` и `Book("Dune", "Herbert", 1965)`. Выведите оба и проверьте что `==` работает (создайте два одинаковых объекта Book и проверьте что они равны).
Создайте датакласс `Team` с полями `name: str` и `members: list`, где `members` по умолчанию — пустой список. Покажите что каждый экземпляр получает свой независимый список: создайте две команды, добавьте участника в первую и проверьте что вторая остаётся пустой. Используйте `field(default_factory=list)`.
from dataclasses import dataclass, field
@dataclass
class Team:
name: str
members: list = ??? # исправьте это
t1 = Team("Alpha")
t2 = Team("Beta")
t1.members.append("Alice")
print(t1.members) # ["Alice"]
print(t2.members) # [] <- должен быть пустым
Решение
from dataclasses import dataclass, field
@dataclass
class Team:
name: str
members: list = field(default_factory=list)
t1 = Team("Alpha")
t2 = Team("Beta")
t1.members.append("Alice")
print(t1.members) # ["Alice"]
print(t2.members) # []
Создайте датакласс `Temperature` с одним полем `celsius: float`. В `__post_init__` проверьте что температура выше абсолютного нуля (-273.15°C) и бросайте `ValueError` с сообщением `"Temperature below absolute zero"` если нет. Протестируйте оба случая: валидный и невалидный.
from dataclasses import dataclass
@dataclass
class Temperature:
celsius: float
def __post_init__(self):
pass # валидация здесь
print(Temperature(100))
print(Temperature(-273))
Temperature(-300) # raises ValueError
Решение
from dataclasses import dataclass
@dataclass
class Temperature:
celsius: float
def __post_init__(self):
if self.celsius < -273.15:
raise ValueError("Temperature below absolute zero")
print(Temperature(100))
print(Temperature(-273))
try:
Temperature(-300)
except ValueError as e:
print(e)
Создайте датакласс `Circle` с полем `radius: float` и вычисляемым полем `area: float`, которое НЕ является частью `__init__` (используйте `field(init=False)`). Вычислите `area` в `__post_init__` как `math.pi * radius ** 2`. Выведите Circle и покажите что `area` включено в repr.
from dataclasses import dataclass, field
import math
@dataclass
class Circle:
radius: float
area: float = field(init=False)
def __post_init__(self):
pass # вычислите area здесь
c = Circle(5)
print(c)
print(c.area)
Решение
from dataclasses import dataclass, field
import math
@dataclass
class Circle:
radius: float
area: float = field(init=False)
def __post_init__(self):
self.area = math.pi * self.radius ** 2
c = Circle(5)
print(c)
print(c.area)
Создайте замороженный датакласс `Coordinate` с полями `lat: float` и `lon: float`. Продемонстрируйте что:
1. Нельзя изменить экземпляр `Coordinate` (покажите ошибку)
2. Экземпляры `Coordinate` можно использовать как ключи словаря — создайте словарь отображающий координаты на названия мест.
from dataclasses import dataclass
@dataclass(frozen=???)
class Coordinate:
lat: float
lon: float
c = Coordinate(50.45, 30.52)
try:
c.lat = 0
except Exception as e:
print(e)
locations = {c: "Kyiv"}
print(locations[Coordinate(50.45, 30.52)]) # Kyiv
Решение
from dataclasses import dataclass
@dataclass(frozen=True)
class Coordinate:
lat: float
lon: float
c = Coordinate(50.45, 30.52)
try:
c.lat = 0
except Exception as e:
print(type(e).__name__, e)
locations = {c: "Kyiv"}
print(locations[Coordinate(50.45, 30.52)]) # Kyiv
Создайте датакласс `Student` с `order=True` и полями: `grade: float`, `name: str`. Создайте список из 5 студентов с разными оценками и именами, отсортируйте его (первичная сортировка по `grade`) и выведите результат. Обратите внимание: упорядочение сравнивает поля в порядке объявления — поставьте `grade` первым.
from dataclasses import dataclass
@dataclass(order=???)
class Student:
grade: float
name: str
students = [
Student(3.5, "Alice"),
Student(4.0, "Bob"),
Student(3.5, "Charlie"),
Student(2.8, "Dave"),
Student(4.0, "Eve"),
]
print(sorted(students))
Решение
from dataclasses import dataclass
@dataclass(order=True)
class Student:
grade: float
name: str
students = [
Student(3.5, "Alice"),
Student(4.0, "Bob"),
Student(3.5, "Charlie"),
Student(2.8, "Dave"),
Student(4.0, "Eve"),
]
for s in sorted(students):
print(s)
Создайте два датакласса: `Address` (поля: `street: str`, `city: str`, `country: str = "Ukraine"`) и `Person` (поля: `name: str`, `age: int`, `address: Address`). Создайте экземпляр `Person` с вложенным `Address`, выведите его и получите доступ к вложенному городу. Затем используйте `asdict()` для конвертации Person в словарь.
from dataclasses import dataclass, asdict
@dataclass
class Address:
street: str
city: str
country: str = "Ukraine"
@dataclass
class Person:
name: str
age: int
address: Address
p = Person("Alice", 30, Address("Main St", "Kyiv"))
print(p)
print(p.address.city)
print(asdict(p))
Решение
from dataclasses import dataclass, asdict
@dataclass
class Address:
street: str
city: str
country: str = "Ukraine"
@dataclass
class Person:
name: str
age: int
address: Address
p = Person("Alice", 30, Address("Main St", "Kyiv"))
print(p)
print(p.address.city) # Kyiv
print(asdict(p))
Создайте замороженный датакласс `Config` с полями `host: str = "localhost"`, `port: int = 8080`, `debug: bool = False`. Используйте `dataclasses.replace()` для создания production-конфигурации из development-конфигурации путём изменения `host` и `debug`. Покажите что оригинал не изменился, а новый экземпляр содержит обновлённые значения.
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Config:
host: str = "localhost"
port: int = 8080
debug: bool = False
dev = Config(debug=True)
prod = replace(dev, ???) # измените host и debug
print(dev)
print(prod)
print(dev is prod) # False
Решение
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Config:
host: str = "localhost"
port: int = 8080
debug: bool = False
dev = Config(debug=True)
prod = replace(dev, host="prod.example.com", debug=False)
print(dev) # Config(host="localhost", port=8080, debug=True)
print(prod) # Config(host="prod.example.com", port=8080, debug=False)
print(dev is prod) # False
Создайте датакласс `Product` с полями `name: str`, `price: float`, `in_stock: bool = True` и `tags: list = field(default_factory=list)`. Создайте 3 экземпляра продуктов, конвертируйте каждый в словарь с помощью `asdict()`, затем сериализуйте список словарей в строку JSON с помощью `json.dumps()`. Выведите результат.
from dataclasses import dataclass, field, asdict
import json
@dataclass
class Product:
name: str
price: float
in_stock: bool = True
tags: list = field(default_factory=list)
products = [
Product("Apple", 0.99, tags=["fruit", "fresh"]),
Product("Laptop", 999.0, tags=["electronics"]),
Product("Pen", 1.5, in_stock=False),
]
# Конвертируйте в JSON и выведите
Решение
from dataclasses import dataclass, field, asdict
import json
@dataclass
class Product:
name: str
price: float
in_stock: bool = True
tags: list = field(default_factory=list)
products = [
Product("Apple", 0.99, tags=["fruit", "fresh"]),
Product("Laptop", 999.0, tags=["electronics"]),
Product("Pen", 1.5, in_stock=False),
]
data = [asdict(p) for p in products]
print(json.dumps(data, indent=2))
Разработайте небольшую систему инвентаря с датаклассами:
- `Category` (frozen, поля: `name: str`, `tax_rate: float = 0.0`)
- `Item` (поля: `name: str`, `price: float`, `quantity: int`, `category: Category`)
- `Inventory` (поле: `items: list = field(default_factory=list)`, метод `add_item(item)`, метод `total_value()` возвращающий сумму price*quantity для всех товаров)
Создайте несколько товаров и выведите общую стоимость инвентаря.
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Category:
name: str
tax_rate: float = 0.0
@dataclass
class Item:
name: str
price: float
quantity: int
category: Category
@dataclass
class Inventory:
items: list = field(default_factory=list)
def add_item(self, item):
pass
def total_value(self):
pass
inv = Inventory()
food = Category("Food", 0.05)
tech = Category("Tech", 0.2)
inv.add_item(Item("Apple", 1.0, 100, food))
inv.add_item(Item("Laptop", 800.0, 5, tech))
print(inv.total_value()) # 4100.0
Решение
from dataclasses import dataclass, field
@dataclass(frozen=True)
class Category:
name: str
tax_rate: float = 0.0
@dataclass
class Item:
name: str
price: float
quantity: int
category: Category
@dataclass
class Inventory:
items: list = field(default_factory=list)
def add_item(self, item):
self.items.append(item)
def total_value(self):
return sum(item.price * item.quantity for item in self.items)
inv = Inventory()
food = Category("Food", 0.05)
tech = Category("Tech", 0.2)
inv.add_item(Item("Apple", 1.0, 100, food))
inv.add_item(Item("Laptop", 800.0, 5, tech))
print(inv.total_value()) # 4100.0
No split tab
Настройки cookies
Мы используем необходимые cookies для работы сайта. С вашего разрешения мы также можем сохранять настройки сайта и использовать аналитические и рекламные cookies, чтобы понимать использование сайта и поддерживать развитие проекта.
* Вы всегда можете изменить свой выбор в настройках сайта.
Выберите категории cookies
Настройки аналитики
Можно отключить аналитику использования платформы. Также можно отправить в Google Analytics запрос на удаление данных об использовании этого сайта, связанных с этим браузером.
Учебный workspace
Учитесь, читая, запуская код и решая задачи.
Практикуйте программирование с пояснениями тем, упражнениями, инструментами browser IDE, проверкой regex и тренировкой печати кода в одном workspace.
Открывайте инструменты во вкладках.Упражнения, IDE-инструменты и тренажеры остаются доступными как вкладки сайта.
Переключайтесь без потери контекста.Переходите между пояснениями, кодом и утилитами, сохраняя свое место.
Используйте sidebar как карту.Левые панели содержат навигацию, настройки, файлы, libraries и управление инструментами.
PythonJavaScriptSQLite
Одна IDE, три практичных режима
Python в браузере.Запускайте небольшие скрипты, пробуйте библиотеки и тренируйте API-запросы без установки.
JavaScript для быстрых экспериментов.Проверяйте код для браузера и сравнивайте идеи рядом с учебными материалами.
SQLite для практики с данными.Открывайте обозреватель базы данных, изучайте таблицы, пишите запросы и учитесь SQL локально.
ТемаIDE
Работайте рядом в split tabs
Держите инструкции перед глазами.Откройте упражнение или справочную страницу рядом с IDE, вместо постоянных переключений.
Сравнивайте инструменты во время обучения.Размещайте проверки regex, пояснения и эксперименты с кодом рядом, когда это нужно для задачи.
Закройте split, когда закончите.Workspace вернётся к одной сфокусированной вкладке, а открытые вкладки сайта останутся доступны.
Тренажер слепой печати кода
Или просто текста
Тренажер рассчитан на физическую клавиатуру.Откройте этот раздел на ноутбуке или компьютере с широким экраном. На телефоне тренировка слепой печати не будет корректной.
Скорость: 0 зн/мин
0 слов/мин
Лучшая скорость (60с): 0 зн/мин
0 слов/мин
Ошибки: 0
Общее время: 0.0 с
Для активации режима слепого набора не подсматривайте на физическую клавиатуру.