Урок повторяет две смежные темы — кортежи и методы списков — поэтому примеры идут двумя блоками: сначала кортежи и распаковка, затем изменяющие методы списков, и в конце — две прикладные задачи из занятия. Весь вывод в комментариях получен запуском кода.
Пример 1. Создание кортежа, индексация, неизменяемость
my_tuple = (1, 2, 3, "apple", True)
print(my_tuple[0]) # 1
print(my_tuple[-1]) # True
print(my_tuple[1:3]) # (2, 3)
try:
my_tuple[0] = 99
except TypeError as error:
print(f"TypeError: {error}")
# TypeError: 'tuple' object does not support item assignment
Что происходит: индексация и срезы у кортежа работают так же, как у списка — отличие только в том, что попытка изменить элемент по индексу сразу бросает TypeError. Неизменяемость — это и есть смысл существования кортежа: гарантия, что данные не поменяются где-то по пути.
Пример 2. Одноэлементный кортеж: запятая обязательна
not_tuple = (5)
real_tuple = (5,)
print(type(not_tuple), not_tuple) # <class 'int'> 5
print(type(real_tuple), real_tuple) # <class 'tuple'> (5,)
Что происходит: кортеж определяют запятые, а не круглые скобки — (5) это просто число 5 в скобках группировки, как в арифметическом выражении. Без запятой Python не видит кортеж.
Пример 3. Операции, count(), index()
a = (1, 2, 3)
b = (4, 5)
print(a + b) # (1, 2, 3, 4, 5) — конкатенация
print(a * 2) # (1, 2, 3, 1, 2, 3) — повторение
print(3 in a) # True
print(len(a + b)) # 5
numbers = (1, 2, 3, 2, 4, 2)
print(numbers.count(2)) # 3
print(numbers.index(2)) # 1 — индекс первого вхождения
try:
numbers.index(99)
except ValueError as error:
print(f"ValueError: {error}")
# ValueError: tuple.index(x): x not in tuple
Что происходит: + и * на кортежах создают новый кортеж, а не меняют исходные — по-другому и быть не может, они неизменяемы. count() и index() — единственные методы кортежа, оба доступны и у списка с тем же смыслом.
Пример 4. Упаковка и распаковка со звёздочкой
packed_tuple = 1, 2, 3 # скобки не обязательны — упаковка
x, y, z = packed_tuple # распаковка по трём переменным
print(x, y, z) # 1 2 3
data = (1, 2, 3, 4, 5)
first, *middle, last = data
print(first, middle, last) # 1 [2, 3, 4] 5
only_first, *rest = data
print(only_first, rest) # 1 [2, 3, 4, 5]
Что происходит: без звёздочки количество переменных обязано совпасть с количеством элементов. Звёздочка перед именем («жадная» переменная) забирает всё, что осталось, — причём именно списком, а не кортежем, даже если распаковывался кортеж. Звёздочка может стоять только у одной переменной в присваивании.
Пример 5. enumerate(): нумерация и правка списка по индексу
languages = ("Python", "Java", "C++")
for index, language in enumerate(languages, start=1):
print(f"{index}: {language}")
# 1: Python
# 2: Java
# 3: C++
numbers2 = [10, 20, 30, 40]
for index, value in enumerate(numbers2):
numbers2[index] = value * 10
print(numbers2) # [100, 200, 300, 400]
Что происходит: enumerate(iterable, start=1) отдаёт пары «номер, значение», начиная с 1 вместо стандартного 0 — удобно для человекочитаемых списков. Во втором фрагменте цикл идёт по enumerate, а меняет элементы напрямую по numbers2[index] — просто перебор без индекса для такой правки не годится.
Пример 6. append против extend против insert
fruits = ["apple", "banana"]
fruits.append("cherry")
print(fruits) # ['apple', 'banana', 'cherry']
fruits.append(["date", "fig"])
print(fruits) # ['apple', 'banana', 'cherry', ['date', 'fig']] — список как один элемент!
fruits2 = ["apple", "banana"]
fruits2.extend(["date", "fig"])
print(fruits2) # ['apple', 'banana', 'date', 'fig']
fruits2.insert(1, "kiwi")
print(fruits2) # ['apple', 'kiwi', 'banana', 'date', 'fig']
Что происходит: append(x) всегда добавляет ровно один элемент — даже если x сам является списком, он попадёт внутрь как есть, вложенным. extend(iterable) добавляет каждый элемент из iterable по отдельности. insert(index, value) вставляет один элемент на конкретную позицию, сдвигая остальные вправо — самая частая путаница в списке методов из справочника.
Пример 7. remove, pop, clear и их ошибки
items = ["a", "b", "c"]
items.remove("b")
print(items) # ['a', 'c']
try:
items.remove("z")
except ValueError as error:
print(f"ValueError: {error}")
# ValueError: list.remove(x): x not in list
popped = items.pop()
print(popped, items) # c ['a']
popped_first = items.pop(0)
print(popped_first, items) # a []
try:
items.pop(5)
except IndexError as error:
print(f"IndexError: {error}")
# IndexError: pop from empty list
items.clear()
print(items) # []
Что происходит: remove(value) ищет значение и удаляет первое совпадение — если такого значения нет, это ValueError, а не молчаливый пропуск. pop() без аргумента снимает последний элемент и возвращает его; pop(index) вне диапазона — это IndexError. clear() опустошает список на месте, в отличие от items = [], которое создало бы новый объект под тем же именем.
Пример 8. sort() против sorted(), reverse() против reversed()
numbers3 = [4, 1, 7, 2, 9]
result = numbers3.sort()
print(result, numbers3) # None [1, 2, 4, 7, 9] — sort() ничего не возвращает
numbers4 = [4, 1, 7, 2, 9]
new_list = sorted(numbers4)
print(new_list, numbers4) # [1, 2, 4, 7, 9] [4, 1, 7, 2, 9] — исходный список цел
words = ["banana", "kiwi", "apple", "fig"]
print(sorted(words, key=len)) # ['fig', 'kiwi', 'apple', 'banana']
print(sorted(words, key=len, reverse=True)) # ['banana', 'apple', 'kiwi', 'fig']
numbers4.reverse()
print(numbers4) # [9, 2, 7, 1, 4] — развернули на месте
print(list(reversed(new_list))) # [9, 7, 4, 2, 1] — reversed() вернул итератор, обернули в list
Что происходит: numbers3.sort() отсортировал список на месте и вернул None — если по ошибке написать numbers3 = numbers3.sort(), переменная станет None и весь список потеряется (это и есть ошибка №2 в топ-3 выше). sorted(), напротив, не трогает исходный список и возвращает новый — с ним такая ошибка невозможна. key=len сортирует по длине строки, а не по алфавиту.
Пример 9. min, max, sum с key
students = [("Alice", 25), ("Bob", 20), ("Carol", 30)]
print(min(students, key=lambda s: s[1])) # ('Bob', 20)
print(max(students, key=lambda s: s[1])) # ('Carol', 30)
print(sum(age for _, age in students)) # 75
Что происходит: min/max принимают тот же параметр key, что и sorted — критерий сравнения вычисляется отдельной функцией для каждого элемента. sum() напрямую с кортежами так не умеет — здесь возраст сначала извлекается генераторным выражением, а потом суммируется. Более глубокий разбор key с несколькими критериями — в уроке 42.
Пример 10. Отчёт по студентам
students2 = [
("Алексей", 80, 100),
("Мария", 50, 100),
("Дмитрий", 70, 100),
("Екатерина", 40, 100),
]
print(f"{'Имя':<15}{'Процент':>10}{'Статус':>12}")
print("-" * 37)
for name, points, max_points in students2:
percent = points / max_points * 100
status = "Незачёт" if percent < 50 else "Зачёт"
print(f"{name:<15}{percent:>10.2f}{status:>12}")
# Имя Процент Статус
# -------------------------------------
# Алексей 80.00 Зачёт
# Мария 50.00 Зачёт
# Дмитрий 70.00 Зачёт
# Екатерина 40.00 Незачёт
Что происходит: цикл распаковывает каждый кортеж-строку сразу в три переменные — это ровно та распаковка из примера 4, только внутри for. Форматирование {name:<15}/{percent:>10.2f} задаёт ширину колонки и выравнивание, собирая табличный вывод без внешних библиотек.
Пример 11. Анализ продаж
sales = [("Bread", 20), ("Milk", 15), ("Eggs", 30), ("Butter", 10), ("Cheese", 25)]
total = 0
top_product = sales[0]
for product, amount in sales:
total += amount
if amount > top_product[1]:
top_product = (product, amount)
print("Общее количество проданных товаров:", total)
# Общее количество проданных товаров: 100
print(f"Самый популярный товар: {top_product[0]} ({top_product[1]} шт.)")
# Самый популярный товар: Eggs (30 шт.)
print(sorted(sales, key=lambda item: -item[1]))
# [('Eggs', 30), ('Cheese', 25), ('Bread', 20), ('Milk', 15), ('Butter', 10)]
Что происходит: цикл вручную ищет максимум, сравнивая amount с текущим лидером и пересобирая top_product новым кортежем — кортежи неизменяемы, поэтому «обновление» всегда означает создание нового. Последняя строка показывает более короткий путь к тому же топу — sorted с key, тот же приём, что и в примере 9.
Что дальше
Проверьте себя в заданиях, а частые ошибки на этой теме собраны в разделе «Ошибки».