Объектно-ориентированное программирование на Python: принципы и практика
Для кого эта статья:
- Студенты и новички в программировании, интересующиеся Python и ООП
- Опытные разработчики, желающие углубить знания в объектно-ориентированном программировании
Преподаватели и тренеры, обучающие Python и концепциям ООП
Объектно-ориентированное программирование на Python — не просто модный термин, а мощный инструмент структурирования кода, который трансформирует хаотичные строки в элегантную архитектуру. Когда я впервые столкнулся с ООП, оно казалось непостижимой магией — но стоило разобраться в базовых концепциях, как сложные проекты стали распадаться на понятные компоненты, словно конструктор Lego. Готовы превратить запутанные скрипты в организованные системы? Давайте разберемся в основах ООП на Python, без лишней теории и с практическими примерами. 🐍
Хотите не просто понять, а мастерски применять ООП в реальных проектах? Программа Обучение Python-разработке от Skypro погружает вас в мир объектно-ориентированного программирования через практику. Вы создадите полноценные веб-приложения с использованием классов и объектов под руководством экспертов, работающих в крупнейших IT-компаниях. Реальные проекты в портфолио вместо сухой теории — ваш путь к позиции Python-разработчика за 9 месяцев.
Что такое ООП в Python: основные концепции и преимущества
Объектно-ориентированное программирование (ООП) — это парадигма, позволяющая структурировать код вокруг "объектов", а не функций и логики. В Python, где всё является объектом, эта парадигма реализована особенно элегантно. ООП строится на четырех фундаментальных принципах:
- Инкапсуляция — объединение данных и методов внутри объекта с контролем доступа к ним
- Наследование — создание новых классов на основе существующих
- Полиморфизм — способность объектов разных классов реагировать на одинаковые сообщения по-разному
- Абстракция — выделение ключевых характеристик объекта, игнорируя несущественные детали
Михаил Васильев, Senior Python Developer
Помню проект, где мы обрабатывали данные из множества источников без ООП. Код превратился в лабиринт из 5000+ строк с дублированием и запутанной логикой. Переписав его с использованием ООП, мы создали иерархию классов для разных источников данных. Базовый класс
DataProcessorсодержал общую логику, а специфические детали реализовали в дочерних классах через наследование. Результат? Код сократился до 2000 строк, стал поддерживаемым, а новые источники данных интегрировались за часы вместо дней. ООП превратило неуправляемый монолит в элегантную, расширяемую систему.
Преимущества ООП в Python проявляются особенно ярко при работе над средними и крупными проектами:
| Преимущество | Описание | Практическое значение |
|---|---|---|
| Модульность | Код организован в логические блоки (классы) | Проще поддерживать и расширять проект |
| Повторное использование | Классы можно использовать многократно | Сокращение объёма кода и времени разработки |
| Абстракция данных | Скрытие внутренней реализации | Возможность изменять внутренний код без влияния на внешние вызовы |
| Коллективная разработка | Чёткое разделение ответственности | Разные разработчики могут работать над разными классами параллельно |
Python реализует ООП с определенными особенностями, делающими его более гибким по сравнению с традиционными объектно-ориентированными языками:
- Множественное наследование (в отличие от Java)
- Динамическая типизация — тип объекта определяется во время выполнения
- Всё является объектом, включая функции и классы
- "Утиная типизация" — поведение объекта важнее его типа
Освоив основы ООП Python, вы получаете мощный инструментарий для создания масштабируемого, поддерживаемого кода — навык, который отличает любителя от профессионального разработчика. 🧩

Классы и объекты в Python: первые шаги
Классы в Python — это чертежи или шаблоны, определяющие структуру и поведение объектов. Объекты же — конкретные экземпляры классов. Представьте класс как схему для постройки дома, а объект — как реальное здание, построенное по этой схеме.
Создать класс в Python просто — используйте ключевое слово class:
class Car:
# Конструктор класса
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
# Метод класса
def get_descriptive_name(self):
return f"{self.year} {self.make} {self.model}"
# Еще один метод
def read_odometer(self):
return f"This car has {self.odometer_reading} miles on it."
# Метод для изменения состояния объекта
def update_odometer(self, mileage):
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
После определения класса можно создавать его экземпляры — объекты:
# Создание объекта класса Car
my_car = Car('Audi', 'A4', 2021)
# Использование методов объекта
print(my_car.get_descriptive_name()) # Выведет: 2021 Audi A4
print(my_car.read_odometer()) # Выведет: This car has 0 miles on it.
# Изменение атрибута через метод
my_car.update_odometer(23500)
print(my_car.read_odometer()) # Выведет: This car has 23500 miles on it.
Обратите внимание на следующие ключевые элементы:
- Метод init — специальный метод, вызываемый автоматически при создании нового экземпляра класса (конструктор)
- Параметр self — обязательный первый параметр всех методов класса, ссылающийся на создаваемый экземпляр
- Атрибуты класса — переменные, хранищие состояние объекта (make, model, year, odometer_reading)
- Методы класса — функции, определяющие поведение объектов этого класса
Анна Соколова, Python-тренер
На курсе для новичков я столкнулась с проблемой: студенты не понимали практическую ценность ООП. Тогда я создала проект "Виртуальный зоопарк". Сначала студенты писали процедурный код для управления животными — получился запутанный массив условных операторов. Затем мы переписали всё через классы: базовый класс
Animalс общими свойствами, и дочерние классыMammal,Bird,Reptileс уникальным поведением. Когда пришло время добавить новый тип животных, один студент сделал это за 5 минут, создав новый класс. Его восторженное "Вау, это реально работает!" показало — концепция ООП наконец-то стала понятной.
Магия классов и объектов Python проявляется в том, как они позволяют моделировать реальный мир в коде. Сравним процедурный подход с объектно-ориентированным для управления банковскими счетами:
| Процедурный подход | ООП подход |
|---|---|
|
|
ООП-версия обладает значительными преимуществами:
- Данные и операции над ними логически сгруппированы вместе
- Каждый счет — самостоятельный объект, отвечающий за своё состояние
- Добавление нового функционала (например, вычисление процентов) требует изменений только внутри класса
- Код становится более читаемым и самодокументируемым
Классы и объекты — основа основ ООП Python, открывающая путь к более сложным концепциям и элегантным решениям. 🏗️
Атрибуты и методы: создание функциональных объектов
Атрибуты и методы — строительные блоки, которые превращают классы из пустых оболочек в функциональные сущности. Атрибуты хранят данные (состояние объекта), а методы определяют поведение (что объект может делать).
В Python существует несколько типов атрибутов:
- Атрибуты экземпляра — уникальны для каждого объекта, определяются через self
- Атрибуты класса — общие для всех экземпляров класса
- Приватные атрибуты — с ограниченным доступом (начинаются с __)
Рассмотрим практический пример, демонстрирующий разные типы атрибутов:
class Employee:
# Атрибут класса — общий для всех экземпляров
company = "Tech Solutions"
# Счётчик созданных экземпляров
employee_count = 0
def __init__(self, name, salary):
# Атрибуты экземпляра — уникальные для каждого объекта
self.name = name
# Приватный атрибут — условно защищен от внешнего доступа
self.__salary = salary
# Увеличиваем счётчик при создании нового сотрудника
Employee.employee_count += 1
# Генерируем уникальный ID для каждого сотрудника
self.id = f"EMP{Employee.employee_count}"
# Метод для доступа к приватному атрибуту
def get_salary(self):
return self.__salary
# Метод для изменения приватного атрибута с валидацией
def set_salary(self, new_salary):
if new_salary > 0:
self.__salary = new_salary
else:
print("Зарплата должна быть положительным числом")
# Статический метод — не требует экземпляра для вызова
@staticmethod
def company_motto():
return "Инновации для будущего"
# Метод класса — может изменять атрибуты класса
@classmethod
def change_company(cls, new_company):
cls.company = new_company
# Обычный метод экземпляра
def details(self):
return f"ID: {self.id}, Name: {self.name}, Company: {Employee.company}"
Использование этого класса:
# Создаем сотрудников
employee1 = Employee("Анна", 75000)
employee2 = Employee("Максим", 85000)
# Доступ к атрибутам экземпляра
print(employee1.name) # Анна
print(employee2.name) # Максим
# Доступ к атрибутам класса
print(Employee.company) # Tech Solutions
print(employee1.company) # Tech Solutions – доступно и через экземпляр
# Работа с приватными атрибутами через методы
print(employee1.get_salary()) # 75000
employee1.set_salary(80000)
print(employee1.get_salary()) # 80000
# Прямой доступ к приватному атрибуту не рекомендуется, но технически возможен
# print(employee1.__salary) # AttributeError: 'Employee' object has no attribute '__salary'
print(employee1._Employee__salary) # 80000 – не рекомендуется так делать!
# Использование статического метода
print(Employee.company_motto()) # Инновации для будущего
# Использование метода класса для изменения атрибута класса
Employee.change_company("Global Tech")
print(Employee.company) # Global Tech
print(employee1.company) # Global Tech – изменилось для всех экземпляров
# Использование обычного метода экземпляра
print(employee1.details()) # ID: EMP1, Name: Анна, Company: Global Tech
Методы в Python-классах делятся на несколько типов:
| Тип метода | Декоратор | Первый параметр | Назначение |
|---|---|---|---|
| Методы экземпляра | – | self | Работа с конкретным экземпляром класса |
| Методы класса | @classmethod | cls | Работа с классом в целом |
| Статические методы | @staticmethod | – | Функциональность, логически связанная с классом |
| Магические методы | – | self | Определение специального поведения (операторы, конвертация и т.д.) |
Магические методы (double underscore methods или dunder methods) заслуживают отдельного внимания — они позволяют объектам взаимодействовать с встроенными функциями и операторами Python:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# Определяем поведение оператора +
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
# Строковое представление для пользователя
def __str__(self):
return f"Vector({self.x}, {self.y})"
# Строковое представление для разработчика
def __repr__(self):
return f"Vector({self.x}, {self.y})"
# Поведение при сравнении ==
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# Определяем длину вектора для функции len()
def __len__(self):
return int((self.x**2 + self.y**2)**0.5)
# Делаем объект вызываемым как функцию
def __call__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
# Использование
v1 = Vector(2, 3)
v2 = Vector(5, 1)
v3 = v1 + v2 # Использует __add__
print(v3) # Вызовет __str__, выведет: Vector(7, 4)
print(v1 == Vector(2, 3)) # True, использует __eq__
print(len(v1)) # 3, использует __len__
v4 = v1(2) # Вызывает объект как функцию через __call__
print(v4) # Vector(4, 6)
Правильное использование атрибутов и методов позволяет создавать интуитивно понятные интерфейсы для ваших классов, делая код более выразительным и легким для понимания. Магические методы — это ещё один уровень элегантности ООП в Python, позволяющий вашим объектам естественно вписываться в язык. 🔮
Наследование, инкапсуляция и полиморфизм на практике
Три столпа ООП — наследование, инкапсуляция и полиморфизм — превращают Python из просто объектно-ориентированного языка в мощный инструмент моделирования реальных систем. Рассмотрим, как эти концепции работают на практике.
Наследование позволяет создавать новые классы на основе существующих, расширяя или изменяя их функциональность:
# Базовый класс
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def make_sound(self):
return "Some generic animal sound"
def info(self):
return f"{self.name} is a {self.species}"
# Дочерний класс, наследующий от Animal
class Dog(Animal):
def __init__(self, name, breed):
# Вызываем конструктор родительского класса
super().__init__(name, species="Dog")
self.breed = breed
# Переопределяем метод родительского класса
def make_sound(self):
return "Woof!"
# Добавляем новый метод, специфичный для собак
def fetch(self, item):
return f"{self.name} fetches the {item}"
# Еще один дочерний класс
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name, species="Cat")
self.color = color
def make_sound(self):
return "Meow!"
def scratch(self):
return f"{self.name} scratches the furniture"
# Использование
animal = Animal("Generic Animal", "Unknown")
dog = Dog("Rex", "German Shepherd")
cat = Cat("Whiskers", "Gray")
print(animal.info()) # Generic Animal is a Unknown
print(dog.info()) # Rex is a Dog
print(cat.info()) # Whiskers is a Cat
print(animal.make_sound()) # Some generic animal sound
print(dog.make_sound()) # Woof!
print(cat.make_sound()) # Meow!
print(dog.fetch("ball")) # Rex fetches the ball
print(cat.scratch()) # Whiskers scratches the furniture
Python также поддерживает множественное наследование, позволяя классу наследоваться от нескольких родителей:
class FlyingMixin:
def fly(self):
return f"{self.name} is flying high!"
class SwimmingMixin:
def swim(self):
return f"{self.name} is swimming"
class Duck(Animal, FlyingMixin, SwimmingMixin):
def __init__(self, name):
super().__init__(name, species="Duck")
def make_sound(self):
return "Quack!"
duck = Duck("Donald")
print(duck.make_sound()) # Quack!
print(duck.fly()) # Donald is flying high!
print(duck.swim()) # Donald is swimming
Инкапсуляция скрывает внутреннюю реализацию и защищает данные от неконтролируемого доступа. В Python инкапсуляция реализуется через соглашения и механизм свойств:
class BankAccount:
def __init__(self, owner, initial_balance=0):
self.owner = owner
# Приватный атрибут (соглашение)
self._balance = initial_balance
# Действительно приватный атрибут (затрудняет прямой доступ)
self.__transaction_log = []
# Использование свойства (property) для контролируемого доступа
@property
def balance(self):
return self._balance
# Сеттер для свойства с валидацией
@balance.setter
def balance(self, value):
if not isinstance(value, (int, float)):
raise ValueError("Balance must be a number")
if value < 0:
raise ValueError("Balance cannot be negative")
# Логируем изменение в приватный журнал
self.__log_transaction(self._balance, value)
self._balance = value
# Приватный метод
def __log_transaction(self, old_value, new_value):
import datetime
self.__transaction_log.append({
"timestamp": datetime.datetime.now(),
"old_value": old_value,
"new_value": new_value
})
# Публичный метод для доступа к приватным данным
def get_transaction_history(self):
return self.__transaction_log.copy() # Возвращаем копию для безопасности
account = BankAccount("Alex", 1000)
print(account.balance) # 1000
# Используем сеттер свойства с валидацией
try:
account.balance = -500 # Вызовет ошибку
except ValueError as e:
print(str(e)) # Balance cannot be negative
account.balance = 1500
print(account.balance) # 1500
# Доступ к приватному атрибуту затруднен, но не невозможен
# print(account.__transaction_log) # AttributeError
# Но все еще можно получить доступ через name mangling (не рекомендуется)
# print(account._BankAccount__transaction_log)
# Вместо этого используем публичный метод
print(account.get_transaction_history())
Полиморфизм позволяет объектам разных классов реагировать на один и тот же метод по-разному. Это обеспечивает гибкость и упрощает код:
# Функция, демонстрирующая полиморфизм
def animal_sounds(animals):
for animal in animals:
# Один и тот же метод работает для разных типов объектов
print(f"{animal.name} says {animal.make_sound()}")
animal_sounds([
Animal("Generic", "Unknown"),
Dog("Rex", "German Shepherd"),
Cat("Whiskers", "Gray"),
Duck("Donald")
])
# Generic says Some generic animal sound
# Rex says Woof!
# Whiskers says Meow!
# Donald says Quack!
# Полиморфизм через протокол — утиная типизация
def process_sequence(sequence):
# Работает с любым объектом, поддерживающим len() и индексацию
print(f"Длина: {len(sequence)}")
print(f"Первый элемент: {sequence[0]}")
print(f"Обход в цикле:")
for item in sequence:
print(f" – {item}")
# Работает с разными типами последовательностей
process_sequence([1, 2, 3])
process_sequence("Python")
process_sequence({"a": 1, "b": 2, "c": 3})
Мощь ООП в Python раскрывается, когда эти три концепции работают вместе, создавая гибкие и расширяемые структуры кода. Например, при разработке системы управления библиотекой:
class LibraryItem:
def __init__(self, title, item_id):
self.title = title
self.item_id = item_id
self._checked_out = False
@property
def checked_out(self):
return self._checked_out
def check_out(self):
if self._checked_out:
return False
self._checked_out = True
return True
def check_in(self):
if not self._checked_out:
return False
self._checked_out = False
return True
def display_info(self):
status = "checked out" if self._checked_out else "available"
return f"{self.title} (ID: {self.item_id}) – {status}"
class Book(LibraryItem):
def __init__(self, title, item_id, author, pages):
super().__init__(title, item_id)
self.author = author
self.pages = pages
def display_info(self):
basic_info = super().display_info()
return f"{basic_info}\nAuthor: {self.author}, Pages: {self.pages}"
class DVD(LibraryItem):
def __init__(self, title, item_id, director, runtime):
super().__init__(title, item_id)
self.director = director
self.runtime = runtime
def display_info(self):
basic_info = super().display_info()
return f"{basic_info}\nDirector: {self.director}, Runtime: {self.runtime} min"
# Демонстрация работы
book = Book("The Hobbit", "B12345", "J.R.R. Tolkien", 366)
dvd = DVD("Inception", "D67890", "Christopher Nolan", 148)
print(book.display_info())
book.check_out()
print(book.display_info())
print("\n" + dvd.display_info())
dvd.check_out()
print(dvd.display_info())
# Полиморфная функция для обработки любых библиотечных элементов
def process_items(items):
for item in items:
print("\nProcessing item:")
print(item.display_info())
process_items([book, dvd])
Эти примеры демонстрируют, как наследование, инкапсуляция и полиморфизм создают мощный фундамент для построения сложных, но гибких и поддерживаемых программных систем. Именно эти концепции делают ООП таким ценным инструментом в арсенале Python-разработчика. 🧠
Практические задания по ООП на Python: закрепляем знания
Теория без практики бесполезна, особенно в программировании. Давайте закрепим знания об основах ООП Python через серию практических заданий, постепенно наращивая сложность. 💪
Задание 1: Создание простого класса
Создайте класс Rectangle, который будет представлять прямоугольник с методами для расчета площади и периметра.
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * (self.length + self.width)
# Проверка
rectangle = Rectangle(5, 3)
print(f"Площадь: {rectangle.area()}") # Должно вывести 15
print(f"Периметр: {rectangle.perimeter()}") # Должно вывести 16
Задание 2: Инкапсуляция и валидация данных
Доработайте класс Rectangle, добавив защиту от отрицательных значений сторон и возможность изменения размеров.
class Rectangle:
def __init__(self, length, width):
self._length = 0 # Защищенные атрибуты
self._width = 0
# Используем сеттеры для начальной установки значений
self.length = length
self.width = width
@property
def length(self):
return self._length
@length.setter
def length(self, value):
if value <= 0:
raise ValueError("Длина должна быть положительным числом")
self._length = value
@property
def width(self):
return self._width
@width.setter
def width(self, value):
if value <= 0:
raise ValueError("Ширина должна быть положительным числом")
self._width = value
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * (self.length + self.width)
# Проверка
rect = Rectangle(5, 3)
print(f"Площадь: {rect.area()}") # 15
# Изменение размеров
rect.length = 7
rect.width = 4
print(f"Новая площадь: {rect.area()}") # 28
try:
rect.length = -2 # Должно вызвать ошибку
except ValueError as e:
print(f"Ошибка: {e}") # Ошибка: Длина должна быть положительным числом
Задание 3: Наследование и полиморфизм
Создайте иерархию геометрических фигур: базовый класс Shape и производные классы Circle, Rectangle, и Triangle.
import math
class Shape:
def area(self):
"""Вычисляет площадь фигуры."""
raise NotImplementedError("Подклассы должны реализовать этот метод")
def perimeter(self):
"""Вычисляет периметр фигуры."""
raise NotImplementedError("Подклассы должны реализовать этот метод")
def description(self):
"""Возвращает описание фигуры."""
return f"{self.__class__.__name__}: площадь = {self.area()}, периметр = {self.perimeter()}"
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
class Rectangle(Shape):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * (self.length + self.width)
class Triangle(Shape):
def __init__(self, a, b, c):
# Проверка неравенства треугольника
if a + b <= c or a + c <= b or b + c <= a:
raise ValueError("Нарушено неравенство треугольника")
self.a = a
self.b = b
self.c = c
def area(self):
# Формула Герона
s = (self.a + self.b + self.c) / 2
return math.sqrt(s * (s – self.a) * (s – self.b) * (s – self.c))
def perimeter(self):
return self.a + self.b + self.c
# Демонстрация полиморфизма
shapes = [
Circle(5),
Rectangle(4, 6),
Triangle(3, 4, 5)
]
for shape in shapes:
print(shape.description())
Задание 4: Композиция и агрегация
Создайте систему классов, моделирующую автомобиль, состоящий из различных компонентов.
class Engine:
def __init__(self, power, volume):
self.power = power
self.volume = volume
self._running = False
def start(self):
if not self._running:
self._running = True
return "Двигатель запущен"
return "Двигатель уже работает"
def stop(self):
if self._running:
self._running = False
return "Двигатель остановлен"
return "Двигатель уже остановлен"
@property
def status(self):
return "работает" if self._running else "остановлен"
class FuelTank:
def __init__(self, capacity):
self.capacity = capacity
self._fuel_level = 0
def fill(self, amount):
if amount <= 0:
return "Количество должно быть положительным"
if self._fuel_level + amount > self.capacity:
actual_fill = self.capacity – self._fuel_level
self._fuel_level = self.capacity
return f"Бак заполнен. Добавлено {actual_fill} л. (не поместилось {amount – actual_fill} л.)"
self._fuel_level += amount
return f"Добавлено {amount} л. топлива"
@property
def fuel_level(self):
return self._fuel_level
class Car:
def __init__(self, make, model, engine_power, engine_volume, tank_capacity):
self.make = make
self.model = model
# Композиция – создаем компоненты внутри класса
self.engine = Engine(engine_power, engine_volume)
self.fuel_tank = FuelTank(tank_capacity)
def start_engine(self):
if self.fuel_tank.fuel_level > 0:
return self.engine.start()
return "Недостаточно топлива"
def stop_engine(self):
return self.engine.stop()
def add_fuel(self, amount):
return self.fuel_tank.fill(amount)
def status(self):
return f"{self.make} {self.model}:\n" \
f"Двигатель: {self.engine.power} л.с., {self.engine.volume} л, {self.engine.status}\n" \
f"Топливный бак: {self.fuel_tank.fuel_level}/{self.fuel_tank.capacity} л"
# Использование
my_car = Car("Toyota", "Camry", 180, 2.5, 60)
print(my_car.status())
print(my_car.add_fuel(45))
print(my_car.start_engine())
print(my_car.status())
print(my_car.stop_engine())
print(my_car.status())
Задание 5: Создание полноценной системы классов
Разработайте мини-систему управления библиотекой с классами для книг, читателей и самой библиотеки. Реализуйте выдачу книг, их возврат и отслеживание сроков.
import datetime
class Book:
def __init__(self, title, author, isbn):
self.title = title
self.author = author
self.isbn = isbn
self._checked_out = False
self._checked_out_by = None
self._due_date = None
@property
def is_checked_out(self):
return self._checked_out
@property
def checked_out_info(self):
if not self._checked_out:
return "Книга доступна"
days_overdue = (datetime.date.today() – self._due_date).days if self._due_date else 0
if days_overdue > 0:
return f"Выдана {self._checked_out_by.name}, просрочена на {days_overdue} дней"
else:
return f"Выдана {self._checked_out_by.name}, вернуть до {self._due_date}"
def __str__(self):
return f"'{self.title}' by {self.author} (ISBN: {self.isbn})"
class Patron:
def __init__(self, name, patron_id):
self.name = name
self.patron_id = patron_id
self._checked_out_books = []
def add_book(self, book):
self._checked_out_books.append(book)
def remove_book(self, book):
if book in self._checked_out_books:
self._checked_out_books.remove(book)
@property
def checked_out_books(self):
return self._checked_out_books.copy() # Возвращаем копию для защиты
def __str__(self):
return f"{self.name} (ID: {self.patron_id}), книг на руках: {len(self._checked_out_books)}"
class Library:
def __init__(self, name):
self.name = name
self.books = []
self.patrons = []
def add_book(self, book):
self.books.append(book)
return f"Книга {book} добавлена в библиотеку {self.name}"
def add_patron(self, patron):
self.patrons.append(patron)
return f"Читатель {patron.name} зарегистрирован в библиотеке"
def check_out_book(self, book_isbn, patron_id, days=14):
# Находим книгу и читателя
book = next((b for b in self.books if b.isbn == book_isbn), None)
patron = next((p for p in self.patrons if p.patron_id == patron_id), None)
if not book:
return "Книга не найдена"
if not patron:
return "Читатель не найден"
if book.is_checked_out:
return f"Книга {book} уже выдана"
# Устанавливаем информацию о выдаче
book._checked_out = True
book._checked_out_by = patron
book._due_date = datetime.date.today() + datetime.timedelta(days=days)
# Добавляем книгу в список выданных читателю
patron.add_book(book)
return f"Книга {book} выдана читателю {patron.name} до {book._due_date}"
def return_book(self, book_isbn):
book = next((b for b in self.books if b.isbn == book_isbn), None)
if not book:
return "Книга не найдена"
if not book.is_checked_out:
return f"Книга {book} не была выдана"
# Удаляем книгу из списка выданных читателю
patron = book._checked_out_by
patron.remove_book(book)
# Сбрасываем информацию о выдаче
book._checked_out = False
book._checked_out_by = None
book._due_date = None
return f"Книга {book} возвращена в библиотеку"
def get_overdue_books(self):
today = datetime.date.today()
overdue_books = [book for book in self.books
if book.is_checked_out and book._due_date < today]
return overdue_books
# Демонстрация работы
library = Library("Городская библиотека")
# Добавляем книги
book1 = Book("Война и мир", "Лев Толстой", "123456")
book2 = Book("Преступление и наказание", "Федор Достоевский", "789012")
book3 = Book("Мастер и Маргарита", "Михаил Булгаков", "345678")
library.add_book(book1)
library.add_book(book2)
library.add_book(book3)
# Регистрируем читателей
patron1 = Patron("Иван Иванов", "P001")
patron2 = Patron("Анна Смирнова", "P002")
library.add_patron(patron1)
library.add_patron(patron2)
# Выдаем книги
print(library.check_out_book("123456", "P001"))
print(library.check_out_book("789012", "P002"))
# Проверяем статус
print(book1.checked_out_info)
print(patron1)
# Возвращаем книгу
print(library.return_book("123456"))
print(book1.checked_out_info)
Попробуйте выполнить эти задания самостоятельно, экспериментируя с различными подходами и решениями. Это лучший способ закрепить понимание основ ООП Python и развить навыки объектно-ориентированного проектирования.
Усложните задачи, добавив собственные требования и функциональность. Например:
- Добавьте дополнительные методы для геометрических фигур (вращение, масштабирование)
- Расширьте модель автомобиля, добавив трансмиссию, колеса, тормозную систему
- Добавьте систему штрафов за просроченные книги в библиотечной системе
- Реализуйте поддержку бронирования книг и очереди читателей
Создание собственных классов и проектирование их взаимодействия — это лучший путь к мастерству в ООП. Чем больше вы практикуетесь, тем более естественным становится процесс объектно-ориентированного мышления. 🚀
Объектно-ориентированное программирование в Python — это не просто набор синтаксических конструкций, а образ мышления, который трансформирует подход к решению задач. Освоив базовые принципы ООП — классы, объекты, наследование, инкапсуляцию и полиморфизм — вы получаете мощные инструменты для создания гибкого, модульного и поддерживаемого кода. Регулярная практика и эксперименты с различными паттернами проектирования сделают ООП вашим естественным подходом к разработке. И помните: лучшие программисты не те, кто пишет сложный код, а те, кто умеет сделать сложные вещи простыми и понятными через грамотное проектирование.
Читайте также
- ООП простыми словами: как понять классы и объекты через аналогии
- Типичные ошибки программистов: как избежать и исправить проблемы
- ООП: основные принципы, преимущества и практическое применение
- Программирование микроконтроллеров: от первых шагов до умных устройств
- Выбор языка программирования для Telegram бота: подробное сравнение
- Объектно-ориентированное программирование: от хаоса к порядку в разработке
- 7 инженерных решений ООП Python для реальных проектов
- Как воплотить ООП в C: подробное руководство по созданию калькулятора
- ООП в Java: практические задания для опытных разработчиков
- Абстракция в ООП: как создать гибкую архитектуру программы