Классы (минимум)
Класс объединяет данные и функции, которые с ними работают. Это шаблон, а объект — конкретный экземпляр по этому шаблону.
Для data engineer нужен минимум. Вы будете читать классы в библиотеках и писать простые dataclass. Наследование достаточно понимать при чтении. Сложное ООП и паттерны сейчас не нужны.
Правило курса: решайте сами, без AI.
class и init
Заголовок раздела «class и init»class Order: def __init__(self, order_id, user, amount, channel="web"): self.order_id = order_id self.user = user self.amount = amount self.channel = channel
def with_vat(self, rate=0.2): return round(self.amount * (1 + rate), 2)
def is_big(self): return self.amount > 100
o = Order(1001, "anna", 49.9)print(o.user, o.amount, o.channel)print(o.with_vat())print(o.is_big())o.amount = 150print(o.is_big())Ответ:
anna 49.9 web59.88FalseTrue| Часть | Что значит |
|---|---|
class Order | Объявление класса. Имя пишут с заглавной буквы |
__init__ | Вызывается при создании объекта. Задаёт начальные данные |
self | Сам объект. Первый параметр каждого метода |
self.amount | Атрибут: данные объекта |
with_vat, is_big | Методы: функции внутри класса |
- Создать объект:
Order(1001, "anna", 49.9). Python сам вызовет__init__. - Читать и менять атрибут:
o.amount. - Вызвать метод:
o.is_big(). Аргументselfпередавать не нужно.
Если вывести объект, вы увидите мало пользы:
print(o)Ответ (адрес у вас будет другой):
<__main__.Order object at 0x1052da120>Писать __init__ и self.x = x для каждого поля скучно. Для этого есть dataclass.
dataclass
Заголовок раздела «dataclass»from dataclasses import dataclass
@dataclassclass Order: order_id: int user: str amount: float channel: str = "web"
def is_big(self) -> bool: return self.amount > 100
d = Order(1002, "boris", 120.0, "app")print(d)print(d.is_big())print(d == Order(1002, "boris", 120.0, "app"))Ответ:
Order(order_id=1002, user='boris', amount=120.0, channel='app')TrueTrue@dataclass сам создаёт:
__init__из списка полей;- читаемый вывод (
repr); - сравнение объектов по значениям полей.
Поля описывают типом: amount: float. Поле со значением по умолчанию ставьте после обязательных.
Из словаря в dataclass
Заголовок раздела «Из словаря в dataclass»Данные из CSV приходят как строки. Преобразуйте их при создании объекта.
rows = [ {"order_id": "1001", "user": "anna", "amount": "49.9", "channel": "web"}, {"order_id": "1002", "user": "boris", "amount": "120.0", "channel": "app"}, {"order_id": "1004", "user": "dina", "amount": "300.0", "channel": "app"},]
orders = [ Order(int(r["order_id"]), r["user"], float(r["amount"]), r["channel"]) for r in rows]print(round(sum(o.amount for o in orders), 2))print([o.order_id for o in orders if o.is_big()])Ответ:
469.9[1002, 1004]Разница с dict: вместо o["amount"] пишем o.amount. Опечатка в имени поля даст ошибку сразу, а не None позже.
frozen: объект нельзя менять
Заголовок раздела «frozen: объект нельзя менять»@dataclass(frozen=True)class Config: path: str limit: int = 10
c = Config("orders.csv")print(c)c.limit = 99Ответ:
Config(path='orders.csv', limit=10)...dataclasses.FrozenInstanceError: cannot assign to field 'limit'Для настроек и записей, которые не должны меняться, ставьте frozen=True.
Класс с состоянием
Заголовок раздела «Класс с состоянием»Иногда объект копит данные между вызовами.
class OrderStats: def __init__(self): self.count = 0 self.total = 0.0
def add(self, order): self.count += 1 self.total += order.amount
def report(self): return f"{self.count} orders, total {self.total}"
st = OrderStats()for o in orders: st.add(o)print(st.report())Ответ:
3 orders, total 469.9self.count и self.total живут между вызовами add. В обычной функции для этого пришлось бы передавать их туда и обратно.
Наследование: читать чужой код
Заголовок раздела «Наследование: читать чужой код»Запись class Child(Parent) значит: новый класс берёт все атрибуты и методы родителя. Свои методы можно добавить или заменить.
class Source: def __init__(self, name): self.name = name
def describe(self): return f"source {self.name}"
def read(self): return []
class CsvSource(Source): def __init__(self, name, path): super().__init__(name) self.path = path
def read(self): return [f"row from {self.path}"]
s = CsvSource("orders", "orders.csv")print(s.describe())print(s.read())print(isinstance(s, Source))Ответ:
source orders['row from orders.csv']Truedescribeпришёл отSource.readклассCsvSourceзаменил своим.super().__init__(name)вызывает__init__родителя.- Самый частый случай в работе — своё исключение:
class BadRowError(Exception): pass. Вы встретите его в уроке про файлы и исключения.
Где это в работе
Заголовок раздела «Где это в работе»| Задача | Как помогают классы |
|---|---|
| Запись данных с известными полями | dataclass: Order, Event, User |
| Настройки pipeline | dataclass(frozen=True): путь, лимит, дата |
| Чужие библиотеки | client = Client(...), client.get(...): это объекты и методы |
| Коннекторы к БД, S3, API | Класс держит соединение между вызовами |
| Pydantic, SQLAlchemy | Те же идеи: класс описывает поля. Наследуется от BaseModel или Base |
Простое правило: если хватает функций и dict, класс не нужен. Класс нужен, когда данные и поведение связаны, или когда нужна понятная структура записи.
Практика
Заголовок раздела «Практика»Задание 1
Заголовок раздела «Задание 1»Напишите класс User с атрибутами name и city и методом greet(). Метод возвращает строку Hi, anna from Moscow. Создайте пользователя и выведите результат greet().
Проверка: Hi, anna from Moscow
Решение
class User: def __init__(self, name, city): self.name = name self.city = city
def greet(self): return f"Hi, {self.name} from {self.city}"
print(User("anna", "Moscow").greet())Задание 2
Заголовок раздела «Задание 2»Возьмите первый класс Order из урока (с __init__). Создайте Order(1001, "anna", 49.9). Выведите with_vat() и is_big(). Потом задайте o.amount = 150 и снова выведите is_big().
Проверка:
59.88FalseTrueРешение
o = Order(1001, "anna", 49.9)print(o.with_vat())print(o.is_big())o.amount = 150print(o.is_big())Задание 3
Заголовок раздела «Задание 3»Перепишите Order как dataclass с полями order_id: int, user: str, amount: float, channel: str = "web". Выведите объект Order(1001, "anna", 49.9). Сравните его с Order(1001, "anna", 49.9) и с Order(1002, "anna", 49.9).
Проверка:
Order(order_id=1001, user='anna', amount=49.9, channel='web')True FalseРешение
from dataclasses import dataclass
@dataclassclass Order: order_id: int user: str amount: float channel: str = "web"
a = Order(1001, "anna", 49.9)print(a)print(a == Order(1001, "anna", 49.9), a == Order(1002, "anna", 49.9))Задание 4
Заголовок раздела «Задание 4»Добавьте в dataclass метод is_big() (сумма больше 100). Преобразуйте список rows из урока в список Order. Выведите сумму всех заказов и order_id крупных.
Проверка:
469.9[1002, 1004]Решение
@dataclassclass Order: order_id: int user: str amount: float channel: str = "web"
def is_big(self) -> bool: return self.amount > 100
orders = [ Order(int(r["order_id"]), r["user"], float(r["amount"]), r["channel"]) for r in rows]print(round(sum(o.amount for o in orders), 2))print([o.order_id for o in orders if o.is_big()])Задание 5
Заголовок раздела «Задание 5»Напишите класс OrderStats с методами add(order) и report(). Добавьте в него все заказы из задания 4. report() возвращает 3 orders, total 469.9.
Проверка: 3 orders, total 469.9
Решение
class OrderStats: def __init__(self): self.count = 0 self.total = 0.0
def add(self, order): self.count += 1 self.total += order.amount
def report(self): return f"{self.count} orders, total {self.total}"
st = OrderStats()for o in orders: st.add(o)print(st.report())Задание 6
Заголовок раздела «Задание 6»Создайте Config как dataclass(frozen=True) с полями path: str и limit: int = 10. Выведите Config("orders.csv"). Попробуйте изменить limit. Прочитайте ошибку.
Проверка: вывод Config(path='orders.csv', limit=10). Последняя строка ошибки: dataclasses.FrozenInstanceError: cannot assign to field 'limit'.
Решение
from dataclasses import dataclass
@dataclass(frozen=True)class Config: path: str limit: int = 10
c = Config("orders.csv")print(c)c.limit = 99Что запомнить
Заголовок раздела «Что запомнить»- Класс — шаблон. Объект — экземпляр.
__init__задаёт начальные данные,self— сам объект. - Метод — функция внутри класса:
obj.method(). @dataclassубирает шаблонный код и даёт читаемый вывод и сравнение.frozen=Trueзащищает объект от случайных изменений.- Классы и наследование (
class Child(Parent)) вы чаще читаете в библиотеках, чем пишете. Для своих данных хватитdataclass.
Дальше: Python Crash Course, глава 9.
Следующий урок: Файлы и исключения.