ООП на Python
Гайд от Матвея 🥸
https://ra1nbow.xyz
[!info] Github
https://github.com/ra1nbow1
Посвящается всем, кто прочитал 2 предыдущих раздела про Пайтон и хочет расширять свои знания. Приступаем к объектно-ориентированному программированию.
Это кто? who?
Так вот, ООП - это как методология программирования, но не такая скучная. Она основана на идее "объектов", которые ведут себя как небольшие, самодостаточные части кода. Каждый объект содержит в себе какие-то данные и функции, которые с ними работают.
Представьте, что ваша программа - это фабрика, а классы - это инструменты в этой фабрике. У каждого инструмента есть свои функции и свои характеристики, но все они работают вместе, чтобы создать конечный продукт - ваше приложение.
Основные плюсы ООП:
- Модульность и повторное использование кода - проще делать сложное: вы можете разбить свою программу на маленькие, управляемые кусочки, что делает ее проще для понимания и разработки. Выживаете от дублирования кода и можете использовать классы снова и снова.
- Инкапсуляция - прятать сложность: вы можете скрыть внутренние детали своих объектов, предоставив только то, что нужно для работы. Таким образом, вы можете сделать свой код более безопасным и чистым.
- Наследование - создавать новое из старого: наследование позволяет вам создавать новые классы на основе существующих. Это как строить новый автомобиль на основе старой модели, но с новыми функциями.
- Полиморфизм - сделать ваш код гибким: полиморфизм позволяет вам использовать объекты разных классов так, как будто они одного класса. Это удобно, если вам нужно использовать разные объекты в одном контексте.
- Расширяемость и поддерживаемость: если вам нужно что-то поменять в вашей программе, ООП позволит вам сделать это быстро и безболезненно. Вы сможете изменить один класс, и это не повлияет на другие части вашей программы.
Так что в итоге, ООП помогает делать ваш код более читаемым, понятным и гибким.
Классы
В объектно-ориентированном программировании, классы и объекты являются основными концепциями. Классы представляют собой шаблоны для создания объектов, а объекты - конкретные экземпляры этих классов. Давайте рассмотрим эти концепции более подробно и приведем примеры.
Класс - это тип данных, описывающий набор атрибутов (переменных) и методов (функций), которые могут иметь объекты этого класса.
Определение класса
class MyClass:
# Атрибуты класса
class_variable = "Я - атрибут"
# Метод класса
def class_method(self):
return "Я - метод"
Объекты
Объект - это экземпляр класса, который содержит конкретные значения атрибутов и может вызывать методы класса.
Создание объекта
# Создание объекта класса MyClass
my_object = MyClass()
Доступ к атрибутам и вызов методов объекта:
# Доступ к атрибутам объекта
print(my_object.class_variable)
# Вызов метода объекта
print(my_object.class_method())
Давайте рассмотрим пример класса **Person**, который представляет собой человека с атрибутами **name** и **age** и методом **say_hello**:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_hello(self):
return f"Привет, я {self.name}, мне {self.age} лет."
# Создание объекта класса Person
person1 = Person("Матвей", 19)
# Доступ к атрибутам объекта
print(person1.name) # Matvey
print(person1.age) # 19
# Вызов метода объекта
print(person1.say_hello()) # Привет, я Матвей, мне 19 лет.
Атрибуты
Теперь немного подробнее. Атрибуты - это переменные, связанные с определенным объектом. Они представляют состояние объекта и могут хранить данные, которые описывают его характеристики или свойства. Атрибуты могут быть как публичными, так и приватными.
Публичные атрибуты
Эти атрибуты доступны извне объекта и могут быть прочитаны и изменены непосредственно. Названия начинаются просто с буквы.
class Person:
def __init__(self, name, age):
self.name = name # Публичный атрибут
self.age = age # Публичный атрибут
person1 = Person("Матвей", 19)
print(person1.name) # Матвей
Приватные атрибуты
Эти атрибуты доступны только внутри класса и могут быть изменены или получены только через публичные методы. Названия начинаются с двух подчеркиваний.
class Person:
def __init__(self, name, age):
self.__name = name # Приватный атрибут
self.__age = age # Приватный атрибут
person1 = Person("Матвей", 19)
# print(person1.__name) # AttributeError: 'Person' object has no attribute '__name'
Методы
Публичные методы
Эти методы доступны извне объекта и могут быть вызваны непосредственно. Названия также начинаются просто с букв.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def get_name(self): # Публичный метод
return self.name
person1 = Person("Матвей", 19)
print(person1.get_name()) # Матвей
Приватные методы
Эти методы доступны только внутри класса и могут быть вызваны только из других методов класса. Названия начинаются с двух подчеркиваний.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __display_info(self): # Приватный метод
return f"Имя: {self.name}, Возраст: {self.age}"
person1 = Person("Matvey", 19)
# print(person1.__display_info()) # AttributeError: 'Person' object has no attribute '__display_info'
В отличие от атрибутов, которые представляют состояние объекта, методы используются для выполнения операций с объектом и обеспечивают его поведение.
Конструкторы
Их иногда называют “инициализаторы”.
- Конструкторы используются для инициализации объектов класса;
- В Python конструктор обозначается методом с именем
**__init__;** - Конструктор вызывается автоматически при создании нового объекта класса;
- В конструкторе можно инициализировать атрибуты объекта, задать начальные значения переменным и выполнить другие инициализационные действия.
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
car1 = Car("BMW", "3-series")
print(car1.brand) # BMW
print(car1.model) # 3-series
Деструкторы
- Деструкторы используются для освобождения ресурсов, занятых объектом, или выполнения других завершающих действий перед удалением объекта из памяти;
- В Python деструктор обозначается методом с именем
**__del__;** - Деструктор вызывается автоматически, когда объект класса удаляется из памяти или перестает быть доступным;
- Деструктор не всегда нужен, так как Python автоматически управляет памятью и освобождает ресурсы после удаления объекта, но он может быть полезен, например, для закрытия файлов или соединений с базой данных.
class MyClass:
def __del__(self):
print("Класс удален")
obj = MyClass()
del obj # Класс удален
Инкапсуляция
Инкапсуляция - это концепция объектно-ориентированного программирования, которая позволяет скрыть внутреннюю реализацию объекта от внешнего мира и обеспечивает доступ к объекту только через определенные методы.
Рассмотрим класс **Person**, где мы будем использовать инкапсуляцию для скрытия атрибута **__age**:
class Person:
def __init__(self, name, age):
self.name = name
self.__age = age # Приватный атрибут
def get_age(self): # Публичный метод для доступа к приватному атрибуту
return self.__age
def set_age(self, age): # Публичный метод для изменения приватного атрибута
if age > 0:
self.__age = age
# Создание объекта класса Person
person1 = Person("Матвей", 19)
# Попытка доступа к приватному атрибуту напрямую вызовет ошибку
# print(person1.__age) # AttributeError: 'Person' object has no attribute '__age'
# Доступ к приватному атрибуту через публичный метод
print(person1.get_age()) # Вывод: 19
# Изменение приватного атрибута через публичный метод
person1.set_age(20)
print(person1.get_age()) # Вывод: 20
В этом примере атрибут **__age** мы объявили как приватный. Это означает, что он доступен только внутри класса. Мы предоставляем публичные методы **get_age()** и **set_age()**, чтобы получать и изменять значение атрибута **__age**. Таким образом, мы скрываем внутреннюю реализацию и защищаем атрибут от прямого доступа извне.
Почему полезно использовать инкапсуляцию - чтобы наши данные не могли быть случайно испорчены или изменены без нашего разрешения. Только наши методы, “знающие” все тонкости работы объекта, имеют к ним доступ. Также это позволяет нам удобнее управлять данными и скрывает сложности реализации от пользователей класса. Если внутренности объекта меняются, но интерфейс остается тем же, внешний мир даже не заметит изменений, как будто мы сделали ремонт в квартире, но соседи и дальше считают, что мы живем как раньше.
Наследование
Давайте представим себе семейную историю. Есть родительские и дочерние классы.
Представьте, что у нас есть класс "Животное" (родительский класс), который определяет основные характеристики животного, например, его имя. Теперь у нас есть два дочерних класса: "Собака" и "Кошка". Эти классы наследуют все атрибуты и методы класса "Животное", такие как возможность называть животное по имени.
Каждый из дочерних классов может также иметь свои уникальные характеристики. Например, у собаки есть специальный метод, который позволяет ей лаять, а у кошки - метод, позволяющий мяукать.
Таким образом, наследование позволяет нам использовать функциональность родительского класса в дочерних классах, а также добавлять или изменять функциональность, специфичную для дочерних классов.
Смотрим пример:
class Animal:
def __init__(self, name):
self.name = name
def make_sound(self):
pass
class Dog(Animal):
def make_sound(self):
return f"{self.name} говорит: Гав!"
class Cat(Animal):
def make_sound(self):
return f"{self.name} говорит: Мяу!"
dog = Dog("Пэс")
print(dog.make_sound()) # Пэс говорит: Гав!
cat = Cat("Кiшка")
print(cat.make_sound()) # Кiшка говорит: Мяу!
Здесь класс Animal - это наш "родительский" класс, а классы Dog и Cat - "дочерние". Оба дочерних класса наследуют атрибут name и метод make_sound(), но переопределяют метод, чтобы предоставить специфичное для них поведение.
Зачем оно надо? Представим, что мы пишем программу, и у нас есть класс, который уже делает много крутых штук. Но потом нам нужно создать новый класс, который делает что-то очень похожее, но с некоторыми дополнениями или изменениями. Неужели нужно писать все заново? Вот именно! Наследование приходит на помощь!
Просто создадим новый класс, который "наследует" все функции и свойства от своего первого класса. Берем уже собранный кусочек и добавляем к нему новые детали, чтобы создать что-то новенькое!
И главное, когда мы наследуем класс, это не только удобно, но и круто, потому что код становится более организованным и проще для понимания. Так что наследование помогает нам создавать классы, как реальные профессионалы!
Полиморфизм
Это просто великолепие! Данная штука позволяет объектам разных классов вести себя по-разному, но при этом использовать один и тот же метод. Вот как это работает:
Есть несколько классов, каждый из которых имеет метод с одним и тем же названием. Но в каждом из этих классов метод делает что-то уникальное, подходящее для этого класса.
Когда мы вызываем метод у объекта, Python знает, к какому классу принадлежит этот объект, и вызывает соответствующий метод для этого класса.
Вот пример. У нас есть классы Dog и Cat, и у каждого из них есть метод make_sound():
class Dog:
def make_sound(self):
print("Гав!")
class Cat:
def make_sound(self):
print("Мяу!")
Теперь мы можем создать объекты этих классов и вызвать их методы:
dog = Dog()
cat = Cat()
dog.make_sound() # Гав!
cat.make_sound() # Мяу!
Классы Dog и Cat оба имеют make_sound(), но каждый из них делает разные звуки. Это и есть полиморфизм. Мы используем один и тот же метод, но он ведет себя по-разному в зависимости от класса объекта.
Используя полиморфизм, мы:
- Уменьшаем дублирование кода: если у нас есть несколько классов с похожими методами, мы можем использовать полиморфизм, чтобы написать один метод, который будет работать для всех этих классов. Это уменьшает количество повторяющегося кода в нашей программе.
- Облегчаем добавление новых функций: когда у нас появляется новый класс, который должен вести себя так же, как уже существующие классы, мы можем просто наследовать его от нужного класса и переопределить соответствующие методы. Таким образом, мы можем добавить новый функционал, не меняя существующий код.
- Улучшаем читаемость кода: используя полиморфизм, мы можем сделать наш код более понятным и легким для понимания. Методы, которые ведут себя по-разному в разных классах, могут иметь одинаковые имена и параметры, что делает код более предсказуемым.
Перегрузка операторов
Перегрузка операторов в Python - это как волшебство, которое позволяет вам изменять поведение стандартных математических, логических и других операторов, чтобы они работали особым образом для ваших собственных классов. Представьте, что вы можете настроить свой собственный мир правил, в котором **+** может означать не только сложение чисел, но и объединение строк или слияние списков - все зависит от того, как вы этот оператор перегрузите. Я в шоке просто.
Например, давайте представим, что у нас есть класс **Cat**, и мы хотим, чтобы оператор **+** добавлял двух котов вместе, а не складывал их весы. Вот как это выглядит на практике:
Класс представляет кота и имеет два атрибута: **name** и **color**.
class Cat:
def __init__(self, name, color):
self.name = name
self.color = color
Мы хотим, чтобы оператор **+** добавлял двух котов вместе. Для этого мы перегружаем оператор **+** в нашем классе **Cat** с помощью метода **__add__**. Дописываем класс:
class Cat:
def __init__(self, name, color):
self.name = name
self.color = color
def __add__(self, other_cat):
return Cat(self.name + " и " + other_cat.name, "смешанный")
Этот метод принимает два аргумента: **self** (ссылка на текущий объект) и **other_cat** (другой кот, с которым мы хотим сложить текущего кота). Затем он создает новый объект класса **Cat**, объединяя имена двух котов в одну строку и устанавливая цвет нового кота как "mixed".
Теперь, когда мы выполняем операцию **cat1 + cat2**, интерпретатор Python вызывает метод **__add__** объекта **cat1** и передает **cat2** в качестве аргумента **other_cat**. В результате создается новый объект **combined_cat**, который представляет двух объединенных котов.
cat1 = Cat("Кiшка1", "серый")
cat2 = Cat("Кiшка2", "белый")
combined_cat = cat1 + cat2
print(combined_cat.name) # Кiшка1 и Кiшка2
print(combined_cat.color) # смешанный
Ну что уж тут говорить, перегрузка операторов добавляет просто безграничные возможности, предел которых определяется только фантазией разработчика.
Ну че? Дальше будем познавать принципы SOLID? Когда-нибудь потом 😁