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

Свой тип

Проблем

Цените се разбъркаха

Пишеш количката на онлайн магазин. Всеки продукт има име и цена, затова ги пазиш в два списъка: на индекс 0 са името и цената на първия продукт, на индекс 1 на втория.

Искаш най-скъпото най-отпред и разменяш цените:

names = ["Хляб", "Мляко", "Кафе"]
prices = [1.20, 2.10, 7.50]
# Най-скъпото отпред: разменяме първата и третата цена.
prices[0], prices[2] = prices[2], prices[0]
for i in range(len(names)):
print(f"{names[i]}: {prices[i]} €")
Изход
Хляб: 7.5 €
Мляко: 2.1 €
Кафе: 1.2 €

Хлябът вече струва 7,50 €. Цените са разменени, а имената не са. Двата списъка трябва да се пипат винаги заедно, но нищо не те задължава. Добавиш ли трети списък с бройките, ще трябва да помниш три.

Идеята

Един продукт, едно място

Името и цената не са отделни неща. Те са части от един продукт. Искаме тип „продукт“, както има тип int за число и str за текст.

Такъв тип си правиш сам. Той се казва клас:

class Product:
def __init__(self):
self.name = ""
self.price = 0.0
self.quantity = 0

self.name, self.price и self.quantity са полетата на продукта (в Python ги наричат и атрибути). Всеки продукт ще има свое име, своя цена и своя бройка.

В Python полетата не се изброяват отделно, а се създават с начална стойност в __init__: празен текст, 0.0 и 0. Реда def __init__(self): засега пиши точно така. Кодът под него се изпълнява за всеки нов продукт, а self е самият този продукт. Подробно ще го разберем в урока „Конструктори“.

Класът е само описание: „всеки продукт има име, цена и бройка“. Продукт още няма.

Името на класа започва с главна буква (Product), а имената на полетата с малка (name, price). Така веднага се вижда кое е тип и кое е стойност.

Идеята

Product(): от описанието към продукт

Конкретен продукт по класа се създава, като извикаш класа като функция: Product(). Той се казва обект. До полетата му стигаш с точка:

shop.py
class Product:
def __init__(self):
self.name = ""
self.price = 0.0
self.quantity = 0
bread = Product()
bread.name = "Хляб"
bread.price = 1.20
bread.quantity = 2
coffee = Product()
coffee.name = "Кафе"
coffee.price = 7.50
coffee.quantity = 1
print(f"{bread.name}: {bread.price * bread.quantity} €")
print(f"{coffee.name}: {coffee.price * coffee.quantity} €")
Изход
Хляб: 2.4 €
Кафе: 7.5 €
  • Класът стои над кода, който го ползва. Python чете файла отгоре надолу и трябва вече да го е видял. Във файла може да има няколко класа.
  • В Python няма new. Product() създава нов обект и го връща. Всеки обект има свои полета: хлябът и кафето не си пречат.
  • Пред bread тип не се пише, но обектът си има тип: type(bread) дава <class '__main__.Product'>.
  • bread.price е „цената на хляба“. Чете се и се записва като обикновена променлива.

Сега името и цената на продукта са на едно място. Разместиш ли продукта, те вървят заедно.

Пробвай

Нищо не е попълнено

Ако създадеш продукт и не попълниш нищо, какво има в полетата?

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

item = Product()
print(item.name)
print(item.price)
print(item.quantity)
Отговор

Пробвай

Кое липсва?

Какво трябва да пише на празното място, за да има нов продукт?

milk = ()
milk.name = "Мляко"
milk.price = 2.10

Виж го

Променливата сочи към обекта

Искаш промоция на кафето и правиш „копие“, за да му смениш цената:

coffee = Product()
coffee.price = 10
sale = coffee
sale.price = 8
print(coffee.price) # 8
Стъпка по стъпка, с думи
  1. Product() прави обекта: __init__ дава начални стойности на полетата. В coffee отива стрелка към него.
  2. По стрелката от coffee: цената на обекта става 10.
  3. sale = coffee копира стрелката, не обекта. Две стрелки, един обект.
  4. По стрелката от sale: цената в същия обект става 8.
  5. coffee сочи същия обект, затова печата 8.

Цената на кафето също стана 8. Причината е, че променливата coffee не е самият обект. Обектът стои отделно в паметта, а coffee пази къде е той: стрелка към обекта. Такава променлива се казва референция.

sale = coffee копира стрелката, а не обекта. Сега две стрелки сочат един обект, затова промяната през sale се вижда и през coffee.

Същото видя в урока за списъците: sale = prices даде второ име на същия списък. Списъкът също е обект.

Идеята

Отстъпката вече работи

В модул „Функции“ отстъпката не се получи: функцията получи цената, смени своя параметър и промяната изчезна. С обект е друго:

def discount(p, percent):
p.price = p.price * (100 - percent) / 100
coffee = Product()
coffee.price = 10
discount(coffee, 10)
print(coffee.price)
Изход
9.0

Параметърът пак е копие, но копие на стрелката. Тя сочи същия обект, затова промяната на p.price остава и след функцията.

По-точно, за любопитните

Ако функцията сложи в параметъра нов обект, тя мести само своето копие на стрелката. Обектът отвън не се променя:

def reset(p):
p = Product()
p.price = 0
coffee = Product()
coffee.price = 10
reset(coffee)
print(coffee.price)
Изход
10
Затова се казва, че Python предава всичко по един и същ начин, както =: параметърът получава стрелка към същия обект. Ако функцията насочи стрелката си другаде, това не стига до променливата отвън.

Идеята

None: стрелка за никъде

Променлива може да не сочи към никакъв обект. Тогава в нея е None: „нищо“. Ако опиташ да прочетеш поле през нея, няма обект, от който да го прочетеш:

gift = None
print(gift.name)

Програмата тръгва, но спира с грешка, когато стигне до реда:

Грешка
AttributeError: 'NoneType' object has no attribute 'name'

Това е най-честата грешка с обекти. Какво е „exception“ и как се хваща, ще видим в модул „Грешки и изключения“.

Списъкът също пази стрелки, а не самите продукти. [None] * 3 прави три места, които още не сочат към продукт. Всяко е None, докато не сложиш там Product():

cart = [None] * 3
print(cart[0] is None)
cart[0] = Product()
cart[0].name = "Хляб"
print(cart[0].name)
Изход
True
Хляб

Пробвай

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

Програмата трябва да отпечата „Хляб“ и „Мляко“. Вместо това печата „Мляко“ два пъти. На кой ред е грешката?

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

Упражнение

Твоите класове

Реши задачите в редактора си. Класът стои най-горе във файла, а обектите се създават в кода под него.

1. Книга. Клас Book с полета за заглавие и автор. Създай две книги и ги отпечатай.

Изход
Под игото от Иван Вазов
Тютюн от Димитър Димов

2. Кола. Клас SimpleCar с поле за скоростта. Създай кола (скоростта ѝ е 0), увеличи скоростта с 10 и я отпечатай. В урока „Поведение“ това ще стане метод на колата.

Изход
Скорост след ускоряване: 10

3. Количката. Количката от първата карта, този път с списък от три обекта Product: хляб 1,20 € (2 броя), мляко 2,10 € (1 брой), кафе 7,50 € (1 брой). Отпечатай сумата за всеки ред и общата сума. Всеки продукт първо се създава с Product(), а после влиза в списъка.

Изход
Хляб x2: 2.4 €
Мляко x1: 2.1 €
Кафе x1: 7.5 €
Общо: 12.0 €

4. Най-скъпото отпред. Подреди същата количка по цена, от най-скъпото към най-евтиното, с метода на мехурчето от урока за списъците. Този път разменяш цели продукти, затова името и цената вървят заедно.

Изход
Кафе: 7.5 €
Мляко: 2.1 €
Хляб: 1.2 €

Още задачи

  • Ученик. Клас Student с име и три оценки (три полета). Създай двама ученици и отпечатай средния успех на всеки.
  • Една книга, две имена. Създай книга, запиши я в две променливи и смени заглавието през втората. Отпечатай заглавието през първата. Обясни с думи защо е така.

Тест

Провери се

1. Каква е разликата между клас и обект?
2. sale = coffee sale.price = 8 Какво става с цената на coffee?
3. Какво има в списъка веднага след cart = [None] * 3?
4. Функция получава продукт като параметър и му сменя цената. Вижда ли се промяната след извикването?
5. gift = None Какво става при gift.name?

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