Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Базовые практики Python
python_basics

Базовые практики Python

Установка Python, терминал, первый запуск, traceback, PEP 8, uv и venv, модули, файлы и документация

Открыть лабораториюv0.6.2Запускается локально из публичного репозитория

Базовые практики Python

  • Маршрут: Основы · тема 1 из 7
  • Навыки: J1, J5, J7
  • До урока: входная тема; опыт программирования не требуется. Нужны компьютер, редактор текста и возможность открыть терминал.
  • Результат: воспроизводимое окружение, каркас пакета и небольшая файловая обработка данных с осмысленным журналированием.
  • Основа: окружение, импорты, pathlib и файловые форматы. Работа в эксплуатации: pyproject.toml, lock-файл, конфигурация и наблюдаемость. Углубление: границы импорта, пакета, сборки и воспроизводимость окружения.
  • Подтверждение: запускаемый проект и лог обработки входного файла.

В этом уроке сначала установим Python, запустим первый файл и разберём минимум синтаксиса. Затем перейдём к устройству проекта и окружения, работе с файлами, документации и журналированию. В конце соберём всё в небольшой запускаемый проект. PEP 8 здесь нужен как общий язык команды, а не как набор правил ради правил.

Все команды с $ выполняются в терминале без самого символа $. Блоки кода в разделах про импорты, форматы и логирование — отдельные примеры: не объединяйте их в один файл, если текст явно этого не требует.

#0. Первый запуск: от установки до понятной ошибки

Курс рассчитан на CPython 3.12–3.14 и использует uv для воспроизводимого окружения. CPython — основная реализация языка Python, а uv устанавливает нужную версию интерпретатора, создаёт изолированное окружение и запускает команды проекта.

#Установите uv и Python

Официальные команды установки uv:

# macOS и Linux curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows PowerShell powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Перезапустите терминал и проверьте установку:

uv --version uv python install 3.14 uv run --python 3.14 python --version

Если политика организации запрещает запуск установочного скрипта, выберите пакетный менеджер из официальной инструкции uv. Команда python --version может указывать на другой системный Python; в курсе используйте uv run python, чтобы запускать версию из проекта.

#Терминал: четыре необходимые команды

pwd # показать текущий каталог (Windows: cd без аргументов) ls # показать содержимое (Windows: dir) mkdir python-start # создать каталог cd python-start # перейти в него

Создайте проект и первый файл:

uv init --python 3.14

Откройте созданный main.py в редакторе и замените его содержимое:

name = "Студент" lessons_completed = 0 print(f"Привет, {name}!") print("Пройдено уроков:", lessons_completed)

Запустите файл из каталога проекта:

uv run python main.py

Ожидаемый вывод:

Привет, Студент! Пройдено уроков: 0

Интерактивный режим (REPL) удобен для коротких проб:

uv run python

После приглашения >>> введите 2 + 2 и получите 4. Выйти можно командой exit() или сочетанием Ctrl-D (Ctrl-Z, затем Enter в Windows).

#Как читать traceback

Намеренно уберите закрывающую кавычку в первой строке main.py и снова запустите файл. Python покажет traceback — сообщение с местом и типом ошибки. Читайте его снизу вверх:

  1. последняя строка называет тип и причину, например SyntaxError;
  2. строка File ".../main.py", line 1 указывает файл и номер строки;
  3. знак ^ показывает место, рядом с которым парсер обнаружил проблему.

Верните кавычку и убедитесь, что команда снова завершается без ошибки. SyntaxError означает, что Python не смог разобрать текст программы. Исключения вроде NameError возникают уже во время выполнения; отдельная тема курса объяснит, какие из них нужно обрабатывать.

#Минимум синтаксиса для этого урока

Переменная связывает имя со значением. = выполняет присваивание, а == сравнивает значения:

price = 120 quantity = 3 total = price * quantity print(total) # 360 print(total == 360) # True

Отступы объединяют команды в блок. После строки, открывающей блок, ставится двоеточие:

if total >= 300: print("Бесплатная доставка") else: print("Доставка оплачивается отдельно")

Список хранит последовательность, словарь — пары ключ–значение, а цикл for по очереди получает элементы:

prices = [120, 80, 200] order = {"id": 17, "paid": True} for item_price in prices: print(item_price) print(order["id"]) # 17

Функция даёт имя повторяемому действию. Параметры перечисляются в скобках, return возвращает результат:

def calculate_total(price, quantity): return price * quantity result = calculate_total(120, 3) print(result) # 360

import подключает готовый модуль. Конструкция with гарантирует завершение работы с ресурсом, а try/except описывает ожидаемую ошибку. Подробные правила будут в темах про исключения и контекстные менеджеры; здесь достаточно увидеть назначение:

from pathlib import Path path = Path("message.txt") path.write_text("Привет\n", encoding="utf-8") try: with path.open(encoding="utf-8") as file: print(file.read()) except OSError as error: print(f"Не удалось прочитать файл: {error}")

Перед продолжением проверьте себя: вы можете объяснить разницу между редактором, терминалом и интерпретатором; запустить main.py; найти номер строки в traceback; предсказать вывод пяти примеров выше. Если нет — повторите этот раздел: последующие разделы опираются на эти действия.

#1. PEP 8 — стиль кода

#Именование

# snake_case — переменные, функции, методы, модули user_name = "Alice" def calculate_total(items): ... MAX_RETRY_COUNT = 3 # UPPER_SNAKE_CASE — константы _private_var = "internal" # _ — соглашение о приватности # PascalCase используют для классов; они подробно появятся в теме ООП: class UserAccount: ... # Имена: описательные, не сокращения # Проблема: calc, n, usr, tmp # Вариант: calculate_total, count, user, temp_file

#Форматирование

# Отступы: 4 пробела (не табуляция) def function(): if condition: do_something() # Максимум 79 символов в строке (PEP 8) или 88/99 (Black) # Перенос с обратным слешем или скобками (предпочтительно): result = ( some_very_long_variable + another_very_long_variable + yet_another_variable ) # Пробелы вокруг операторов: x = 5 + 3 # Вариант: x=5+3 # Проблема: func(a=1, b=2) # Вариант: keyword args — без пробелов func(a =1, b= 2) # Проблема: # Пустые строки: # 2 перед определением класса или функции на верхнем уровне # 1 внутри класса между методами

#Автоформатирование

pip install black isort ruff black myfile.py # форматирует код по стандарту Black isort myfile.py # сортирует импорты ruff check myfile.py # линтер: находит проблемы ruff check --fix . # автоисправление

#2. Импорты

Модуль — это файл Python, например billing.py. Обычный пакет — каталог с модулями и файлом __init__.py. Начиная с Python 3.3 существуют и namespace packages без __init__.py, но для первого проекта явный файл обычно делает структуру понятнее.

myproject/ ├── pyproject.toml ├── src/ │ └── myapp/ │ ├── __init__.py │ ├── __main__.py │ └── reports.py └── tests/

import myapp.reports ищет модуль по каталогам из sys.path. В этот список попадают, среди прочего, каталог запуска, стандартная библиотека, site-packages активного окружения и пути из PYTHONPATH. Не стоит исправлять импорты случайным sys.path.append(...): установите проект в окружение или запускайте модуль через python -m myapp.reports.

#Порядок и стиль

# Правильный порядок (isort это делает автоматически): # 1. Стандартная библиотека import os import sys from pathlib import Path from typing import Optional # 2. Сторонние пакеты import requests import fastapi from pydantic import BaseModel # 3. Локальные модули from myapp.models import User from .utils import helper_function # Не используйте wildcard импорты (засоряют namespace): from os import * # Проблема: — что именно импортировано? # Alias для длинных имён: import numpy as np import pandas as pd from datetime import datetime as dt

Относительный импорт начинается с точки и считается от текущего пакета:

from .models import User # соседний модуль from ..common import settings # пакет уровнем выше

Он уместен внутри пакета. Файл с относительными импортами нужно запускать как модуль (python -m myapp.reports), а не как отдельный скрипт по пути.

#Циклические импорты — как избежать

# Проблема: # a.py: from b import B # b.py: from a import A ← ImportError! # Решение 1: Перенести импорт внутрь функции def create_b(): from b import B # импорт при вызове, не при загрузке модуля return B() # Решение 2: TYPE_CHECKING from typing import TYPE_CHECKING if TYPE_CHECKING: from b import B # импорт только при статическом анализе def process(b: "B") -> None: # строковая аннотация pass # Решение 3: Реструктуризация кода # Вынести общие части в третий модуль common.py

#__all__ — публичный API модуля

# mymodule.py __all__ = ["PublicClass", "public_function"] class PublicClass: ... def public_function(): ... def _private_function(): ... # не экспортируется # from mymodule import * — импортирует только то что в __all__

#3. Виртуальные окружения и зависимости

#venv — стандартный инструмент

venv входит в Python и изолирует установленные Python-пакеты. virtualenv решает ту же задачу как сторонний инструмент и поддерживает дополнительные сценарии и старые версии Python. Conda управляет не только Python-пакетами, но и самим Python и нативными библиотеками; её чаще выбирают для data science и проектов со сложными системными зависимостями. Для этого курса достаточно venv, которым uv управляет автоматически.

# Создание python -m venv .venv # Активация source .venv/bin/activate # Linux/macOS .venv\Scripts\activate # Windows # Деактивация deactivate # Управление зависимостями pip install requests pip freeze > requirements.txt pip install -r requirements.txt

#uv — workflow проекта

uv init myproject # создать проект с pyproject.toml uv add requests # добавить runtime-зависимость uv add --dev pytest ruff mypy uv add pydantic-settings # пакет для примера конфигурации ниже uv remove requests uv sync --locked # синхронизировать строго по uv.lock uv run python main.py # выполнить команду в окружении проекта uv run pytest
# pyproject.toml [project] name = "myproject" version = "0.1.0" requires-python = ">=3.12" dependencies = [ "requests>=2.32", "pydantic>=2.0", "pydantic-settings>=2.0", ] [dependency-groups] dev = ["pytest", "ruff", "mypy"]

pyproject.toml описывает прямые требования проекта, а uv.lock фиксирует полное разрешение зависимостей. Для библиотек диапазоны совместимости должны отражать реально протестированный API; для приложения CI/deploy используют lock-файл. Poetry, Hatch и PDM остаются допустимыми альтернативами, но курс использует один воспроизводимый workflow, совпадающий с этим проектом.

#Установка проекта и версия пакета

Команда pip install . собирает и устанавливает текущую версию проекта. Editable-установка pip install -e . связывает окружение с рабочим исходным кодом: изменения модулей видны без повторной установки. Такой режим удобен при разработке, но релизную сборку всё равно нужно проверять обычной установкой.

pyproject.toml служит стандартной точкой обмена между frontend сборки (pip, uv) и backend (setuptools, Hatchling и другие). Здесь же хранятся метаданные проекта и прямые зависимости. Точное решение всех транзитивных зависимостей относится к lock-файлу.

В старых проектах те же настройки могут находиться в исполняемом setup.py или декларативном setup.cfg. Для нового проекта начинайте с pyproject.toml; legacy-файлы поддерживайте только там, где их требует существующая сборка.

Не дублируйте номер версии вручную в нескольких файлах. Для установленного дистрибутива его можно получить из метаданных:

from importlib.metadata import PackageNotFoundError, version try: __version__ = version("myproject") except PackageNotFoundError: __version__ = "0+unknown"

#4. Работа с файлами и директориями

#pathlib — современный подход

from pathlib import Path # Создание и навигация project = Path("/Users/alice/projects/myapp") config = project / "config" / "settings.yaml" # / — оператор! print(config.name) # "settings.yaml" print(config.stem) # "settings" print(config.suffix) # ".yaml" print(config.parent) # /Users/alice/projects/myapp/config print(config.exists()) # True/False print(config.is_file()) # True/False print(config.is_dir()) # True/False # Чтение / запись text = config.read_text(encoding="utf-8") config.write_text("key: value\n", encoding="utf-8") data = config.read_bytes() # Обход директории for py_file in project.rglob("*.py"): print(py_file) for item in project.iterdir(): if item.is_dir(): print(f"Dir: {item.name}") # Создание Path("/tmp/new_dir").mkdir(parents=True, exist_ok=True) # Переименование / перемещение old = Path("old_name.txt") new = old.rename("new_name.txt") old.replace("/other/dir/file.txt") # перезаписывает если существует

pathlib покрывает повседневную работу с путями и небольшими файлами. Для копирования каталогов, перемещения между файловыми системами и создания архивов используйте shutil:

import shutil shutil.copy2("source.txt", "backup/source.txt") shutil.copytree("assets", "backup/assets", dirs_exist_ok=True) shutil.move("draft.txt", "archive/draft.txt") shutil.make_archive("project-backup", "zip", "project") # Удаляет дерево целиком: путь должен быть проверен заранее. shutil.rmtree("temporary-build")

#Чтение файлов — правильно

# Всегда указывайте encoding with open("data.txt", encoding="utf-8") as f: content = f.read() # Большой файл — читаем построчно with open("huge.log", encoding="utf-8") as f: for line in f: # итерация без загрузки всего файла print(line.rstrip("\n")) # Бинарные файлы with open("image.png", "rb") as f: header = f.read(8) # первые 8 байт # Запись with open("output.txt", "w", encoding="utf-8") as f: f.write("Hello\n") f.writelines(["line1\n", "line2\n"]) # Дозапись with open("log.txt", "a", encoding="utf-8") as f: f.write("New log entry\n")

Ошибки файловой границы обрабатывайте по ожидаемым типам и только там, где можете восстановиться или добавить контекст:

from pathlib import Path try: text = Path("config.json").read_text(encoding="utf-8") except FileNotFoundError as error: raise RuntimeError("Файл конфигурации не найден") from error except UnicodeDecodeError as error: raise RuntimeError("Конфигурация должна быть в UTF-8") from error

Голый except: перехватывает даже KeyboardInterrupt и SystemExit. except Exception: уже, но для большинства мест тоже слишком широк: ловите конкретную ошибку, которую действительно умеете обработать.


#5. JSON, CSV, YAML, TOML

#JSON

import json data = {"name": "Alice", "age": 30, "scores": [1, 2, 3]} # Сериализация json_str = json.dumps(data) json_pretty = json.dumps(data, indent=2, ensure_ascii=False) # unicode сохраняется # Десериализация parsed = json.loads(json_str) # Файлы with open("data.json", "w", encoding="utf-8") as f: json.dump(data, f, indent=2, ensure_ascii=False) with open("data.json", encoding="utf-8") as f: loaded = json.load(f) # Кастомный encoder для нестандартных типов import datetime class DateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return obj.isoformat() return super().default(obj) json.dumps({"ts": datetime.datetime.now()}, cls=DateTimeEncoder)

#CSV

import csv # Запись with open("data.csv", "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=["name", "age", "email"]) writer.writeheader() writer.writerow({"name": "Alice", "age": 30, "email": "a@b.com"}) writer.writerows([ {"name": "Bob", "age": 25, "email": "b@b.com"}, {"name": "Charlie", "age": 35, "email": "c@b.com"}, ]) # Чтение with open("data.csv", encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: print(row["name"], row["age"]) # row — dict: {"name": "Alice", "age": "30", ...} # Внимание: все значения — строки! Нужно явное приведение типов

#YAML

pip install pyyaml
import yaml config_text = """ database: host: localhost port: 5432 name: mydb debug: true allowed_hosts: - localhost - 127.0.0.1 """ # Безопасная загрузка (не выполняет Python-код) config = yaml.safe_load(config_text) print(config["database"]["port"]) # 5432 (int, не строка!) # Из файла with open("config.yaml", encoding="utf-8") as f: config = yaml.safe_load(f) # Сохранение with open("output.yaml", "w", encoding="utf-8") as f: yaml.dump(config, f, allow_unicode=True, default_flow_style=False) # Важно: НЕ используйте yaml.load() без Loader — это опасно! # yaml.load(data) # Проблема: может выполнить произвольный код # yaml.safe_load(data) # Вариант: безопасно

#TOML (Python 3.11+)

import tomllib # встроен в Python 3.11+ with open("pyproject.toml", "rb") as f: # обязательно "rb"! data = tomllib.load(f) # Или из строки: text = """ [tool.mypy] strict = true python_version = "3.12" """ config = tomllib.loads(text) print(config["tool"]["mypy"]["strict"]) # True

#6. Документация

#Docstrings

def calculate_bmi(weight_kg: float, height_m: float) -> float: """Вычисляет индекс массы тела (ИМТ). Args: weight_kg: Вес в килограммах. height_m: Рост в метрах. Должен быть положительным. Returns: ИМТ = weight_kg / height_m². Raises: ValueError: Если height_m <= 0. Examples: >>> calculate_bmi(70, 1.75) 22.857142857142858 """ if height_m <= 0: raise ValueError(f"height_m must be positive, got {height_m}") return weight_kg / height_m ** 2

Стиль docstring выбирают один раз на весь проект. В Google style используются секции Args, Returns и Raises. NumPy style оформляет Parameters, Returns и Raises отдельными секциями с подчёркиваниями. reStructuredText записывает поля как :param name:, :type name: и :return:; этот формат часто встречается в проектах на Sphinx.

Docstring хранится в func.__doc__ и показывается через help(func). Если её нет, __doc__ равен None, а help() всё равно покажет имя и сигнатуру функции, но не сможет вывести содержательное описание.

#doctest — тесты в документации

def add(a: int, b: int) -> int: """Складывает два числа. >>> add(2, 3) 5 >>> add(-1, 1) 0 >>> add(0, 0) 0 """ return a + b # Запуск doctests: # python -m doctest module.py -v # pytest --doctest-modules module.py

#7. Логирование

import logging # Базовая конфигурация logging.basicConfig( level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) # Правильный способ: logger для каждого модуля logger = logging.getLogger(__name__) def process_order(order_id: int) -> None: # Ленивая %-подстановка: строка формируется только при реальной записи, # а не при каждом вызове (важно для DEBUG в горячих путях). logger.debug("Processing order %s", order_id) try: result = execute_order(order_id) logger.info("Order %s completed: %s", order_id, result) except Exception: logger.exception("Failed to process order %s", order_id) raise # Уровни: DEBUG < INFO < WARNING < ERROR < CRITICAL # Структурированное логирование import json import sys class JSONFormatter(logging.Formatter): def format(self, record): return json.dumps({ "time": self.formatTime(record), "level": record.levelname, "message": record.getMessage(), "module": record.module, }) # Форматтер нужно привязать к handler'у, иначе он не применится: handler = logging.StreamHandler(sys.stdout) handler.setFormatter(JSONFormatter()) # force=True заменяет конфигурацию из предыдущего учебного примера. logging.basicConfig(handlers=[handler], level=logging.INFO, force=True)

#8. __name__ == "__main__"

# module.py def main(): print("Running as main program") def helper(): return 42 # Этот блок выполняется только при прямом запуске: # python module.py # НЕ выполняется при: import module if __name__ == "__main__": main() # Зачем: позволяет использовать файл и как модуль, и как скрипт # import module → можно использовать helper() # python module.py → запускается main()

#Аргументы командной строки

Для небольшого CLI используйте argparse: он разбирает аргументы, проверяет типы и сам формирует справку.

import argparse parser = argparse.ArgumentParser(description="Собрать отчёт") parser.add_argument("input", help="Путь к исходному JSON") parser.add_argument("--limit", type=int, default=100) args = parser.parse_args()

#9. Конфигурация через переменные окружения

import os from typing import Optional # os.environ — словарь переменных окружения database_url = os.environ["DATABASE_URL"] # KeyError если нет database_url = os.environ.get("DATABASE_URL", "sqlite:///dev.db") # с дефолтом debug = os.getenv("DEBUG", "0") == "1" # bool # python-dotenv для .env файлов # pip install python-dotenv from dotenv import load_dotenv load_dotenv(".env") # загружает .env в os.environ # Pydantic Settings — лучший способ (FastAPI) from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): database_url: str redis_url: str = "redis://localhost:6379" debug: bool = False api_key: Optional[str] = None model_config = SettingsConfigDict(env_file=".env") settings = Settings() # автоматически читает из переменных окружения

#10. Полезные встроенные функции

# enumerate — индекс + элемент for i, item in enumerate(["a", "b", "c"], start=1): print(f"{i}. {item}") # zip — параллельный обход (останавливается на коротком) names = ["Alice", "Bob", "Charlie"] scores = [85, 92, 78] for name, score in zip(names, scores): print(f"{name}: {score}") # zip_longest (заполняет None или custom fillvalue) from itertools import zip_longest # sorted с key users = [{"name": "Charlie", "age": 30}, {"name": "Alice", "age": 25}] by_name = sorted(users, key=lambda u: u["name"]) by_age_desc = sorted(users, key=lambda u: u["age"], reverse=True) # map, filter (предпочитайте comprehensions) squares = list(map(lambda x: x**2, range(10))) evens = list(filter(lambda x: x % 2 == 0, range(10))) # Лучше: squares = [x**2 for x in range(10)] evens = [x for x in range(10) if x % 2 == 0] # any / all has_admin = any(u["role"] == "admin" for u in users) all_active = all(u["active"] for u in users) # vars / dir / type / id class SomeObject: pass obj = SomeObject() print(vars(obj)) # obj.__dict__ print(dir(obj)) # все атрибуты и методы print(type(obj)) # <class 'SomeObject'> print(id(obj)) # уникальный идентификатор на время жизни объекта # В CPython id() обычно связан с адресом памяти. Язык Python этого не обещает. # isinstance vs type isinstance(True, int) # True — bool наследует int type(True) is int # False — точный тип bool

#11. Что проверить перед тестом

  • В импортах различайте модуль, пакет и установленный дистрибутив.
  • Не смешивайте прямые зависимости из pyproject.toml с зафиксированным решением из lock-файла.
  • Не прячьте зависимости в глобальном состоянии и не исправляйте структуру проекта случайными изменениями sys.path.
  • Не оптимизируйте код без измерений. Для рабочего приложения заранее предусмотрите тесты и наблюдаемость: хотя бы осмысленные логи и разделение ошибок чтения, обработки и записи.

#Практика: от пробы к рабочему решению

#Шаг 1. Соберите минимальный пример

Соберите проект, который одинаково запускается из чистого окружения, читает вход через pathlib и явно задаёт кодировку. Результат основы — команда запуска и один проверяемый файл, а не снимок экрана из среды разработки.

#Шаг 2. Проверьте поведение

Сохраните код в probe.py и выполните python probe.py. Отсутствие ошибки означает, что запись и чтение Unicode воспроизводимы.

import json import tempfile from pathlib import Path with tempfile.TemporaryDirectory() as directory: report = Path(directory) / "report.json" payload = {"message": "проверка", "count": 2} report.write_text( json.dumps(payload, ensure_ascii=False, sort_keys=True), encoding="utf-8", ) assert json.loads(report.read_text(encoding="utf-8")) == payload

#Шаг 3. Доведите решение до рабочего сценария

Практика с подсказками: вынесите путь и уровень журналирования в конфигурацию, запретите вывод исходной записи целиком и добавьте отдельные ветви для ошибки чтения и ошибки записи. Сначала напишите проверку, затем меняйте код.

#Шаг 4. Объясните внутренний механизм

Докажите разницу между python module.py и python -m package.module на маленьком пакете. В записке отделите правила импорта языка от поведения конкретной раскладки проекта и среды запуска.

#Упражнения

  1. Преобразуйте код использующий os.path.join(), os.path.exists(), os.listdir() в эквивалент на pathlib.
  2. Напишите read_config(path: str) -> dict читающий JSON/YAML/TOML в зависимости от расширения файла.
  3. Создайте модуль с функцией calculate() и блоком if __name__ == "__main__". Проверьте что он работает и как import, и как скрипт.
  4. Настройте логирование с двумя handlers: один пишет INFO+ в stdout, другой ERROR+ в файл error.log.
  5. Используя pydantic_settings, создайте Settings класс для приложения. Добавьте валидацию: port должен быть от 1024 до 65535.

#Самопроверка

  • Упражнение 1 выполнено, если используются /, Path.exists() и Path.iterdir(), а код не склеивает путь строками.
  • Для упражнения 2 проверьте JSON и TOML положительными тестами, неизвестное расширение — тестом на исключение; YAML требует отдельно установленный pyyaml.
  • В упражнении 3 импорт не должен печатать или запускать calculate(), прямой запуск — должен.
  • В упражнении 4 создайте два Handler с уровнями INFO и ERROR; один вызов logger.error(...) обязан попасть в оба назначения.
  • В упражнении 5 проверьте допустимые границы 1024 и 65535, а также ошибки для 1023 и 65536.

#Практическая лаборатория

Закрепите тему в лаборатории «Воспроизводимая настройка и CLI». Вы отделите TOML, окружение и системные ошибки, а затем докажете, что запуск не зависит от текущего каталога и не раскрывает секреты.

git clone --branch v0.6.2 --depth 1 https://gitlab.potapov.me/courses/python-labs.git cd python-labs uv sync --group test uv run --group test pytest python_basics/tests

Далее: Базовые типы данных