Сегодня погружаемся в продвинутые аспекты ООП в Python: магические методы и декораторы. Эти инструменты позволяют создавать более выразительный и функциональный код. Магические методы позволяют изменить поведение объектов класса, а декораторы помогают добавлять функциональность к функциям и классам, не изменяя их исходный код.

В конце статьи мы напишем программу с использованием декоратора, который будет логировать вызовы функций.

Магические Методы

Магические методы (или «дуnder methods») – это встроенные методы с двумя подчеркиваниями в начале и конце имени, такие как __init__, __str__, __repr__, и другие. Они предоставляют объектам классов дополнительные возможности, такие как изменение их представления или поведение в операциях.

Основные Магические Методы

__init__ — конструктор, инициализирующий объект при его создании.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

__str__ — возвращает строковое представление объекта для пользователей. Этот метод используется, когда объект передается в print().

def __str__(self):
    return f"{self.name}, {self.age} years old"

__repr__ — возвращает строковое представление объекта, предназначенное для разработчиков. Используется, например, в интерактивной оболочке Python. Обычно __repr__ дает больше информации об объекте, чем __str__.

def __repr__(self):
    return f"Person(name='{self.name}', age={self.age})"

__len__ — позволяет определить длину объекта, если это применимо (например, для списков, строк и т.д.).

def __len__(self):
    return len(self.name)  # Например, возвращаем длину имени

__getitem__, __setitem__, __delitem__ — позволяют обращаться к объекту, как к словарю или списку. Полезно, если вы хотите создать пользовательский контейнер данных.

__call__ — позволяет сделать объект вызываемым, как функцию.

def __call__(self, greeting):
    return f"{greeting}, {self.name}!"

Пример использования магических методов

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"{self.name}, {self.age} years old"

    def __repr__(self):
        return f"Person(name='{self.name}', age={self.age})"

    def __call__(self, greeting):
        return f"{greeting}, {self.name}!"

# Использование
p = Person("Alice", 30)
print(p)                  # Alice, 30 years old
print(repr(p))            # Person(name='Alice', age=30)
print(p("Hello"))         # Hello, Alice!

Декораторы

Декораторы — это функции, которые модифицируют другие функции или классы, добавляя к ним функциональность. Декораторы особенно полезны, если необходимо выполнять код до или после основной функции, не изменяя её структуру.

Основы Декораторов

Декораторы создаются как функции, которые принимают другую функцию и возвращают новую функцию.

Пример простого декоратора:

def my_decorator(func):
    def wrapper():
        print("Something before the function.")
        func()
        print("Something after the function.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

При вызове say_hello() мы увидим:

Something before the function.
Hello!
Something after the function.

Декораторы с аргументами

Если нужно передавать аргументы декорируемой функции, оборачивающая функция (wrapper) также должна принимать аргументы.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Начало выполнения функции.")
        result = func(*args, **kwargs)
        print("Конец выполнения функции.")
        return result
    return wrapper

@my_decorator
def add(x, y):
    return x + y

print(add(2, 3))

Декораторы для классов

Декораторы можно применять и к классам, модифицируя их поведение при создании объектов. Например, можно автоматически добавлять методы или атрибуты к каждому экземпляру.

Пример программы: Декоратор для логирования вызовов функций

Создадим декоратор log_calls, который будет выводить информацию о каждом вызове функции, включая её имя, переданные аргументы и результат.

from datetime import datetime

def log_calls(func):
    def wrapper(*args, **kwargs):
        start_time = datetime.now()
        print(f"Вызов {func.__name__}() с аргументами {args} и {kwargs}")
        
        result = func(*args, **kwargs)
        
        end_time = datetime.now()
        print(f"{func.__name__}() вернула {result}")
        print(f"Время выполнения: {end_time - start_time}")
        return result
    return wrapper

@log_calls
def multiply(a, b):
    return a * b

@log_calls
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

# Примеры вызовов
multiply(3, 4)
greet("Alice", greeting="Hi")

Объяснение работы программы

Декоратор log_calls — оборачивает переданную функцию в wrapper, которая логирует информацию о вызове.

При каждом вызове декорированной функции выводятся её имя, аргументы, результат выполнения и время работы.

Вывод программы

При вызове multiply(3, 4) и greet(«Alice», greeting=»Hi») мы увидим примерно следующее:

Вызов multiply() с аргументами (3, 4) и {}
multiply() вернула 12
Время выполнения: 0:00:00.000002

Вызов greet() с аргументами ('Alice',) и {'greeting': 'Hi'}
greet() вернула Hi, Alice!
Время выполнения: 0:00:00.000001

Заключение

Магические методы и декораторы добавляют гибкость в Python, позволяя создавать расширяемые и легко читаемые классы и функции. Магические методы, такие как __str__, __repr__, __call__, делают объекты более функциональными, а декораторы помогают добавить новые возможности без изменения исходного кода.