Уроки языка 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.