Введение в объектно-ориентированное программирование

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

Основные понятия ООП

Создание классов

Класс в Python создается с помощью ключевого слова class:

Базовое создание класса
# Простой класс
class Человек:
    pass  # Пустой класс

# Создание объекта (экземпляра класса)
человек1 = Человек()
print(type(человек1))  # <class '__main__.Человек'>

# Класс с атрибутами
class Автомобиль:
    # Атрибуты класса
    количество_колес = 4
    тип_двигателя = "бензиновый"

# Создание объектов
машина1 = Автомобиль()
машина2 = Автомобиль()

# Доступ к атрибутам класса
print(f"Количество колес: {машина1.количество_колес}")
print(f"Тип двигателя: {машина2.тип_двигателя}")

# Изменение атрибута класса
Автомобиль.количество_колес = 6
print(f"Новое количество колес: {машина1.количество_колес}")

Метод __init__ и атрибуты экземпляра

Метод __init__ - это специальный метод, который автоматически вызывается при создании объекта:

Инициализация объектов
class Студент:
    def __init__(self, имя, возраст, курс):
        # Атрибуты экземпляра
        self.имя = имя
        self.возраст = возраст
        self.курс = курс
        self.оценки = []
    
    def представиться(self):
        return f"Меня зовут {self.имя}, мне {self.возраст} лет, я учусь на {self.курс} курсе"
    
    def добавить_оценку(self, оценка):
        self.оценки.append(оценка)
    
    def средний_балл(self):
        if not self.оценки:
            return 0
        return sum(self.оценки) / len(self.оценки)

# Создание объектов с параметрами
студент1 = Студент("Анна", 20, 2)
студент2 = Студент("Борис", 22, 4)

# Использование методов
print(студент1.представиться())
print(студент2.представиться())

# Работа с оценками
студент1.добавить_оценку(5)
студент1.добавить_оценку(4)
студент1.добавить_оценку(5)

print(f"Средний балл {студент1.имя}: {студент1.средний_балл()}")

Методы класса и статические методы

В Python есть три типа методов: методы экземпляра, методы класса и статические методы:

Разные типы методов
class Математика:
    число_pi = 3.14159
    
    # Метод экземпляра (первый параметр - self)
    def умножить_на_пи(self, число):
        return число * self.число_pi
    
    # Метод класса (первый параметр - cls)
    @classmethod
    def установить_пи(cls, новое_значение):
        cls.число_pi = новое_значение
    
    # Статический метод (не принимает self или cls)
    @staticmethod
    def сложить(a, b):
        return a + b

# Использование методов
математика = Математика()

# Метод экземпляра
print(f"5 * π = {математика.умножить_на_пи(5)}")

# Метод класса
Математика.установить_пи(3.14)
print(f"Новое значение π: {Математика.число_pi}")

# Статический метод
print(f"Сумма 3 + 7 = {Математика.сложить(3, 7)}")

Свойства и инкапсуляция

Python позволяет контролировать доступ к атрибутам с помощью свойств и соглашений об именовании:

Свойства и инкапсуляция
class БанковскийСчет:
    def __init__(self, владелец, баланс=0):
        self.владелец = владелец
        self.__баланс = баланс  # Приватный атрибут (два подчеркивания)
        self._история = []      # Защищенный атрибут (одно подчеркивание)
    
    # Свойство для доступа к балансу
    @property
    def баланс(self):
        return self.__баланс
    
    # Свойство для установки баланса с проверкой
    @баланс.setter
    def баланс(self, значение):
        if значение < 0:
            raise ValueError("Баланс не может быть отрицательным")
        self.__баланс = значение
    
    def пополнить(self, сумма):
        if сумма <= 0:
            raise ValueError("Сумма должна быть положительной")
        self.__баланс += сумма
        self._история.append(f"Пополнение на {сумма}")
        return self.__баланс
    
    def снять(self, сумма):
        if сумма <= 0:
            raise ValueError("Сумма должна быть положительной")
        if сумма > self.__баланс:
            raise ValueError("Недостаточно средств")
        self.__баланс -= сумма
        self._история.append(f"Снятие {сумма}")
        return self.__баланс
    
    def показать_историю(self):
        return self._история

# Использование класса
счет = БанковскийСчет("Иван Иванов", 1000)

# Доступ к публичному атрибуту
print(f"Владелец: {счет.владелец}")

# Доступ к приватному атрибуту через свойство
print(f"Баланс: {счет.баланс}")

# Изменение баланса через свойство
try:
    счет.баланс = 2000
    print(f"Новый баланс: {счет.баланс}")
except ValueError as e:
    print(f"Ошибка: {e}")

# Операции со счетом
try:
    счет.пополнить(500)
    print(f"Баланс после пополнения: {счет.баланс}")
    
    счет.снять(300)
    print(f"Баланс после снятия: {счет.баланс}")
except ValueError as e:
    print(f"Ошибка: {e}")

# Просмотр истории
print("История операций:")
for операция in счет.показать_историю():
    print(f"- {операция}")

Специальные методы (магические методы)

Python предоставляет множество специальных методов, которые позволяют определять поведение объектов:

Магические методы
class КомплексноеЧисло:
    def __init__(self, действительная, мнимая):
        self.действительная = действительная
        self.мнимая = мнимая
    
    # Метод для строкового представления объекта
    def __str__(self):
        if self.мнимая >= 0:
            return f"{self.действительная} + {self.мнимая}i"
        else:
            return f"{self.действительная} - {abs(self.мнимая)}i"
    
    # Метод для "официального" строкового представления
    def __repr__(self):
        return f"КомплексноеЧисло({self.действительная}, {self.мнимая})"
    
    # Метод для сложения двух комплексных чисел
    def __add__(self, другое):
        if isinstance(другое, КомплексноеЧисло):
            return КомплексноеЧисло(
                self.действительная + другое.действительная,
                self.мнимая + другое.мнимая
            )
        return NotImplemented
    
    # Метод для проверки равенства
    def __eq__(self, другое):
        if isinstance(другое, КомплексноеЧисло):
            return (self.действительная == другое.действительная and 
                    self.мнимая == другое.мнимая)
        return False

# Использование специальных методов
число1 = КомплексноеЧисло(3, 4)
число2 = КомплексноеЧисло(1, -2)

# Использование __str__
print(f"Число 1: {число1}")
print(f"Число 2: {число2}")

# Использование __repr__
print(f"repr: {repr(число1)}")

# Использование __add__
сумма = число1 + число2
print(f"Сумма: {сумма}")

# Использование __eq__
число3 = КомплексноеЧисло(3, 4)
print(f"число1 == число3: {число1 == число3}")
print(f"число1 == число2: {число1 == число2}")

Практические примеры

Рассмотрим несколько практических примеров использования классов:

Система управления библиотекой
class Книга:
    def __init__(self, название, автор, год_издания, isbn):
        self.название = название
        self.автор = автор
        self.год_издания = год_издания
        self.isbn = isbn
        self.доступна = True
    
    def __str__(self):
        статус = "доступна" if self.доступна else "выдана"
        return f"'{self.название}' by {self.автор} ({self.год_издания}) - {статус}"

class Библиотека:
    def __init__(self, название):
        self.название = название
        self.книги = []
    
    def добавить_книгу(self, книга):
        self.книги.append(книга)
        print(f"Книга '{книга.название}' добавлена в библиотеку")
    
    def найти_книгу(self, название):
        for книга in self.книги:
            if название.lower() in книга.название.lower():
                return книга
        return None
    
    def выдать_книгу(self, название):
        книга = self.найти_книгу(название)
        if книга and книга.доступна:
            книга.доступна = False
            print(f"Книга '{книга.название}' выдана")
        elif книга:
            print(f"Книга '{книга.название}' уже выдана")
        else:
            print(f"Книга '{название}' не найдена")
    
    def вернуть_книгу(self, название):
        книга = self.найти_книгу(название)
        if книга and not книга.доступна:
            книга.доступна = True
            print(f"Книга '{книга.название}' возвращена")
        elif книга:
            print(f"Книга '{книга.название}' уже в библиотеке")
        else:
            print(f"Книга '{название}' не найдена в библиотеке")
    
    def показать_все_книги(self):
        if not self.книги:
            print("Библиотека пуста")
            return
        
        print(f"\nКниги в библиотеке '{self.название}':")
        for i, книга in enumerate(self.книги, 1):
            print(f"{i}. {книга}")

# Использование системы библиотеки
библиотека = Библиотека("Городская библиотека")

# Добавление книг
библиотека.добавить_книгу(Книга("Война и мир", "Л.Н. Толстой", 1869, "978-5-17-006400-7"))
библиотека.добавить_книгу(Книга("Преступление и наказание", "Ф.М. Достоевский", 1866, "978-5-17-006500-4"))
библиотека.добавить_книгу(Книга("Мастер и Маргарита", "М.А. Булгаков", 1967, "978-5-17-082100-5"))

# Просмотр всех книг
библиотека.показать_все_книги()

# Работа с книгами
библиотека.выдать_книгу("Война и мир")
библиотека.выдать_книгу("Война и мир")  # Попытка повторной выдачи
библиотека.вернуть_книгу("Война и мир")
библиотека.вернуть_книгу("Неизвестная книга")  # Попытка вернуть несуществующую книгу

Практические задания

Задания
  1. Создайте класс "Автомобиль" с атрибутами: марка, модель, год выпуска, пробег. Добавьте методы для увеличения пробега и получения информации об автомобиле.
  2. Реализуйте класс "Треугольник" с тремя сторонами. Добавьте методы для проверки существования треугольника, вычисления периметра и площади.
  3. Создайте класс "Банк" с возможностью создания счетов, перевода денег между счетами и получения общей суммы всех средств в банке.
  4. Разработайте класс "Калькулятор" с методами для выполнения базовых арифметических операций и хранения истории вычислений.
Решения:

Задание 1:

class Автомобиль:
    def __init__(self, марка, модель, год_выпуска, пробег=0):
        self.марка = марка
        self.модель = модель
        self.год_выпуска = год_выпуска
        self.пробег = пробег
    
    def увеличить_пробег(self, расстояние):
        if расстояние < 0:
            raise ValueError("Расстояние не может быть отрицательным")
        self.пробег += расстояние
        print(f"Пробег увеличен на {расстояние} км. Общий пробег: {self.пробег} км")
    
    def получить_информацию(self):
        return (f"{self.марка} {self.модель} ({self.год_выпуска})\n"
                f"Пробег: {self.пробег} км")
    
    def __str__(self):
        return self.получить_информацию()

# Пример использования
авто = Автомобиль("Toyota", "Camry", 2020, 15000)
print(авто)
авто.увеличить_пробег(500)
print(авто)

Задание 2:

import math

class Треугольник:
    def __init__(self, сторона_a, сторона_b, сторона_c):
        self.сторона_a = сторона_a
        self.сторона_b = сторона_b
        self.сторона_c = сторона_c
        
        if not self.существует():
            raise ValueError("Треугольник с такими сторонами не существует")
    
    def существует(self):
        # Проверка неравенства треугольника
        return (self.сторона_a + self.сторона_b > self.сторона_c and
                self.сторона_a + self.сторона_c > self.сторона_b and
                self.сторона_b + self.сторона_c > self.сторона_a)
    
    def периметр(self):
        return self.сторона_a + self.сторона_b + self.сторона_c
    
    def площадь(self):
        # Формула Герона
        p = self.периметр() / 2
        return math.sqrt(p * (p - self.сторона_a) * (p - self.сторона_b) * (p - self.сторона_c))
    
    def __str__(self):
        return (f"Треугольник со сторонами: {self.сторона_a}, {self.сторона_b}, {self.сторона_c}\n"
                f"Периметр: {self.периметр()}\n"
                f"Площадь: {self.площадь():.2f}")

# Пример использования
try:
    треугольник = Треугольник(3, 4, 5)
    print(треугольник)
except ValueError as e:
    print(f"Ошибка: {e}")

Задание 3:

class Счет:
    def __init__(self, номер, баланс=0):
        self.номер = номер
        self.баланс = баланс

class Банк:
    def __init__(self, название):
        self.название = название
        self.счета = []
    
    def создать_счет(self, номер, начальный_баланс=0):
        счет = Счет(номер, начальный_баланс)
        self.счета.append(счет)
        print(f"Счет {номер} создан с балансом {начальный_баланс}")
        return счет
    
    def перевести_деньги(self, номер_отправителя, номер_получателя, сумма):
        отправитель = next((счет for счет in self.счета if счет.номер == номер_отправителя), None)
        получатель = next((счет for счет in self.счета if счет.номер == номер_получателя), None)
        
        if not отправитель:
            print(f"Счет отправителя {номер_отправителя} не найден")
            return False
        
        if not получатель:
            print(f"Счет получателя {номер_получателя} не найден")
            return False
        
        if отправитель.баланс < сумма:
            print(f"Недостаточно средств на счете {номер_отправителя}")
            return False
        
        отправитель.баланс -= сумма
        получатель.баланс += сумма
        print(f"Переведено {сумма} со счета {номер_отправителя} на счет {номер_получателя}")
        return True
    
    def общая_сумма(self):
        return sum(счет.баланс for счет in self.счета)
    
    def показать_счета(self):
        print(f"\nСчета в банке '{self.название}':")
        for счет in self.счета:
            print(f"Счет {счет.номер}: {счет.баланс}")

# Пример использования
банк = Банк("Мой Банк")
счет1 = банк.создать_счет("ACC001", 1000)
счет2 = банк.создать_счет("ACC002", 500)

банк.показать_счета()
print(f"Общая сумма: {банк.общая_сумма()}")

банк.перевести_деньги("ACC001", "ACC002", 300)
банк.показать_счета()
print(f"Общая сумма: {банк.общая_сумма()}")

Задание 4:

class Калькулятор:
    def __init__(self):
        self.история = []
    
    def сложить(self, a, b):
        результат = a + b
        операция = f"{a} + {b} = {результат}"
        self.история.append(операция)
        return результат
    
    def вычесть(self, a, b):
        результат = a - b
        операция = f"{a} - {b} = {результат}"
        self.история.append(операция)
        return результат
    
    def умножить(self, a, b):
        результат = a * b
        операция = f"{a} * {b} = {результат}"
        self.история.append(операция)
        return результат
    
    def разделить(self, a, b):
        if b == 0:
            raise ZeroDivisionError("Деление на ноль невозможно")
        результат = a / b
        операция = f"{a} / {b} = {результат}"
        self.история.append(операция)
        return результат
    
    def показать_историю(self):
        if not self.история:
            print("История пуста")
            return
        
        print("История вычислений:")
        for операция in self.история:
            print(f"- {операция}")
    
    def очистить_историю(self):
        self.история.clear()
        print("История очищена")

# Пример использования
калькулятор = Калькулятор()

print(f"Сложение: {калькулятор.сложить(5, 3)}")
print(f"Вычитание: {калькулятор.вычесть(10, 4)}")
print(f"Умножение: {калькулятор.умножить(7, 2)}")
try:
    print(f"Деление: {калькулятор.разделить(15, 3)}")
    print(f"Деление на ноль: {калькулятор.разделить(10, 0)}")
except ZeroDivisionError as e:
    print(f"Ошибка: {e}")

калькулятор.показать_историю()