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

Вступление

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Мы инициализировали нашу last_nameпеременную строкой, "Fish"потому что знаем, что большинство рыб будут иметь это как свою фамилию.

Давайте также добавим другие методы:

fish.py
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__()метода:

fish.py
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, мы можем сказать, что a Rhombus есть a Parallelogram , как и Goldfish есть aFish .

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

class Trout(Fish):

TroutКласс является потомком Fishкласса. Мы это знаем из-за включения этого слова Fishв круглые скобки.

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

fish.py
...
class Trout(Fish):
    pass

Теперь мы можем создать Troutобъект без необходимости определять какие-либо дополнительные методы.

fish.py
...
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класса, хотя мы не определяли эти методы в Troutдочернем классе. Нам нужно только , чтобы передать значение "Terry"в first_nameпеременной , потому что все остальные переменные были инициализированы.

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

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

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

fish.py
...
class Clownfish(Fish):

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

Затем давайте создадим Clownfishобъект, чтобы увидеть, как это работает:

fish.py
...
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родительскому классу.

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

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

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

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

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

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

В свете этого мы будем переопределять __init__()метод конструктора и swim_backwards()метод. Нам не нужно модифицировать swim()метод, так как акулы — это рыба, которая может плавать. Давайте посмотрим на этот дочерний класс:

fish.py
...
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теперь переменная имеет значение Boolean True. Каждый экземпляр класса также может переопределять эти параметры.

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

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

fish.py
...
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()функции вы можете получить доступ к унаследованным методам, которые были перезаписаны в объекте класса.

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

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

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

Чтобы увидеть, как это работает, давайте изменим наш Troutдочерний класс. Поскольку форель — это, как правило, пресноводная рыба, добавим waterпеременную в __init__()метод и установите ее равной строке "freshwater", но затем сохраним остальные переменные и параметры родительского класса:

fish.py
...
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, вызывая переменную в нашем экземпляре объекта.

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

fish.py
...
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_namelast_nameи eyelids.

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

Многократное наследование

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

Чтобы показать, как многократное наследование работает, давайте создадим Coral_reefдочерний класс, чем наследуем от Coralкласса и Sea_anemoneкласса. Мы можем создать метод в каждом, а затем использовать passключевое слово в Coral_reefдочернем классе:

coral_reef.py
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наследуется от двух родительских классов.

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

coral_reef.py
...
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 (не повторяйте себя) разработки программного обеспечения, что позволяет делать больше с меньшим количеством кода и повторением. Наследование также заставляет программистов задуматься о том, как они разрабатывают программы, которые они создают, чтобы обеспечить эффективный и понятный код.

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *