Уроки языка Python с практическими упражнениями

Раздел: Образование -> Обучение Python

Практические примеры для изучения Python

Задание 1. Вычисление факториала числа

Как реализовать вычисление факториала числа без использования рекурсии?

Наиболее эффективным и безопасным способом является итеративный подход с циклом. Он не вызывает переполнения стека и работает за линейное время.


def factorial_iterative(n):
    result = 1
    for i in range(1, n + 1):
        result *= i
    return result

print(factorial_iterative(5))

Python set (тип set в python)

120

практические задания по python (практические задания по python)

Пример использует один цикл, переменная result накапливает произведение. Этот вариант подходит для обучения циклам и переменным.

Какие ещё существуют способы вычисления факториала?

Можно применить рекурсивную функцию или встроенную функцию из модуля math.

Вариант A (рекурсия)


def factorial_recursive(n):
    if n == 0:
        return 1
    return n * factorial_recursive(n - 1)

print(factorial_recursive(5))

уроки python с заданиями (уроки python с заданиями)

120

Этот вариант наглядно демонстрирует принцип рекурсии, но для больших n (например, 1000) вызовет ошибку RecursionError из-за ограничения глубины стека.

Вариант B (math.factorial)


import math
print(math.factorial(5))
120

Встроенная функция работает быстро и надёжно, но не позволяет кастомизировать логику (например, изменить тип результата).

Типичные ошибки:

  • Забыть обработать случай n=0 в рекурсивной реализации - возникает бесконечная рекурсия.
  • Использовать рекурсию для очень больших чисел - превышение лимита рекурсии.
  • Не импортировать модуль math перед вызовом math.factorial.

Решение: для учебных целей подходит итеративный вариант, для продакшена - math.factorial.

Задание 2. Поиск всех вхождений подстроки в строке

Как найти все индексы, где встречается заданная подстрока?

Основной способ - использование метода find в цикле. Он возвращает индекс первого вхождения, а затем мы продолжаем поиск с позиции, следующей за найденной.


def find_all_substrings(text, substring):
    indices = []
    start = 0
    while True:
        pos = text.find(substring, start)
        if pos == -1:
            break
        indices.append(pos)
        start = pos + 1
    return indices

print(find_all_substrings("ababa", "aba"))
[0, 2]

Метод str.find принимает начальную позицию, что позволяет последовательно перебирать все вхождения. Алгоритм работает за O(n*m) в худшем случае, но для большинства практических задач это приемлемо.

Какие альтернативные подходы существуют?

Вариант A (регулярные выражения)


import re

text = "ababa"
substring = "aba"
# Используем finditer для нахождения всех совпадений
indices = [m.start() for m in re.finditer(substring, text)]
print(indices)
[0, 2]

Модуль re удобен, когда нужны сложные шаблоны. Однако для простого поиска подстроки он избыточен и может быть медленнее.

Вариант B (срезы и enumerate)


def find_all_slices(text, substring):
    n = len(substring)
    return [i for i in range(len(text) - n + 1) if text[i:i+n] == substring]

print(find_all_slices("ababa", "aba"))
[0, 2]

Этот способ использует списковое включение и срезы. Он нагляден, но для длинных строк может быть менее эффективным из-за создания множества временных строк.

Типичные ошибки:

  • Не учитывать, что метод find возвращает -1, если подстрока не найдена - приводит к зацикливанию.
  • При использовании регулярных выражений забыть экранировать специальные символы (например, точка).
  • Ошибка на единицу при срезах - выйти за границы строки.

Решение: для простых строк достаточно цикла с str.find, для паттернов - re.

Задание 3. Подсчёт частоты слов в текстовом файле

Как посчитать, сколько раз каждое слово встречается в файле, игнорируя регистр и знаки препинания?

Эффективное решение - считывание всего файла, очистка текста от знаков препинания, приведение к нижнему регистру и использование collections.Counter.


import re
from collections import Counter

word_counter = Counter()

with open('sample.txt', 'r', encoding='utf-8') as file:
    content = file.read()
    # Удаляем знаки препинания и приводим к нижнему регистру
    words = re.findall(r'\b\w+\b', content.lower())
    word_counter.update(words)

# Вывод трёх самых частых слов
print(word_counter.most_common(3))
[('python', 10), ('код', 7), ('урок', 5)]

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

Какие альтернативные способы подсчёта частоты слов существуют?

Вариант A (словарь вручную)


def count_words_manual(filename):
    freq = {}
    with open(filename, 'r', encoding='utf-8') as f:
        for line in f:
            words = re.findall(r'\b\w+\b', line.lower())
            for word in words:
                freq[word] = freq.get(word, 0) + 1
    return freq

freq_dict = count_words_manual('sample.txt')
# Сортировка по убыванию
sorted_freq = sorted(freq_dict.items(), key=lambda x: x[1], reverse=True)
print(sorted_freq[:3])
[('python', 10), ('код', 7), ('урок', 5)]

Ручное управление словарём позволяет полностью контролировать логику, но требует больше кода. Подходит для обучения работе со словарями.

Вариант B (группировка по сортировке)


def count_by_sorting(filename):
    words = []
    with open(filename, 'r', encoding='utf-8') as f:
        for line in f:
            words.extend(re.findall(r'\b\w+\b', line.lower()))
    words.sort()
    freq = {}
    i = 0
    while i < len(words):
        count = 1
        while i + count < len(words) and words[i] == words[i + count]:
            count += 1
        freq[words[i]] = count
        i += count
    return freq

print(count_by_sorting('sample.txt'))
{'python': 10, 'код': 7, ...}

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

Типичные ошибки:

  • Не обработать кодировку файла - возможны ошибки UnicodeDecodeError. Рекомендуется явно указывать encoding='utf-8'.
  • Использовать split() вместо re.findall - приводит к тому, что знаки препинания прилипают к словам.
  • Не учитывать пустой файл - тогда цикл не выполнится, но ошибки не будет.

Решение: при работе с файлами всегда указывать кодировку, для разбиения на слова использовать регулярное выражение \b\w+\b.

Расширенные примеры заданий и их решений

Пример 1. Использование filter, map и reduce для обработки данных

Задание: дан список чисел. Оставить только чётные, возвести их в квадрат и найти сумму.

Пример

from functools import reduce

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Фильтруем чётные
filtered = filter(lambda x: x % 2 == 0, numbers)
# Возводим в квадрат
squared = map(lambda x: x ** 2, filtered)
# Суммируем
result = reduce(lambda acc, x: acc + x, squared)

print(result)
Sum of squares of even numbers: 220

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

Пример 2. Декоратор для логирования вызовов функции

Задание: создать декоратор, который выводит имя функции, аргументы и результат.

Пример

import functools

def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Вызов {func.__name__} с args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"Результат: {result}")
        return result
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(3, 5)
Вызов add с args=(3, 5), kwargs={}
Результат: 8

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

Пример 3. Генератор чисел Фибоначчи с использованием yield

Задание: реализовать бесконечный генератор чисел Фибоначчи и вывести первые 10 чисел.

Пример

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
first_10 = [next(fib) for _ in range(10)]
print(first_10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Генераторы позволяют создавать последовательности, не храня их целиком в памяти. Это особенно полезно для больших или бесконечных данных. Каждый вызов next продолжает выполнение с места последнего yield.

Уроки Python с заданиями - comments

En
уроки python с заданиями (python)