Статьи

ООП в Python: часть 2

В  первой части этой статьи я представил 3 столпа объектно-ориентированного программирования. Я рассмотрел Инкапсуляцию, следующая большая тема — Наследование (это будет описано в 2 статьях).

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

Наследование — это концепция объектно-ориентированного программирования, которая помогает программистам:

  1. Модель  — это  отношения (не верно для каждого языка программирования, бывают случаи, когда разделяется только реализация)
  2. Повторное использование кода — помогает разработчикам соблюдать  принцип DRY  и повторно использовать существующую реализацию и логику в коде.
  3. Расширение функциональности — в некоторых случаях исходный код используемых классов не может быть изменен (другие используют его, либо он не является общедоступным, либо просто запрещен); в этом случае расширение функциональности класса может быть сделано путем применения наследования.

Python поддерживает одиночное и множественное наследование. Существуют серьезные различия в том, как реализовать наследование и как оно работает в Python 2.x и 3.x. Все примеры, которые я приведу, относятся к Python 3.x.

Одиночное наследование в Python

В Python наследование может быть сделано через   синтаксис класса  MySubClass (MyBaseClass) . В основном после нового имени класса в скобках я указываю   имя суперкласса .
Я буду придерживаться традиционного примера  животных,  являющихся  базовым  (или так называемым  суперклассом,  и различных видов животных, называемых  подклассами .

class Animal:
    __name = None
    __age = 0
    __is_hungry = False
    __nr_of_legs = 0

    def __init__(self, name, age, is_hungry, nr_of_legs):
        self.name = name
        self.age = age
        self.is_hungry = is_hungry
        self.nr_of_legs = nr_of_legs

    #
    # METHODS
    #

    def eat(self, food):
        print("{} is eating {}.".format(self.name, food))
    
    #
    # PROPERTIES
    #
    
    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self,new_name):
        self.__name = new_name

    @property
    def age(self):
        return self.__age
    
    @age.setter
    def age(self,new_age):
        self.__age = new_age

    @property
    def is_hungry(self):
        return self.__is_hungry
    
    @is_hungry.setter
    def is_hungry(self,new_is_hungry):
        self.__is_hungry = new_is_hungry

    @property
    def nr_of_legs(self):
        return self.__nr_of_legs
    
    @nr_of_legs.setter
    def nr_of_legs(self,new_nr_of_legs):
        self.__nr_of_legs = new_nr_of_legs

В   классе Animal я определил 4 закрытых члена ( __name, __age, __is_hungry, __nr_of_legs ). Для всех 4 членов я создал свойства, используя  создание декоратора  @property, представленное в первой части статьи ООП в Python. Я создал конструктор с 4 параметрами (не считая параметра self), который использует свойства для установки значений для закрытых членов. Помимо 4-х свойств, я создал метод, который называется «  съесть» («я», «еда»)) , в котором выводится, что «  X есть Y» , где «X» — это имя животного, а «Y» — еда, передаваемая в качестве параметра. Класс  Animal  служит базовым классом для   класса Snake .

class Snake(Animal):
    __temperature = 28    
    
    def __init__(self, name, temperature):
        super().__init__(name, 2, True, 0)
        self.temperature = temperature 

    #
    # METHODS
    #

    def eat(self, food):
        if food == "meat":
            super().eat(food)
        else:
            raise ValueError    

    #
    # PROPERTIES
    #

    @property
    def temperature(self):
        return self.__temperature
    
    @temperature.setter
    def temperature(self,new_temperature):
        if new_temperature < 10 or new_temperature > 40:
            raise ValueError
        self.__temperature = new_temperature

Конструктор   класса Snake принимает 2 аргумента:  имя  змеи и ее температуру . Для   закрытого члена __teuration я создал свойство, поэтому я использую его в конструкторе для хранения значения, переданного конструктору. В конструкторе сначала я вызываю конструкцию базового класса, используя   ключевое слово super () ( есть другие методы для вызова конструктора родительского класса, но в Python 3.x это рекомендуемый способ ). При вызове конструктора базового класса я передал несколько предопределенных значений, таких как   ноль nr_of_legs , поскольку у змей нет ног и  is_hungry как Истина, потому что змеи, как правило, «голоднее», чем другие животные 

Как и в других языках программирования, я также могу переопределять методы в Python. Я переписал метод eat и добавил дополнительную логику. В случае, если пища, которая дается змее, не является мясом, я поднимаю  ValueError , в противном случае я вызываю метод eat, который определен в базовом классе ( Animal ).

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

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

В Python синтаксис для наследования от нескольких классов очень прост, все, что нужно сделать, это перечислить базовые классы в круглых скобках после имени нового класса, например:  class MySubClass (MyBaseClass1, MyBaseClass2, MyBaseClass3) .

class Phone:
    
    def __init__(self):
        print("Phone constructor invoked.")
    
    def call_number(self, phone_number):
        print("Calling number {}".format(phone_number))
class Computer:

    def __init__(self):
        print("Computer constructor invoked.")

    def install_software(self, software):
        print("Computer installing the {} software".format(software))
class SmartPhone(Phone,Computer):

    def __init__(self):
        super().__init__()

I defined 3 classes (Phone, Computer, SmartPhone), 2 are base classes (Phone and Computer) and one derived class SmartPhone. Logically this seams to be correct, a smartphone can make calls and can install software, so the SmartPhone class can call_number(inherited from Phone class) and can install_software (inherited from the Computer class).

#
# will be discussed later why only the constructor of Phone class was invoked
#
>>> my_iphone = SmartPhone()
Phone constructor invoked. 

>>> my_iphone.call_number("123456789")
Calling number 123456789

>>> my_iphone.install_software("python")
Computer installing the python software

If I look at the constructor if the SmartPhone class, its pretty simple, it invokes the base class’s constructor. That’s correct, it invokes a constructor, the question is from which base class? As it can be seen on the code it invokes only the constructor of the Phone class. The question is why? The response will come in the next part!