Усі три рівні працюють із тим самим прайсом, що й лекція. Три правила, які діють скрізь і не обговорюються:
- кожен
openу текстовому режимі має явнийencoding="utf-8"; - жодного
f.close()руками — тількиwith; - шляхи будуються через
pathlib, а не склеюються рядками.
Рівні 2 і 3 краще робити у тимчасовій теці (tempfile.mkdtemp), як у практиці,
щоб експерименти з видаленням і перейменуванням нічого не зачепили.
рівень 1Рівень 1 — База
Зроби теку проєкту з двома файлами: prays.txt і zvit.py.
У prays.txt запиши щонайменше вісім товарів у форматі назва;ціна, по рядку
на товар, з українськими назвами.
zvit.py має прочитати прайс обходом циклом, порахувати кількість товарів,
загальну суму й найдорожчий товар, а потім записати звіт у zvit.txt — по рядку
на товар плюс три підсумкові рядки.
Найважливіша частина: шлях до prays.txt будуй від самого скрипта, а не від
поточної теки:
ТЕКА_СКРИПТА = Path(__file__).parent
прайс = ТЕКА_СКРИПТА / "prays.txt"
Зроблено, якщо: виконуються всі три перевірки.
cd /і потімpython3 /повний/шлях/до/zvit.py— скрипт знаходить свій прайс і не падає зFileNotFoundError.zvit.txt, відкритий у редакторі чи браузері, показує українські назви, а не мотлох.len(Path("zvit.txt").read_text(encoding="utf-8").splitlines())дорівнюєкількість_товарів + 3— тобто жоден\nне загубився і жоден зайвий не додався.
рівень 2Рівень 2 — Плюс
Тепер про кодування — на числах, а не на віру.
- Візьми той самий рядок прайсу й запиши його у три файли:
utf8.txt(encoding="utf-8"),cp1251.txt(encoding="cp1251") іbite.txt, куди поклади сирі байти черезPath("bite.txt").write_bytes(b"\xff\xfe\x00\x41"). - Напиши функцію
прочитати_будь_як(шлях), яка спершу пробуєutf-8, а приUnicodeDecodeError—cp1251. Якщо не вийшло й так, вона має кинути виняток далі, а не повертатиNoneі не мовчати. - Порівняй розміри: скільки байтів займає той самий текст у кожному кодуванні й скільки в ньому символів.
Зроблено, якщо: у тебе є чотири записані результати.
assert прочитати_будь_як(utf8) == прочитати_будь_як(cp1251)проходить — два різні файли дають той самий рядок;прочитати_будь_як(bite)падає, і ти можеш назвати тип винятку;- три числа: байтів у UTF-8, байтів у cp1251, символів у рядку — і ти можеш пояснити одним реченням, звідки береться різниця між першим і третім;
- окремо перевір, що станеться, якщо
utf8.txtпрочитати зencoding="cp1251": запиши, який рядок вийшов і чи був виняток.
рівень 3Рівень 3 — Виклик
Напиши функцію перейменувати_розширення(тека, було, стало, сухий_запуск=True).
Вона рекурсивно знаходить у теці всі файли з розширенням було і перейменовує
їх на стало, лишаючи назву незмінною. Повертає список пар (старий, новий).
Три вимоги:
сухий_запуск=True(за замовчуванням) нічого не змінює — лише повертає список того, що зробила б;- обхід дерева впорядкований (
sorted), інакше результат плаватиме між запусками; - якщо цільове імʼя вже зайняте, файл пропускається, а його імʼя потрапляє в окремий список конфліктів.
Побудуй для перевірки дерево щонайменше з трьох рівнів і пʼятьма файлами .txt,
серед яких обовʼязково буде пара nazva.txt + вже наявний nazva.md — щоб конфлікт
справді стався.
Зроблено, якщо:
- Список файлів у дереві до сухого запуску й після нього однаковий:
assert до == після(порівнюйsorted(тека.rglob("*"))). - Сухий запуск повернув рівно 5 пар, а справжній — 4 перейменування й 1 конфлікт.
- Повторний справжній виклик повертає порожній список: перейменовувати вже нічого.
- Ти можеш пояснити двома реченнями, чому
сухий_запуск=Trueстоїть саме за замовчуванням, а не навпаки.
Підказки
Path.with_suffixуже все вміє.Path("dani/zvit.txt").with_suffix(".md")даєdani/zvit.md— рахувати індекси крапки не треба. Але памʼятай проprays.2026.csvіз лекції:with_suffixзамінює лише останнє розширення.rglob— цеglobіз**/.тека.rglob("*.txt")знайде.txtна всіх рівнях;тека.glob("*.txt")— лише на верхньому. Прогони обидва й порівняй довжину списків, це найшвидший спосіб запамʼятати різницю.- Перейменовує не
rename, аreplace.Path.renameу Windows падає, якщо цільовий файл існує, аPath.replaceтихо його затирає. Оскільки нам конфлікт треба помітити, перевіряйновий.exists()самостійно перед викликом, а не покладайся на поведінку методу. - Не перейменовуй під час обходу.
for файл in тека.rglob("*.txt")віддає генератор, який читає диск на ходу; змінювати теку в цей момент — надійний спосіб отримати непередбачуваний результат. Спершусписок = sorted(тека.rglob(...)), потім цикл по готовому списку. - Кодування не вгадується. У рівні 2 порядок спроб
utf-8→cp1251не випадковий: у зворотному порядкуcp1251прочитає будь-що без винятку, і доutf-8черга ніколи не дійде. Це той самий висновок, що в інтерактиві 3.