Към съдържанието
wannacode

Списък, който расте

Проблем

Количката е пълна

Пишеш количката на онлайн магазин и пазиш продуктите в списък, както в урока за масивите: всеки продукт на своя индекс. След края обаче не може да се пише: cart[3] = "Масло" спира програмата с IndexError: list assignment index out of range. Затова за четвърти продукт правиш нов, по-дълъг списък. А когато клиентът махне продукт, остава дупка:

cart = ["Хляб", "Мляко", "Кафе"]
# Четвърти продукт: cart[3] = "Масло" спира с IndexError,
# затова правим нов, по-дълъг списък.
cart = cart + ["Масло"]
# Клиентът маха млякото.
cart[1] = None
for item in cart:
print(item)
Изход
Хляб
None
Кафе
Масло
Сега всеки цикъл по количката трябва да прескача None. А броят продукти вече не е len(cart).

Това е много код за нещо, което всеки магазин прави постоянно.

Идеята

append и remove: списъкът се грижи сам

Списъкът има методи, които правят това вместо теб. Отвътре той пак пази елементите в масив, но копирането и местенето прави сам. Колекция, която расте и се свива така, се казва списък:

cart = []
cart.append("Хляб")
cart.append("Мляко")
cart.append("Кафе")
cart.append("Масло")
cart.remove("Мляко")
print(cart)
print("Продукти:", len(cart))
Изход
['Хляб', 'Кафе', 'Масло']
Продукти: 3
  • append добавя в края.
  • remove маха елемента с тази стойност и затваря дупката: „Кафе“ се мести на мястото на „Мляко“. Ако стойността е няколко пъти, маха първата.
  • len(cart) е колко елемента има в момента.
  • print печата целия списък в квадратни скоби, а текста в кавички.

Ето какво прави списъкът вътре. Колко места да има масивът, не избираш ти, а CPython, програмата, която изпълнява кода ти. Списък от 4 продукта в кода има точно 4 места, затова масивът се пълни на петия:

cart = ["Хляб", "Мляко", "Кафе", "Масло"]
cart.append("Сирене")
cart.remove("Мляко")
print(cart)
print("Продукти:", len(cart))
Стъпка по стъпка, с думи
  1. cart сочи обект list. Вътре има брояч ob_size и масив ob_item с 4 места.
  2. Продуктите от кода заемат местата един по един. Масивът е пълен: 4 от 4.
  3. Няма място. append заделя масив с 8 места и елементите отиват в него.
  4. ob_item вече сочи новия масив. „Сирене“ влиза на първото свободно място.
  5. remove маха „Мляко“, а всичко след него се мести с едно място наляво. Дупка не остава.
  6. Отвън се вижда само списък с четири продукта. Колко места има отвътре, решава CPython: езикът не го обещава.

Идеята

Какво има в списъка

Списъкът не пита какво слагаш в него. Текст и число могат да стоят едно до друго:

cart = []
cart.append("Хляб")
cart.append(5)
print(cart)
Изход
['Хляб', 5]

Това изглежда удобно, но грешката излиза по-късно, там, където очакваш текст. Например цикъл, който печата продуктите с главни букви:

cart = ["Хляб", 5]
for item in cart:
print(item.upper())
Грешка
AttributeError: 'int' object has no attribute 'upper'

„Хляб“ се отпечатва, а на числото програмата спира. Затова един списък пази един вид неща: само продукти, само цени.

Можеш да го запишеш с подсказка за типа (type hint): list[str] значи „списък от текст“.

cart: list[str] = []
cart.append("Хляб")
cart.append(5)
print(cart)
Изход
['Хляб', 5]

Python не проверява подсказката, докато програмата върви: числото пак влиза. Проверяват я редакторът и инструменти като mypy, преди да пуснеш програмата. В този урок подсказки няма, но ще ги срещаш в чужд код.

Идеята

Индекси: [i], insert, index, in

Списъкът има индекси като масива, от 0 до len(tasks) - 1, и те се пишат със същите [ ]:

tasks = ["Фактура за март", "Обаждане до банката", "Отчет за склада"]
print(tasks[0])
tasks[1] = "Среща в банката"
tasks.insert(0, "Кафе с екипа")
print(tasks)
print(tasks.index("Отчет за склада"))
print("Обаждане до банката" in tasks)
Изход
Фактура за март
['Кафе с екипа', 'Фактура за март', 'Среща в банката', 'Отчет за склада']
3
False
  • tasks[i] чете, tasks[i] = x заменя.
  • insert(i, x) вмъква на индекс i и мести останалите надясно.
  • index(x) търси и връща индекса, а x in tasks казва само дали го има.
По-точно, за любопитните
  • Индекс извън списъка спира програмата. Отрицателният индекс брои отзад: tasks[-1] е последната задача. А index на нещо, което го няма, също спира програмата, затова първо питай с in:
tasks = ["Фактура за март", "Обаждане до банката", "Отчет за склада"]
try:
print(tasks[3])
except IndexError as e:
print(f"IndexError: {e}")
print(tasks[-1])
try:
tasks.index("Заплати")
except ValueError as e:
print(f"ValueError: {e}")
Изход
IndexError: list index out of range
Отчет за склада
ValueError: 'Заплати' is not in list
  • in и index сравняват с ==. Затова намират текст, който е равен, а не само същия обект.
  • Вмъкването и махането отпред са бавни при голям списък: всички елементи след индекса се местят с едно място. За един милион елемента това е един милион премествания.

Пробвай

Кой е на ред?

Списъкът с чакащите при лекаря. Иван влиза, а Ана има час и се нарежда втора.

Какво ще отпечата програмата?

waiting = ["Иван", "Мария", "Петър"]
waiting.pop(0)
waiting.insert(1, "Ана")
print(waiting, len(waiting))
Отговор

Идеята

Обхождане

Списъкът се обхожда с for, когато ти трябват само елементите. Когато ти трябва и номерът, има enumerate:

cart = ["Хляб", "Кафе", "Масло"]
for item in cart:
print(f"- {item}")
for number, item in enumerate(cart, start=1):
print(f"{number}. {item}")
Изход
- Хляб
- Кафе
- Масло
1. Хляб
2. Кафе
3. Масло

enumerate(cart, start=1) дава на всяка стъпка две стойности: номера и елемента. Без start=1 номерата започват от 0, като индексите. Може и с индекс, както в урока за масивите, for i in range(len(cart)), но enumerate е обичайният начин в Python.

По-точно, за любопитните
  • Не махай елементи от списъка, докато го обхождаш с for. Програмата не спира, но прескача елементи:
cart = ["Хляб", "Хляб", "Мляко"]
for item in cart:
if item == "Хляб":
cart.remove(item)
print(cart)
Изход
['Хляб', 'Мляко']

Вторият „Хляб“ остана. remove премести всичко наляво, а цикълът продължи от следващия индекс и не го видя. Грешка няма, затова такъв пропуск се хваща трудно.

  • Ако трябва да махаш, обхождай с индекс отзад напред. Така махането не мести елементите, които още не си проверил:
cart = ["Хляб", "Хляб", "Мляко"]
for i in range(len(cart) - 1, -1, -1):
if cart[i] == "Хляб":
cart.pop(i)
print(cart)
Изход
['Мляко']
В модул „Модерен Python“ ще видиш по-кратък начин, който прави нов списък само с елементите, които искаш.

Идеята

Числа в списък

Списък от оценки е като списък от текст. Числото стои в списъка, както си е:

scores = [85, 64, 91]
total = 0
for score in scores:
total += score
print("Средно:", total / len(scores))
print("Средно:", sum(scores) / len(scores))
Изход
Средно: 80.0
Средно: 80.0
  • / дава дробно число, затова средното е 80.0, а не 80.
  • sum събира всички числа в списъка. Двата реда смятат едно и също.
По-точно, за любопитните
  • В Python и числото е обект. За малките числа, от -5 до 256, Python пази готови обекти, а за по-големите прави нови. Затова is, който пита „същият обект ли е?“, дава различни отговори:
a = int("256")
b = int("256")
c = int("257")
d = int("257")
print(a is b)
print(c is d)
print(c == d)
Изход
True
False
True

Числата и текста сравнявай с ==: той пита дали стойностите са равни. is е за None: if found is None.

Пробвай

Намери грешката

Залата пази заетите места в списък. Клиентът се отказва от място 2 и програмата трябва да отпечата [12, 7, 5]. Вместо това печата [12, 2, 5]. На кой ред е грешката?

Избери реда с грешката.

Идеята

Подреждане и списък, който не се променя

sort() подрежда списъка на място: текста по азбучен ред, числата по големина. sorted(...) не пипа нищо, а връща нов, подреден списък.

cities = ["София", "Пловдив", "Варна"]
cities.sort()
print(cities)
prices = (30, 5, 12)
print(sorted(prices))
Изход
['Варна', 'Пловдив', 'София']
[5, 12, 30]

Цените са в кръгли скоби: (30, 5, 12). Това е кортеж (tuple), списък, който е неизменим. Можеш да го четеш и обхождаш, но не можеш да добавяш, махаш или подреждаш в него:

days = ("пн", "вт", "ср")
days.append("чт")
Грешка
AttributeError: 'tuple' object has no attribute 'append'

Затова по-горе цените са подредени със sorted: той връща нов списък, а кортежът остава същият. Кортежът е за неща, които не се менят: дните от седмицата, двете координати на точка.

По-точно, за любопитните
  • Текстът се подрежда по кодовете на символите. Главните букви са преди малките, затова „Ябълка“ е преди „ананас“:
fruit = ["ананас", "Ябълка", "банан", "Круша"]
fruit.sort()
print(fruit)
Изход
['Круша', 'Ябълка', 'ананас', 'банан']
  • Как се подреждат твои обекти, например продукти по цена, ще видиш в урока „Класове в класове“.

Упражнение

Списъци в класове

Реши задачите в редактора си. Списъкът е поле на класа с _ отпред („не го пипай отвън“), а кодът под класовете вика само методите му.

1. Рафт с книги. Клас BookShelf с методи add_book(title), който слага книгата в края, и take_book(), който връща и маха първата книга (или None, ако рафтът е празен). Добави count() и __str__(). Сложи „Под игото“, „Тютюн“ и „Железният светилник“, вземи една книга и отпечатай останалите.

Изход
Взе: Под игото
Остават 2: ['Тютюн', 'Железният светилник']

2. Журнал на събитията. Клас Event с дата и описание и клас EventLog с методи add_event(date, description) и get_events_between(start, end), който връща списък със събитията между двете дати, включително. Датите са текст във вида 2026-03-31. В този вид текстът се подрежда като датите, затова <= и >= ги сравняват вярно. Добави четири събития: 2026-01-15 „Годишна инвентаризация“, 2026-03-31 „Отчет за първото тримесечие“, 2026-05-20 „Нов доставчик на кафе“, 2026-09-01 „Нов учебен сезон“. Отпечатай тези от 2026-03-01 до 2026-06-30.

Изход
2026-03-31 Отчет за първото тримесечие
2026-05-20 Нов доставчик на кафе

3. Тетрадка. Клас Note със заглавие и съдържание и клас Notebook с методи add_note, find_note(title) (връща бележката или None), delete_note(title) (връща True, ако е изтрил) и count(). Добави бележки „ООП“, „Колекции“ и „SQL“, намери „Колекции“, изтрий „ООП“ и провери, че вече я няма.

Изход
Колекции: списъкът расте сам
Изтрита: ООП
Бележки: 2
Няма бележка ООП.

4. Смятане наум. Клас Question с текст и верен отговор и клас MathGame с списък от въпроси, метод add_question и метод start(). start задава въпросите един по един, чете отговора и накрая казва колко са верните. Въпросите са 2 + 2, 3 * 4 и 15 - 9.

Вход
4
12
7
Изход
2 + 2 = Вярно!
3 * 4 = Вярно!
15 - 9 = Грешно, отговорът е 6.
Резултат: 2 от 3

Още задачи

  • Без повторения. Прочети 6 имена и ги събери в списък, като пропускаш тези, които вече са в него. При вход Иван, Мария, Иван, Ана, Мария, Петър изходът е ['Иван', 'Мария', 'Ана', 'Петър'].
  • Най-скъпият продукт. В списък има цени. Намери най-високата и на кой индекс е, без max.
  • Обърни списъка. Напиши функция, която връща нов списък със същите елементи в обратен ред. Не променяй първоначалния.
  • Списък за пазаруване. Чети команди add мляко, remove мляко, list и end и пази списъка между тях. Командата за триене на продукт, който го няма, казва „Няма такъв продукт.“

Тест

Провери се

1. С какво append и remove са по-удобни от писането по индекс?
2. cart досега пази само текст. Какво прави cart.append(5)?
3. seats е [12, 2, 7, 5]. Какво остава след seats.pop(2)?
4. В tasks има 3 задачи. Какво прави print(tasks[3])?
5. days = ("пн", "вт"). Какво става при days.append("ср")?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·