Геттеры (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": "[email protected]"} wrapper.users = new_user
Заключение
Использование геттеров и сеттеров можно рассматривать как хорошую привычку при написании классов в Python. Обеспечивая контроль доступа к переменным объекта, вы повышаете безопасность вашего кода и позволяете изменять логику работы приложения.
Правильное использование геттеров и сеттеров может усилить функциональность вашего кода и сделать его более надежным.
В завершение рекомендуем вам посмотреть видео:
https://www.youtube.com/watch?v=H323khQJL40
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: