Геттеры (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.tech")
Мы можем получить доступ к словарю атрибутов объекта 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.tech")
website.__dict__["description"] = "The latest news and trends in highload technologies"
print(website.__dict__)
На дисплей будет выведены пары «ключ-значение»:
{'name': 'HighloadToday', 'url': 'https://highload.tech', '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. Обеспечивая контроль доступа к переменным объекта, вы повышаете безопасность вашего кода и позволяете изменять логику работы приложения.
Правильное использование геттеров и сеттеров может усилить функциональность вашего кода и сделать его более надежным.
В завершение рекомендуем вам посмотреть видео:
https://www.youtube.com/watch?v=H323khQJL40


Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: