Абстрактни класове
Проблем
Нула квадратни метра
Пишеш програма за плочки на тераса. Терасата е от три части: правоъгълник 4 × 3 м, кръгла част с радиус 1 м и триъгълник с основа 2 м и височина 3 м. Има родител Shape с area(), който връща 0, а всяка фигура го презаписва.
Колегата, който е писал Triangle, е забравил:
class Triangle(Shape): def __init__(self, base, height): self._base = base self._height = heightterrace = [Rectangle(4, 3), Circle(1), Triangle(2, 3)]total = 0for s in terrace: total += s.area()print(f"Плочки: {total:.2f} м²")Плочки: 15.14 м²Трябва 18,14 м². Програмата не дава грешка, а поръчката е с 3 м² по-малко. Нулата на родителя е скрила липсващия метод.
А какво е Shape()? Фигура без форма. Няма смисъл да съществува такъв обект.
Идеята
Абстрактен клас: обещание, което наследниците изпълняват
Shape не е истинска фигура. Той казва само „всяка фигура има лице“. Затова го направи абстрактен:
from abc import ABC, abstractmethod
class Shape(ABC): @abstractmethod def area(self): passABC(Abstract Base Class, „абстрактен базов клас“) иabstractmethodидват от модулаabc, затова най-горе еfrom abc import ABC, abstractmethod.- Абстрактният метод е с
@abstractmethodотгоре. Тялото му е самоpass, „нищо“. Не можеш да дадеш разумно лице на „фигура изобщо“, затова не даваш никакво. - Абстрактният клас наследява
ABCи не може да има обекти:Shape()спира програмата сTypeError. - Всеки наследник трябва да напише метода. Иначе и от него не се правят обекти.
Python няма компилатор, който да прегледа програмата предварително. Проверката е при създаването на обекта. Сега забравеният Triangle спира програмата още на реда, на който се прави терасата, преди да се сметне грешна поръчка:
„TypeError: Can't instantiate abstract class Triangle without an implementation for abstract method 'area'“.
Програма, която спира с ясна грешка, е по-добра от тиха грешка в поръчката.По-точно, за любопитните
- Наследник, който не пише абстрактния метод, остава абстрактен сам, без да го казваш. Обещанието минава към неговите наследници.
- Наследник на
ABCбез нито един абстрактен метод не е абстрактен: обекти от него се правят. Забраната идва от абстрактните методи. @abstractmethodбезABCне спира нищо:Shape()се създава, аarea()връщаNone.
Виж го
Триъгълникът изпълнява обещанието
Добави метода в Triangle:
class Triangle(Shape): # ... __init__ както преди
def area(self): return self._base * self._height / 2terrace = [Rectangle(4, 3), Circle(1), Triangle(2, 3)]total = 0for s in terrace: total += s.area()print(f"Плочки: {total:.2f} м²")Плочки: 18.14 м²Цикълът е същият. Списъкът пази фигури от три класа, а s.area() вика метода на всяка. Полиморфизмът от миналия урок работи и с абстрактен родител.
Пробвай
Намери грешката
Избери реда с грешката.
Shape() е опит да се създаде обект от абстрактен клас. Първият ред е наред: [None] * 3 създава списък от три празни места за фигури, а не фигури. В тях можеш да сложиш правоъгълник, кръг или триъгълник. Python спира на четвъртия ред с TypeError: Can't instantiate abstract class Shape without an implementation for abstract method 'area'.
Идеята
Абстрактен, но не празен
Абстрактният клас може да има всичко, което има обикновеният: полета, конструктор, обикновени методи. Нека всяка фигура има име и метод, който я описва:
class Shape(ABC): def __init__(self, name): self._name = name
@abstractmethod def area(self): pass
def describe(self): return f"{self._name}: {self.area():.2f} м²"describe() е написан веднъж, в Shape. Той вика area(), който в Shape е само обещание. Не е проблем: describe() винаги се вика върху истинска фигура, а тя има лице.
От Shape не се правят обекти, но __init__ му е полезен: наследниците го викат.
class Circle(Shape): def __init__(self, radius): super().__init__("кръг") self._radius = radius
# ... area() както предиterrace = [Rectangle(4, 3), Circle(1), Triangle(2, 3)]for s in terrace: print(s.describe())правоъгълник: 12.00 м²кръг: 3.14 м²триъгълник: 3.00 м²Родителят пише общото (името, описанието), а наследниците пишат различното (лицето).
@abstractmethoddef area(self): pass
def describe(self): return f"{self._name}: {self.area():.2f} м²"
s = Circle(1)print(s.describe())Стъпка по стъпка, с думи
- Shape обещава area(): @abstractmethod, а тялото е само pass. Обект само от Shape не може да има.
- Обектът е кръг: частта Circle има свой area(). Тя изпълнява обещанието.
- s.describe() е написан в Shape и важи за всяка фигура.
- Той вика area(). Обектът е кръг, затова се изпълнява методът на Circle: π · 1 · 1.
- describe() сглобява текста. Общото е в родителя, различното в наследника.
- Изходът е лицето на кръга, с два знака след запетаята.
Пробвай
Чий метод?
Какво ще отпечата програмата?
# Shape и Circle са от горната карта
s = Circle(2)print(s.describe())Обектът е кръг с радиус 2. describe() е на Shape, но area() в него е на кръга: π · 2 · 2 ≈ 12,57.
Пробвай
Кое липсва?
Какво трябва да пише на празното място, за да не може да се създаде обект от Shape?
class Shape():
@abstractmethod
def area(self):
passABC. Без него @abstractmethod не спира нищо: Shape() се създава, а area() връща None.Упражнение
Фигури и животни
Реши задачите в редактора си.
1. Лице и обиколка. Абстрактен клас Shape с име и два абстрактни метода: area() и perimeter(). Наследници Circle (радиус) и Rectangle (ширина и височина). Сложи кръг с радиус 5 и правоъгълник 4 × 2,5 в списък и отпечатай двете стойности с два знака след точката.
Кръг: лице 78.54, обиколка 31.42Правоъгълник: лице 10.00, обиколка 13.002. Животните. Абстрактен клас Animal с абстрактен метод sound() и обикновен метод breathe(), който печата „Животното диша.“ Наследници Cat и Dog. Извикай звука на двете в цикъл, после breathe() на първото.
Мяу, мяу!Бау, бау!Животното диша.Упражнение
Превозни средства и изпълнения
3. Превозни средства. Абстрактен клас Vehicle с абстрактен метод move(). SimpleCar и Bicycle го наследяват. Bicycle има и свой метод pedal().
Колата потегляВелосипедът потегляВъртя педалитеgarage[1].pedal(): работи, защото там има велосипед. А garage[0].pedal()? Защо програмата спира чак на този ред, а не по-рано?4. Изпълнения. Абстрактен клас Performance със заглавие и абстрактен метод start(). DanceMove и Song го наследяват. Song има и метод sing().
Започва танцът moonwalkЗапочва песента 'Bohemian Rhapsody'Пея 'Bohemian Rhapsody'Още задачи
- Триъгълник. Добави
Triangleкъм задача 1 с трите страни. Обиколката е сумата им, а лицето се смята по формулата на Херон: s = обиколка / 2, лице = √(s(s − a)(s − b)(s − c)). За страни 3, 4 и 5 лицето е 6,00, а обиколката 12,00. - Абстрактен служител. Направи
Employeeот миналия урок абстрактен, с абстрактенmonthly_pay(). ДобавиFullTime(заплатата е цялата) и оставиManagerиIntern. Какво печели програмата? Какво губи?
Тест