Тема, шрифт, мова та інші налаштування інтерфейсу.
Статистика використання. Вимкнена, доки ви її не дозволите.
Реклама та вимірювання реклами. Вимкнені, доки ви їх не дозволите.
ПРО САЙТ
Усі відкриті вкладки сайту
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 у __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 | Виклик що повертає значення за замовч. |
| `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 с
Щоб тренувати сліпий друк, не підглядайте на фізичну клавіатуру.