Поведение
Проблем
Всеки тегли по своему
Банката има клас Account със собственик и баланс. Парите се теглят на две места в програмата. Банкоматът проверява дали има достатъчно пари. Плащането с карта е писано от друг човек и не проверява:
account = Account("Мария", 100)
# Банкоматът проверява дали има достатъчно пари.if account.balance >= 70: account.balance -= 70
# Плащането с карта е писано от друг човек и проверка няма.account.balance -= 50
print(account.balance)-20Сметката е на минус. Правилото „не можеш да изтеглиш повече, отколкото имаш“ е важно, а е написано (или забравено) поотделно на всяко място, където се тегли.
Идеята
Методите живеят при данните
Логиката за теглене е част от сметката, затова я сложи в класа Account. Функция, написана в класа, е метод на обекта:
class Account: def __init__(self, owner, balance): self.owner = owner self.balance = balance
def deposit(self, amount): self.balance += amount
def withdraw(self, amount): if amount <= self.balance: self.balance -= amount else: print(f"Няма достатъчно пари: {self.owner}")
account = Account("Мария", 100)account.withdraw(70)account.withdraw(50)account.deposit(20)print(account.balance)Няма достатъчно пари: Мария50- Методът е
def, отместен в класа. Първият му параметър еself, както при__init__. - Вика се през обект и точка:
account.withdraw(70), както полетата. Python слагаaccountвselfсам, затова подаваш само сумата. - Вътре
self.balanceе балансът на тази сметка, на обекта пред точката.self.е задължително: без негоbalanceне е полето. - Проверката е написана веднъж. Който тегли през метода, минава през нея.
maria = Account("Мария", 100)peter = Account("Петър", 40)maria.withdraw(70)peter.withdraw(70)print(maria.balance, peter.balance)Стъпка по стъпка, с думи
- Две сметки, два обекта. Всеки има свой баланс.
- maria.withdraw(70): в self отива стрелка към обекта пред точката.
- В метода self.balance е балансът на Мария: 100 - 70 = 30.
- Същият метод, друг обект: сега self сочи Петър.
- 70 е повече от 40: методът печата отказ и балансът на Петър не се пипа.
- Кодът на метода е един. Полетата са на обекта, през който го викаш.
Плащането с карта вече не може да „забрави“ проверката, ако тегли с withdraw. Може обаче още да пише направо в полето. Как да го спрем, ще видим в урока „Капсулация“.
Идеята
Метод или функция
Функциите от модул „Функции“ стоят извън класа. Те получават всичко като параметри и нямат свой обект. Методът работи с полетата на обекта, през който е извикан:
def apply_discount(price, percent): return price * (100 - percent) / 100
price = apply_discount(10, 10)account.withdraw(70)Метод на обекта не можеш да извикаш през името на класа. Не е ясно от чия сметка да тегли:
Account.withdraw(70)TypeError: Account.withdraw() missing 1 required positional argument: 'amount'70 отиде в self, а за amount не остана нищо. Всъщност account.withdraw(70) е съкратено от Account.withdraw(account, 70): обектът пред точката става първият аргумент. Методи на самия клас, без обект, също има. Ще ги видим в урока „Общото за всички: атрибути на класа“.
Пробвай
Чия сметка?
Какво ще отпечата програмата?
maria = Account("Мария", 100)peter = Account("Петър", 40)maria.deposit(30)peter.withdraw(10)print(maria.balance, peter.balance)Всеки обект има свой баланс. maria.deposit(30) сменя само баланса на Мария, а peter.withdraw(10) само на Петър. Петър има 40, затова тегленето на 10 минава.
Пробвай
Намери грешката
account.withdraw(30) спира програмата с грешка. На кой ред е грешката?Избери реда с грешката.
Без self. името balance е обикновена променлива, а такава в метода няма:
NameError: name 'balance' is not definedПолето е self.balance. Следващият ред го пише правилно, затова грешката е само в проверката.
Идеята
Методът може и да отговаря
Метод на обекта може да връща резултат, както функциите от модул „Функции“. Продуктът знае цената и бройката си, затова може сам да каже колко струва:
class Product: # полетата и конструкторът, както преди
def total(self): return self.price * self.quantity
cart = [ Product("Хляб", 1.20, 2), Product("Мляко", 2.10, 1), Product("Кафе", 7.50, 1),]amount = 0for p in cart: amount += p.total()print(f"Общо: {amount} €")Общо: 12.0 €Сравни с първия урок от модула: там сумата се смяташе в кода под класа от полетата. Сега този код само пита всеки продукт. Ако утре се добави отстъпка за количество, променяш само total().
Идеята
Как изглежда обектът като текст?
Отпечаташ ли продукт направо, излиза нещо безполезно:
coffee = Product("Кафе", 7.50, 1)print(coffee)<__main__.Product object at 0x7f3a2c1b2d10>Python печата името на класа и адреса на обекта в паметта, който е различен при всяко пускане. По него не разбираш нищо за продукта.
Класът може да каже как да се показва. Методът се казва __str__ и връща текст:
class Product: # полетата и конструкторът, както преди
def total(self): return self.price * self.quantity
def __str__(self): return f"{self.name} x{self.quantity}: {self.total()} €"
coffee = Product("Кафе", 7.50, 1)bread = Product("Хляб", 1.20, 2)print(coffee)print(f"В количката: {bread}")Кафе x1: 7.5 €В количката: Хляб x2: 2.4 €Не го викаш сам. print(coffee) и f-низът f"В количката: {bread}" го викат вместо теб, когато трябва обектът да стане текст. Двете долни черти отпред и отзад значат точно това: методът е за Python, а той го вика сам в точния момент, както __init__ при създаването.
С + обаче се долепя само текст до текст. "В количката: " + bread спира с
TypeError: can only concatenate str (not "Product") to strПолзвай f-низ или "В количката: " + str(bread): str() също вика __str__.
По-точно, за любопитните
Освен __str__ има и __repr__: текстът за програмиста. Него ползва списък, когато го печаташ, затова без __repr__ print(cart) пак дава <__main__.Product object at …>. По договорка __repr__ изглежда като кода, който създава обекта:
class Product: # полетата и конструкторът, както преди
def __repr__(self): return f'Product("{self.name}", {self.price}, {self.quantity})'
cart = [Product("Кафе", 7.50, 1), Product("Хляб", 1.20, 2)]print(cart)[Product("Кафе", 7.5, 1), Product("Хляб", 1.2, 2)]Пробвай
Книга в изречение
Какво ще отпечата програмата?
class Book: def __init__(self, title, author): self.title = title self.author = author
def __str__(self): return f"{self.title} от {self.author}"
book = Book("Тютюн", "Димитър Димов")print(f"Чета: {book}")f"Чета: {book}" трябва да направи book на текст. Затова се вика __str__ на книгата, а той връща заглавието и автора.Упражнение
Обекти, които правят нещо
Реши задачите в редактора си. Вземи класовете от миналите уроци и им добави методи.
Person: методintroduce(), който печата името и възрастта.SimpleCar: методaccelerate(), който увеличава скоростта с 10. Сега кодът под класа не пипа скоростта направо.DanceMove: методperform(), който „изпълнява“ стъпката.Book: методread(), който печата „Прочитане на [заглавие] от [автор]“.
Създай по един обект от всеки клас и извикай методите му:
Здравей, аз съм Мария и съм на 34 г.Скорост след ускоряване: 10Изпълнявам moonwalk за 5 секундиПрочитане на Под игото от Иван ВазовУпражнение
Кола с гориво
Клас Car с марка, скорост, гориво (в литри) и изминати километри. Конструкторът получава марката и горивото, скоростта и километрите започват от 0.
accelerate()увеличава скоростта с 10.refuel(liters)долива гориво, но резервоарът побира най-много 50 литра.drive(distance)харчи по 6 литра на 100 км и добавя разстоянието към километрите. Ако горивото не стига, печата съобщение и колата не тръгва.__str__()връща марката, километрите и горивото (горивото с един знак след точката:{self.fuel:.1f}).
car = Car("Toyota", 20)car.accelerate()car.accelerate()print(f"Скорост: {car.speed}")car.drive(100)print(car)car.drive(300)car.refuel(40)print(car)car.drive(300)print(car)Скорост: 20Toyota: 100 км, 14.0 лНяма достатъчно гориво за 300 кмToyota: 100 км, 50.0 лToyota: 400 км, 32.0 лdistance * 6 / 100. В Python / винаги дава дробно число, затова за 30 км излиза 1,8 литра.Още задачи
- Отстъпка. Добави към
Productметодapply_discount(percent), който намалява цената на продукта. Сравни го със функцията от модул „Функции“, която връщаше нова цена. - Сметка с история. Добави към
Accountброяч на операциите: всяко внасяне и успешно теглене го увеличава с 1. Метод__str__()връща собственика, баланса и броя на операциите.
Тест