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

Геттери (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. Забезпечуючи контроль доступу до змінних об’єктів, ви підвищуєте безпеку вашого коду та дозволяєте змінювати логіку роботи програми.

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

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

Офіційно: GitHub Copilot Chat тепер доступний на iOS та Android

Сервіс GitHub, який належить Microsoft, випустив Copilot Chat на iOS та Android. GitHub Copilot Chat…

08.05.2024

OpenAI знищила 100 000 книг, за якими тренували GPT-3. Причетні теж кудись зникли

Компанія OpenAI видалила два величезних набори даних «books1» і «books2», які використовувалися для навчання моделі…

08.05.2024

Реєстр військозообов’язаних «Оберіг» показуватиме статус отримання повістки

В реєстрі військовозообов'язаних «Оберіг» буде відображатися статус отримання повістки. Але цей процес буде відбуватися згідно…

08.05.2024

«Хакнуть» систему — босам «зріжуть» бонуси: Microsoft удосконалює програму кібербезпеки

Корпорація Microsoft в світлі останніх подій вирішила прив'язати зарплати керівників до функціонування систем безпеки: якщо…

08.05.2024

Українські програмісти створили Lağoda QT — гру-головоломку кримськотатарською мовою

Українські програмісти створили безплатну гру-головоломку Lağoda QT.  Кожен рівень — вірш одного з видатних кримськотатарських…

07.05.2024

В Copilot для Microsoft 365 додали українську мову

Корпорація Microsoft оголосила про підтримку української мови у Copilot для Microsoft 365. Українська мова входить…

07.05.2024