Уроки курса
1 Введение в Python и философия дзен
30 мин
2 Переменные и динамическая типизация
30 мин
3 Базовые типы данных: числа, строки и булевы значения
30 мин
4 Изменяемые и неизменяемые объекты (Mutable vs Immutable)
30 мин
5 Форматирование строк и f-строки
30 мин
6 Углубленная работа со списками
30 мин
7 Кортежи и их особенности
30 мин
8 Словари под капотом
30 мин
9 Множества и математические операции
30 мин
10 Генераторы списков (List Comprehensions)
30 мин
11 Генераторы словарей и множеств
30 мин
12 Встроенные функции для коллекций
30 мин
13 Условные операторы и логические выражения
30 мин
14 Циклы while и управление потоком
30 мин
15 Итерация с циклом for
30 мин
16 Конструкции for...else и while...else
30 мин
17 Функции enumerate и zip
30 мин
18 Создание собственных функций (def)
30 мин
19 Позиционные и именованные аргументы
30 мин
20 Проблема изменяемых аргументов по умолчанию
30 мин
21 Произвольное число аргументов (*args и **kwargs)
30 мин
22 Область видимости переменных (LEGB)
30 мин
23 Анонимные функции (lambda)
30 мин
24 Функции высшего порядка
30 мин
25 Замыкания (Closures)
30 мин
26 Введение в объектно-ориентированное программирование
30 мин
27 Атрибуты классов и экземпляров
30 мин
28 Магический метод __init__
30 мин
29 Методы экземпляра
30 мин
30 Инкапсуляция и сокрытие данных
30 мин
31 Декоратор @property
30 мин
32 Наследование классов
30 мин
33 Переопределение методов и функция super()
30 мин
34 Полиморфизм в Python
30 мин
35 Магические методы строк (__str__ и __repr__)
30 мин
36 Обработка исключений (try-except)
30 мин
37 Блоки else и finally
30 мин
38 Генерация собственных исключений (raise)
30 мин
39 Открытие и чтение файлов
30 мин
40 Запись данных в файлы
30 мин
41 Контекстные менеджеры (with)
30 мин
42 Работа с форматом JSON
30 мин
43 Модули и импорты
30 мин
44 Полезные модули стандартной библиотеки
30 мин
45 Модуль datetime
30 мин
46 Модуль collections
30 мин
47 Виртуальные окружения (venv)
30 мин
48 Установка сторонних пакетов через pip
30 мин
49 Организация структуры Python-проекта
30 мин
50 Финальный проект: создание приложения
30 мин

Полезные модули стандартной библиотеки

Практика использования встроенных модулей, таких как math и random, для решения типовых задач.

Прогресс урока: 0%

Введение в стандартную библиотеку Python

Добро пожаловать в урок, посвященный стандартной библиотеке Python! Как часто вы ловили себя на мысли, что пытаетесь написать сложный алгоритм сортировки, математический расчет или генератор случайных чисел с нуля? В программировании есть золотое правило: «Не изобретай велосипед». Python славится своим подходом «батарейки в комплекте» (batteries included). Это означает, что вместе с установкой языка вы получаете огромный набор готовых, оптимизированных и тщательно протестированных модулей для решения самых разнообразных задач.

Стандартная библиотека — это ваш швейцарский нож. В ней есть инструменты для работы с математикой, датами, архивами, сетевыми протоколами, базами данных и многим другим. В рамках этого урока мы сфокусируемся на тех модулях, которые чаще всего нужны разработчику уровня Intermediate в повседневной практике. Мы детально разберем модули math, random, datetime, collections и затронем магию itertools.

Почему это так важно? Представьте ситуацию: вам нужно подсчитать частоту встречаемости слов в огромном тексте. Вы можете написать цикл с использованием словаря, проверять наличие ключа, увеличивать счетчик... А можете импортировать Counter из модуля collections и решить задачу в одну строку. Использование стандартной библиотеки не только делает ваш код короче и чище (соответствуя Дзену Python), но и значительно ускоряет работу программы, так как большинство встроенных модулей написаны на языке C и оптимизированы на низком уровне.

В этом уроке мы будем использовать подход Microlearning, разбивая каждую тему на небольшие, легко усваиваемые блоки. После каждого концептуального блока вас ждут практические примеры и задания в формате Active Recall, чтобы вы сразу закрепляли материал. А в конце мы объединим полученные знания в рамках небольшого проекта (Project-Based Learning). Готовы расширить свой арсенал инструментов? Тогда начинаем!

Модуль math: Математические функции под рукой

Начнем наше погружение с модуля math. Хотя Python из коробки поддерживает базовые арифметические операции (сложение, вычитание, умножение, деление, возведение в степень и взятие остатка от деления), для более сложных вычислений требуется специализированный инструмент. Модуль math предоставляет доступ к математическим функциям, определенным в стандарте языка C.

Важно отметить, что модуль math работает только с вещественными числами (типа float) и целыми числами (типа int). Если вам нужно работать с комплексными числами, в Python есть отдельный модуль cmath. Давайте рассмотрим основные функции, которые предоставляет модуль math.

Округление и модули:

  • math.ceil(x): Округляет число x до ближайшего целого вверх (к большему). Например, math.ceil(4.2) вернет 5.
  • math.floor(x): Округляет число x до ближайшего целого вниз (к меньшему). Например, math.floor(4.8) вернет 4.
  • math.trunc(x): Усекает дробную часть числа x, оставляя только целую. В отличие от floor, по-разному ведет себя с отрицательными числами. math.trunc(-4.8) вернет -4, а math.floor(-4.8) вернет -5.
  • math.fabs(x): Возвращает абсолютное значение (модуль) числа x в виде вещественного числа. Хотя встроенная функция abs() делает то же самое, math.fabs() всегда возвращает float.

Константы: Модуль содержит важнейшие математические константы с высокой точностью:

  • math.pi: Число Пи (3.141592653589793).
  • math.e: Число Эйлера, основание натурального логарифма (2.718281828459045).
  • math.tau: Число Тау, равное 2 * Пи.
  • math.inf: Положительная бесконечность (эквивалентно float('inf')). Удобно использовать для инициализации минимума при поиске.
  • math.nan: Значение «не число» (Not a Number, эквивалентно float('nan')).

Тригонометрия и логарифмы:

  • math.sin(x), math.cos(x), math.tan(x): Синус, косинус и тангенс (угол x задается в радианах). Для перевода градусов в радианы используйте math.radians(), а обратно — math.degrees().
  • math.log(x, base): Логарифм x по основанию base. Если основание не указано, вычисляется натуральный логарифм.
  • math.log10(x) и math.log2(x): Десятичный и двоичный логарифмы соответственно.
  • math.sqrt(x): Квадратный корень. Хотя его можно вычислить как x ** 0.5, функция math.sqrt() работает быстрее и точнее.

Давайте посмотрим, как эти функции применяются на практике.

python
import math

# Примеры округления
print(f'Округление вверх (4.1): {math.ceil(4.1)}')   # Вывод: 5
print(f'Округление вниз (4.9): {math.floor(4.9)}')   # Вывод: 4
print(f'Усечение (-4.9): {math.trunc(-4.9)}')        # Вывод: -4

# Работа с константами
radius = 5
area = math.pi * (radius ** 2)
print(f'Площадь круга с радиусом 5: {area:.2f}')    # Вывод: 78.54

# Тригонометрия (вычисление синуса 30 градусов)
angle_degrees = 30
angle_radians = math.radians(angle_degrees)
sin_30 = math.sin(angle_radians)
print(f'Синус 30 градусов: {sin_30:.1f}')           # Вывод: 0.5

# Квадратный корень и факториал
print(f'Квадратный корень из 144: {math.sqrt(144)}') # Вывод: 12.0
print(f'Факториал 5 (5! = 1*2*3*4*5): {math.factorial(5)}') # Вывод: 120

Какая функция из модуля math вернет значение -5 для числа -4.8?

Модуль random: Генерация псевдослучайных чисел

Следующий важнейший модуль — random. Без случайности наш мир (и наши программы) был бы слишком предсказуемым. Игры, криптография, статистическое моделирование, машинное обучение — все они полагаются на случайные числа. Однако важно понимать: модуль random генерирует псевдослучайные числа. Они создаются по детерминированному алгоритму (в Python используется вихрь Мерсенна), зависящему от начального значения (seed). Поэтому этот модуль нельзя использовать для целей криптографии или безопасности (для этого существует модуль secrets).

Давайте рассмотрим основные инструменты, которые дает нам random.

Генерация чисел:

  • random.random(): Возвращает случайное число с плавающей точкой в диапазоне от 0.0 до 1.0 (включая 0.0, но не включая 1.0). Это базовая функция, на основе которой работают многие другие.
  • random.uniform(a, b): Возвращает случайное число с плавающей точкой в диапазоне от a до b (включая обе границы).
  • random.randint(a, b): Возвращает случайное целое число N, такое что a <= N <= b. Очень полезная функция, когда нужно выбрать номер или индекс.
  • random.randrange(start, stop[, step]): Работает аналогично встроенной функции range(), но возвращает случайно выбранный элемент из этой последовательности. Позволяет задать шаг. Например, random.randrange(0, 101, 2) вернет случайное четное число от 0 до 100.

Работа с последовательностями: Очень часто нам нужно применить случайность не к числам, а к коллекциям данных (спискам, кортежам, строкам).

  • random.choice(seq): Возвращает один случайно выбранный элемент из непустой последовательности seq. Идеально подходит для выбора случайного победителя из списка участников.
  • random.choices(population, weights=None, k=1): Возвращает список элементов размера k, выбранных из population с возвращением (один элемент может быть выбран несколько раз). Параметр weights позволяет задать вероятность выбора каждого элемента.
  • random.sample(population, k): Возвращает список из k уникальных элементов, выбранных из population без возвращения (каждый элемент выбирается не более одного раза). Подходит для выбора уникальных победителей в лотерее.
  • random.shuffle(x): Перемешивает последовательность x на месте (in-place). Это означает, что оригинальный список изменится. Работает только с изменяемыми последовательностями (например, списками).

Управление генератором:

  • random.seed(a=None): Инициализирует генератор случайных чисел. Если передать одно и то же значение a, генератор будет выдавать одну и ту же последовательность чисел. Это критически важно для воспроизводимости результатов, например, при тестировании алгоритмов машинного обучения или отладке.

Посмотрим на код.

python
import random

# Фиксируем seed для воспроизводимости (вывод всегда будет одинаковым)
random.seed(42)

# Генерация чисел
print(f'Случайный float [0.0, 1.0): {random.random():.3f}')
print(f'Случайный float от 10 до 20: {random.uniform(10, 20):.2f}')
print(f'Случайное целое от 1 до 100: {random.randint(1, 100)}')
print(f'Случайное четное от 0 до 10: {random.randrange(0, 11, 2)}')

# Работа с коллекциями
names = ["Алексей", "Мария", "Иван", "Елена", "Дмитрий"]
print(f'\nСписок участников: {names}')

print(f'Случайный победитель (choice): {random.choice(names)}')

# Выбор 3 победителей без повторений (sample)
winners = random.sample(names, 3)
print(f'Топ-3 уникальных победителя (sample): {winners}')

# Симуляция броска монетки (choices с весами)
coin = ["Орел", "Решка"]
# Допустим, монетка кривая, и "Орел" выпадает в 80% случаев
flips = random.choices(coin, weights=[0.8, 0.2], k=10)
print(f'Бросаем кривую монетку 10 раз: {flips}')

# Перемешивание списка (shuffle)
cards = ["Туз", "Король", "Дама", "Валет", "10"]
print(f'\nКолода до перемешивания: {cards}')
random.shuffle(cards)
print(f'Колода после перемешивания: {cards}')

Какая функция модуля random изменит исходный список, перемешав его элементы?

Модуль datetime: Путешествие во времени

Работа с датами и временем — одна из самых сложных тем в программировании. Временные зоны, високосные годы, переходы на летнее/зимнее время, различные форматы вывода — все это может свести с ума, если писать логику самостоятельно. К счастью, в Python есть модуль datetime, который предоставляет мощные объекты для управления датами и временем.

Модуль datetime содержит несколько ключевых классов. Самые важные для нас:

  • date: Представляет только дату (год, месяц, день).
  • time: Представляет только время (часы, минуты, секунды, микросекунды) вне зависимости от даты.
  • datetime: Объединяет дату и время. Это самый часто используемый класс.
  • timedelta: Представляет разницу во времени (продолжительность). Используется для арифметики дат.

Создание объектов datetime:

Чаще всего вам нужно получить текущее время. Для этого используется метод класса datetime.now(). Если вам нужно получить время в формате UTC (Coordinated Universal Time), используйте datetime.utcnow() (хотя в современных версиях Python рекомендуется использовать now(datetime.UTC)).

Вы можете создать конкретную дату, передав аргументы в конструктор: dt = datetime.datetime(2023, 12, 31, 23, 59, 59).

Форматирование дат (Strftime и Strptime):

Это критически важный навык. Вам постоянно придется преобразовывать объекты datetime в строки для отображения пользователю и парсить строки обратно в объекты datetime.

  • strftime(format): (String Format Time). Преобразует объект datetime в строку по заданному формату.
  • strptime(date_string, format): (String Parse Time). Анализирует строку и создает из нее объект datetime.

Для форматирования используются специальные коды, начинающиеся со знака %. Некоторые основные коды:

  • %Y: Год с веком (например, 2023).
  • %m: Месяц как число с нулем в начале (01-12).
  • %d: День месяца с нулем в начале (01-31).
  • %H: Часы в 24-часовом формате (00-23).
  • %M: Минуты (00-59).
  • %S: Секунды (00-59).

Например, формат '%d.%m.%Y %H:%M:%S' выведет дату в привычном нам виде: 31.12.2023 23:59:59.

Арифметика дат (timedelta):

С объектами datetime можно выполнять математические операции! Если вы вычтете одну дату из другой (date1 - date2), вы получите объект timedelta, показывающий разницу в днях, секундах и микросекундах.

Вы также можете прибавлять или вычитать timedelta к объекту datetime. Например, чтобы узнать дату, которая наступит через 30 дней: future_date = datetime.now() + timedelta(days=30).

python
from datetime import datetime, timedelta, date

# Получение текущего времени
now = datetime.now()
print(f'Текущее время: {now}')

# Форматирование datetime в строку (strftime)
formatted_time = now.strftime('%d.%m.%Y %H:%M:%S')
print(f'Отформатированное время: {formatted_time}')

# Извлечение частей даты
print(f'Только год: {now.year}, только месяц: {now.month}')

# Парсинг строки в datetime (strptime)
date_string = "2024-05-15 14:30"
dt_obj = datetime.strptime(date_string, "%Y-%m-%d %H:%M")
print(f'\nРаспарсенный объект: {dt_obj}, тип: {type(dt_obj)}')

# Арифметика дат с timedelta
today = date.today()
print(f'\nСегодня: {today}')

# Создаем timedelta на 7 дней
one_week = timedelta(days=7)

# Прибавляем и вычитаем время
next_week = today + one_week
last_week = today - one_week
print(f'Через неделю будет: {next_week}')
print(f'Неделю назад было: {last_week}')

# Вычисление разницы между датами
new_year = date(now.year + 1, 1, 1)
days_until_new_year = new_year - today
print(f'До Нового Года осталось: {days_until_new_year.days} дней')

Какой метод класса datetime используется для преобразования строки '31/12/2023' в объект datetime?

Модуль collections: Продвинутые структуры данных

Модуль collections предоставляет специализированные контейнерные типы данных, которые являются альтернативой встроенным структурам (dict, list, set, tuple). Когда вы чувствуете, что стандартный словарь или список не справляются элегантно с задачей, скорее всего, решение есть в collections. Мы рассмотрим три самых популярных инструмента: Counter, defaultdict и namedtuple.

1. Counter (Счетчик)

Counter — это подкласс словаря (dict), разработанный специально для подсчета хешируемых объектов. Это инструмент, который должен знать каждый разработчик. Вместо того чтобы писать цикл с условием «если ключ есть в словаре, увеличь значение, иначе добавь со значением 1», вы просто передаете итерируемый объект в Counter.

Он подсчитает количество вхождений каждого элемента. Например, Counter('hello') вернет Counter({'l': 2, 'h': 1, 'e': 1, 'o': 1}).

Самый полезный метод Counter — это most_common(n). Он возвращает список из n наиболее часто встречающихся элементов и их количества от наибольшего к наименьшему. Идеально для задач типа «найти топ-5 самых частых слов в тексте».

2. defaultdict (Словарь с умолчанием)

Как вы знаете, попытка получить значение по несуществующему ключу в обычном словаре вызывает ошибку KeyError. Чтобы избежать этого, мы используем методы .get() или конструкцию setdefault(). defaultdict решает эту проблему изящнее.

При создании defaultdict вы передаете ему функцию (фабрику), которая будет вызываться для создания значения по умолчанию, если запрошенный ключ отсутствует. Например, defaultdict(list) создаст пустой список, если ключа нет, а defaultdict(int) создаст значение 0. Это невероятно удобно при группировке данных.

3. namedtuple (Именованный кортеж)

Обычные кортежи (tuples) великолепны, но обращаться к их элементам по индексам (например, student[0], student[1]) неудобно и делает код трудночитаемым. Что значит индекс 1? Это возраст или оценка?

namedtuple решает эту проблему, позволяя создавать подклассы кортежей с именованными полями. Вы получаете преимущества кортежа (неизменяемость, экономия памяти) и удобочитаемость объектов (обращение через точку: student.name, student.age). По сути, это легковесный класс для хранения данных без методов.

python
from collections import Counter, defaultdict, namedtuple

# --- 1. Counter ---
text = "python is awesome and python is easy to learn because python is readable"
words = text.split()

# Подсчет частоты слов
word_counts = Counter(words)
print("Counter результат:", word_counts)

# Топ-2 самых частых слов
top_words = word_counts.most_common(2)
print("Топ-2 слова:", top_words) # [('python', 3), ('is', 3)]

# --- 2. defaultdict ---
# Задача: сгруппировать слова по их длине
# Используем list как фабрику по умолчанию
words_by_length = defaultdict(list)

for word in words:
    words_by_length[len(word)].append(word)

print("\ndefaultdict группировка (длина: список слов):")
for length, w_list in words_by_length.items():
    print(f"{length}: {w_list}")

# --- 3. namedtuple ---
# Создаем тип 'Point' с полями 'x' и 'y'
Point = namedtuple('Point', ['x', 'y'])

# Создаем экземпляр
p1 = Point(10, 20)

print(f"\nnamedtuple Point: x={p1.x}, y={p1.y}")
# Кортежные свойства сохраняются
print(f"Доступ по индексу: {p1[0]}")

# Попытка изменить значение вызовет ошибку, так как кортежи неизменяемы!
# p1.x = 15 # AttributeError: can't set attribute

Какой инструмент из модуля collections лучше всего подойдет для подсчета голосов за кандидатов на выборах, если голоса представлены в виде списка строк с именами?

Модуль itertools: Магия итераторов

Модуль itertools — это скрытая жемчужина стандартной библиотеки Python. Он содержит набор функций для создания и работы с итераторами (объектами, по которым можно пройтись в цикле for). Функции в itertools вдохновлены конструкциями из функциональных языков программирования. Они очень быстры, потребляют минимум памяти (так как вычисляют значения лениво, по одному) и позволяют создавать сложные циклы в пару строк кода.

Давайте рассмотрим несколько мощных функций из этого модуля.

Бесконечные итераторы:

  • itertools.count(start=0, step=1): Генерирует бесконечную последовательность чисел: start, start+step, start+2*step и т.д. В отличие от range(), у него нет конца. Часто используется с функцией zip() для добавления индексов к бесконечным потокам данных.
  • itertools.cycle(iterable): Бесконечно циклично повторяет элементы из переданной последовательности. Например, cycle('ABC') будет выдавать A, B, C, A, B, C, A... до бесконечности.
  • itertools.repeat(elem, n=None): Повторяет один и тот же элемент elem бесконечно или n раз.

Комбинаторные итераторы (Самое интересное!):

  • itertools.product(*iterables, repeat=1): Вычисляет декартово произведение входных итерируемых объектов. Это эквивалент вложенных циклов for. Вместо того чтобы писать три вложенных цикла, вы можете использовать product(), что сделает код плоским и читаемым.
  • itertools.permutations(iterable, r=None): Генерирует все возможные перестановки (уникальные порядки) элементов длины r. Порядок имеет значение (A, B не равно B, A).
  • itertools.combinations(iterable, r): Генерирует все возможные комбинации элементов длины r. Порядок НЕ имеет значения (выведет только A, B, комбинацию B, A пропустит). Идеально для задач типа «выбрать 2 человек из 10».

Осторожно с бесконечностью! Так как итераторы в itertools могут быть бесконечными или генерировать огромные объемы данных (например, перестановки списка из 20 элементов создадут $20!$ вариантов), никогда не пытайтесь превратить их в список целиком с помощью list() без предварительного ограничения. Используйте break в циклах или функцию itertools.islice для получения среза.

python
import itertools

# 1. Бесконечные итераторы (нужно ограничивать!)
print("Пример itertools.count:")
for i in itertools.count(start=10, step=5):
    print(i, end=" ")
    if i >= 30:
        break  # Обязательно прерываем бесконечный цикл
print("\n")

# 2. Декартово произведение (вместо вложенных циклов)
colors = ['Red', 'Blue']
sizes = ['S', 'M', 'L']

print("Декартово произведение (product):")
# Аналог: for c in colors: for s in sizes: ...
for color, size in itertools.product(colors, sizes):
    print(f"{color}-{size}", end=" | ")
print("\n")

# 3. Перестановки и комбинации
letters = ['A', 'B', 'C']

print("Перестановки по 2 (permutations - порядок важен):")
# Даст ('A', 'B') и ('B', 'A')
perms = list(itertools.permutations(letters, 2))
print(perms)

print("\nКомбинации по 2 (combinations - порядок НЕ важен):")
# Даст только ('A', 'B'), но не ('B', 'A')
combs = list(itertools.combinations(letters, 2))
print(combs)

Project-Based Learning: Анализатор логов

Теория — это прекрасно, но знания закрепляются только на практике. В этом блоке мы объединим знания, полученные о модулях datetime, collections и re (регулярные выражения, которые мы упомянем для полноты картины), чтобы решить реальную задачу.

Сценарий: Вы — администратор сервера. У вас есть текстовый файл логов (журнал событий), где каждая строка содержит дату, время, уровень ошибки и сообщение. Вам нужно написать скрипт, который проанализирует этот лог и выдаст следующую статистику:

  1. Общее количество логов каждого уровня (INFO, WARNING, ERROR).
  2. Какая дата была самой проблемной (больше всего ERROR).

Структура лога:

2023-10-25 10:15:30 [INFO] User logged in
2023-10-25 10:17:12 [ERROR] Database connection failed
2023-10-25 11:05:00 [WARNING] Disk space low
...

Подход к решению (Scaffolding):

  1. Парсинг строк: Нам нужно извлечь из каждой строки дату и уровень лога. Мы можем использовать строковые методы (например, split).
  2. Подсчет уровней: Здесь идеально подойдет Counter из модуля collections. Мы будем добавлять извлеченные уровни логов в список, а затем скормим его Counter.
  3. Анализ дат: Для поиска самой проблемной даты мы можем использовать defaultdict(int). Ключом будет дата (объект date или просто строка), а значением — счетчик ошибок. Если мы встречаем уровень 'ERROR', мы увеличиваем счетчик для этой даты.

Давайте посмотрим, как это будет выглядеть в коде. Обратите внимание, как использование модулей из стандартной библиотеки сокращает объем кода и делает логику прозрачной.

python
from collections import Counter, defaultdict
from datetime import datetime

# Эмулируем данные из файла
log_data = [
    "2023-10-25 10:15:30 [INFO] User logged in",
    "2023-10-25 10:17:12 [ERROR] Database connection failed",
    "2023-10-25 11:05:00 [WARNING] Disk space low",
    "2023-10-26 08:00:00 [INFO] System restart",
    "2023-10-26 09:12:33 [ERROR] Timeout exception",
    "2023-10-26 09:15:00 [ERROR] Out of memory",
    "2023-10-27 14:00:00 [INFO] Data backup complete"
]

# 1. Инициализация инструментов
level_counter = Counter()           # Для подсчета уровней (INFO, ERROR...)
error_by_date = defaultdict(int)    # Для подсчета ошибок по датам

# 2. Обработка данных
for line in log_data:
    # Парсинг строки: разделяем по пробелу
    # Пример: ['2023-10-25', '10:15:30', '[INFO]', 'User', 'logged', 'in']
    parts = line.split()
    date_str = parts[0]
    # Очищаем уровень от скобок [INFO] -> INFO
    level = parts[2].strip('[]') 
    
    # Обновляем счетчик уровней
    level_counter.update([level])
    
    # Если это ошибка, увеличиваем счетчик для конкретной даты
    if level == 'ERROR':
        error_by_date[date_str] += 1

# 3. Вывод результатов
print("--- Статистика уровней логов ---")
# Выводим в порядке убывания частоты
for level, count in level_counter.most_common():
    print(f"{level}: {count}")

print("\n--- Анализ ошибок по датам ---")
# Находим дату с максимальным количеством ошибок
# Функция max принимает ключ (key) для определения максимума по значению словаря
if error_by_date:
    worst_date = max(error_by_date, key=error_by_date.get)
    error_count = error_by_date[worst_date]
    print(f"Самая проблемная дата: {worst_date} ({error_count} ошибок)")
else:
    print("Ошибок не найдено!")

Задание

Практическое задание: Модифицируйте анализатор логов

  • Скопируйте код анализатора логов в свою среду разработки.
  • Измените код так, чтобы вместо строк для ключей в error_by_date использовались настоящие объекты datetime.date.
  • Для этого используйте функцию datetime.strptime() для парсинга date_str, а затем извлеките дату с помощью метода .date().
  • Добавьте логику, которая выведет самую 'старую' запись в логах (минимальную дату).
10 баллов

Резюме и лучшие практики

Мы проделали большой путь по стандартной библиотеке Python. Давайте закрепим основные принципы и лучшие практики (Best Practices), которые отличают джуниора от уверенного разработчика уровня Intermediate.

1. Знайте свои инструменты

Прежде чем писать сложный алгоритм сортировки, группировки или математических вычислений, задайте себе вопрос: «А нет ли этого в стандартной библиотеке?». В 90% случаев ответ — «Да». Потратьте время на чтение официальной документации (docs.python.org/3/library/). Это лучшее вложение времени в ваше развитие.

2. Импорты должны быть чистыми

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

Избегайте импорта со звездочкой: from math import *. Это загрязняет глобальное пространство имен и делает непонятным, откуда взялась конкретная функция (например, sin()). Лучше использовать import math и вызывать math.sin(), или импортировать конкретные функции: from math import sin, cos.

3. Идиоматичный Python (Pythonic Way)

  • Вместо ручного подсчета элементов в цикле — используйте collections.Counter.
  • Вместо обработки KeyError в словарях — используйте collections.defaultdict.
  • Вместо нечитаемых кортежей с индексами — используйте collections.namedtuple (или современные dataclasses).
  • Вместо вложенных циклов — посмотрите в сторону itertools.product.
  • Для генерации случайных чисел всегда помните про разницу между криптографически стойкими (secrets) и обычными (random) генераторами.

Стандартная библиотека — это фундамент, на котором строятся мощные и эффективные приложения. Освоив ее, вы научитесь писать код быстрее, а работать он будет надежнее. В следующем уроке мы перейдем к созданию собственных архитектур с помощью Объектно-Ориентированного Программирования, где эти встроенные инструменты станут вашими верными помощниками при создании атрибутов и методов классов.

Какая форма импорта считается нарушением PEP 8 и плохой практикой, так как загрязняет пространство имен?