8 встроенных декораторов Python для написания элегантного кода
Разработчики могут изменять поведение функции с помощью декораторов, не изменяя ее исходный код. Это обеспечивает лаконичный и гибкий способ улучшения и расширения функциональности функций.
Python, обладающий чистым и понятным синтаксисом, является широко распространенным языком программирования высокого уровня. Python разработан для удобства использования, что подчеркивает простоту и снижение затрат на сопровождение программ. Он поставляется с обширной библиотекой, которая уменьшает необходимость написания кода с нуля и повышает производительность разработчиков. Одна из мощных возможностей Python, которая способствует элегантности кода, – декораторы.
Что такое декораторы Python?
В Python декоратор – это функция, которая позволяет изменять поведение другой функции, не меняя ее основной логики. Она принимает другую функцию в качестве аргумента и возвращает функцию с расширенной функциональностью. Таким образом, вы можете использовать декораторы для добавления дополнительной логики в существующие функции, чтобы увеличить возможность повторного использования с помощью всего нескольких строк кода. В этой статье мы рассмотрим восемь встроенных декораторов Python, которые помогут вам писать более элегантный и удобный код.
1. @atexit.register
Декоратор @atexit.register используется для регистрации функции, которая будет выполняться при завершении программы. Эта функция может быть использована для выполнения любой задачи, когда программа собирается завершить работу, будь то нормальное выполнение или непредвиденная ошибка.
Пример:
import atexit
# Register the exit_handler function
@atexit.register
def exit_handler():
print("Exiting the program. Cleanup tasks can be performed here.")
# Rest of the program
def main():
print("Inside the main function.")
# Your program logic goes here.
if __name__ == "__main__":
main()
Выход:
Inside the main function.
Exiting the program. Cleanup tasks can be performed here.
В приведенной выше реализации @atexit.register упоминается над определением функции. Он определяет функцию exit_handler() как функцию выхода. По сути, это означает, что всякий раз, когда программа достигает точки завершения, либо в результате нормального выполнения, либо из-за непредвиденной ошибки, вызывающей преждевременный выход, будет вызвана функция exit_handler().
2. @dataclasses.dataclass
@dataclasses.dataclass – это мощный декоратор, который используется для автоматической генерации общих специальных методов для классов, таких как “init“, “repr” и других. Он помогает писать более чистый и лаконичный код, избавляя вас от необходимости писать шаблонные методы для инициализации и сравнения экземпляров вашего класса. Это также помогает предотвратить ошибки, обеспечивая последовательную реализацию общих специальных методов в вашей кодовой базе.
Пример:
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
point = Point(x=3, y=2)
# Printing object
print(point)
# Checking for the equality of two objects
point1 = Point(x=1, y=2)
point2 = Point(x=1, y=2)
print(point1 == point2)
Выход:
Point(x=3, y=2)
True
Декоратор @dataclass, примененный над определением класса Point, дает сигнал Python использовать поведение по умолчанию для генерации специальных методов. Это автоматически создает метод init, который инициализирует атрибуты класса, такие как x и y, при инстанцировании объекта. В результате такие экземпляры, как point, могут быть созданы без необходимости явного кодирования. Более того, метод repr, отвечающий за строковое представление объектов, также настраивается автоматически. Это гарантирует, что при выводе объекта, например точки, будет получено четкое и упорядоченное представление, как показано в выводе: Point(x=3, y=2). Кроме того, сравнение равенства (==) между двумя экземплярами, point1 и point2, выдает True. Это примечательно, потому что по умолчанию Python проверяет равенство на основе местоположения в памяти. Однако в контексте объектов класса данных равенство определяется содержащимися в них данными. Это происходит потому, что декоратор @dataclass генерирует метод eq, который проверяет равенство данных, присутствующих в объектах, а не проверяет одинаковые места в памяти.
3. @enum.unique
Декоратор @enum.unique, находящийся в модуле enum, используется для обеспечения уникальности значений всех членов перечисления. Это помогает предотвратить случайное создание нескольких членов перечисления с одинаковыми значениями, что может привести к путанице и ошибкам. При обнаружении дублирующихся значений выдается ошибка ValueError.
Пример:
from enum import Enum, unique
@unique
class VehicleType(Enum):
CAR = 1
TRUCK = 2
MOTORCYCLE = 3
BUS = 4
# Попытка создать перечисление с дублирующимся значением приведет к ошибке ValueError
try:
@unique
class DuplicateVehicleType(Enum):
CAR = 1
TRUCK = 2
MOTORCYCLE = 3
# BUS and MOTORCYCLE have duplicate values
BUS = 3
except ValueError as e:
print(f"Error: {e}")
Выход:
Error: duplicate values found in : BUS -> MOTORCYCLE
В приведенной выше реализации “BUS” и “MOTORCYCLE” имеют одинаковое значение “3”. В результате декоратор @unique вызывает ошибку ValueError с сообщением о том, что были найдены дубликаты значений. Вы также не можете использовать один и тот же ключ более одного раза или присваивать одно и то же значение разным членам. Таким образом, он помогает предотвратить дублирование значений для нескольких членов перечисления.
4. @partial
Частичный декоратор – это мощный инструмент, который используется для создания частичных функций. Частичные функции позволяют заранее задать некоторые аргументы исходной функции и создать новую функцию с уже заполненными аргументами.
Пример:
from functools import partial
# Original function
def power(base, exponent):
return base ** exponent
# Creating a partial function with the exponent fixed to 2
square = partial(power, exponent=2)
# Using the partial function
result = square(3)
print("Output:",result)
Выход:
Output: 9
В приведенной выше реализации у нас есть функция “power”, которая принимает два аргумента “base” и “exponent” и возвращает результат возведения основания в степень экспоненты. Мы создали частичную функцию с именем “square”, используя исходную функцию, в которой экспонента предварительно установлена на 2. Таким образом, мы можем расширить функциональность исходных функций с помощью частичного декоратора.
5. @singledispatch
Декоратор @singledisptach используется для создания общих функций. Он позволяет определять различные реализации функций с одним и тем же именем, но разными типами аргументов. Это особенно полезно, когда вы хотите, чтобы ваш код вел себя по-разному для разных типов данных.
Пример:
from functools import singledispatch
# Decorator
@singledispatch
def display_info(arg):
print(f"Generic: {arg}")
# Registering specialized implementations for different types
@display_info.register(int)
def display_int(arg):
print(f"Received an integer: {arg}")
@display_info.register(float)
def display_float(arg):
print(f"Received a float: {arg}")
@display_info.register(str)
def display_str(arg):
print(f"Received a string: {arg}")
@display_info.register(list)
def display_sequence(arg):
print(f"Received a sequence: {arg}")
# Using the generic function with different types
display_info(39)
display_info(3.19)
display_info("Hello World!")
display_info([2, 4, 6])
Выход:
Received an integer: 39
Received a float: 3.19
Received a string: Hello World!
Received a sequence: [2, 4, 6]
В приведенной выше реализации мы сначала разработали общую функцию display_info() с помощью декоратора @singledisptach, а затем зарегистрировали ее реализацию отдельно для int, float, string и list. Результат показывает работу display_info() для отдельных типов данных.
6. @classmethod
Декоратор @classmethod используется для определения методов класса внутри класса. Методы класса привязаны к классу, а не к объекту класса. Основное различие между статическими методами и методами класса заключается в их взаимодействии с состоянием класса. Методы класса имеют доступ к состоянию класса и могут его изменять, в то время как статические методы не имеют доступа к состоянию класса и работают независимо.
Пример:
class Student:
total_students = 0
def __init__(self, name, age):
self.name = name
self.age = age
Student.total_students += 1
@classmethod
def increment_total_students(cls):
cls.total_students += 1
print(f"Class method called. Total students now: {cls.total_students}")
# Creating instances of the class
student1 = Student(name="Tom", age=20)
student2 = Student(name="Cruise", age=22)
# Calling the class method
Student.increment_total_students() #Total students now: 3
# Accessing the class variable
print(f"Total students from student 1: {student1.total_students}")
print(f"Total students from student 2: {student2.total_students}")
Выход:
Class method called. Total students now: 3
Total students from student 1: 3
Total students from student 2: 3
В приведенной выше реализации класс Student имеет переменную класса total_students. Декоратор @classmethod используется для определения метода класса increment_total_students() для увеличения переменной total_students. Каждый раз, когда мы создаем экземпляр класса Student, общее количество студентов увеличивается на единицу. Мы создали два экземпляра класса, а затем использовали метод класса для изменения переменной total_students до 3, что также отразилось на экземплярах класса.
7. @staticmethod
Декоратор @staticmethod используется для определения статических методов в классе. Статические методы – это методы, которые могут быть вызваны без создания экземпляра класса. Статические методы часто используются, когда им не нужно обращаться к параметрам, связанным с объектом, и они больше относятся к классу в целом.
Пример:
class MathOperations:
@staticmethod
def add(x, y):
return x + y
@staticmethod
def subtract(x, y):
return x - y
# Using the static methods without creating an instance of the class
sum_result = MathOperations.add(5, 4)
difference_result = MathOperations.subtract(8, 3)
print("Sum:", sum_result)
print("Difference:", difference_result)
Выход:
Sum: 9
Difference: 5
В приведенной выше реализации мы использовали @staticmethod, чтобы определить статический метод add() для класса “MathOperations”. Мы сложили два числа “4” и “5”, в результате чего получили “9”, не создавая никакого экземпляра класса. Аналогично вычитаем два числа “8” и “3”, чтобы получить “5”. Таким образом, можно генерировать статические методы для выполнения служебных функций, которые не требуют состояния экземпляра.
8. @property
Декоратор @property используется для определения методов getter для атрибута класса. Методы getter – это методы, которые возвращают значение атрибута. Эти методы используются для инкапсуляции данных, которая определяет, кто может получить доступ к деталям класса или экземпляра.
Пример:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
# Getter method for the radius.
return self._radius
@property
def area(self):
# Getter method for the area.
return 3.14 * self._radius**2
# Creating an instance of the Circle class
my_circle = Circle(radius=5)
# Accessing properties using the @property decorator
print("Radius:", my_circle.radius)
print("Area:", my_circle.area)
Выход:
Radius: 5
Area: 78.5
В приведенной выше реализации класс “Circle” имеет атрибут “radius”. Мы использовали @property для установки методов получения радиуса и площади. Это обеспечивает чистый и последовательный интерфейс для доступа пользователей класса к этим атрибутам.
Подведение итогов
В этой статье рассказывается о некоторых наиболее универсальных и функциональных декораторах, которые вы можете использовать, чтобы сделать свой код более гибким и читабельным. Эти декораторы позволяют расширить функциональные возможности исходной функции, чтобы сделать ее более организованной и менее склонной к ошибкам. Они похожи на волшебные штрихи, благодаря которым ваши программы на Python выглядят аккуратно и работают без сбоев.