Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Industrial programming >> Python

Декоратор @property в Python: чистые, обратно совместимые геттеры и сеттеры

Освоение декоратора @property в Python

Встроенный декоратор @property в Python — мощный инструмент для инкапсуляции доступа к атрибутам. Он позволяет определять пользовательскую логику, выполняемую при чтении или записи атрибута, не заставляя вызывающих код менять свой код. В следующих разделах мы пройдёмся по реальному примеру — обработке преобразования температур — покажем, как применять ограничения, и объясним, почему @property является идиоматическим решением.


Класс без геттеров и сеттеров

Предположим, нам нужен простой класс, хранящий температуру в Цельсиях и умеющий конвертировать в Фаренгейты:

class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

Использование простое:

# Создание нового объекта
human = Celsius()
# Установка температуры
human.temperature = 37
# Чтение температуры
print(human.temperature)
# Конвертация в Фаренгейты
print(human.to_fahrenheit())

Вывод

37
98.60000000000001

Результат с плавающей точкой ожидаем из-за двоичного представления десятичных чисел. При инспекции human.__dict__ вы увидите, что Python хранит значение в обычной записи словаря:

>>> human.__dict__
{'temperature': 37}

Добавление ограничений с геттерами и сеттерами

Реальные объекты часто нуждаются в валидации. Например, температура не может опускаться ниже абсолютного нуля (-273.15 °C). Один прямой способ — скрыть атрибут и предоставить явные методы-геттер/сеттер:

class Celsius:
    def __init__(self, temperature=0):
        self.set_temperature(temperature)
    def to_fahrenheit(self):
        return (self.get_temperature() * 1.8) + 32
    # геттер
    def get_temperature(self):
        return self._temperature
    # сеттер
    def set_temperature(self, value):
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible.")
        self._temperature = value

Теперь класс принудительно соблюдает нижнюю границу, но любой существующий код, обращавшийся к obj.temperature, должен быть обновлён для вызова obj.get_temperature() и obj.set_temperature(val). Это крупный рефакторинг для больших кодовых баз.

Кроме того, «приватные» переменные Python — лишь соглашение об именах — _temperature всё ещё доступен напрямую, нарушая инкапсуляцию. Вот где сияет @property.


Использование класса property

Python предлагает класс property, связывающий функции-геттер и сеттер с именем атрибута:

class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
    # геттер
    def get_temperature(self):
        print("Getting value...")
        return self._temperature
    # сеттер
    def set_temperature(self, value):
        print("Setting value...")
        if value < -273.15:
            raise ValueError("Temperature below -273.15 is not possible")
        self._temperature = value
    # экспозиция как свойство
    temperature = property(get_temperature, set_temperature)

С этой настройкой следующий код ведёт себя точно так же, как исходный простой класс, но с валидацией за кулисами:

human = Celsius(37)
print(human.temperature)  # триггерит геттер
print(human.to_fahrenheit())  # использует геттер внутри
human.temperature = -300  # триггерит сеттер и вызывает ValueError

Вывод

Setting value...
Getting value...
37
Getting value...
98.60000000000001
Setting value...
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible

Обратите внимание: код, обращающийся к temperature, остался неизменным; только внутренняя реализация получила валидацию.

Совет: Фактические данные живут в _temperature; temperature — просто удобный, безопасный интерфейс.


Декоратор @property

Хотя класс property работает, синтаксис декораторов Python предлагает чистый, более читаемый способ достичь того же результата. Декоратор автоматически создаёт объект property и привязывает геттер и сеттер к имени атрибута.

class Celsius:
    def __init__(self, temperature=0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
    @property
    def temperature(self):
        print("Getting value...")
        return self._temperature
    @temperature.setter
    def temperature(self, value):
        print("Setting value...")
        if value < -273.15:
            raise ValueError("Temperature below -273 is not possible")
        self._temperature = value

Использование остаётся прежним:

human = Celsius(37)
print(human.temperature)
print(human.to_fahrenheit())
# Попытка установить невалидную температуру
try:
    Celsius(-300)
except ValueError as e:
    print(e)

Вывод

Setting value...
Getting value...
37
Getting value...
98.60000000000001
Setting value...
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible

Использование @property сохраняет пространство имён чистым, устраняет необходимость в отдельных именах геттеров/сеттеров и является рекомендуемым паттерном для большинства Python-разработчиков.

Python

  1. Освоение типов данных в Python: практическое руководство
  2. Мастерство операторов Python: Полное руководство
  3. Оператор pass в Python: практическое руководство
  4. Аргументы функций в Python: позиционные, именованные и параметры по умолчанию
  5. Словари в Python: создание, манипуляции и продвинутые техники
  6. Наследование в Python: концепции, синтаксис и практические примеры
  7. Перегрузка операторов в Python: практическое руководство
  8. Итераторы в Python: как они работают и как создать свои
  9. Замыкания в Python: как вложенные функции захватывают переменные
  10. Декоратор @property в Python: чистые, обратно совместимые геттеры и сеттеры