Применение геттер и сеттер методов для закрытых атрибутов классов
Одним из основополагающих принципов существования всего живого в нашем мироздании является инстинкт самосохранение – набор своеобразных предохранителей, оберегающих живые существа от какого-либо вреда. Ведь, нам никогда не придет в голову залпом пить кипяток, поскольку это очень больно, а неадекватно взбудораженное поведение наших домашних любимцев, всегда предупредит нас о приближающихся катаклизмах в виде землетрясений или цунами.
Абсолютно на таком же принципе построено и объектно-ориентированное программирование (ООП), которое по большому счету и призвано моделировать те или иные, необходимые нам при разработке программ, кусочки окружающего нас бытия. Как и во всех иных языках, в ООП на Python такой принцип называется инкапсуляцией. Под этим термином, в частности, подразумевается ограничение доступа к некоторым составляющим объект компонентам (методам и атрибутам) так, чтобы они были доступны только через, определенные, создающиеся внутри их классов, специальные методы – геттеры и сеттеры.
- Геттеры представляют собой методы, используемые в ООП для получения доступа к закрытым атрибутам (свойствам) интересующего нас класса.
- Сеттеры являются методами, применяемыми в ООП с целью присвоения (изменения) значения у закрытых атрибутов (свойств) интересующего нас класса.
Далее в этой статье на практических примерах мы с вами рассмотрим все основные аспекты и возможности применения геттеров и сеттеров для реализации механизма оптимальной (гибкой) инкапсуляции.
Принципы инкапсуляции в Python
Особенностью инкапсуляции в Python является то, что в этом интерпретаторе она предопределяется лишь соглашением о формате имен атрибутов, которые в свою очередь могут ассоциироваться с общедоступными, приватными или закрытыми компонентами классов.
Так, например, одиночное подчеркивание в начале имени атрибута всегда свидетельствует о том, что свойство или метод является приватным и, не может быть использован вне методов своего класса. Однако, этот атрибут вне класса всегда будет доступен по своему имени с одиночным подчеркиванием:
class PrimerA:
def _private(self):
print("Данный метод является 100% приватным!")
a = PrimerA()
a._private()
Данный метод является 100% приватным!
Двойное же подчеркивание в начале имени атрибута делает его абсолютно закрытым от доступа по имени вне того класса, в котором этот атрибут был создан:
class PrimerB:
def __private(self):
print("Данный метод закрыт от доступа вне своего класса!")
b = PrimerB()
b.__private()
Traceback (most recent call last):
File "<stdin>", line 6, in <module>
AttributeError: 'PrimerB' object has no attribute '__private'.
Примечание: Хотя атрибут с двойным начальным подчеркиванием и не имеет доступа по своему имени вне создавшего его класса, однако до него можно все же “достучаться” через имя его класса. Так, для доступа к нашему экземпляру b для этого нужно просто вместо выражения b.__private() применить выражение b._PrimerB__private(). Вместе с тем, данный обходной путь доступа к закрытым атрибутам классов в отличии от применения геттеров и сеттеров не только крайне громоздок, но и не пригоден для обеспечения гибкой инкапсуляции.
Вполне очевидно, что закрытые атрибуты по аналогии со все тем же инстинктом самосохранения для живых существ, должны предотвращать некорректное изменение тех свойств, которые в соответствующих классах могут иметь только строго определенные значения. Следовательно, все попытки изменить или прочитать вышеназванные свойства в теории должны предварительно проверяться на корректность, предусматриваемых в этих попытках действий. Такие проверки как раз и осуществляют геттеры и сеттеры, выступающие своеобразными стражами, предохраняющими классы и их экземпляры от некорректных изменений.
Сравниваем простой класс с классом, где есть геттер и сеттер
Давайте попробуем создать наш первый класс с закрытым атрибутом, обрабатывающемся через соответствующие предельно простые (без каки-либо проверок) геттеры и сеттеры:
class PrimerC():
def __init__(self, a):
# Инициализация закрытого атрибута (свойства) 'a'
# в экземпляре класса
self.__a = a
# Геттер метод, возвращающий значение закрытого атрибута 'a'
# для экземпляра класса
def get_a(self):
return self.__a
# Сеттер метод, изменяющий значение закрытого атрибута 'a'
# для экземпляра класса
def set_a(self, a):
self.__a = a
Из приведенного выше кода видно, что класс PrimerC состоит из трех следующих методов:
- Конструктор класса __init__, используемый для инициализации у экземпляров данного класса соответствующего закрытого атрибута __a.
- Метод get_a, являющейся геттером и использующейся для получения значений из названного выше закрытого атрибута __a.
- Метод set_a, являющейся сеттером и использующейся с целью изменения значения соответствующего, уже инициализированного закрытого атрибута.
Посмотрим теперь, как с помощью определенных в классе вышеназванных геттера и сеттера можно достучаться до закрытого атрибута этого класса:
# Создание экземпляра класса PrimerC
ekz = PrimerC(10)
# Получение значения закрытого атрибута 'a' с помощью
# геттер метода get_a()
print(ekz.get_a())
# Изменения значения закрытого атрибута 'a' с помощью
# сеттер метода set_a()
ekz.set_a(45)
print(ekz.get_a())
10
45
Примечание: Отнюдь необязательно для каждого закрытого атрибута в классе создавать пару из геттер и сеттер метода. Так, довольно часто сеттер можно объединять с конструктором класса и, таким образом инициализировать закрытии атрибуты только лишь в процессе создания экземпляров соответствующих классов.
Если бы вместо закрытого был бы просто объявлен общедоступный атрибут, то всякая надобность в геттере и сеттере отпала бы, а мы смогли бы получать и изменять этот атрибут напрямую через соответствующее имя экземпляра:
class PrimerD():
def __init__(self, a):
self.a = a
ekz = PrimerD(100)
print(ekz.a)
ekz.a = 50
print(ekz.a)
100
50
Таким образом, у классов, которые подобно классу PrimerD состоят только лишь из общедоступных атрибутов, функции инкапсуляции полностью отсутствуют. Вместе с тем, такие классы аналогично мертвой материи мироздания вполне имеют право на существования, которое может быть регламентировано лишь нашими собственными потребностями в этих классах при разработке того или иного программного обеспечения.
Использования декораторов при определении геттеров и сеттеров
Вот и настало время нам с вами смоделировать прототип настоящего сеттера, который реализуя свое истинное предназначение будет предотвращать некорректное изменение закрытого атрибута класса исходя из определенных условий. С этой целью давайте создадим аналог класса PrimerC, сеттер метод которого должен будет предварительно проверять передаваемое ему значение и инициализировать им закрытый атрибут класса лишь тогда, когда оно (значение) окажется четным и положительным. В противном же случае, данный сеттер метод должен будет приравнивать вышеназванный закрытий атрибут к двум. Реализуем все вышеперечисленное в следующем коде:
class PrimerCnew:
def __init__(self, a):
# Вызов метода set_a(), инициализирующего закрытий атрибут
# 'a' после проверки соответствующих условий
self.set_a(a)
# Геттер метод, возвращающий значение закрытого атрибута 'a'
# для экземпляра класса
def get_a(self):
return self.__a
# Сеттер метод, изменяющий значение закрытого атрибута 'a'
# для экземпляра класса
def set_a(self, a):
# Проверка на корректность значения, которым планируется
# инициализировать закрытый атрибут
if a > 0 and a % 2 == 0:
self.__a = a
else:
self.__a = 2
Теперь проверим, как наш новый класс работает при создании собственных экземпляров:
# Создание экземпляра для класса 'PrimerCnew'
ekz = PrimerCnew(16)
print(ekz.get_a())
16
На практики непосредственное применение в экземплярах классов геттер и сеттер методов для доступа к закрытым атрибутам зачастую оказывается весьма неудобным и искажающим стандартный синтаксис процессом. Поэтому, с целью решения этой проблемы для геттер методов используется декоратор @property, а для сеттер методов – декоратор @method_name.setter, где вместо method_name должно быть подставлено имя конкретного метода, используемого в качестве сеттера. Для применения этих декораторов в классах, они просто должны быть размещены сразу же перед определением соответствующих геттер или сеттер методов.
Примечание: Использование декораторов в Python является очень мощным и не имеющим аналогов средством для актуализации и самосовершенствования его кода. Более подробно об аспектах и специфики применения декораторов при кодинге вы можете узнать из статьи «Декораторы, как средство для самосовершенствования кода в Python».
В итоге, благодаря использованию вышеназванных декораторов мы сможем получать доступ к закрытым атрибутам класса точно также, как мы это делали в отношении обычных (общедоступных) атрибутов.
Давайте посмотрим, как применять соответствующие декораторы на практики:
class Decorators:
def __init__(self, var):
# Инициализация закрытого атрибута класса
self.a = var
# Имя закрытого атрибута класса и имена соответствующих
# геттер и сеттер методов должны быть одинаковыми
@property
def a(self):
return self.__a
@a.setter
def a(self, var):
if var > 0 and var % 2 == 0:
self.__a = var
else:
self.__a = 2
Примечание: Единственным неудобством в использовании @property и @method_name.setter декораторов является то, что имена геттер и сеттер методов, к которым они были применены, в обязательном порядке должны совпадать с именем соответствующего закрытого атрибута класса.
Посмотрим теперь на практике, как во вновь созданном экземпляре класса Decorators можно достучаться до его закрытого атрибута a:
# Создание экземпляра для класса 'Decorators'
ekz = Decorators(23)
print(ekz.a)
23
В сущности, для закрытого атрибута a в классе Decorators мы определили одноименные геттер и сеттер методы с такими же именами. Но, благодаря соответствующим декораторам интерпретатор Python прекрасно сам может различить какой из этих двух методов нужно воспринимать как геттер, а какой, как сеттер. Так, например, благодаря декоратору @a.setter при создании экземпляров класса в соответствующем методе всегда предварительно будет осуществляться проверка тех значений, которыми должен быть инициализирован их закрытый атрибут.
Использования PROPERTY() при работе с закрытыми атрибутами классов
Того же эффекта, что и от использования декораторов при доступе к закрытым атрибутам классов, можно добиться и благодаря применению в этих классах функции property(). В частности, это объясняется тем, что использование данной функции в соответствующих классах обеспечивает создание так называемого вычисляемого свойства, преобразующего методы таких классов в своеобразные свойства для создаваемых ими экземпляров.
Одним из преимуществ использования функции property() перед декораторами, является, в частности то, что здесь уже не нужно «заморачиваться» с одноименными геттерами и сеттерами, имена которых должны еще к тому же обязательно совпадать с именами обслуживаемых ими закрытых атрибутов. Следовательно, при применении вышеназванной функции можно, в сущности, основываться на точно такой же структуре определения класса, которую мы использовали и при создании класса PrimerCnew. Давайте попробуем это сделать с учетом использования вышеназванной функции:
class Property:
def __init__(self, var):
# Вызов метода set_a(), инициализирующего закрытий атрибут 'a'
# после проверки соответствующих условий
self.set_a(var)
# Геттер метод, возвращающий значение соответствующего закрытого
# атрибута для экземпляра класса
def get_a(self):
return self.__a
# Сеттер метод, изменяющий значение закрытого атрибута 'a'
# для экземпляра класса
def set_a(self, var):
# Проверка на корректность значения, которым планируется
# инициализировать закрытый атрибут
if var > 0 and var % 2 == 0:
self.__a = var
else:
self.__a = 2
a = property(get_a, set_a)
Как видно из вышеприведенного кода, он абсолютно аналогичен тому коду, которые был использован нами при создании класса PrimerCnew. Единственным отличием данного кода является его последняя строка, где закрытый атрибут нашего класса инициализируется функцией property(), передающей в данный атрибут соответствующие, присущие только ему, геттер и сеттер методы. Таким образом, благодаря этой последней строке в классе мы обеспечиваем для нашего закрытого атрибута точно такой же доступ, как и для всех обычных общедоступных атрибутов:
# Создание экземпляра для класса 'Property'
ekz = Property(28)
print(ekz.a)
28
Но, что, если нехорошие люди, злобные и кровожадные хакеры захотят нарушить гармонию микромира наших программ и взломать коды геттер и сеттер методов соответствующих классов с тем, чтобы исказить изначально запрограммированные свойства создаваемых ими экземпляров? На этот случай можно попытаться закрыть соответствующие геттер и сеттер методы в классе точно также, как мы это делали с атрибутами – путем добавления двойного подчеркивания к началу имени данных методов. При этом, доступ к таким закрытым методам в классе можно организовать при помощи все той же функции property(). Конечно же, такой способ создания неизменяемых экземпляров классов отнюдь не гарантирует 100% защиты от посягательств этих недобрых людей, но тем не менее существенно снижает риск возникновения подобных ситуаций. Давайте же посмотрим, как может выглядеть определение класса с закрытым геттером и сеттером:
class PropertyNew:
def __init__(self, var):
# Вызов метода set_a(), инициализирующего закрытий атрибут 'a'
# после проверки соответствующих условий
self.__set_a(var)
# Закрытый геттер метод, возвращающий значение такого же закрытого
# атрибута для экземпляра класса
def __get_a(self):
return self.__a
# Закрытый сеттер метод, изменяющий значение такого же закрытого
# атрибута 'a' для экземпляра класса
def __set_a(self, var):
# Проверка на корректность значения, которым планируется
# инициализировать закрытый атрибут
if var > 0 and var % 2 == 0:
self.__a = var
else:
self.__a = 2
a = property(__get_a, __set_a)
Теперь же давайте испробуем, как будет работать этот вновь созданный класс с полностью закрытыми компонентами:
# Создание экземпляра для класса 'PropertyNew'
ekz = PropertyNew(12)
print(ekz.a)
12
В рамках последнего приведенного кода мы на основе класса PropertyNew создали полностью неизменяемый экземпляр (объект) ekz, который исходя из заданных в закрытом сеттере условий его класса может быть инициализирован соответствующим значением только лишь в процессе своего создания.
Заключение
Благодаря этой статье мы с вами узнали, что такое инкапсуляция и как ее реализовать в классах Python с помощью так называемых геттер и сеттер методов. В сущности, инкапсуляция является тем основополагающим принципом объектно-ориентированного программирования, без которого практически невозможно гарантировать реальную надежность и безопасность функционирования создаваемых нами программ. Однако, под инкапсуляцией подразумевается лишь принцип определения классов (программных объектов), декларирующий необходимость ограничения доступа (обеспечения приватности) для их компонентов.
Реализация же такого ограниченного доступа к отдельным компонентам классов возлагается на соответствующие, подробно описанные в этой статье, геттер и сеттер методы, являющиеся своеобразными стражами приватности вышеназванных компонентов. Поэтому, полученные в данной статье знания об особенностях применения этих геттер и сеттер методов, вполне вероятно в плане надежности и безопасности помогут вам вознести вновь создаваемые вами программы на качественно новый, более высокий уровень.
Возможно будет интересно
Как практиковаться в Python?
Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?
Шпаргалка по модулю itertools
Шпаргалка по всем функциям модуля itertools, создающим разнообразные итераторы.