Рубріки: Теория

Геттеры (Getters) и сеттеры (Setters): управление атрибутами в Python

Сергій Бондаренко

Использование геттеров и сеттеров для инкапсуляции данных – один из основных принципов объектно-ориентированного программирования. О том, что это такое и как работает, читайте в этом материале.

Атрибуты что это и как используются

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

В Python у каждого объекта есть свой набор атрибутов, которые определяют его состояние и поведение. Атрибуты могут быть:

  • переменными (данными, хранящимися внутри объекта);
  • методами (функциями, определенными внутри класса).

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

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

В этом классе атрибутами выступают координаты x и y, которые могут быть переменными, хранящимися внутри объекта.

Вот как это выглядит в коде:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

Мы создаем и инициализируем класс Point с двумя атрибутами x и y, то есть координатами точки. Атрибуты устанавливаются в конструкторе класса с помощью метода __init__.

После этого мы можем создать объект класса Point и использовать его для представления геометрической точки на плоскости. Например:

p = Point(2, 3)
print(p.x)  # 2
print(p.y)  # 3

В этом примере мы создаем объект p класса Point и передаем ему значения 2 и 3 в качестве координат x и y соответственно. Затем мы используем атрибуты x и y объекта p, чтобы получить значения его координат.

Атрибут (attribute) в программировании (переменная или метод) относится к объекту. В Python все является объектом.

В том числе и классы: они — конструкторы объектов, то есть предоставляют шаблон для создания экземпляров класса. У классов есть свои собственные атрибуты и методы, которые можно использовать для создания экземпляров этого класса.

Способы получения и установки атрибутов

Есть несколько способов получения и установки атрибутов в Python.

Применение точечной нотации

Это означает, что мы обращаемся к атрибуту объекта, используя точку и имя атрибута. Например, если у нас есть объект Person с атрибутом name, то мы можем получить его значение, используя выражение Person.name.

Пример точечной нотации в коде:

class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species

class Cat(Animal):
    def __init__(self, name, color):
        super().__init__(name, species="Cat")
        self.color = color

class Camel(Animal):
    def __init__(self, name, humps):
        super().__init__(name, species="Camel")
        self.humps = humps

my_cat = Cat("Мартуня", "black")
my_camel = Camel("Barmaley", 2)

print(my_cat.name) # Output: Мартуня
print(my_camel.humps) # Output: 2

В этом примере есть базовый класс Animal, а также два подкласса: Cat и Camel. У каждого экземпляра класса Cat есть атрибуты name и color, а у каждого экземпляра класса Camel атрибуты name и humps.

Мы можем обратиться к атрибутам каждого экземпляра, используя точечную нотацию, например, my_cat.name и my_camel.humps.

Использование функций getattr(), setattr() и delattr()

Эти функции позволяют получать, устанавливать или удалять атрибуты объекта динамически. Например, мы можем получить значение атрибута объекта Person, используя функцию getattr(Person, "name"), а затем установить новое значение, используя функцию setattr(Person, "name", "Alice").

Рассмотрим, как это выглядит в коде.

Напишем класс Car, у которого будет три атрибута: make, model и year. Затем создадим объект car1 класса Car и установим его атрибуты:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

# Создаем экземпляр класса car1
car1 = Car('Ford', 'Mustang', 2021)

# Получаем значение атрибута объекта car1 с помощью getattr()
make = getattr(car1, 'make')
print(make)  # 'Ford'

# Изменяем значение атрибута объекта car1 с помощью setattr()
setattr(car1, 'year', 2022)
year = getattr(car1, 'year')
print(year)  # 2022

# Удаляем атрибут объекта car1 с помощью delattr()
delattr(car1, 'model')
try:
    model = getattr(car1, 'model')
except AttributeError as e:
    print(e)  # 'Car' object has no attribute 'model'

С помощью getattr() мы получаем значение атрибута make. Затем изменяем значение атрибута year с помощью setattr() и получаем его новое значение. И, наконец, просто удаляем атрибут model с помощью delattr().

Внимание! При попытке получить значение атрибута model после удаления возникнет исключение AttributeError, которое мы обработали в блоке try-except.

Использование словарей атрибутов

В Python у объектов есть словарь атрибутов, который содержит пары «ключ-значение» для всех атрибутов объекта. Мы можем получить доступ к словарю атрибутов объекта, используя выражение obj.dict. Мы также можем установить значение атрибута, добавив новую пару «ключ-значение» в словарь.

Например, давайте создадим объект, который будет представлять сайт о технологиях HighloadToday:

class Website:
    def __init__(self, name, url):
        self.name = name
        self.url = url

website = Website("HighloadToday", "https://highload.today")

Мы можем получить доступ к словарю атрибутов объекта Website, используя выражение website.dict.

Для установки новых значений в словарь атрибутов мы можем просто добавить новую пару «ключ-значение», например:

website.__dict__["description"] = "The latest news and trends in highload technologies"

Теперь в словаре атрибутов объекта website будет добавлен новый атрибут с ключом "description" и значением "The latest news and trends in high-load technologies".

В этом можно убедиться, выведя на экран словарь:

class Website:
    def __init__(self, name, url):
        self.name = name
        self.url = url
website = Website("HighloadToday", "https://highload.today") 
website.__dict__["description"] = "The latest news and trends in highload technologies"
print(website.__dict__)

На дисплей будет выведены пары «ключ-значение»:

{'name': 'HighloadToday', 'url': 'https://highload.today', 'description': 'The latest news and trends in highload technologies'}

Определение геттеров и сеттеров в программировании

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

Геттер в Python — это метод, который возвращает значение атрибута объекта класса. Он обычно используется для получения значения приватного атрибута класса, который должен быть скрыт от изменений извне класса.

Геттеры в Python могут быть реализованы в виде метода, имя которого начинается с префикса get_. Например, если мы хотим создать геттер для атрибута name в классе, мы можем определить метод с именем get_name, который возвращает значение атрибута name.

Сеттер в Python — это метод, который устанавливает значение атрибута объекта класса. Он обычно используется для установки значения приватного атрибута класса, который должен быть скрыт от изменений извне класса.

Сеттеры в Python могут быть реализованы в виде метода, имя которого начинается с префикса set_. Например, если мы хотим создать сеттер для атрибута name в классе, мы можем определить метод с именем set_name, который устанавливает значение атрибута name.

Реализация инкапсуляции данных

Геттеры и сеттеры — один из способов реализации инкапсуляции данных, важнейшей концепции в объектно-ориентированном программировании (ООП).

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

Суть инкапсуляции в том, чтобы скрыть детали реализации объекта, при этом обеспечив доступ к нему через публичный интерфейс. 

В Python инкапсуляция реализуется через использование атрибутов с разным уровнем доступа. Атрибуты могут быть публичными, защищенными или приватными, в зависимости от того, каким образом они объявлены:

  • Публичные атрибуты доступны для чтения и записи из любого места в программе. Они объявляются без использования двойного подчеркивания в начале имени, например: my_attribute.
  • Защищенные атрибуты доступны только изнутри класса и его потомков. Они объявляются с использованием одного подчеркивания в начале имени, например: _my_attribute.
  • Приватные атрибуты доступны только внутри класса. Они объявляются с использованием двойного подчеркивания в начале имени, например: __my_attribute.

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

Пример использования декоратора property

В Python геттеры и сеттеры можно определить как явным образом, используя методы геттера и сеттера, так и через использование декораторов (property). В простых случаях лучше использовать декораторы, чтобы сократить код и сделать его более читабельным.

Допустим, мы хотим, чтобы в классе Person при установке возраста (age) значения, выходящие за пределы допустимого диапазона (от 0 до 150 лет), обрезались до соответствующих крайних значений. Кроме того, мы хотим, чтобы при получении возраста значения были возвращены в годах, а при установке — в месяцах.

Пишем такой код:

class Person:
    def __init__(self, name, age):
        self._name = name
        self._age = age
    @property
    def name(self):
        return self._name
    @property
    def age(self):
        return self._age // 12  # возвращаем возраст в годах
    @age.setter
    def age(self, value):
        if value < 0:
            value = 0
        elif value > 150:
            value = 150
        self._age = value * 12  # пересчитываем месяцы в года

В коде мы определяем геттер age, который возвращает возраст в годах (self._age // 12) и сеттер age, который устанавливает значение возраста в месяцах (self._age = value * 12). При этом, перед установкой нового значения возраста в месяцах, мы проверяем, не выходит ли оно за пределы допустимого диапазона (0-150 лет), и при необходимости обрезаем значение до крайних значений (0 или 150).

Дескрипторы Python

Дескрипторы в Python — это механизм, позволяющий определять, как атрибуты должны работать внутри классов.

Это специальные объекты, которые могут быть присоединены к атрибутам класса, чтобы контролировать доступ к ним. Их можно использовать для реализации различных паттернов проектирования, таких как свойства (properties), методы, классы, статические методы и другие.

В основном дескрипторы позволяют переопределить, как работают операции доступа к атрибутам, такие как чтение, запись и удаление.

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

Пример использования дескриптора property

Пример использования дескриптора property в Python для реализации геттера и сеттера для атрибута класса Celsius:

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

class Temperature:
    temperature = Celsius()
    def __init__(self, initial_temp):
        self.temperature.temperature = initial_temp
    def to_fahrenheit(self):
        return self.temperature.to_fahrenheit()

Этот код позволяет работать с температурами в градусах Цельсия и переводить значения температуры из одной шкалы в другую.

Он определяет два класса: Celsius и Temperature. Класс Celsius представляет собой температурную шкалу Цельсия и определяет атрибут temperature, а также функции get_temperature()set_temperature() и  to_fahrenheit()

Дескриптор property используется для определения геттера get_temperature и сеттера set_temperature. В сеттере мы добавляем проверку на то, что температура не может быть ниже абсолютного нуля (−273 градуса Цельсия). Функция to_fahrenheit() преобразует температуру в градусах Цельсия в градусы Фаренгейта. 

Класс Temperature содержит объект класса Celsius и определяет функции __init__() и to_fahrenheit(). Функция __init__() инициализирует значение температуры с помощью метода temperature объекта Celsius, а функция to_fahrenheit() преобразует температуру в градусах Цельсия в градусы Фаренгейта с помощью метода to_fahrenheit() объекта Celsius.

Управление доступом к данным

Геттеры и сеттеры могут использоваться для управления доступом к данным в классах Python. Например, если вы хотите ограничить доступ к определенным атрибутам объекта, вы можете использовать геттеры и сеттеры для установки и получения значений этих атрибутов.

Рассмотрим, например, ситуацию, когда у нас есть класс BankAccount с конфиденциальной информацией о балансе счета. Мы хотим ограничить доступ к этой информации для всех, кроме владельца счета и банковского работника, который имеет доступ к информации по работе.

Мы можем использовать геттеры и сеттеры для реализации этой защиты. Для этого мы создадим приватную переменную __balance внутри класса и создадим геттер и сеттер для доступа к ней:

class BankAccount:
    def __init__(self, owner, balance):
        self.__owner = owner
        self.__balance = balance

    def get_balance(self, user):
        if user == self.__owner or user == "bank_employee":
            return self.__balance
        else:
            raise ValueError("Access denied")

    def set_balance(self, user, new_balance):
        if user == "bank_employee":
            self.__balance = new_balance
        else:
            raise ValueError("Access denied")

Создаем класс BankAccount. В конструкторе __init__ определяем два приватных атрибута — __owner и __balance.

Обратите внимание! Название переменных начинаются с двойного подчеркивания. Это означает, что они приватные и защищены от изменений извне.

Далее определяем метод get_balance, который возвращает значение баланса аккаунта лишь в том случае, если человек, запрашивающий баланс, — владелец аккаунта или банковский сотрудник.

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

Если пользователь, пытающийся выполнить какую-либо операцию, не соответствует условиям, заданным в методах get_balance и set_balance, то генерируется исключение ValueError с сообщением "Access denied".

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

Использование геттеров и сеттеров для кэширования

Рассмотрим такой код на Python:

class Cache:
    def __init__(self):
        self._cache = None
    @property
    def data(self):
        # Если кэш не пустой, то возвращаем его значение
        if self._cache is not None:
            print("Возвращаем данные из кэша")
            return self._cache
        # Иначе читаем данные из базы данных и сохраняем их в кэше
        print("Читаем данные из базы данных")
        data = "some data from database"
        self._cache = data
        return data
    @data.setter
    def data(self, value):
        # Удаляем значение кэша при установке нового значения
        print("Удаляем данные из кэша")
        self._cache = None

Здесь есть класс Cache с атрибутом _cache, который используется для хранения кэшированных данных:

  • если кэш не пустой, то при обращении к свойству data возвращается значение из кэша;
  • иначе — читаются данные из базы данных и сохраняются в кэше.

При установке нового значения через сеттер data значение кэша удаляется, чтобы при следующем обращении данные снова были прочитаны из базы данных:

cache = Cache()

# Первый вызов свойства data, данные будут прочитаны из базы данных
print(cache.data)

# Повторный вызов свойства data, данные будут взяты из кэша
print(cache.data)

# Изменяем значение свойства data, при следующем вызове данные будут прочитаны из базы данных
cache.data = "new data"

# Последующий вызов свойства data, данные будут прочитаны из базы данных
print(cache.data)

В этом примере, при первом обращении к свойству data, данные будут прочитаны из базы данных и сохранены в кэше. При следующих обращениях к свойству data, данные будут взяты из кэша, без обращения к базе данных.

При изменении значения свойства data, значение кэша будет удалено, так что при следующем обращении к свойству data, данные снова будут прочитаны из базы данных.

Ограничения доступа к данным из удаленного API

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

Код для такой задачи можно представить приблизительно так:

import requests

class APIWrapper:
    def __init__(self, base_url, api_key):
        self.base_url = base_url
        self.api_key = api_key
    @property
    def users(self):
        url = f"{self.base_url}/users"
        headers = {"Authorization": f"Bearer {self.api_key}"}
        response = requests.get(url, headers=headers)
        return response.json()
    @users.setter
    def users(self, data):
        url = f"{self.base_url}/users"
        headers = {"Authorization": f"Bearer {self.api_key}"}
        response = requests.post(url, headers=headers, json=data)
        return response.json()

Создается класс-обертка для доступа к удаленному API. У него есть два метода:

  • users, который возвращает список пользователей из API;
  • users.setter, который отправляет данные на сервер для создания нового пользователя.

При этом для доступа к данным из API используются геттеры и сеттеры. Так, чтобы получить список пользователей из API, можно сделать следующее:

wrapper = APIWrapper("https://api.example.com", "API_KEY")
users = wrapper.users

А создать нового пользователя можно так:

new_user = {"name": "Bondarenko Serge", "email": "serge.bond@example.com"}
wrapper.users = new_user

Заключение

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

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

В завершение рекомендуем вам посмотреть видео:

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023