Свой тип
Проблем
Цените се разбъркаха
Пишеш количката на онлайн магазин. Всеки продукт има име и цена, затова ги пазиш в два списъка: на индекс 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 = 0self.name, self.price и self.quantity са полетата на продукта (в Python ги наричат и атрибути). Всеки продукт ще има свое име, своя цена и своя бройка.
В Python полетата не се изброяват отделно, а се създават с начална стойност в __init__: празен текст, 0.0 и 0. Реда def __init__(self): засега пиши точно така. Кодът под него се изпълнява за всеки нов продукт, а self е самият този продукт. Подробно ще го разберем в урока „Конструктори“.
Класът е само описание: „всеки продукт има име, цена и бройка“. Продукт още няма.
Името на класа започва с главна буква (Product), а имената на полетата с малка (name, price). Така веднага се вижда кое е тип и кое е стойност.
Идеята
Product(): от описанието към продукт
Конкретен продукт по класа се създава, като извикаш класа като функция: Product(). Той се казва обект. До полетата му стигаш с точка:
class Product: def __init__(self): self.name = "" self.price = 0.0 self.quantity = 0
bread = Product()bread.name = "Хляб"bread.price = 1.20bread.quantity = 2
coffee = Product()coffee.name = "Кафе"coffee.price = 7.50coffee.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)В Python полетата нямат стойност „по подразбиране“. Имат точно това, което __init__ им е дал: празен текст (печата се като празен ред), 0.0 и 0. Поле, което __init__ не създава, просто го няма: item.color спира програмата с AttributeError: 'Product' object has no attribute 'color'.
Пробвай
Кое липсва?
Какво трябва да пише на празното място, за да има нов продукт?
milk = ()
milk.name = "Мляко"
milk.price = 2.10Product: името на класа, извикано със скоби, създава обект. new в Python няма.Виж го
Променливата сочи към обекта
Искаш промоция на кафето и правиш „копие“, за да му смениш цената:
coffee = Product()coffee.price = 10sale = coffeesale.price = 8print(coffee.price) # 8Стъпка по стъпка, с думи
- Product() прави обекта: __init__ дава начални стойности на полетата. В coffee отива стрелка към него.
- По стрелката от coffee: цената на обекта става 10.
- sale = coffee копира стрелката, не обекта. Две стрелки, един обект.
- По стрелката от sale: цената в същия обект става 8.
- 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 = 10discount(coffee, 10)print(coffee.price)9.0Параметърът пак е копие, но копие на стрелката. Тя сочи същия обект, затова промяната на p.price остава и след функцията.
По-точно, за любопитните
Ако функцията сложи в параметъра нов обект, тя мести само своето копие на стрелката. Обектът отвън не се променя:
def reset(p): p = Product() p.price = 0
coffee = Product()coffee.price = 10reset(coffee)print(coffee.price)10=: параметърът получава стрелка към същия обект. Ако функцията насочи стрелката си другаде, това не стига до променливата отвън.Идеята
None: стрелка за никъде
Променлива може да не сочи към никакъв обект. Тогава в нея е None: „нищо“. Ако опиташ да прочетеш поле през нея, няма обект, от който да го прочетеш:
gift = Noneprint(gift.name)Програмата тръгва, но спира с грешка, когато стигне до реда:
AttributeError: 'NoneType' object has no attribute 'name'Това е най-честата грешка с обекти. Какво е „exception“ и как се хваща, ще видим в модул „Грешки и изключения“.
Списъкът също пази стрелки, а не самите продукти. [None] * 3 прави три места, които още не сочат към продукт. Всяко е None, докато не сложиш там Product():
cart = [None] * 3print(cart[0] is None)cart[0] = Product()cart[0].name = "Хляб"print(cart[0].name)TrueХлябПробвай
Намери грешката
Програмата трябва да отпечата „Хляб“ и „Мляко“. Вместо това печата „Мляко“ два пъти. На кой ред е грешката?
Избери реда с грешката.
milk = bread не прави нов продукт. Сега milk и bread сочат един и същ обект, и името „Мляко“ замества „Хляб“. Поправката е milk = Product().
Упражнение
Твоите класове
1. Книга. Клас Book с полета за заглавие и автор. Създай две книги и ги отпечатай.
Под игото от Иван ВазовТютюн от Димитър Димов2. Кола. Клас SimpleCar с поле за скоростта. Създай кола (скоростта ѝ е 0), увеличи скоростта с 10 и я отпечатай. В урока „Поведение“ това ще стане метод на колата.
Скорост след ускоряване: 103. Количката. Количката от първата карта, този път с списък от три обекта 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с име и три оценки (три полета). Създай двама ученици и отпечатай средния успех на всеки. - Една книга, две имена. Създай книга, запиши я в две променливи и смени заглавието през втората. Отпечатай заглавието през първата. Обясни с думи защо е така.
Тест