💻 Примеры из повторения

⚡ Кортежи и списки на одном экране

point = (10, 20)          # кортеж — неизменяемый
first, *rest = [1, 2, 3, 4]  # распаковка с остатком

items = [3, 1, 2]
items.sort()               # меняет items, возвращает None
new_items = sorted(items)  # не меняет items, возвращает новый список
print(point, first, rest, items, new_items)
КонструкцияЧто делаетМеняет исходное?
tup.count(x) / .index(x)сколько раз / где первое вхождениенет — кортеж неизменяем
lst.append() / .extend()добавить один элемент / несколькода, на месте
lst.sort() / lst.reverse()отсортировать / развернуть на местеда, возвращают None
sorted(lst) / reversed(lst)то же, но без изменения исходногонет, возвращают новый объект
Топ-3 ошибки: (5) вместо (5,) для кортежа из одного элемента · items = items.sort() — запись None вместо списка · распаковка с числом переменных, не совпадающим с длиной кортежа.

Урок повторяет две смежные темы — кортежи и методы списков — поэтому примеры идут двумя блоками: сначала кортежи и распаковка, затем изменяющие методы списков, и в конце — две прикладные задачи из занятия. Весь вывод в комментариях получен запуском кода.

Пример 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.

Что дальше

Проверьте себя в заданиях, а частые ошибки на этой теме собраны в разделе «Ошибки».