Мини-проекты: собираем всё вместе
Три законченные программы шаг за шагом — «угадай число», частотный словарь русского текста по словам и по буквам и трекер привычек с сохранением в JSON — и метод, как разбивать любую задачу на функции.
Зачем это нужно
Заголовок раздела «Зачем это нужно»За модуль 0.2 у вас накопился полный ящик инструментов: переменные и строки, условия и циклы, словари, функции, классы, файлы, JSON, исключения, отладка. Но инструменты — ещё не умение строить. Самоучки часто застревают именно здесь: каждую тему поняли, а перед пустым файлом «напиши программу» не знают, с чего начать.
Этот урок — мастерская. Мы соберём три законченные программы, каждая чуть сложнее предыдущей:
| Проект | Что тренирует | Связь с LLM |
|---|---|---|
| «Угадай число» | цикл с вводом, random, try/except, двоичный поиск | случайность и seed; поиск делением пополам |
| Частотный словарь | строки, словари, sorted, подсчёт слов и букв русского текста | первый шаг любого токенизатора; закон Ципфа |
| Трекер привычек | файлы и JSON, даты, аргументы командной строки | данные, которые переживают перезапуск программы |
Главное в уроке — не сами программы, а метод: как превратить задачу в набор небольших функций. Этим методом вы будете пользоваться всю ступень 4, когда соберёте токенизатор, и всю ступень 5, когда напишете свою мини-LLM.
Как разбить задачу на функции
Заголовок раздела «Как разбить задачу на функции»Декомпозициядекомпозиция задачиEN problem decompositionРазбиение большой задачи на шаги, каждый из которых решает отдельная небольшая функция. — разбиение большой задачи на шаги, каждый из которых решает небольшая функция. Порядок действий одинаков для любой задачи:
-
Одна фраза и пример. «Программа печатает пять самых частых слов текста». Вход: файл с текстом. Выход: таблица из пяти строк «слово — частота». Не можете сформулировать — писать код рано.
-
Шаги-глаголы. Прочитать текст → разбить на слова → посчитать → отобрать топ → напечатать. Каждый шаг описывается глаголом и понятен без остальных.
-
Функция на каждый шаг. Сначала сигнатура и docstring с примером, потом тело. Именно так выглядят заготовки домашних работ курса (урок 0.1.3): договор о входе и выходе записан до кода.
-
Логика отдельно от ввода-вывода. Функции, которые считают, не вызывают
input()иprint(). Такие чистые функциичистая функцияEN pure functionРезультат зависит только от аргументов, а сама функция ничего не печатает, не читает с клавиатуры и не меняет внешние данные. Такие функции легко тестировать. зависят только от аргументов, поэтому их легко проверить. Ввод-вывод собирается в тонкую функциюmain. -
Проверка сразу. Написали функцию — проверьте её парой
assert, включая краевые случаикраевой случайEN edge caseНеобычный вход на границе допустимого — пустой список, ноль, пустая строка. Именно на них чаще всего ломается код.: пустой текст, ноль, один элемент. Баг, пойманный сразу, стоит минуту; баг, найденный после сборки всей программы, — вечер (урок 0.2.5). -
Сборка.
main()соединяет функции, аif __name__ == "__main__":запускает её только при запуске файла как скрипта (урок 0.2.4).
flowchart TB T["Задача: пять самых частых слов текста"] --> A["прочитать файл<br/>Path.read_text"] T --> B["разбить на слова<br/>words_ru"] T --> C["посчитать<br/>count"] T --> D["отобрать топ<br/>top_n"] T --> E["напечатать<br/>print_table"]
Признаки хорошей функции: делает одно дело, и имя говорит, что она делает или что возвращает (load, mark,
top_n, streak); помещается на экран (ориентир — до 20 строк); по docstring понятно, что она вернёт, не читая тела.
Если описание функции требует союза «и» («читает файл и считает слова»), скорее всего, это две функции.
Проект 1. Угадай число
Заголовок раздела «Проект 1. Угадай число»Задача. Компьютер загадывает число от 1 до 100, человек угадывает. На каждую попытку — подсказка «больше» или «меньше», всего 7 попыток. На буквы вместо числа программа не падает, а переспрашивает.
План. Три функции: hint — логика подсказки (чистая), ask_number — ввод с проверкой, play — одна партия.
import random
def hint(secret, guess): """Подсказка игроку: "больше", "меньше" или "угадали".""" if guess < secret: return "больше" if guess > secret: return "меньше" return "угадали"
def ask_number(prompt, low, high): """Спрашивает число, пока человек не введёт целое от low до high.""" while True: # повторяем, пока не получим годный ответ text = input(prompt) try: n = int(text) except ValueError: # «двадцать» вместо 20 — не падаем, а переспрашиваем print("Это не целое число, попробуйте ещё раз") continue if low <= n <= high: return n # return выходит и из цикла, и из функции print(f"Нужно число от {low} до {high}")
def play(low=1, high=100, max_tries=7): """Одна партия. Возвращает номер удачной попытки или None.""" secret = random.randint(low, high) # случайное целое, обе границы включены print(f"Я загадал число от {low} до {high}. У вас {max_tries} попыток.") for attempt in range(1, max_tries + 1): guess = ask_number(f"Попытка {attempt}: ", low, high) answer = hint(secret, guess) if answer == "угадали": print(f"Угадали с {attempt}-й попытки!") return attempt print("Моё число", answer) print(f"Попытки кончились. Я загадал {secret}.") return None
assert hint(50, 30) == "больше" # проверки чистой функции — без клавиатурыassert hint(50, 70) == "меньше"assert hint(50, 50) == "угадали"
if __name__ == "__main__": play()Сохраните код в guess.py и запустите python guess.py. Партия может выглядеть так:
Я загадал число от 1 до 100. У вас 7 попыток.Попытка 1: 50Моё число меньшеПопытка 2: двадцатьЭто не целое число, попробуйте ещё разПопытка 2: 25Моё число большеПопытка 3: 37Моё число большеПопытка 4: 43Моё число меньшеПопытка 5: 40Моё число большеПопытка 6: 42Угадали с 6-й попытки!Обратите внимание на устройство. while True в ask_number — намеренно бесконечный цикл: выйти из него можно только
через return, когда ответ годный. Случайно написанный бесконечный циклбесконечный циклEN infinite loopЦикл, условие выхода из которого никогда не выполняется. Остановить программу — Ctrl+C. останавливают
Ctrl+C. А проверить через assert удаётся только hint: функции с input() ждут человека. Именно поэтому логику
отделяют от ввода-вывода.
Почему 7 попыток хватает всегда
Заголовок раздела «Почему 7 попыток хватает всегда»Лучшая стратегия — называть середину оставшегося диапазона. Ответ «больше» или «меньше» отбрасывает половину вариантов: 100 → 50 → 25 → 13 → 7 → 4 → 2 → 1. Это двоичный поискдвоичный поискEN binary searchПоиск делением диапазона пополам. Среди 100 вариантов находит ответ не больше чем за 7 шагов, потому что 2^7 = 128 ≥ 100.. За попыток он различает вариантов, а . Проверим ботом, который играет по этой стратегии против всех 100 возможных чисел:
def solve(secret, low=1, high=100): """Бот: угадывает число делением пополам, возвращает число попыток.""" tries = 0 while True: tries += 1 guess = (low + high) // 2 # середина оставшегося диапазона answer = hint(secret, guess) if answer == "угадали": return tries if answer == "больше": low = guess + 1 # отбрасываем левую половину else: high = guess - 1 # отбрасываем правую половину
results = [solve(s) for s in range(1, 101)]print("худший случай:", max(results), "| в среднем:", sum(results) / len(results))худший случай: 7 | в среднем: 5.8Это та же идея, что «биты удваивают число вариантов» из урока 0.0.2 и «делим код пополам» из урока 0.2.5. Для чисел от 1 до 1000 понадобится 10 попыток: .
Как улучшить. Счёт по нескольким партиям; уровни сложности (1–10, 1–100, 1–1000) с автоматическим числом попыток —
наименьшим , при котором (в математике его записывают , урок 1.0.2); обратная игра,
где загадываете вы, а угадывает solve.
Проект 2. Частотный словарь
Заголовок раздела «Проект 2. Частотный словарь»Задача. Напечатать самые частые слова русского текста, а потом — самые частые буквы и их сочетания. Звучит скромно, но это первый шаг любого токенизаторатокенизаторEN tokenizer: словарь LLM строят, начиная с подсчёта того, какие слова и кусочки встречаются в корпусе чаще всего (модуль 4.2).
flowchart LR A["текст"] --> B["нормализация<br/>lower, ё → е"] B --> C["токены<br/>слова (words) или буквы (letters)"] C --> D["частоты<br/>count без стоп-слов"] D --> E["топ-N<br/>top_n"] D --> F["покрытие<br/>coverage"] E --> G["таблица<br/>print_table"]
Пять функций конвейера — words_ru, letters, count, top_n и coverage — вы напишете сами в практике
в конце урока, их сигнатуры и примеры уже там. Можно сначала решить практику, а потом вернуться сюда. Здесь —
«клей», который превращает эти функции в программу freq.py:
import sysfrom pathlib import Path
# сюда вставьте пять функций из практики: words_ru, letters, count, top_n, coverage
# Учебный список стоп-слов, подобранный под наш абзац. Готовые списки длиннее: сотни слов.STOP_RU = {"и", "в", "на", "а", "с", "из", "что", "чтобы", "очень", "какие", "свои", "всего"}
def letter_ngrams(text, n): """Цепочки из n соседних букв внутри слов; через пробелы и знаки препинания цепочки не строятся.""" grams = [] for word in words_ru(text): for i in range(len(word) - n + 1): # все срезы длины n: word[0:n], word[1:n+1], … grams.append(word[i:i + n]) return grams
def print_table(pairs, width): """Таблица: токен, частота и полоска из █ — гистограмма прямо в терминале.""" for tok, c in pairs: print(f"{tok:<{width}}{c:>3} {'█' * c}")
def main(args): if len(args) != 2 or args[0] not in ("words", "letters"): print("Использование: python freq.py words|letters файл.txt") return mode, path = args text = Path(path).read_text(encoding="utf-8") if mode == "words": tokens = words_ru(text) freq = count(tokens, STOP_RU) else: tokens = letters(text) freq = count(tokens) print(f"токенов: {len(tokens)}, разных: {len(set(tokens))}") print_table(top_n(freq, 5), width=14 if mode == "words" else 3) print(f"половину употреблений дают {coverage(freq, 0.5)} самых частых из {len(freq)}") if mode == "letters": for n, name in [(2, "пары"), (3, "тройки")]: print(f"самые частые {name} букв:") print_table(top_n(count(letter_ngrams(text, n)), 5), width=4)
if __name__ == "__main__": main(sys.argv[1:])Слова после имени скрипта — аргументы командной строкиаргументы командной строкиEN command-line argumentsСлова после имени скрипта: в python habits.py done python это done и python. В Python доступны через sys.argv.. Python кладёт их
в список sys.argv: sys.argv[0] — имя скрипта, дальше — всё, что вы написали. Поэтому main получает
sys.argv[1:], например ["words", "ru.txt"]. Обратите внимание на {tok:<{width}}: ширину поля в f-строке можно
взять из переменной.
Сохраните в ru.txt (в кодировке UTF-8) абзац о том, как модель учится на тексте:
Языковая модель читает текст и учится предсказывать следующее слово.Чтобы предсказывать хорошо, модель читает очень много текста: книги, статьи, код.Перед обучением текст режут на токены. Частые слова получают свои токены,а редкие слова собирают из кусочков. Поэтому словарь токенизатора начинаетсяс простого подсчёта: какие слова и кусочки встречаются в тексте чаще всего.Запустим python freq.py words ru.txt:
токенов: 53, разных: 45слова 3 ███модель 2 ██предсказывать 2 ██текст 2 ██токены 2 ██половину употреблений дают 14 самых частых из 34В первой строке посчитаны все слова, во второй — только те, что остались после удаления стоп-слов: 45 разных слов
минус 11 стоп-слов из нашего списка дают 34. При равной частоте слова идут по алфавиту — так устроена top_n.
Две находки. Первая — морфология. В тексте слово «текст» встречается четыре раза, но в трёх формах: «текст» (2), «текста», «тексте». Для словаря это три разных слова. Можно приводить слова к начальной форме морфологическим анализатором (для русского известен pymorphy), но LLM поступают иначе: их токенизатор режет слова на подслова, и общий кусок «текст» становится одним токеном для всех форм (урок 4.2.1).
Вторая — стоп-словастоп-словаEN stop wordsСамые частые служебные слова («и», «в», «на», «же»), которые убирают при подсчёте частот, чтобы увидеть содержательные слова.. Без фильтра на втором месте стояло бы «и». Служебные слова встречаются везде и ничего не говорят о теме текста, поэтому при анализе частот их убирают. Но из корпуса для обучения LLM их не выбрасывают никогда: модель должна уметь писать «и», «в» и «на» не хуже, чем «нейросеть».
Буквы, пары и тройки букв
Заголовок раздела «Буквы, пары и тройки букв»В китайском письме пробелов между словами нет, поэтому split() там не поможет (урок 0.2.1), и самый простой
токен — отдельный иероглиф. Посмотрим так же на русский текст: забудем о словах и посчитаем отдельные буквы,
а потом пары и тройки соседних букв. Запускаем python freq.py letters ru.txt:
токенов: 314, разных: 28о 35 ███████████████████████████████████е 32 ████████████████████████████████т 32 ████████████████████████████████а 26 ██████████████████████████с 26 ██████████████████████████половину употреблений дают 6 самых частых из 28самые частые пары букв:ст 8 ████████ва 7 ███████ов 7 ███████то 7 ███████ре 6 ██████самые частые тройки букв:лов 5 █████ова 5 █████сло 5 █████екс 4 ████кст 4 ████Буквы распределены очень неравномерно: «о», «е» и «т» вместе дают почти треть текста, а половину всех букв покрывают 6 самых частых из 28. А тройки соседних букв показывают удивительную вещь: куски слов проступают сами из частот. «Сло» и «лов» — это «слово», «слова» и «словарь», а «тек», «екс» и «кст» — «текст» во всех формах, хотя программа о словах ничего не знает. С китайским текстом происходит то же самое: частые пары соседних иероглифов чаще всего оказываются настоящими словами, хотя среди них попадаются и стыки двух слов. Отличать слова от стыков умеют специальные сегментаторы вроде jieba (урок 0.2.1). А алгоритм BPE, которым строят словари Qwen и DeepSeek, идёт по пути наших пар: снова и снова склеивает самую частую пару соседей (урок 4.2.2).
Покрытие и закон Ципфа
Заголовок раздела «Покрытие и закон Ципфа»Функция coverage(freq, share) отвечает на вопрос: сколько самых частых токенов дают долю share всех употреблений?
В нашем коротком абзаце эффект слабый: половину употреблений дают 14 слов из 34, потому что почти каждое слово
встречается один раз. Но чем больше текст, тем сильнее перекос. Его описывает закон Ципфазакон ЦипфаEN Zipf's lawЧастота слова примерно обратно пропорциональна его месту в частотном списке, поэтому немногие частые слова покрывают большую часть любого текста.:
частота слова примерно обратно пропорциональна его месту в списке. Второе по частоте слово встречается примерно
вдвое реже первого, третье — втрое и так далее. Горстка частых слов покрывает огромную долю любого текста, а длинный
хвост редких встречается по разу. Для китайских иероглифов есть точные цифры — о них в «Китайском углу».
Отсюда главный компромисс токенизатора, к которому мы вернёмся на ступени 4: частые слова выгодно хранить целиком,
а редкие собирать из кусочков. Запустите freq.py на большой книге в UTF-8 и посмотрите, как растёт перекос.
Проект 3. Трекер привычек
Заголовок раздела «Проект 3. Трекер привычек»Задача. Отмечать из терминала, что вы сегодня сделали («занимался Python», «учил английский»), и видеть отчёт: серию дней подряд и сколько дней из последних семи привычка выполнялась. Данные должны пережить перезапуск программы — храним их в JSON. Это отметка о занятияхотметка о занятияхEN check-in (daily study log)Буквально «пробить карточку». В учебных сообществах — публикация конспекта или решения в срок как подтверждение, что вы занимаетесь. из урока 0.0.3, только своими руками.
Данные. Словарь «привычка → список дат». Даты храним строками формата ГГГГ-ММ-ДД: такие строки читаются
человеком, сортируются правильно и без проблем ложатся в JSON.
{ "python": ["2026-09-28", "2026-09-30", "2026-10-01", "2026-10-02", "2026-10-03"], "английский": ["2026-09-29", "2026-10-03"]}Новые инструменты — модуль datetime из стандартной библиотеки:
| Код | Результат | Что делает |
|---|---|---|
date.today() | date(2026, 10, 3) | сегодняшняя дата |
date(2026, 10, 3).isoformat() | '2026-10-03' | дата → строка |
date.fromisoformat("2026-10-03") | date(2026, 10, 3) | строка → дата |
date(2026, 10, 3) - timedelta(days=1) | date(2026, 10, 2) | вчера; timedelta — промежуток времени |
"""Трекер привычек (отметки о занятиях) с сохранением в JSON.
Запуск: python habits.py done python # отметить привычку «python» сегодня python habits.py report # отчёт: серии и дни за неделю"""import jsonimport sysfrom datetime import date, timedeltafrom pathlib import Path
DATA_FILE = Path("habits.json")WEEK_GOAL = 4 # цель недели — 4 дня из 7 (урок 0.0.3 советует начинать с 3–4)
# ─── Хранение ────────────────────────────────────────────────────────────────def load(path): """Читает трекер из JSON. Если файла ещё нет — пустой трекер.""" if not path.exists(): return {} return json.loads(path.read_text(encoding="utf-8"))
def save(data, path): """Сохраняет трекер: UTF-8, русский текст и эмодзи остаются читаемыми.""" path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
# ─── Логика: чистые функции, без input/print и без date.today() ─────────────def mark(data, habit, day): """Отмечает привычку в день day (строка "2026-10-03"). Повторная отметка ничего не меняет.""" days = data.setdefault(habit, []) if day not in days: days.append(day) days.sort()
def streak(days, today): """Сколько дней подряд выполнялась привычка. Если сегодня ещё не отмечено, серия считается до вчера: день ещё не закончился.""" done = set(days) d = today if today.isoformat() in done else today - timedelta(days=1) count = 0 while d.isoformat() in done: count += 1 d -= timedelta(days=1) return count
def week_count(days, today): """Сколько дней из последних семи (включая сегодня) привычка выполнялась.""" week = {(today - timedelta(days=i)).isoformat() for i in range(7)} return len(week & set(days))
def report(data, today): """Строки отчёта по всем привычкам в алфавитном порядке.""" lines = [] for habit in sorted(data): days = data[habit] week = week_count(days, today) goal = "✓ цель недели" if week >= WEEK_GOAL else "" lines.append(f"{habit:<10} серия {streak(days, today):>2} дн. | за неделю {week}/7 {goal}") return lines
# ─── Ввод-вывод: тонкая обёртка над логикой ─────────────────────────────────def main(args): today = date.today() data = load(DATA_FILE) if len(args) == 2 and args[0] == "done": mark(data, args[1], today.isoformat()) save(data, DATA_FILE) print(f"Отмечено! {args[1]} — {today.isoformat()}") elif args == ["report"]: for line in report(data, today) or ["Пока нет ни одной привычки"]: print(line) else: print("Использование: python habits.py done <привычка> | report")
if __name__ == "__main__": main(sys.argv[1:])Разберём решения, которые делают этот код надёжным:
today— аргумент, а не вызовdate.today()внутри. Иначеstreakдавала бы разные ответы в разные дни, и проверить её было бы невозможно. С аргументом проверка пишется на фиксированной дате.- Краевой случай серии. Вечером, пока вы ещё не отметились, серия не должна обнуляться: день не закончился. Поэтому отсчёт идёт от вчерашнего дня, если сегодня пусто. Такие правила лучше записывать прямо в docstring.
- Повторная отметка безопасна.
markпроверяетday not in days, поэтому двойной запускdoneне испортит данные. setdefault.data.setdefault(habit, [])возвращает список дат привычки, а если её ещё нет — сначала кладёт в словарь пустой список и возвращает его. Одна строка вместо проверкиif habit not in data(этот приём был в подсказках практики урока 0.2.4).ensure_ascii=False— чтобы «английский» в файле читалось по-русски, а не как\u0430\u043d\u0433…(урок 0.2.4).
Проверки логики — отдельный файл test_habits.py рядом:
from datetime import datefrom habits import mark, streak, week_count
today = date(2026, 10, 3)days = ["2026-09-30", "2026-10-01", "2026-10-02", "2026-10-03"]assert streak(days, today) == 4assert streak(days[:-1], today) == 3 # сегодня ещё не отмечено — серия не прерванаassert streak(["2026-09-30"], today) == 0assert week_count(days + ["2026-09-01"], today) == 4data = {}mark(data, "python", "2026-10-03")mark(data, "python", "2026-10-03") # повторная отметкаassert data == {"python": ["2026-10-03"]}print("все проверки прошли")А так выглядит работа с трекером в терминале (отчёт — после нескольких дней отметок):
$ python habits.py done pythonОтмечено! python — 2026-10-03$ python habits.py done английскийОтмечено! английский — 2026-10-03$ python habits.py reportpython серия 4 дн. | за неделю 5/7 ✓ цель неделианглийский серия 1 дн. | за неделю 2/7карточки серия 1 дн. | за неделю 2/7«Карточки» в последний раз отмечены вчера, поэтому серия равна 1, а не 0.
Как улучшить. Команда undo, чтобы снять ошибочную отметку; календарь месяца из · и █; статистика по дням
недели; своя цель для каждой привычки.
Практика
Заголовок раздела «Практика»Соберите ядро частотного словаря: пять функций конвейера из проекта 2. Тесты проверят каждую по отдельности, а последний — весь конвейер на коротком русском тексте, по словам и по буквам. Функции зависят друг от друга, поэтому идите сверху вниз и после каждой нажимайте «▶ Запустить».
# Мини-проект: частотный словарь русского текста — по словам и по буквам.
# Пять функций — пять шагов конвейера: текст → токены → частоты → топ → покрытие.
# Замените ... в каждой функции своим кодом. Порядок важен: следующие функции
# используют предыдущие, поэтому идите сверху вниз и проверяйте кнопкой «▶ Запустить».
def words_ru(text):
"""Слова русского текста.
Правила: всё в нижний регистр, «ё» заменить на «е»; слово — непрерывная
цепочка букв (ch.isalpha()); всё остальное (пробелы, знаки, цифры) — разделители.
words_ru("Ёжик в тумане, ёжик!") -> ["ежик", "в", "тумане", "ежик"]
"""
# TODO: подсказка — замените каждый «не буквенный» символ пробелом и вызовите .split()
...
def letters(text):
"""Список русских букв текста по порядку — токенизация по буквам.
Правила: всё в нижний регистр, «ё» заменить на «е»; буква — символ ch,
для которого "а" <= ch <= "я" (строки сравниваются по номерам символов в Юникоде).
Пробелы, знаки препинания, цифры, латиница и эмодзи пропускаются.
letters("Ёж и hedgehog 🦔!") -> ["е", "ж", "и"]
"""
# TODO
...
def count(tokens, stopwords=()):
"""Частоты токенов: словарь {токен: сколько раз встретился}.
Токены из stopwords не считаются.
count(["кот", "и", "кот"], {"и"}) -> {"кот": 2}
"""
# TODO: идиома из урока 0.2.1 — freq[tok] = freq.get(tok, 0) + 1
...
def top_n(freq, n):
"""n самых частых токенов: список пар (токен, частота).
Порядок — по убыванию частоты, при равной частоте — по алфавиту (обычное сравнение строк).
top_n({"б": 2, "а": 2, "в": 5}, 2) -> [("в", 5), ("а", 2)]
"""
# TODO: sorted(..., key=...) с ключом-кортежем из урока 0.2.2
...
def coverage(freq, share):
"""Сколько самых частых токенов нужно взять, чтобы они покрыли
не меньше доли share всех употреблений (share от 0 до 1).
Для пустого словаря — 0.
coverage({"а": 5, "б": 3, "в": 2}, 0.8) -> 2 # 5 + 3 = 8 из 10 — это 80%
"""
# TODO: идите по top_n(freq, len(freq)) и копите сумму частот
...
# ─── Демонстрация (кнопка «▶ Запустить») ─────────────────────────────────────
TEXT_RU = "Модель читает текст и предсказывает следующее слово. Чтобы предсказывать хорошо, модель читает очень много текста."
STOP_RU = {"и", "в", "на", "чтобы", "очень"}
try:
freq_words = count(words_ru(TEXT_RU), STOP_RU)
print("слова:", top_n(freq_words, 3))
freq_letters = count(letters(TEXT_RU))
print("буквы:", top_n(freq_letters, 3))
print("покрытие 50% (слова):", coverage(freq_words, 0.5))
except Exception as e:
print("Демо упало:", type(e).__name__, e)
Что дальше
Заголовок раздела «Что дальше»Модуль «Python с нуля» пройден. Дальше по программе — модуль 0.3 «NumPy — язык тензоров»: те же подсчёты, но без циклов, над миллионами чисел сразу. Именно на NumPy (а потом на PyTorch) написан весь код ступеней 1–5.
Знаний уже хватает на первую домашнюю работу курса — s0-hw01-python-warmup: функции для анализа русского и английского текста с эмодзи и автотестами. По программе она завершает ступень 0 после NumPy, но начать можно сейчас:
python homework/check.py s0-hw01-python-warmupЕщё лучшая практика — свои мини-проекты. Несколько идей по силам уже сейчас:
- Карточки из глоссария. Программа задаёт термин по-русски и просит английский перевод; ошибки сохраняет в JSON — своя маленькая тетрадь ошибок.
- Счётчик байтов и токенов-символов для ваших заметок: сколько в них русских букв, латиницы и эмодзи и сколько байт они занимают в UTF-8.
- Генератор текста по биграммам. Посчитайте, какое слово чаще всего идёт после каждого, и начните «писать»: берёте слово, выбираете случайного соседа с учётом частот, повторяете. Это и есть простейшая языковая модель — биграммная, ей посвящён урок 4.1.2.
Каждый проект кладите в отдельную папку под Git (урок 0.1.1) с коротким README: что делает и как запустить.
Проверь себя
Заголовок раздела «Проверь себя»- Любую задачу раскладывайте по одной схеме: фраза и пример → шаги-глаголы → функция на шаг с docstring → логика
без ввода-вывода →
assertсразу → сборка вmain(). - «Угадай число»: цикл
while Trueсtry/exceptдля ввода и двоичный поиск — на вариантов хватает попыток, если . - Частотный словарь: слова, буквы, пары и тройки букв, стоп-слова, топ-N и покрытие. Частоты подчиняются закону Ципфа, и на этом стоит словарь любого токенизатора.
- Трекер привычек: JSON переживает перезапуск,
datetimeсчитает дни,sys.argvдаёт аргументы командной строки, а дата, переданная аргументом, делает логику проверяемой.
Дальше: модуль 0.3 «NumPy — язык тензоров», урок 0.3.1 «Массивы, формы, индексация»: почему векторизация в сотни раз быстрее циклов, которые вы писали весь этот модуль.