The Python Programming Guide

Классы

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

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

Классы могут иметь свои переменные (атрибуты), свои функции (методы), которые определяют новый тип данных и его поведение. Определяется класс с помощью ключевого слова class и синтаксис выглядит следующим образом:

class <название класса>:
    <атрибуты класса>
    <методы класса>

Создадим пустой класс с помощью знакомого оператора pass:

class Person:
    pass

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

joe = Person()
vlad = Person()

print(type(joe), joe)
print(type(vlad), vlad)
<class '__main__.Person'> <__main__.Person object at 0x7fb0bd3279d0>
<class '__main__.Person'> <__main__.Person object at 0x7fb0bd327fa0>

Оба созданных объекта имеют один и тот же тип, однако не являются одним и тем же объектом. Проверить последнее утверждение можно с помощью оператора is.

Конструктор

В предыдущем примере может возникнуть вопрос - если не определялось никаких функций/методов, то как было вызвано создание объекта? Ответ - почти все классы неявно наследуются от базового класса object, в котором определён набор специальных методов. Одним из таких методов является __init__(), который и вызывается при создании нового объекта.

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

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

alex = Person("alex", 27)
print(alex.name, alex.age)
alex 27

Доступ к параметрам или методам объекта осуществляется указанием соответствующего названия переменной после точки ..

Заметка

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

Атрибуты

Атрибуты можно разделись на два вида:

Пример:

class Person:
    species = "homo sapiens"
    count = 0

    def __init__(self, name, age):
        self.name = name
        self.age = age
        Person.count += 1

В указанном примере мы создаем класс Person с двумя атрибутами класса species и count, а также двумя атрибутами экземпляра name и age. Класс содержит один специальный метод __init__(), который содержит наши два атрибута экземпляров. Значения для атрибутов переданы в качестве аргументов методу __init__(). Внутри метода __init__() атрибут класса count увеличивается на единицу.

Создадим объект класса Person и выведем пару значений:

person_1 = Person("Jack", 36)
print("Имя:", person_1.name)
print("Счетчик людей:", person_1.count)
Имя: Jack
Счетчик людей: 1

Как можно видеть, атрибут count имеет значение 1. Теперь создадим еще один объект класса Person:

person_2 = Person("Alex", 14)
print("Имя:", person_2.name)
print("Счетчик людей:", person_2.count)
Имя: Alex
Счетчик людей: 2

Значение атрибута count увеличилось еще на единицу. Как уже говорилось ранее, это связано с тем, что для объекта person_1 и person_2 атрибут класса count является общим.

Методы

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

Однако, есть тип методов, который может быть вызван напрямую при помощи имени класса. Такие методы называются статичными методами и указываются с помощью декоратора @staticmethod перед созданием метода:

class Person:
    @staticmethod
    def get_details():
        print("Это класс Person")

Person.get_details()
Это класс Person

Как видите, нам не нужно создавать экземпляр класса Person, чтобы вызвать метод get_details(). Для этого достаточно использовать название класса.

Заметка

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

Модификаторы доступа

Модификаторы доступа в Python используются для модификации области видимости переменных по умолчанию. Есть три типа модификаторов доступов в Python ООП:

Пример:

class Person:
    def __init__(self):
        self.name = "Jack"
        self._age = 29
        self.__height = 185

person_1 = Person()
print(person_1.name)
print(person_1._age)
print(person_1.__height)
Jack
29
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute '__height'

Довольно неожиданный результат, сработало только по логике правильно только два модификатора - публичный и приватный. На деле можно получить и значение приватной переменной следующим образом:

print(person_1._Person__height)
185

Это указывает на то, что в Python нету явных модификаторов доступа. Данная тема до сих пор вызывает у многих вопросы. Некоторые считают это большим минусов в ООП Python, другие наоборот плюсом. Можно сослаться на фразу из философии дизайна Python - “Явное лучше чем неявное”. На деле, если при разработке в классе встречается переменная с подчеркиванием в начале названия, т.е. модификатором, то стоит дважды подумать, стоит ли ее изменять или же это сломает логику работы класса, которую в нее заложил разработчик.

Подсказка

Почитать философию дизайна Python можно импортировав модуль this в REPL:

import this

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

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

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

Пример:

class Person:
    def person_method(self):
        print("Это родительский метод из класса Person")

#   наследование класса Person классом Student
class Student(Person):
    def student_method(self):
        print("Это метод из дочернего класса")

Выше мы создаем два класса: Person и Student, который наследует класс Person. Чтобы наследовать класс, вам нужно только вписать название родительского класса внутри скобок, которая следует за названием дочернего класса. Класс Person содержит метод person_method(), а дочерний класс содержит метод student_method(). Однако, так как класс Student наследует класс Person, он также наследует и метод person_method():

student_1 = Student()
student_1.person_method()
Это родительский метод из класса Person

Также в Python поддерживается множественное наследование. Родительский класс может иметь несколько дочерних, и, аналогично, дочерний класс может иметь несколько родительских классов.

Пример наследования по цепочке:

class Person:
    def person_method(self):
        print("Это родительский метод из класса Person")

#   наследование класса Person классом Student
class Student(Person):
    def student_method(self):
        print("Это метод из дочернего класса Student")

#   наследование класса Student классом Worker
class Worker(Person):
    def worker_method(self):
        print("Это метод из дочернего класса Worker")

Пример наследования от нескольких классов:

class Microphone:
    def microphone_method(self):
        print("Это родительский метод из класса Microphone")

class Camera:
    def camera_method(self):
        print("Это родительский метод из класса Camera")

#   наследование класса Microphone и Camera классом CellPhone
class CellPhone(Microphone, Camera):
    def cell_phone_method(self):
        print("Это метод из дочернего класса Worker")

Полиморфизм

Термин полиморфизм буквально означает наличие нескольких форм. В контексте объектно-ориентированного программирования, полиморфизм означает способность объекта вести себя по-разному. Полиморфизм в программировании реализуется через перегрузку метода, либо через его переопределение.

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

class Calculator:
    def sum(self, a, b, c = None):
        if c is not None:
            print(a + b + c)
        else:
            print(a + b)

calc = Calculator()
calc.sum(5, 9)
calc.sum(5, 9, 3)
14
17

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

class Person:
    def get_details(self):
        print("Это родительский метод из класса Person")

#   наследование класса Person классом Student
class Student(Person):
    def get_details(self):
        print("Это метод из дочернего класса Student")

#   наследование класса Student классом Worker
class Worker(Person):
    def get_details(self):
        print("Это метод из дочернего класса Worker")

person_1 = Person()
person_1.get_details()

person_2 = Student()
person_2.get_details()

person_3 = Worker()
person_3.get_details()
Это родительский метод из класса Person
Это метод из дочернего класса Student
Это метод из дочернего класса Worker

Инкапсуляция

Инкапсуляция — это третий столп объектно-ориентированного программирования. Инкапсуляция просто означает скрытие данных. Как правило, в объектно-ориентированном программировании один класс не должен иметь прямого доступа к данным другого класса. Вместо этого, доступ должен контролироваться через методы класса.

Чтобы предоставить контролируемый доступ к данным класса в Python, используются модификаторы доступа и свойства. Мы уже ознакомились с тем, как действуют модификаторы доступа. В этом разделе мы посмотрим, как действуют свойства.

Предположим, что нам нужно убедиться в том, что модель автомобиля должна датироваться между 2000 и 2018 годом. Если пользователь пытается ввести значение меньше 2000 для модели автомобиля, значение автоматически установится как 2000, и если было введено значение выше 2018, оно должно установиться на 2018. Если значение находится между 2000 и 2018 — оно остается неизменным. Мы можем создать свойство атрибута модели, которое реализует эту логику. Взглянем на пример:

# создаем класс Car
class Car:
 
    # создаем конструктор класса Car
    def __init__(self, model):
        # Инициализация свойств.
        self.model = model
 
    # создаем свойство модели.
    @property
    def model(self):
        return self.__model
 
    # Сеттер для создания свойств.
    @model.setter
    def model(self, model):
        if model < 2000:
            self.__model = 2000
        elif model > 2018:
            self.__model = 2018
        else:
            self.__model = model
 
    def getCarModel(self):
        return "Год выпуска модели " + str(self.model)
 
carA = Car(2088)  
print(carA.getCarModel())

Свойство имеет три части. Вам нужно определить атрибут, который является моделью в скрипте выше. Затем, вам нужно определить свойство атрибута, используя декоратор @property. Наконец, вам нужно создать установщик свойства, который является дескриптором @model.setter в примере выше. Теперь, если вы попробуете ввести значение выше 2018 в атрибуте модели, вы увидите, что значение установлено на 2018:

car_a = Car(2088)  
print(car_a.get_car_model())
Год выпуска модели 2018