СИЛА ДЕКОРАТОРОВ PYTHON

Эти встроенные декораторы Python могут изменить вашу жизнь

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

«Простое лучше, чем сложное».

Лучшая функция Python, которая применяет эту философию из «дзен Python», — это декоратор.

Декораторы могут помочь вам написать меньше и более простой код для реализации сложной логики и повторного использования его везде.

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

Разговор дешевый. Давайте посмотрим, как я выбрал 69 (я имею в виду 9) декораторов, которые покажут вам, насколько элегантен Python.

1. @lru_cache: ускорьте свои программы за счет кэширования

Самый простой способ ускорить ваши функции Python с помощью трюков с кэшированием — использовать метод @lru_cache decorator.

Этот декоратор можно использовать для кэширования результатов функции, чтобы последующие вызовы функции с теми же аргументами не выполнялись снова.

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

Давайте посмотрим на наглядный пример:

import time

def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

start_time = time.perf_counter()
print(fibonacci(30))
end_time = time.perf_counter()
print(f"The execution time: {end_time - start_time:.8f} seconds")
# The execution time: 0.18129450 seconds

Приведенная выше программа вычисляет N-е число Фибоначчи с помощью функции Python. Это отнимает много времени, потому что когда вы вычисляете fibonacci(30), многие предыдущие числа Фибоначчи будут вычисляться много раз в процессе рекурсии.

Теперь давайте ускорим его с помощью декоратора @lru_cache:

from functools import lru_cache
import time

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

start_time = time.perf_counter()
print(fibonacci(30))
end_time = time.perf_counter()
print(f"The execution time: {end_time - start_time:.8f} seconds")
# The execution time: 0.00002990 seconds

Как видно из приведенного выше кода, после использования декоратора @lru_cache мы можем получить тот же результат за 0.00002990 секунд, что намного быстрее, чем предыдущие 0.18129450 секунд.

У декоратора @lru_cache есть параметр maxsize, который указывает максимальное количество результатов, сохраняемых в кэше. Когда кеш заполнен и необходимо сохранить новый результат, последний использовавшийся результат удаляется из кеша, чтобы освободить место для нового. Это называется стратегией наименее недавно использовавшейся (LRU).

По умолчанию для maxsize установлено значение 128. Если для него установлено значение None, как в нашем примере, функции LRU отключены, и кеш может неограниченно расти.

2. @total_ordering: декоратор класса, который заполняет отсутствующие методы упорядочения

@total_ordering decorator из модуля functools используется для создания отсутствующих методов сравнения для класса Python на основе тех, которые определены.

Вот пример:

from functools import total_ordering

@total_ordering
class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade
    def __eq__(self, other):
        return self.grade == other.grade
    def __lt__(self, other):
        return self.grade < other.grade

student1 = Student("Alice", 85)
student2 = Student("Bob", 75)
student3 = Student("Charlie", 85)

print(student1 < student2)  # False
print(student1 > student2)  # True
print(student1 == student3)  # True
print(student1 <= student3) # True
print(student3 >= student2) # True

Как показано в приведенном выше коде, в классе Student нет определений для методов __ge__, __gt__ и __le__. Однако благодаря декоратору @total_ordering все результаты наших сравнений между разными экземплярами верны.

Преимущества этого декоратора очевидны:

  • Это может сделать ваш код чище и сэкономить ваше время. Так как не нужно писать все методы сравнения.
  • Некоторые старые классы могут не определять достаточно методов сравнения. Безопаснее добавить к нему декоратор @total_ordering для дальнейшего использования.

Новичок в трейдинге? Попробуйте криптотрейдинговые боты или копи-трейдинг на лучших криптобиржах

3. @contextmanager: создайте настраиваемый контекстный менеджер

В Python есть механизм диспетчера контекста, помогающий правильно управлять ресурсами.

В основном нам просто нужно использовать операторы with:

with open("test.txt",'w') as f:
    f.write("Sajawal is writing!")

Как видно из приведенного выше кода, мы можем открыть файл с помощью оператора with, чтобы он автоматически закрывался после записи. Нам не нужно явно вызывать функцию f.close(), чтобы закрыть файл.

Иногда нам нужно определить настраиваемый менеджер контекста для некоторых особых требований. В этом случае декоратор @contextmanager — наш друг.

Например, следующий код реализует простой настраиваемый менеджер контекста, который может печатать соответствующую информацию при открытии или закрытии файла.

from contextlib import contextmanager

@contextmanager
def file_manager(filename, mode):
    print("The file is opening...")
    file = open(filename,mode)
    yield file
    print("The file is closing...")
    file.close()

with file_manager('test.txt', 'w') as f:
    f.write('Sajawal is writing!')
# The file is opening...
# The file is closing...

4. @property: настройка геттеров и сеттеров для классов Python

Геттеры и сеттеры являются важными понятиями объектно-ориентированного программирования (ООП).

Для каждой переменной экземпляра класса метод получения возвращает ее значение, а метод установки устанавливает или обновляет ее значение. Учитывая это, геттеры и сеттеры также известны как аксессоры и мутаторы соответственно.

Они используются для защиты ваших данных от прямого и неожиданного доступа или изменения.

Различные языки ООП имеют разные механизмы определения геттеров и сеттеров. В Python мы можем просто использовать декоратор @property.

class Student:
    def __init__(self):
        self._score = 0
    @property
    def score(self):
        return self._score
    @score.setter
    def score(self, s):
        if 0 <= s <= 100:
            self._score = s
        else:
            raise ValueError('The score must be between 0 ~ 100!')

Sajawal = Student()
Sajawal.score=99
print(Sajawal.score)# 99
Sajawal.score = 999 # ValueError: The score must be between 0 ~ 100!

Как видно из приведенного выше примера, переменная score не может быть установлена ​​равной 999, что является бессмысленным числом. Потому что мы ограничили его допустимый диапазон внутри функции установки с помощью декоратора @property.

Без сомнения, добавление этого сеттера может успешно избежать неожиданных ошибок или результатов.

5. @cached_property: кэшируйте результат метода как атрибут

Python 3.8 представил новый мощный декоратор модуля functool@cached_property. Он может преобразовать метод класса в свойство, значение которого вычисляется один раз, а затем кэшируется как обычный атрибут на время жизни экземпляра.

Вот пример:

from functools import cached_property

class Circle:
    def __init__(self, radius):
        self.radius = radius
    @cached_property
    def area(self):
        return 3.14 * self.radius ** 2

circle = Circle(10)
print(circle.area)
# prints 314.0
print(circle.area)
# returns the cached result (314.0) directly

В приведенном выше коде мы декорировали метод area с помощью метода @cached_property. Таким образом, нет повторяющихся вычислений для circle.area одного и того же неизменного экземпляра.

6. @classmethod: определение методов класса в классе Python

Внутри класса Python есть 3 возможных типа методов:

  • Методы экземпляра: методы, привязанные к экземпляру. Они могут получать доступ к данным экземпляра и изменять их. Метод экземпляра вызывается для экземпляра класса и может получить доступ к данным экземпляра через параметр self.
  • Методы класса: методы, привязанные к классу. Они не могут изменять данные экземпляра. Метод класса вызывается для самого класса и получает класс в качестве первого параметра, который условно называется cls.
  • Статические методы: методы, не привязанные к экземпляру или классу.

Методы экземпляра могут быть определены как обычные функции Python, если их первый параметр равен self. Однако для определения метода класса нам нужно использовать декоратор @classmethod.

Для демонстрации в следующем примере определяется метод класса, который можно использовать для получения экземпляра Circle через диаметр:

class Circle:
    def __init__(self, radius):
        self.radius = radius
    @classmethod
    def from_diameter(cls, diameter):
        return cls(diameter / 2)
    @property
    def diameter(self):
        return self.radius * 2
    @diameter.setter
    def diameter(self, diameter):
        self.radius = diameter / 2
    
c = Circle.from_diameter(8)
print(c.radius)  # 4.0
print(c.diameter)  # 8.0

7. @staticmethod: определение статических методов в классе Python

Как уже упоминалось, статические методы не привязаны к экземпляру или классу. Они включены в класс просто потому, что логически принадлежат ему.

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

Чтобы определить статический метод, нам просто нужно использовать декоратор @staticmethod. Давайте посмотрим на пример:

class Student:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
        self.nickname = None
    def set_nickname(self, name):
            self.nickname = name
        @staticmethod
        def suitable_age(age):
            return 6 <= age <= 70
    
    print(Student.suitable_age(99)) # False
    print(Student.suitable_age(27)) # True
    print(Student('me', 'you').suitable_age(27)) # True

8. @dataclass: определяйте специальные классы с меньшим количеством кода

Декоратор @dataclass (представленный в Python 3.7) может автоматически генерировать несколько специальных методов для класса, таких как __init__, __repr__, __eq__, __lt__ и так далее.

Следовательно, это может сэкономить нам много времени при написании этих основных методов. Если класс в основном используется для хранения данных, декоратор @dataclass — наш лучший друг.

Чтобы продемонстрировать, в следующем примере просто определяются два поля данных класса с именем Point. Благодаря декоратору @dataclass его достаточно использовать:

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float
point = Point(1.0, 2.0)
print(point)
# Point(x=1.0, y=2.0)

9. @atexit.register: регистрация функции, которая будет выполняться после нормального завершения программы.

Декоратор @register из модуля atexit может позволить нам выполнить функцию при выходе из интерпретатора Python.

Этот декоратор очень полезен для выполнения последних задач, таких как освобождение ресурсов или просто прощание!

Вот пример:

import atexit

@atexit.register
def goodbye():
    print("Bye bye!")
print("Hello Reader!")

Выходы:

Hello Reader!
Bye bye!

Как видно из примера, из-за использования декоратора @register терминал напечатал «Пока-пока!» даже если мы не вызывали функцию goodbye явно.

Присоединяйтесь к Coinmonks, Каналу Telegram и Каналу Youtube, узнайте о криптотрейдинге и инвестировании

Также читайте