Глава 18 · Canvas по-настоящему

Состояние рисования

Инструмент, цвет, толщина и точка начала жеста — параметры, а не сами фигуры.

Что должна помнить программа между событиями

Три переменные из раздела 18.3 (tekuschaya_figura, tekuschij_cvet, tolschina) — честный прототип, но по мере роста приложения к ним добавляются точка начала жеста, последняя точка карандаша, id черновой фигуры превью — и глобальных переменных становится слишком много, чтобы держать их связь в голове.

DrawingStatetool: Toolcolor: strwidth: intstart_x: float | Nonestart_y: float | Nonelast_x: float | Nonelast_y: float | Nonepreview_id: int | None
Один объект вместо восьми отдельных переменных — те же данные, но с понятной границей.

Новое: Enum — заранее известный список вариантов

Пока инструмент хранится строкой, любое слово выглядит допустимым: "pryamougolnik", "pryamougolnick", "квадрат". Python не возражает — он и не знает, какие варианты вы считаете правильными. Опечатка всплывёт гораздо позже и совсем не там: значение просто не совпадёт ни с одной веткой if, и нажатие мыши тихо перестанет что-либо рисовать.

Но у инструмента конечное, заранее известное число вариантов — карандаш, линия, прямоугольник, овал, ластик. Для таких случаев в Python есть перечисление (Enum из модуля enum): вы один раз выписываете все допустимые значения, и дальше пользуетесь только ими.

enum_vpervye.py
from enum import Enum

class Tool(Enum):
    PENCIL = "pencil"
    LINE = "line"
    RECTANGLE = "rectangle"

print(Tool.RECTANGLE)          # Tool.RECTANGLE
print(Tool.RECTANGLE.value)    # rectangle
print(Tool.RECTANGLE is Tool.PENCIL)   # False
print(Tool.RECTANGL)           # AttributeError — опечатка видна СРАЗУ

Три вещи, которые стоит запомнить про Enum:

Главное отличие от строки: Tool.RECTANGL — это AttributeError при первом же обращении, а "pryamougolnick" — совершенно «нормальная» строка, о которой Python промолчит.

Собираем DrawingState

Теперь соберём оба инструмента вместе: Enum для инструмента и @dataclass (раздел 14.23) для самого объекта состояния — он избавляет от рукописного __init__ и сразу даёт понятный repr:

drawing_state.py
from dataclasses import dataclass
from enum import Enum

class Tool(Enum):
    PENCIL = "pencil"
    LINE = "line"
    RECTANGLE = "rectangle"
    OVAL = "oval"
    ERASER = "eraser"

@dataclass
class DrawingState:
    tool: Tool = Tool.PENCIL
    color: str = "#111827"
    width: int = 4
    start_x: float | None = None
    start_y: float | None = None
Enum вместо строк — когда это того стоит
tekuschaya_figura = "pryamougolnik" работает, но опечатку в строке Python не заметит, пока код не выполнится и фигура тихо не окажется «неизвестной». Tool.RECTANGLE — то же самое по смыслу, но опечатку поймает уже редактор или python -c "import module". Enum здесь оправдан ровно потому, что вариантов инструмента конечное известное число. Если вариант всего один или их список постоянно меняется — обычной строки достаточно, перечисление только добавит церемоний.
Реальное окно: кнопка «Карандаш» на панели инструментов выглядит нажатой (утоплена), остальные — обычные
Реальное окно: выбран Карандаш — кнопка визуально «утоплена», а не просто где-то в памяти хранится строка.
Реальное окно: кнопка «Линия» выглядит нажатой (утоплена), кнопка «Карандаш» вернулась в обычное состояние
Реальное окно: переключились на Линию — прежняя кнопка вернулась в обычный вид, новая «утоплена».
Выбранный инструмент виден, а не только хранится в переменной
Состояние — не только то, что помнит Python. Пользователь должен ВИДЕТЬ, какой инструмент выбран сейчас, не глядя в код. В финальном приложении set_tool() одновременно меняет self.state.tool И relief нужной кнопки — та же связка «модель → отражение на экране», что и render() в главе 17.

Путь этой главы

От глобальных переменных к PaintApp
V1 — глобальные (18.3)
3 отдельные переменные
просто, но растёт бесконтрольно
V2 — DrawingState
@dataclass
параметры инструмента в одном месте
V3 — PaintApp (18.29)
DrawingState + документ фигур
полная архитектура приложения
Практика: моделируем состояние рисования
Автоматическая проверка — выбор инструмента и параметров без Tkinter
Открыть практику →