В этой заметке я хотел бы привести краткое описание методов регулирования доступа к атрибутам класса в питоне с помощью декораторов и с помощью присвоения специальным образом имен атрибутам в соответсвии с хорошим стилем программирования, описанным в PEP 8 . Статья написана на основе обсуждений данной темы на StackOverflow.com, нескольких мануалов и личного опыта автора.
Занимаясь написанием программ, выполняющие научные расчеты, я перешел с c++
на питон, чтобы использовать всю мощь библиотек, к которым относятся numpy, scipy, matplotlib, pyquante и прочие, распространяющиеся под свободной лицензией и находящихся в избытке на github. Однако, на начальном этапе такого перехода у меня возник дискомфорт, связанный с отсутствием таких привычных в с++ модификаторов доступа, как privat
, public
и protected
, а также методов get()
и set()
, что и побудило меня написать эту заметку. На StackOverflow первое, что бросается в глаза при обсуждении отсутствия модификаторов доступа в питоне, — это всеобщее согласие с большой долей ответственности разработчика при написании кода, которая побуждает его соблюдать хороший стиль программирования, описанный в PEP 8 . Проблема заключается в том, что в питоне всегда можно получить доступ к любому атрибуту любого класса, однако, согласно хорошему стилю, такой доступ пользователями библиотеки классов должен быть осуществлен не прямо, а с помощью некоторого интерфейса и только там, где это нужно и предусмотрено разработчиком.
Рассмотрим класс, содержащий атрибут attr
:
class MyClass:
def __init__(self):
self.attr = 0
Существует очевидная возможность доступа к атрибуту напрямую после создания объекта класса:
a=Class()
a.attr
Этим способом обращения лучше не злоупотреблять. Для того, чтобы имитировать наличие модификатора доступа privat, можно использовать подчеркивание в начале всех имен атрибутов:
class MyClass:
def __init__(self):
self._attr = 0
Конечно, в этом случае к атрибуту можно обратиться извне класса как:
a._attr
Но каждый раз видя этот код вы будете знать, что: а) осуществляется обращения к атрибуту объекта а
и б) ваш код не соответствует хорошему стилю программирования, поскольку атрибут этого класса с таким именем не предусматривает, что к нему будут обращаться вне определения класса.
Если все же есть необходимость предоставить пользователю доступ к атрибуту данного класса, лучше использовать декоратор @property
. Например:
class MyClass:
def __init__(self):
self.attr = 0
@property
def attr(self):
return self._attr
Теперь обращение к атрибуту извне класса может быть осуществлено привычным нам способом:
a.attr
В функцию attr(self)
можно вложить дополнительную функциональность, обрабатывая определенным образом данные. Например можно сделать так, чтобы она возвращала только целую часть числа, модуль и т. д. В эту функцию можно также передать аргумент, с помощью которого управлять выводом значения self._attr
, получая желаемый результат. Обрабатывая список аргументов этой функции, можно сделать что-то вроде ее перегрузки. Например, если переменная self._attr
представлена массивом, то можно реализовать эту функцию так, что в случае обращения в виде a.attr
мы получим весь массив, а в случае обращения a.attr(j)
— один из его элементов.
Отметим важную деталь, что в данном случае у нас нет никакой возможности изменить значение переменно self._attr извне определения класса, не нарушая при этом хорошего стиля программирования. Другими словами, мы можем это сделать так:
a._attr = 0
Но не можем так:
a.attr = 0
Чтобы вторая опция стала доступной, существует другой декоратор @setter
. Ниже приведен пример его использования:
class MyClass:
def __init__(self):
self.attr = 0
@property
def attr(self):
return self._attr
@attr.setter
def attr(self,value):
self._attr = value
Вторым способом имитации модификатора доступа privat
является присвоение имен атрибутам, начинающихся двойным подчеркиванием self.__attr (но не заканчивающихся! — такие имена, как правило, зарезервированы системой). В этом случае будет задействован так называемый name mangling, который заключается в том, что интерпретатор питона автоматически переименовывает атрибут self.__attr
в self._classname__attr
, где classname
— имя текущего класса. В этом случае нет возможности обратиться к атрибуту даже таким варварским методом, как а.__attr
. Внутри определения класса этот атрибут можно вызвать просто как self.__attr
. Для осуществления доступа к таким именам извне часто используют встроенные функции __getattr__()
, __setattr__()
и зарезервированную переменную __dict__
.
Описанные выше методы дают очень широкий спектр возможностей для управления доступом к атрибутам класса, вполне восполняющий отсутствие специальных модификаторов.
Автор: freude