Декораторы
Декоратор — это функция, которая принимает другую функцию, добавляет поведение и возвращает новую функцию. Внутри используется вложенная функция-обёртка, поэтому декораторы строятся на замыканиях.
Где применяются декораторы
- Измерение времени выполнения.
- Логирование вызовов функций.
- Ограничение частоты вызовов (throttling).
- Проверка и валидация входных данных.
- Ограничение доступа.
- Автоматическое повторение выполнения при ошибке (retry).
- Кеширование результатов.
- Автоматическое изменение данных.
Аргументы декорируемой функции
Чтобы декоратор работал с функциями, принимающими любые аргументы, обёртка использует *args и **kwargs и передаёт их в оригинал.
import logging
logging.basicConfig(
filename="functions.log",
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
encoding="utf-8"
)
def log_decorator(func):
def wrapper(*args, **kwargs):
logging.info(f"Функция {func.__name__} вызвана с аргументами: {args}, {kwargs}")
result = func(*args, **kwargs)
logging.info(f"Функция {func.__name__} вернула: {result}")
return result
return wrapper
@log_decorator
def add(a, b):
return a + b
@log_decorator
def say_hello():
print("Привет!")
print(add(3, 5))
say_hello()
Возвращаемое значение
Если оригинальная функция возвращает значение, декоратор должен либо вернуть его, либо заменить на другой результат. Вот пример с потерей результата:
def upper_decorator(func):
def wrapper(*args, **kwargs):
print("Выполняем функцию, но ничего не возвращаем")
func(*args, **kwargs).upper()
return wrapper
@upper_decorator
def get_text():
return "hello"
result = get_text()
print("Результат:", result) # None
И правильный вариант с возвратом:
def upper_decorator(func):
def wrapper(*args, **kwargs):
print("Выполняем функцию и возвращаем результат")
return func(*args, **kwargs).upper()
return wrapper
@upper_decorator
def get_text():
return "hello"
result = get_text()
print("Результат:", result) # HELLO
Сохранение метаданных через functools.wraps
При создании декоратора оригинальная функция теряет своё имя и документацию, потому что заменяется вложенной функцией wrapper. Декоратор functools.wraps помогает сохранить метаданные.
def simple_decorator(func):
def wrapper(*args, **kwargs):
"""Вложенная функция wrapper"""
print("\nДекорированная функция: ")
print(f"Оригинальное имя функции: {func.__name__}")
print(f"Оригинальная документация: {func.__doc__}")
return func(*args, **kwargs)
return wrapper
@simple_decorator
def example_function():
"""Это оригинальная функция."""
print("Привет!")
print("Имя декорированной функции:", example_function.__name__)
print("Документация декорированной функции:", example_function.__doc__)
example_function()
Решение с functools.wraps:
import functools
def simple_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("\nДекорированная функция")
return func(*args, **kwargs)
return wrapper
@simple_decorator
def example_function():
"""Это оригинальная функция."""
print("Привет!")
print("Имя декорированной функции:", example_function.__name__)
print("Документация декорированной функции:", example_function.__doc__)
example_function()
Декоратор-фабрика
Чтобы декоратор принимал аргументы, создаётся функция-декоратор-фабрика, которая возвращает сам декоратор.
def message_decorator(message):
def decorator(func):
def wrapper():
print(message)
return func()
return wrapper
return decorator
@message_decorator("Начинаем выполнение")
def analise_data():
print("Данные проанализированы")
@message_decorator("Загрузка данных...")
def load_data():
print("Данные загружены")
analise_data()
print()
load_data()
Пример retry с параметром attempts:
import time
def retry(attempts):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(attempts):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"Попытка {i+1} не удалась: {e}")
time.sleep(5)
print("Все попытки исчерпаны.")
return wrapper
return decorator
@retry(3)
def get_data(filename):
"""Читает данные из файла"""
with open(filename, "r", encoding="utf-8") as file:
return file.read()
# Для запуска создайте рядом файл data.txt
data = get_data("data.txt")
if data:
print("Содержимое файла:")
print(data)
Несколько декораторов
Декораторы выполняются снизу вверх. Важно учитывать порядок, так как один декоратор может изменить результат перед следующим.
def border_decorator(func):
def wrapper():
print("*" * 100)
func()
print("*" * 100)
return wrapper
def repeat_decorator(func):
def wrapper():
for _ in range(3):
func()
return wrapper
@border_decorator
@repeat_decorator
def print_line():
print("-" * 100)
print_line()
Основы ООП
Объектно-ориентированное программирование (ООП) — это подход, в котором основное внимание уделяется объектам и их взаимодействию. ООП описывает сущности реального мира в виде объектов, объединяющих данные (поля) и поведение (методы).
Класс и объект
- Класс — шаблон (чертёж), по которому создаются объекты. Описывает, какие данные будут у объектов и какие методы можно выполнять.
- Объект — конкретный экземпляр класса. У него есть своё состояние и возможность выполнять действия.
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
book1 = Book("1984", "George Orwell")
book2 = Book("Brave New World", "Aldous Huxley")
print(book1.title, book1.author)
print(book2.title, book2.author)
ООП vs функциональный подход
| Объектно-ориентированный подход | Функциональный подход |
|---|---|
| Основан на объектах и классах | Основан на функциях как строительных блоках |
| Объекты хранят состояние и имеют поведение | Данные и поведение разделены |
| Программа — система взаимодействующих объектов | Часто используются чистые функции |
| Удобен для моделирования реальных сущностей | Хорош для математических расчётов и анализа данных |
| Хорошо подходит для сложных расширяемых программ | Хорошо подходит для обработки потоков информации |
В Python можно использовать оба подхода, они часто дополняют друг друга.
Преимущества ООП
- Упрощает моделирование реальных сущностей.
- Позволяет структурировать сложные программы.
- Облегчает расширение и сопровождение.
- Повышает переиспользуемость и читаемость кода.
Создание класса и правила имён
# Пустой класс без полей и методов
class ClassName:
pass
- Имя класса пишется с заглавной буквы.
- Несколько слов —
CamelCase. - Не стоит использовать имена встроенных типов:
list,str,objectи т.д.
Магический метод __init__
__init__ — это инициализатор, который автоматически вызывается при создании объекта и устанавливает его начальное состояние.
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
Важно: конструктором объекта считается __new__, который создаёт объект, а __init__ инициализирует уже созданный объект.
Особенности __init__
- Вызывается автоматически при создании объекта.
- Называется строго
__init__. - Обязан принимать первым аргументом
self. - Через
selfзадаются поля объекта. - Может отсутствовать, если класс не имеет состояния.
Поля, методы и атрибуты
- Поля (свойства) — переменные, хранящие состояние объекта. Задаются через
selfв__init__. - Методы — функции внутри класса, описывающие поведение объекта.
- Атрибут — общее название любой части объекта, доступной через точку:
object.something.
Параметр self
self — обязательный первый параметр методов класса, ссылка на текущий объект. self не является зарезервированным словом, но принято использовать именно его.
class Book:
def __init__(self, book_title, book_author):
self.title = book_title
self.author = book_author
def get_description(self):
return f"{self.title} — {self.author}"
def show_info(self):
print(self.get_description())
def make_author_upper(self):
self.author = self.author.upper()
book = Book("1984", "George Orwell")
book.show_info()
book.make_author_upper()
book.show_info()
Без self переменные title и author были бы локальными переменными метода и исчезли бы после его завершения.
Создание объекта и доступ к полям
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
book = Book("1984", "George Orwell")
print(book.title)
print(book.author)
book.title = "Animal Farm"
print(book.title)
Каждый объект класса — независимый экземпляр. Изменение в одном объекте не влияет на другие.