Классы
Объектно-ориентированное программирование (ООП) — это парадигма программирования, где различные компоненты программы моделируются на основе реальных объектов. Объект — это что-либо, у чего есть какие-либо характеристики и то, что может выполнить какую-либо функцию.
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
вне любого метода. - атрибуты экземпляров - являются собственностью экземпляра класса, объявляются обычно внутри любого метода.
Пример:
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 ООП:
- публичный (public) - доступ открыт из любого места вне класса;
- защищенный (protected) - доступ открыт только внутри того же пакета, создается с добавлением к названию переменной одного нижнего подчеркивания
_
в начале; - приватный (private) - доступ открыт только внутри класса, создается с добавлением к названию переменной двойного нижнего подчеркивания
__
в начале.
Пример:
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