Наследование в Python 3

Объектно-ориентированное программирование позволяет создавать многоразовые шаблоны кода и таким образом уменьшать количество повторяемых блоков кода в программе. Один из методов, с помощью которого это достигается, называется наследованием; при этом один подкласс может использовать код из другого базового класса.

Данное руководство ознакомит вас с основными аспектами наследования в Python: с работой родительских и дочерних классов, переопределением методов и атрибутов, функцией super() и множественным наследованием.

Что такое наследование?

Наследование подразумевает, что дочерний класс использует код, созданный в родительском классе.

Дочерний класс, или подкласс – это класс, который наследует код из родительского, или базового класса.

Предположим, что у нас есть класс Parent с переменными last_name, height, и eye_color. Подкласс Child может наследовать эти переменные из класса Parent, то есть повторно использовать этот код. Это позволяет уменьшить объём кода и снизить избыточность.

Родительские классы

Родительский, или базовый класс создаёт шаблон кода, который в дальнейшем может наследоваться дочерними классами. Родительским классом может быть любой класс. Базовые классы – это не просто шаблоны, а полноценные функционирующие классы.

Предположим, у нас есть общий родительский класс Bank_account с дочерними классами Personal_account и Business_account. Многие параметры классов Personal_account и Business_account будут совпадать. Такие параметры можно наследовать из родительского класса Bank_account. В подклассе Business_account будут индивидуальные параметры (например, методы сбора деловых документов и форм и переменная employee_identification_number).

Аналогичным образом, класс Animal может содержать методы eating() и sleeping(), а подкласс Snake помимо вышеперечисленных наследуемых методов может также содержать методы hissing() и slithering().

Для примера попробуйте создать родительский класс Fish (в дальнейшем мы используем его, чтобы создать различные подклассы с типами рыб).

Создайте файл fish.py и добавьте в него метод конструктора __init__(), который будет содержать переменные first_name и last_name для каждого подкласса (или объекта) Fish.

Читайте также: Создание классов и определение объектов в Python 3

class Fish:
def __init__(self, first_name, last_name="Fish"):
self.first_name = first_name
self.last_name = last_name

Переменная last_name содержит строку “Fish”, потому что почти все подклассы будут использовать это значение.

Добавьте в файл другие методы:

class Fish:
def __init__(self, first_name, last_name="Fish"):
self.first_name = first_name
self.last_name = last_name
def swim(self):
print("The fish is swimming.")
def swim_backwards(self):
print("The fish can swim backwards.")

Теперь в файле есть методы swim() и swim_backwards(), которые будут наследоваться подклассами.

Добавьте другие атрибуты в метод __init__():

class Fish:
def __init__(self, first_name, last_name="Fish",
skeleton="bone", eyelids=False):
self.first_name = first_name
self.last_name = last_name
self.skeleton = skeleton
self.eyelids = eyelids
def swim(self):
print("The fish is swimming.")
def swim_backwards(self):
print("The fish can swim backwards.")

Создание родительского класса ничем не отличается от создания обычного класса. Единственный нюанс: нужно заранее продумывать, какие параметры смогут наследовать дочерние классы.

Дочерние классы

Дочерние классы, или подклассы – это классы, которые наследуют параметры родительских классов. Каждый дочерний класс сможет использовать методы и переменные родительского класса.

К примеру, дочерний класс Goldfish может наследовать из класса Fish функцию swim().

Дочерние классы начинаются немного иначе. В первой строке нужно передать родительский класс:

class Trout(Fish):

Класс Trout является дочерним по отношению к классу Fish (родительский класс нужно указать в круглых скобках).

В дочернем классе можно добавить больше методов, переопределить методы родительского класса или просто принять его методы с помощью ключевого слова pass, например:

...
class Trout(Fish):
pass

Теперь создайте объект Trout и наследуйте методы родительского класса, не добавляя новых:

...
class Trout(Fish):
pass
terry = Trout("Terry")
print(terry.first_name + " " + terry.last_name)
print(terry.skeleton)
print(terry.eyelids)
terry.swim()
terry.swim_backwards()

Теперь в файле есть объект Trout, который использует все методы класса Fish несмотря на то, что они не объявлены в самом объекте. Нужно только передать значение “Terry” переменной first_name; все остальные переменные уже инициализированы.

Запустите программу. Вы получите:

Terry Fish
bone
False
The fish is swimming.
The fish can swim backwards.

Создайте другой дочерний класс, теперь уже добавив новые методы. Класс будет называться Clownfish, его индивидуальный метод – live_with_anemone.

...
class Clownfish(Fish):
def live_with_anemone(self):
print("The clownfish is coexisting with sea anemone.")

Создайте объект Clownfish:

...
casey = Clownfish("Casey")
print(casey.first_name + " " + casey.last_name)
casey.swim()
casey.live_with_anemone()

Запустив программу, вы получите такой вывод:

Casey Fish
The fish is swimming.
The clownfish is coexisting with sea anemone.

Как видите, объект casey использует методы __init__() и swim() родительского класса Fish и индивидуальный метод live_with_anemone().

Если попробовать использовать метод live_with_anemone() в объекте Trout, получится ошибка:

terry.live_with_anemone()
AttributeError: 'Trout' object has no attribute 'live_with_anemone'

Это потому, что метод live_with_anemone() принадлежит исключительно дочернему классу Clownfish.

Переопределение методов родительского класса

Только что вы создали дочерний класс Trout, который с помощью ключевого слова pass полностью наследует методы родительского класса Fish, и дочерний класс Clownfish, который не только наследует методы родительского класса, но и использует свой собственный метод.

Однако в некоторых случаях нужно использовать не все, а только отдельные методы родительского класса. Для этого методы родительского класса можно переопределять.

Создавая родительский и дочерний класс, важно учитывать проектирование программы, чтобы переопределение не создало ненужного или избыточного кода.

Для примера создайте дочерний класс Shark. При создании класса Fish использовался параметр skeleton=”bone”, но в случае с классом Sharkэто неверно. Переопределите этот параметр.

Примечание: Что касается проектирования программы, если бы переопределяемый параметр использовался несколькими классами, а не одним, лучше было бы создать для них отдельный родительский класс.

Переопределите метод конструктора __init__() и метод swim_backwards(). Метод swim() изменять не нужно. Дочерний класс будет выглядеть так:

...
class Shark(Fish):
def __init__(self, first_name, last_name="Shark",
skeleton="cartilage", eyelids=True):
self.first_name = first_name
self.last_name = last_name
self.skeleton = skeleton
self.eyelids = eyelids
def swim_backwards(self):
print("The shark cannot swim backwards, but can sink backwards.")

Параметры метода конструктора __init__() были переопределены; теперь переменная last_name имеет значение “Shark”, переменная skeleton имеет значение “cartilage”, а eyelids – значение True.

Метод swim_backwards() теперь выводит другое значение, а не то, которое определено в родительском классе Fish.

Теперь создайте экземпляр класса Shark, который будет использовать метод swim() родительского класса Fish.

...
wally = Shark("Wally")
print(wally.first_name + " " + wally.last_name)
wally.swim()
wally.swim_backwards()
print(wally.eyelids)
print(wally.skeleton)

Запустите этот код, и вы получите:

Wally Shark
The fish is swimming.
The shark cannot swim backwards, but can sink backwards.
True
cartilage

В дочернем классе Shark методы __init__() и swim_backwards() успешно переопределены.

Функция super()

С помощью функции super() вы можете получить доступ к унаследованным методам, которые были перезаписаны в объекте класса.

Функция super() вызывает родительский метод в дочерний и использует его. Например, это позволяет переопределить один из аспектов родительского метода, а затем вызвать остальную часть исходного родительского метода.

К примеру, в программе, которая выставляет оценки студентам, внутри родительского класса Grade может быть дочерний класс Weighted_grade. В классе Weighted_grade можно переопределить метод родительского класса calculate_grade() и унаследовать остальные методы без изменений. Для этого и нужна функция super().

Обычно функция super() используется в методе конструктора __init__(), потому что именно там, скорее всего, появятся уникальные методы дочернего класса.

Попробуйте изменить дочерний класс Trout. Добавьте переменную water в метод __init__() и присвойте ей значение “freshwater”. Остальные методы родительского класса можно наследовать без изменений.

...
class Trout(Fish):
def __init__(self, water = "freshwater"):
self.water = water
super().__init__(self)
...

Метод __init__() класса Trout был переопределён. Он иначе реализует методы __init__() родительского класса Fish. В методе __init__() класса Trout был явно инициализирован метод __init__() класса Fish.

Поскольку метод переопределён, больше не нужно передавать first_name как параметр Trout. Если бы вы передали параметр, параметр freshwater был бы сброшен. Поэтому переменную first_name нужно инициализировать путём вызова в объекте.

Теперь можно вызвать инициализированные переменные родительского класса, а также использовать уникальную дочернюю переменную.

...
terry = Trout()
# Инициализация first name
terry.first_name = "Terry"
# Использование родительского метода __init__() с помощью функции super()
print(terry.first_name + " " + terry.last_name)
print(terry.eyelids)
# Дочерний метод __init__()
print(terry.water)
# Родительский метод swim()
terry.swim()
Terry Fish
False
freshwater
The fish is swimming.

Как видите, объект terry класса Trout  может использовать свой метод __init__() с переменной water и родительский метод __init__() класса Fish с переменными first_name, last_name и eyelids.

Множественное наследование

Множественное наследование подразумевает, что класс может наследовать атрибуты и методы из нескольких родительских классов одновременно. Это позволяет программам сократить избыточность, но также может усложнить код, поэтому множественное наследование нужно использовать только с учетом общей конструкции программы.

Попробуйте создать класс Coral_reef, дочерний по отношению к классам Coral и Sea_anemone. Создайте в каждом классе метод и передайте его с помощью ключевого слова pass в дочерний класс Coral_reef.

class Coral:
def community(self):
print("Coral lives in a community.")
class Anemone:
def protect_clownfish(self):
print("The anemone is protecting the clownfish.")
class CoralReef(Coral, Anemone):
pass

Класс Coral содержит метод community(), который выводит одну строку текста, а класс Anemone содержит метод protect_clownfish(), который отображает другую строку. После этого оба класса вызываются в кортеж. Таким образом класс Coral может наследовать оба родительских класса.

Читайте также: Кортежи в Python 3

Создайте объект класса Coral:

...
great_barrier = CoralReef()
great_barrier.community()
great_barrier.protect_clownfish()

Объект great_barrier класса CoralReef использует методы обоих родительских классов.

Запустите код:

Coral lives in a community.
The anemone is protecting the clownfish.

Как видите, множественное наследование работает правильно.

Множественное наследование позволяет использовать методы нескольких родительских классов внутри одного дочернего класса. Если в родительских классах присутствует один и тот же метод, дочерний класс унаследует его из того родительского класса, который идёт первым в кортеже.

Заключение

Теперь вы знакомы с основами наследования в Python 3 и умеете создавать родительские и дочерние классы, переопределять методы, использовать функцию super() и использовать множественное наследование.

Наследование в объектно-ориентированном программировании позволяет соблюдать принцип разработки DRY («don’t repeat yourself»), благодаря чему программа содержит меньше повторяющихся блоков кода. Наследование также заставляет разработчиков заранее продумывать конструкцию программы.

Tags: ,

Добавить комментарий