💻 Рабочие примеры

⚡ Шпаргалка по методам списков

fruits = ["apple", "banana"]
fruits.append("cherry")
fruits.extend(["date", "fig"])
fruits.sort()
print(fruits)                     # ['apple', 'banana', 'cherry', 'date', 'fig']
print(sorted(fruits, reverse=True))  # ['fig', 'date', 'cherry', 'banana', 'apple']
КонструкцияМеняет список?Возвращает
append(x) / extend(it)ДаNone
sort() / reverse()ДаNone
sorted() / reversed()Нетновый список / итератор
pop(i)Даудалённый элемент
remove(x) / index(x)только removeNone / индекс
Топ-3 ошибки: numbers = numbers.sort() превращает список в None · append([4, 5]) вместо extend([4, 5]) добавляет вложенный список · remove()/index() на отсутствующем значении роняют ValueError.

Примеры идут от базового добавления/удаления элементов к сортировке по ключу и агрегатам. Весь вывод в комментариях получен реальным запуском кода, а не подобран по памяти.

Пример 1. append(), extend(), insert() — три способа что-то добавить

Три метода добавления решают разные задачи: один объект в конец, несколько элементов в конец, один объект в середину. Разница особенно заметна на append() со списком внутри — он не «разворачивает» аргумент.

numbers = [1, 2, 3]
numbers.append(4)
print(numbers)              # [1, 2, 3, 4]

numbers.extend([5, 6])
print(numbers)              # [1, 2, 3, 4, 5, 6]

numbers.insert(1, "new")
print(numbers)              # [1, 'new', 2, 3, 4, 5, 6]

nested = [1, 2, 3]
nested.append([4, 5])
print(nested)                # [1, 2, 3, [4, 5]]

Что происходит: append(4) добавляет ровно один объект. extend([5, 6]) перебирает список-аргумент и добавляет его элементы по одному. А вот nested.append([4, 5]) кладёт список [4, 5] как единый вложенный элемент — это не ошибка, а прямое следствие того, что append() никогда не разбирает аргумент на части.

Пример 2. Ловушка: append() вместо extend() на практике

Самая частая путаница — попытка «дописать список заказов» через append(). Код не падает, но результат и длина списка оказываются не такими, как ожидалось.

orders = [("apple", 3), ("banana", 5)]
new_orders = [("cherry", 2), ("date", 7)]

# неправильно: append кладёт весь список одним элементом
wrong = list(orders)
wrong.append(new_orders)
print(wrong)
# [('apple', 3), ('banana', 5), [('cherry', 2), ('date', 7)]]
print(len(wrong))  # 3 — а не 4!

# правильно: extend добавляет элементы по одному
right = list(orders)
right.extend(new_orders)
print(right)
# [('apple', 3), ('banana', 5), ('cherry', 2), ('date', 7)]
print(len(right))  # 4

Разбор: после append(new_orders) в списке 3 элемента, а не 4 — третий элемент сам оказался списком. Проверка простая: если после добавления ожидаете рост длины ровно на количество новых записей — нужен extend(); если нужен один новый «пункт» (даже если это список) — append().

Пример 3. insert() с отрицательным и «слишком большим» индексом

Индекс в insert() не обязан попадать в границы списка — Python аккуратно приводит его к ближайшей допустимой позиции вместо ошибки.

letters = ["a", "b", "c", "d"]
letters.insert(-1, "X")
print(letters)      # ['a', 'b', 'c', 'X', 'd']  — перед последним элементом

letters.insert(100, "END")
print(letters)      # ['a', 'b', 'c', 'X', 'd', 'END']  — индекс > len -> в конец

letters.insert(-100, "START")
print(letters)      # ['START', 'a', 'b', 'c', 'X', 'd', 'END']  — индекс < -len -> в начало

Что важно: insert(-1, x) вставляет перед последним элементом, а не после — это частая ошибка при попытке «дописать в конец». Индексы за пределами диапазона не вызывают IndexError: слишком большой уводит вставку в конец списка, слишком маленький (по модулю) — в начало.

Пример 4. remove(), pop(), clear() — что удаляется и что возвращается

Три метода удаления отличаются тем, что именно они принимают — значение или индекс — и что возвращают.

fruits = ["apple", "banana", "cherry", "banana"]
fruits.remove("banana")
print(fruits)          # ['apple', 'cherry', 'banana']

last_fruit = fruits.pop()
print(last_fruit)      # banana
print(fruits)          # ['apple', 'cherry']

try:
    fruits.remove("kiwi")
except ValueError as error:
    print("ValueError:", error)
    # ValueError: list.remove(x): x not in list

fruits.clear()
print(fruits)           # []

Что происходит: remove("banana") удаляет только первое совпадение по значению. pop() без аргумента забирает последний элемент и возвращает его — это удобно, когда удалённое значение нужно использовать дальше. Оба метода возвращают None, кроме pop(); и оба падают, если элемента/индекса нет.

Пример 5. remove() vs pop() vs del vs срез — четыре способа убрать элемент

В реальном коде удаление элемента можно записать четырьмя разными способами. У них разный интерфейс и разный побочный эффект на исходный список.

source = ["a", "b", "c", "d", "e"]

by_remove = list(source)
by_remove.remove("c")
print(by_remove)        # ['a', 'b', 'd', 'e']  — по значению

by_pop = list(source)
removed = by_pop.pop(2)
print(by_pop, removed)  # ['a', 'b', 'd', 'e'] c  — по индексу, элемент возвращается

by_del = list(source)
del by_del[2]
print(by_del)            # ['a', 'b', 'd', 'e']  — по индексу, ничего не возвращает

by_slice = list(source)
tail = by_slice[3:]
print(tail)               # ['d', 'e']            — срез создаёт НОВЫЙ список
print(by_slice)           # ['a', 'b', 'c', 'd', 'e']  — оригинал не тронут

Разбор: remove() ищет по значению и падает с ValueError, если его нет. pop(i) и del list[i] работают по индексу и падают с IndexError, но только pop() отдаёт удалённый элемент. Срез [3:] вообще ничего не удаляет — он строит новый список из части старого, поэтому by_slice остался нетронутым.

⚠️ Проверить по документации: оператор del не входит в материалы этого урока (там разбирались только методы списка), но часто встречается рядом с ними в реальном коде. Синтаксис и поведение сверьте по разделу The del statement.

Пример 6. index() и count() — поиск и подсчёт

Оба метода читают список, не изменяя его. index() ищет позицию, count() — количество совпадений.

fruits = ["apple", "banana", "cherry", "banana", "cherry"]
print(fruits.index("banana"))        # 1
print(fruits.index("banana", 2))     # 3 — поиск начиная с индекса 2
print(fruits.count("cherry"))        # 2
print(fruits.count("orange"))        # 0 — count не падает на отсутствующем значении

try:
    fruits.index("kiwi")
except ValueError as error:
    print("ValueError:", error)
    # ValueError: 'kiwi' is not in list

Что происходит: у "banana" два вхождения — на индексах 1 и 3. Второй вызов index("banana", 2) ищет только начиная с индекса 2 и поэтому находит второе вхождение. count(), в отличие от index(), никогда не падает — на отсутствующем значении просто возвращает 0.

Пример 7. sort() меняет список на месте, sorted() — нет: почему это важно

Разница между «на месте» и «новый список» становится критичной внутри функций: функция, которая незаметно мутирует переданный ей список, — источник трудноуловимых багов в остальном коде.

def top_three(items):
    # sorted() не трогает items вызывающего кода
    return sorted(items, reverse=True)[:3]


scores = [4, 9, 1, 7, 3]
best = top_three(scores)
print(best)     # [9, 7, 4]
print(scores)   # [4, 9, 1, 7, 3]  — исходный список не изменился

numbers = [4, 1, 7, 2, 9]
numbers.sort()
print(numbers)   # [1, 2, 4, 7, 9]
result = numbers.sort()
print(result)    # None — sort() ничего не возвращает

Что важно: top_three() использует sorted() именно потому, что не должна портить список, который ей передали, — вызывающий код по-прежнему видит scores в исходном порядке. А numbers.sort() возвращает None: если написать numbers = numbers.sort(), список будет потерян.

Пример 8. key= для сортировки: строки без учёта регистра, словари, кортежи

key — функция, которая вычисляет значение для сравнения для каждого элемента. Один и тот же параметр работает и для простых строк, и для пар «ключ — значение» словаря.

words = ["Banana", "apple", "kiwi", "Cherry"]
print(sorted(words))              # ['Banana', 'Cherry', 'apple', 'kiwi'] — по Unicode
print(sorted(words, key=str.lower))
# ['apple', 'Banana', 'Cherry', 'kiwi'] — без учёта регистра

prices = {"apple": 120, "banana": 45, "cherry": 300}
print(sorted(prices.items(), key=lambda item: item[1]))
# [('banana', 45), ('apple', 120), ('cherry', 300)]

people = [("Alice", 25), ("Bob", 20), ("Carol", 30)]
print(sorted(people, key=lambda person: person[1]))
# [('Bob', 20), ('Alice', 25), ('Carol', 30)]

Разбор: без key заглавные буквы сортируются раньше строчных (Unicode-порядок), поэтому "Banana" оказывается перед "apple". key=str.lower убирает эту зависимость от регистра. prices.items() превращает словарь в список кортежей (ключ, значение), и key=lambda item: item[1] сортирует именно по значению, а не по ключу.

Пример 9. Устойчивость сортировки и сортировка по нескольким ключам

Сортировка в Python устойчива (stable): элементы с одинаковым ключом сохраняют исходный взаимный порядок. На этом свойстве строится сортировка по нескольким критериям через кортеж.

students = [("Bob", 4), ("Alice", 5), ("Ann", 4), ("Zoe", 5)]
by_grade = sorted(students, key=lambda s: s[1], reverse=True)
print(by_grade)
# [('Alice', 5), ('Zoe', 5), ('Bob', 4), ('Ann', 4)]
# внутри одинаковой оценки порядок из исходного списка сохранён

orders = [("banana", 2, 45), ("apple", 1, 120), ("apple", 3, 120), ("banana", 1, 45)]
by_name_then_qty = sorted(orders, key=lambda o: (o[0], -o[1]))
print(by_name_then_qty)
# [('apple', 3, 120), ('apple', 1, 120), ('banana', 2, 45), ('banana', 1, 45)]
# сначала по названию, внутри одного названия — по количеству по убыванию

Что происходит: у Alice и Zoe одинаковая оценка 5, но Alice шла раньше в исходном списке — и осталась раньше в результате. Во втором примере key возвращает кортеж (название, -количество): сортировка идёт сначала по первому полю, а при совпадении — по второму; минус переворачивает направление только для количества, не трогая порядок по названию.

Пример 10. min(), max() с key= и default=

min() и max() принимают тот же параметр key, что и сортировка, а параметр default спасает от падения на пустой коллекции.

words = ["apple", "banana", "kiwi", "grape"]
print(min(words, key=len))    # kiwi
print(max(words, key=len))    # banana

empty = []
print(min(empty, default="список пуст"))   # список пуст

try:
    min(empty)
except ValueError as error:
    print("ValueError:", error)
    # ValueError: min() arg is an empty sequence

tuples = [(3, 6), (1, 7, 9), (12, 5), (1, 3, 7)]
tuples.sort(key=max, reverse=True)
print(tuples)
# [(12, 5), (1, 7, 9), (1, 3, 7), (3, 6)]

Что происходит: без default вызов min()/max() на пустой коллекции — это ValueError, а не None и не 0. В последнем примере key=max — необычный, но рабочий приём: ключом сортировки становится максимальный элемент каждого вложенного кортежа.

Пример 11. sum() со start и ограничение по типам

sum() складывает элементы итерируемого объекта, начиная со значения start (по умолчанию 0). Работает с числами, но не со строками.

scores = [4, 1, 7, 2, 9]
print(sum(scores))          # 23
print(sum(scores, 1000))    # 1023 — start прибавляется к сумме

prices = (19.99, 5.50, 2.25)
print(sum(prices))          # 27.74 — sum() работает и с кортежами

try:
    sum(["a", "b", "c"])
except TypeError as error:
    print("TypeError:", error)
    # TypeError: unsupported operand type(s) for +: 'int' and 'str'

Разбор: start=1000 — не порог и не ограничение, а именно начальное значение, к которому прибавляются элементы. Для строк sum() не работает — Python явно требует "".join(...) вместо сложения строк через + в цикле сложения.

Пример 12. reversed() как итератор и способы скопировать список

reversed(), в отличие от .reverse(), не трогает исходный список и возвращает итератор — его нужно обернуть в list() или "".join(), чтобы увидеть элементы.

letters = "Python"
reversed_obj = reversed(letters)
print(reversed_obj)          # <reversed object at 0x...>
print("".join(reversed_obj)) # nohtyP

numbers = [1, 2, 3, 4]
print(list(reversed(numbers)))  # [4, 3, 2, 1]
print(numbers)                   # [1, 2, 3, 4] — оригинал не тронут

original = [1, 2, 3]
copy_a = original.copy()
copy_b = original[:]
copy_a.append(4)
copy_b.append(5)
print(original)   # [1, 2, 3]         — независим от копий
print(copy_a)      # [1, 2, 3, 4]
print(copy_b)       # [1, 2, 3, 5]

Что важно: list.copy() и срез [:] — два равнозначных способа получить независимую копию списка: изменения в copy_a и copy_b никак не повлияли на original. Без копирования (copy_a = original) обе переменные указывали бы на один и тот же список.

⚠️ Проверить по документации: list.copy() в материалах урока не разбирался. Это поверхностная (shallow) копия — вложенные списки внутри неё всё равно останутся общими. Подробности — в разделе copy — Shallow and deep copy operations.

Что делать дальше

Если методы count()/index() кажутся смутно знакомыми — они уже встречались у кортежей в уроке 24. Списки кортежей из урока 25 — хороший полигон для sort(key=...). Дальше переходите к заданиям; если что-то не запустилось так, как ожидалось — загляните в типичные ошибки или в исходные примеры репозитория.