Поиск по сайту:

Понимание наследования классов в Python 3


Введение

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

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

Предпосылки

У вас должен быть установлен Python 3 и настроена среда программирования на вашем компьютере или сервере. Если у вас не настроена среда программирования, вы можете обратиться к руководствам по установке и настройке среды программирования на вашем сервере, подходящей для вашей операционной системы (Ubuntu, CentOS, Debian и т. д.).

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

Наследование — это когда класс использует код, созданный внутри другого класса. Если мы думаем о наследовании с точки зрения биологии, мы можем думать о ребенке, наследующем определенные черты от своего родителя. То есть ребенок может унаследовать рост родителя или цвет глаз. Дети также могут носить одну и ту же фамилию со своими родителями.

Классы, называемые дочерними классами или подклассами, наследуют методы и переменные от родительских классов или базовых классов.

Мы можем думать о родительском классе с именем Parent, который имеет переменные класса для last_name, height и eye_color, которые дочерний класс Child будет наследовать от Parent.

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

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

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

Допустим, у нас есть общий родительский класс Bank_account, у которого есть дочерние классы Personal_account и Business_account. Многие методы между личными и бизнес-аккаунтами будут схожими, например, методы снятия и внесения денег, поэтому они могут принадлежать родительскому классу Bank_account. Подкласс Business_account будет иметь специфичные для него методы, включая, возможно, способ сбора деловых записей и форм, а также переменную employee_identification_number.

Точно так же класс Animal может иметь методы eating() и sleeping(), а подкласс Snake может включать собственные специальные методы hissing() и slithering().

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

Информация: Чтобы следовать примеру кода в этом руководстве, откройте интерактивную оболочку Python в вашей локальной системе, выполнив команду python3. Затем вы можете копировать, вставлять или редактировать примеры, добавляя их после приглашения >>>.

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

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() в класс Fish, чтобы каждый подкласс также мог использовать эти методы.

Поскольку большинство рыб, которых мы будем создавать, считаются хрящевыми (поскольку их скелет состоит из хрящей), мы можем добавить еще несколько атрибутов в метод __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(), объявленный в . Fish без объявления.

Мы можем думать о каждом дочернем классе как о классе родительского класса. То есть, если у нас есть дочерний класс с именем Rhombus и родительский класс с именем Parallelogram, мы можем сказать, что Rhombus — это Параллелограмм, так же как Золотая рыбка — это Рыба.

Первая строка дочернего класса выглядит немного иначе, чем не дочерние классы, поскольку вы должны передать родительский класс в дочерний класс в качестве параметра:

class Trout(Fish):

Класс Trout является дочерним по отношению к классу Fish. Мы знаем это из-за включения слова 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 terry, в котором используются все методы класса Fish, хотя мы не определяли эти методы в Дочерний класс Форель. Нам нужно было только передать значение Terry в переменную first_name, потому что все остальные переменные были инициализированы.

Когда мы запустим программу, мы получим следующий вывод:

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

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

...
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()

Когда мы запустим программу, мы получим следующий вывод:

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

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

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

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

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

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

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

До сих пор мы рассматривали дочерний класс Trout, в котором использовалось ключевое слово pass для наследования всех поведений родительского класса Fish, и другой дочерний класс Clownfish, унаследовавший все поведение родительского класса, а также создавший свой собственный уникальный метод, специфичный для дочернего класса. Однако иногда мы хотим использовать некоторые из поведений родительского класса, но не все из них. Когда мы изменяем методы родительского класса, мы переопределяем их.

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

Мы создадим дочерний класс Shark для родительского класса Fish. Поскольку мы создали класс Fish с идеей, что будем создавать в основном костистых рыб, нам придется внести коррективы для класса 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 теперь установлена равной строке хрящ, а переменная velids теперь установлена в логическое значение True . Каждый экземпляр класса также может переопределять эти параметры.

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

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

...
sammy = Shark("Sammy")
print(sammy.first_name + " " + sammy.last_name)
sammy.swim()
sammy.swim_backwards()
print(sammy.eyelids)
print(sammy.skeleton)

Когда мы запустим этот код, мы получим следующий вывод:

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

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

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

Функция супер()

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

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

В программе, которая оценивает учащихся, нам может понадобиться дочерний класс для Weighted_grade, который наследуется от родительского класса 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, а если бы мы передали параметр, мы бы сбросили вместо пресной воды. Поэтому мы инициализируем first_name, вызывая переменную в экземпляре нашего объекта.

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

...
terry = Trout()

# Initialize first name
terry.first_name = "Terry"

# Use parent __init__() through super()
print(terry.first_name + " " + terry.last_name)
print(terry.eyelids)

# Use child __init__() override
print(terry.water)

# Use parent swim() method
terry.swim()
Output
Terry Fish False freshwater The fish is swimming.

Вывод показывает, что объект terry дочернего класса Trout может использовать как дочернюю переменную __init__() water, а также иметь возможность вызывать родительские переменные Fish __init__() для first_name, last_name и веки.

Встроенная функция Python super() позволяет нам использовать методы родительского класса даже при переопределении некоторых аспектов этих методов в наших дочерних классах.

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

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

Чтобы показать, как работает множественное наследование, давайте создадим дочерний класс 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(). , который печатает другую строку. Затем мы вызываем оба класса в кортеж наследования. Это означает, что CoralReef наследуется от двух родительских классов.

Давайте теперь создадим экземпляр объекта CoralReef:

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

Объект great_barrier устанавливается как объект CoralReef и может использовать методы обоих родительских классов. Когда мы запустим программу, мы увидим следующий вывод:

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

Вывод показывает, что методы обоих родительских классов эффективно использовались в дочернем классе.

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

Хотя его можно эффективно использовать, множественное наследование следует выполнять с осторожностью, чтобы наши программы не стали двусмысленными и трудными для понимания другими программистами.

Заключение

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

Наследование в объектно-ориентированном кодировании может позволить придерживаться принципа DRY (не повторяйся) разработки программного обеспечения, позволяя делать больше с меньшим количеством кода и повторений. Наследование также заставляет программистов думать о том, как они разрабатывают создаваемые ими программы, чтобы код был эффективным и понятным.