Атрибуты, методы и принципы ООП (наследование, инкапсуляция, полиморфизм)
В прошлом уроке вы научились создавать классы и объекты, а также инициализировать их атрибуты. Теперь давайте копнём глубже: разберём, как объекты взаимодействуют и как принципы объектно-ориентированного программирования (ООП) делают код понятным, гибким и лёгким в поддержке.
Атрибуты и методы: подробности
Вы уже знаете: атрибуты — это данные объекта (его характеристики), а методы — это функции, которые определяют его поведение.
Атрибуты класса и атрибуты экземпляра
Важно различать два типа атрибутов:
-
Атрибуты экземпляра (Instance Attributes): Принадлежат конкретному объекту. Вы определяете их внутри метода
__init__, и они уникальны для каждого созданного объекта.class Car: def __init__(self, brand, model): self.brand = brand # Атрибут экземпляра self.model = model # Атрибут экземпляра car1 = Car("Toyota", "Camry") car2 = Car("Lada", "Granta") print(car1.brand) # Toyota print(car2.brand) # Lada -
Атрибуты класса (Class Attributes): Принадлежат самому классу, а не его экземплярам. Они общие для всех объектов этого класса и определяются внутри тела класса, но вне методов.
class Car: # Атрибут класса # Общий для всех машин, произведенных этим "заводом" wheels = 4 def __init__(self, brand, model): self.brand = brand self.model = model car1 = Car("Toyota", "Camry") car2 = Car("Lada", "Granta") print(car1.wheels) # 4 print(car2.wheels) # 4 print(Car.wheels) # 4 (доступ к атрибуту класса через сам класс) # Можно изменить атрибут класса, и это повлияет на все экземпляры Car.wheels = 6 print(car1.wheels) # 6Совет: Атрибуты класса часто используют для констант или значений по умолчанию, которые одинаковы для всех объектов.
Методы экземпляра и специальные методы
-
Методы экземпляра (Instance Methods): Это обычные методы, которые вы уже видели. Они принимают
selfкак первый аргумент и работают с атрибутами конкретного экземпляра.class Dog: def __init__(self, name, breed): self.name = name self.breed = breed def bark(self): # Метод экземпляра return f"{self.name} says Woof!" my_dog = Dog("Buddy", "Golden Retriever") print(my_dog.bark()) # Buddy says Woof! -
Специальные методы (Special Methods): Их ещё называют "магическими" методами или "dunder-методами" (от double underscore – двойное подчёркивание). Их имена начинаются и заканчиваются двойным подчёркиванием (например,
__init__,__str__). Эти методы позволяют классам взаимодействовать со встроенными функциями Python и операторами. Подробнее о них поговорим на следующей странице.
Принципы ООП: фундамент чистого кода
ООП базируется на четырёх основных принципах. Они помогают создавать масштабируемые и поддерживаемые системы:
- Наследование (Inheritance)
- Инкапсуляция (Encapsulation)
- Полиморфизм (Polymorphism)
- Абстракция (Abstraction) (часто рассматривается как концепция, тесно связанная с другими принципами, а не отдельный принцип реализации)
Давайте рассмотрим первые три — их чаще всего используют на практике.
1. Наследование: "Это как..."
Наследование позволяет создать новый класс (дочерний класс или подкласс) на основе существующего (родительский класс или суперкласс). Дочерний класс наследует все атрибуты и методы родительского класса. Он может добавлять свои или переопределять унаследованные.
Это принцип "это как..." или "является разновидностью". Например, "Собака является разновидностью Животного".
class Animal: # Родительский класс
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Этот метод должен быть переопределен в дочернем классе")
class Dog(Animal): # Дочерний класс, наследует от Animal
def speak(self): # Переопределение метода speak
return f"{self.name} лает: Гав!"
class Cat(Animal): # Дочерний класс, наследует от Animal
def speak(self): # Переопределение метода speak
return f"{self.name} мяукает: Мяу!"
my_dog = Dog("Шарик")
my_cat = Cat("Мурка")
print(my_dog.speak()) # Шарик лает: Гав!
print(my_cat.speak()) # Мурка мяукает: Мяу!
Зачем это нужно? Наследование помогает повторно использовать код (DRY - Don't Repeat Yourself). Если у нескольких классов есть общая функциональность, вынесите её в родительский класс.
2. Инкапсуляция: "Скрытие деталей"
Инкапсуляция — это объединение данных (атрибутов) и методов, работающих с ними, в один блок (класс). Она также подразумевает скрытие внутренней реализации объекта от внешнего мира. Это значит, что данные объекта должны быть доступны и изменяемы только через его методы, а не напрямую.
В Python нет строгих механизмов приватности, как в некоторых других языках (например, private). Вместо этого используют соглашения:
- Один нижний пробел
_attribute: Указывает, что атрибут или метод считается "защищённым" (protected) и предназначен для использования внутри класса или его подклассов. Это соглашение, а не строгий запрет. - Два нижних пробела
__attribute: Вызывает "искажение имени" (name mangling), делая доступ к атрибуту извне класса сложнее (но не невозможным). Это используют для предотвращения конфликтов имён при наследовании.
class BankAccount:
def __init__(self, balance):
self.__balance = balance # "Приватный" атрибут с искажением имени
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"Внесено {amount}. Новый баланс: {self.__balance}")
else:
print("Сумма должна быть положительной.")
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
print(f"Снято {amount}. Новый баланс: {self.__balance}")
else:
print("Недостаточно средств или неверная сумма.")
def get_balance(self): # Метод для получения баланса
return self.__balance
account = BankAccount(1000)
account.deposit(500)
account.withdraw(200)
print(f"Текущий баланс: {account.get_balance()}")
# Попытка прямого доступа (не рекомендуется, но возможна через искаженное имя)
# print(account.__balance) # Вызовет AttributeError
# print(account._BankAccount__balance) # Работает, но так делать не стоит!
Зачем это нужно? Инкапсуляция защищает данные от некорректного изменения извне и упрощает поддержку кода. Изменения во внутренней реализации класса не влияют на внешний код, использующий этот класс.
3. Полиморфизм: "Множество форм"
Полиморфизм (от греч. poly — много, morph — форма) означает, что объекты разных классов могут по-разному отвечать на один и тот же вызов метода, в зависимости от их типа.
Проще говоря, у вас есть набор объектов, и вы вызываете один и тот же метод для каждого из них. Каждый объект выполнит его по-своему, в соответствии со своей внутренней реализацией.
class Dog:
def speak(self):
return "Гав!"
class Cat:
def speak(self):
return "Мяу!"
class Duck:
def speak(self):
return "Кря!"
def make_sound(animal):
# Здесь make_sound не знает, какой именно объект ему передали,
# но он знает, что у него есть метод speak().
# И каждый объект реализует speak() по-своему.
return animal.speak()
dog = Dog()
cat = Cat()
duck = Duck()
animals = [dog, cat, duck]
for animal in animals:
print(make_sound(animal))
# Вывод:
# Гав!
# Мяу!
# Кря!
Зачем это нужно? Полиморфизм делает код гибким и расширяемым. Вы можете работать с объектами разных типов единообразно, не зная их конкретной реализации. Это особенно полезно при работе с коллекциями объектов или при создании универсальных функций.
Практическое задание
Давайте закрепим знания.
Задание: Создайте систему для управления сотрудниками компании, используя принципы ООП.
-
Базовый класс
Employee(Сотрудник):- Атрибуты экземпляра:
name(имя),employee_id(ID сотрудника). - Метод
display_info(), который выводит имя и ID сотрудника. - Атрибут класса
company_name(название компании), например, "ООО 'Рога и Копыта'".
- Атрибуты экземпляра:
-
Дочерний класс
Developer(Разработчик):- Наследуется от
Employee. - Дополнительный атрибут экземпляра:
programming_language(язык программирования). - Переопределите метод
display_info(), чтобы он также выводил язык программирования. - Добавьте новый метод
write_code(), который выводит сообщение вроде: "Имя пишет код на язык программирования."
- Наследуется от
-
Дочерний класс
Manager(Менеджер):- Наследуется от
Employee. - Дополнительный атрибут экземпляра:
department(отдел). - Переопределите метод
display_info(), чтобы он также выводил отдел. - Добавьте новый метод
manage_team(), который выводит сообщение вроде: "Имя управляет командой в отделе отдел."
- Наследуется от
-
Инкапсуляция (опционально, для продвинутых):
- Попробуйте сделать
employee_id"защищённым" атрибутом (_employee_id) и создайте методget_employee_id()для его получения.
- Попробуйте сделать
-
Полиморфизм:
- Создайте список из нескольких объектов
DeveloperиManager. - Пройдитесь по этому списку в цикле и вызовите для каждого объекта метод
display_info(). Убедитесь, что каждый объект выводит информацию в соответствии со своей ролью.
- Создайте список из нескольких объектов
Пример структуры кода для начала:
class Employee:
company_name = "ООО 'Рога и Копыта'" # Атрибут класса
def __init__(self, name, employee_id):
self.name = name
self._employee_id = employee_id # Защищенный атрибут
def display_info(self):
print(f"Имя: {self.name}, ID: {self._employee_id}, Компания: {Employee.company_name}")
def get_employee_id(self):
return self._employee_id
class Developer(Employee):
def __init__(self, name, employee_id, programming_language):
super().__init__(name, employee_id) # Вызов конструктора родителя
self.programming_language = programming_language
def display_info(self):
# Дополняем информацию родителя
super().display_info()
print(f"Язык программирования: {self.programming_language}")
def write_code(self):
print(f"{self.name} пишет код на {self.programming_language}.")
# Добавьте класс Manager по аналогии
# Создание объектов и демонстрация полиморфизма
employees = []
employees.append(Developer("Иван Петров", "DEV001", "Python"))
employees.append(Manager("Мария Сидорова", "MGR002", "Разработки"))
for emp in employees:
emp.display_info()
# Здесь можно добавить проверку типа, если нужно вызвать специфичные методы
# if isinstance(emp, Developer):
# emp.write_code()
Попробуйте реализовать это задание самостоятельно. Это поможет вам лучше понять, как работают эти принципы на практике.
На следующей странице мы углубимся в "магические" методы Python, которые позволяют нам делать наши объекты ещё мощнее и интуитивно понятнее, взаимодействуя со стандартными операциями языка.