Какие основные встроенные декораторы доступны в Python?

Ответ

В Python декораторы — это функции, которые изменяют или расширяют поведение других функций или методов без изменения их исходного кода. Вот основные встроенные декораторы и те, что часто используются из стандартной библиотеки functools:

  1. @property

    • Назначение: Позволяет превратить метод класса в атрибут, обеспечивая контролируемый доступ к данным (геттер).
    • Почему: Используется для инкапсуляции, создания "вычисляемых" атрибутов и обеспечения валидации при установке значений (через @<attr>.setter). Улучшает читаемость кода, позволяя обращаться к методам как к атрибутам.
    • Пример:

      class Circle:
          def __init__(self, radius):
              self._radius = radius # Приватный атрибут
      
          @property
          def radius(self):
              """Геттер для радиуса."""
              return self._radius
      
          @radius.setter
          def radius(self, value):
              """Сеттер для радиуса с валидацией."""
              if value < 0:
                  raise ValueError("Радиус не может быть отрицательным")
              self._radius = value
      
      c = Circle(10)
      print(c.radius) # Вызывает геттер: 10
      c.radius = 15   # Вызывает сеттер
      print(c.radius) # 15
      # c.radius = -5 # Вызовет ValueError
  2. @classmethod

    • Назначение: Объявляет метод как метод класса. Первый аргумент такого метода — сам класс (cls), а не экземпляр (self).
    • Почему: Полезен для создания альтернативных конструкторов (фабричных методов), которые могут создавать экземпляры класса различными способами, или для операций, которые относятся ко всему классу, а не к конкретному экземпляру.
    • Пример:

      class MyClass:
          def __init__(self, data):
              self.data = data
      
          @classmethod
          def from_string(cls, string_data):
              """Фабричный метод для создания экземпляра из строки."""
              return cls(int(string_data))
      
          @classmethod
          def default_instance(cls):
              """Создает экземпляр с данными по умолчанию."""
              return cls(0)
      
      obj1 = MyClass(10)
      obj2 = MyClass.from_string("20")
      obj3 = MyClass.default_instance()
      print(obj1.data, obj2.data, obj3.data) # Вывод: 10 20 0
  3. @staticmethod

    • Назначение: Объявляет метод как статический. Он не принимает ни self, ни cls в качестве первого аргумента и ведет себя как обычная функция, логически связанная с классом.
    • Почему: Используется для утилитарных функций, которые не зависят от состояния экземпляра или класса, но логически принадлежат классу. Помогает организовать код.
    • Пример:

      class MathUtils:
          @staticmethod
          def add(a, b):
              """Статический метод для сложения двух чисел."""
              return a + b
      
          @staticmethod
          def multiply(a, b):
              """Статический метод для умножения двух чисел."""
              return a * b
      
      print(MathUtils.add(5, 3))      # Вывод: 8
      print(MathUtils.multiply(5, 3)) # Вывод: 15
  4. @functools.cache (Python 3.9+)

    • Назначение: Декоратор для мемоизации (кеширования) результатов функции. Сохраняет результаты вызовов функции с одними и теми же аргументами.
    • Почему: Значительно повышает производительность для чистых функций (без побочных эффектов), которые часто вызываются с одними и теми же аргументами и выполняют дорогостоящие вычисления.
    • Пример:

      import functools
      import time
      
      @functools.cache
      def fibonacci(n):
          if n <= 1:
              return n
          return fibonacci(n - 1) + fibonacci(n - 2)
      
      start_time = time.time()
      fibonacci(30) # Вычисляется один раз
      print(f"Time for first call: {time.time() - start_time:.4f}s")
      
      start_time = time.time()
      fibonacci(30) # Результат берется из кеша
      print(f"Time for second call: {time.time() - start_time:.4f}s")
  5. @functools.lru_cache

    • Назначение: Декоратор для кеширования результатов функции с ограничением по размеру (Least Recently Used - наименее недавно использованные).
    • Почему: Позволяет управлять потреблением памяти кешем, автоматически удаляя наименее недавно использованные элементы, когда кеш достигает максимального размера. Идеален для функций, работающих с ограниченным набором часто используемых входных данных.
    • Пример:

      import functools
      
      @functools.lru_cache(maxsize=128) # Кеш на 128 последних результатов
      def get_web_page(url):
          """Имитация дорогостоящего запроса к веб-странице."""
          print(f"Fetching {url}...")
          # time.sleep(1) # Имитация задержки
          return f"Content of {url}"
      
      print(get_web_page("http://example.com/page1")) # Вывод: Fetching http://example.com/page1...
      print(get_web_page("http://example.com/page2")) # Вывод: Fetching http://example.com/page2...
      print(get_web_page("http://example.com/page1")) # Результат берется из кеша, без 'Fetching...'

Ответ 18+ 🔞

А, ну вот, опять эти декораторы, блядь! Слушай, давай разберём, как нормальные люди, а не как эти заумные учебники, которые мозги выносят. Представь, что декоратор — это как наклейка на функцию, которая её улучшает, не лезя внутрь. Красота, ёпта!

1. @property — чтоб не пиздеть лишнего Ну, типа, сделал ты в классе приватный атрибут _radius, а потом думаешь: "Блин, а как к нему красиво обращаться, да ещё и проверку впихнуть?". Вот @property и спасает. Он превращает метод в атрибут, чтоб ты мог писать circle.radius, а не circle.get_radius(). А если хочешь менять значение — прикручиваешь сеттер. Без него — только читать, сука.

class Circle:
    def __init__(self, radius):
        self._radius = radius # Спрятали, чтоб не ебали

    @property
    def radius(self):
        """Геттер для радиуса."""
        return self._radius

    @radius.setter
    def radius(self, value):
        """Сеттер для радиуса с валидацией."""
        if value < 0:
            raise ValueError("Радиус не может быть отрицательным, ты чё, охренел?")
        self._radius = value

c = Circle(10)
print(c.radius) # 10 — просто как атрибут, а на деле метод!
c.radius = 15   # Всё ок
# c.radius = -5 # Вылетит с криком "ValueError", пиздец

2. @classmethod — фабрика по производству объектов Это когда тебе надо создать объект не через __init__, а как-то по-хитрому. Например, из строки, из JSON'а, или вообще готовый шаблонный экземпляр. Первый аргумент — cls (сам класс), а не self. По сути, альтернативный конструктор, ёбта!

class MyClass:
    def __init__(self, data):
        self.data = data

    @classmethod
    def from_string(cls, string_data):
        """Выковыриваем число из строки и делаем объект."""
        return cls(int(string_data))

    @classmethod
    def default_instance(cls):
        """Дефолтный объект, чтоб не париться."""
        return cls(0)

obj1 = MyClass(10)
obj2 = MyClass.from_string("20") # Вот она, магия!
obj3 = MyClass.default_instance()
print(obj1.data, obj2.data, obj3.data) # 10 20 0

3. @staticmethod — обычная функция, которая притворяется частью класса Ни self, ни cls не получает. Просто висит внутри класса для порядка, чтоб не захламлять пространство имён. Типа утилитка, которая по смыслу относится к классу, но не лезет в его внутренности.

class MathUtils:
    @staticmethod
    def add(a, b):
        """Сложить два числа — что может быть проще?"""
        return a + b

    @staticmethod
    def multiply(a, b):
        """Умножить, блядь."""
        return a * b

print(MathUtils.add(5, 3))      # 8
print(MathUtils.multiply(5, 3)) # 15

4. @functools.cache (Python 3.9+) — память как у слона Охуенная штука для тяжёлых функций, которые постоянно вызываются с одними и теми же аргументами. Запоминает результат и в следующий раз не вычисляет, а сразу отдаёт из кеша. Но будь осторожен — кеш растёт бесконечно, если функция вызывается с разными аргументами.

import functools
import time

@functools.cache
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

start_time = time.time()
fibonacci(30) # Считает долго, пиздец
print(f"Первый раз: {time.time() - start_time:.4f}s")

start_time = time.time()
fibonacci(30) # А тут уже мгновенно, из кеша!
print(f"Второй раз: {time.time() - start_time:.4f}s")

5. @functools.lru_cache — умный кеш с ограничением То же самое, но с лимитом по размеру. Если кеш переполнится — выкинет самые старые (давно не использованные) результаты. Идеально, когда аргументов много, но часто повторяются только некоторые.

import functools

@functools.lru_cache(maxsize=128) # Храним 128 последних результатов
def get_web_page(url):
    """Представь, что тут долгий запрос в интернет."""
    print(f"Качаю {url}...")
    # time.sleep(1) # Имитация задержки
    return f"Контент {url}"

print(get_web_page("http://example.com/page1")) # Напечатает "Качаю..."
print(get_web_page("http://example.com/page2")) # Опять "Качаю..."
print(get_web_page("http://example.com/page1")) # А тут уже тишина, взял из кеша!

Вот и вся магия, блядь. Главное — не переусердствуй, а то задекорируешь всё подряд и потом сам не разберёшься, что к чему. Удачи, ёпта!