Атрибуты объектов Python - методология доступа
-
03-07-2019 - |
Вопрос
Предположим, у меня есть класс с некоторыми атрибутами.Как лучше всего (в смысле Pythonic-ООП) получить доступ к этим атрибутам?Точно так же , как obj.attr
?Или , может быть, написать get accessors ?Каковы общепринятые стили именования для таких вещей ?
Редактировать: Можете ли вы подробнее рассказать о лучших практиках именования атрибутов одинарным или двойным начальным подчеркиванием?Я вижу, что в большинстве модулей используется единственное подчеркивание.
Если этот вопрос уже задавался (а у меня есть подозрение, что так оно и было, хотя поиск не принес результатов), пожалуйста, укажите на него - и я закрою этот вопрос.
Решение
Общепринятый способ делать что-то - это просто использовать простые атрибуты, например, так
>>> class MyClass:
... myAttribute = 0
...
>>> c = MyClass()
>>> c.myAttribute
0
>>> c.myAttribute = 1
>>> c.myAttribute
1
Если вы обнаружите, что вам нужно иметь возможность писать геттеры и сеттеры, то то, что вы хотите найти, это "свойства класса python" и Статья Райана Томайко о Добытчиках / Сеттерах / Фуксорах отличное место для начала (хотя и немного длинноватое)
Другие советы
Что касается одинарных и двойных ведущих подчеркиваний:оба они указывают на одну и ту же концепцию "приватности".То есть люди будут знать, что атрибут (будь то метод, "обычный" атрибут данных или что-либо еще) не является частью общедоступного API объекта.Люди будут знать, что прикоснуться к нему напрямую - значит навлечь беду.
Кроме того, атрибуты подчеркивания с двойным начертанием (но не атрибуты подчеркивания с одним начертанием) являются имя-искалеченное чтобы обеспечить доступ к ним случайно из подклассов или откуда-либо еще за пределами текущего класса менее вероятно.Вы все еще можете получить к ним доступ, но не так тривиально.Например:
>>> class ClassA:
... def __init__(self):
... self._single = "Single"
... self.__double = "Double"
... def getSingle(self):
... return self._single
... def getDouble(self):
... return self.__double
...
>>> class ClassB(ClassA):
... def getSingle_B(self):
... return self._single
... def getDouble_B(self):
... return self.__double
...
>>> a = ClassA()
>>> b = ClassB()
Теперь вы можете получить простой доступ a._single
и b._single
и получить _single
атрибут, созданный ClassA
:
>>> a._single, b._single
('Single', 'Single')
>>> a.getSingle(), b.getSingle(), b.getSingle_B()
('Single', 'Single', 'Single')
Но пытаясь получить доступ к __double
атрибут на a
или b
экземпляр напрямую работать не будет:
>>> a.__double
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: ClassA instance has no attribute '__double'
>>> b.__double
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: ClassB instance has no attribute '__double'
И хотя методы, определенные в ClassA
может получить доступ к нему напрямую (при вызове в любом экземпляре):
>>> a.getDouble(), b.getDouble()
('Double', 'Double')
Методы, определенные на ClassB
не может:
>>> b.getDouble_B()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in getDouble_B
AttributeError: ClassB instance has no attribute '_ClassB__double'
И прямо в этой ошибке вы получаете подсказку о том, что происходит.Тот самый __double
имя атрибута, к которому осуществляется доступ внутри класса, искажается, чтобы включить имя класса, к которому осуществляется доступ в.Когда ClassA
пытается получить доступ self.__double
, это фактически превращается - во время компиляции - в доступ к self._ClassA__double
, и аналогично для ClassB
.(Если метод в ClassB
должны были назначить __double
, не включенный в код для краткости, он бы поэтому не касался ClassA
's __double
но создайте новый атрибут.) У этого атрибута нет другой защиты, поэтому вы все равно можете получить к нему прямой доступ, если знаете правильное имя:
>>> a._ClassA__double, b._ClassA__double
('Double', 'Double')
Так почему же это проблема?
Ну, это проблема каждый раз, когда вы хотите наследовать и изменить поведение любого кода, имеющего дело с этим атрибутом.Вам либо нужно переопределить все, что касается этого атрибута с двойным подчеркиванием напрямую, либо вам нужно угадать имя класса и исказить его вручную.Проблема усугубляется, когда этот атрибут с двойным подчеркиванием на самом деле является методом:переопределение метода или вызов метода в подклассе означает выполнение изменения имени вручную или переопределение всего кода, вызывающего метод, чтобы не использовать имя с двойным подчеркиванием.Не говоря уже о динамическом доступе к атрибуту, с getattr()
:там вам тоже придется вручную что-то переделывать.
С другой стороны, поскольку атрибут лишь тривиально переписан, он обеспечивает лишь поверхностную "защиту".Любой фрагмент кода все еще может получить доступ к атрибуту путем ручного изменения, хотя это приведет к их код, зависящий от имени ваш class и усилия с вашей стороны по рефакторингу вашего кода или переименованию вашего класса (при сохранении того же видимого пользователем имени, что является обычной практикой в Python) без необходимости нарушат их код.Они также могут "обмануть" Python, заставив его изменить имя для них, назвав их класс таким же, как ваш:обратите внимание, что в искаженном имени атрибута отсутствует имя модуля.И, наконец, атрибут двойного подчеркивания по-прежнему виден во всех списках атрибутов и во всех формах самоанализа, которые не заботятся о пропуске атрибутов, начинающихся с a (одинокий) подчеркивание.
Итак, если вы используете имена с двойным подчеркиванием, используйте их крайне экономно, поскольку они могут оказаться довольно неудобными, и никогда не используйте их для методов или что-либо еще, что подкласс может когда-либо захотеть переопределить, переопределить или получить доступ напрямую.И поймите, что искажение имени с двойным начальным подчеркиванием предлагает никакой реальной защиты.В конце концов, использование одного символа подчеркивания в начале приносит вам столько же пользы и причиняет меньше (потенциальной, будущей) боли.Используйте единственное начальное подчеркивание.
Редактировать:Можете ли вы подробнее рассказать о лучших практиках именования атрибутов одинарным или двойным начальным подчеркиванием?Я вижу, что в большинстве модулей используется единственное подчеркивание.
Одиночное подчеркивание не означает ничего особенного для python, это просто лучшая практика - говорить "эй, вы, вероятно, не хотите получать доступ к этому, если не знаете, что делаете".Однако двойное подчеркивание заставляет python внутренне искажать имя, делая его доступным только из класса, в котором оно определено.
Двойное начальное И завершающее подчеркивание обозначает специальную функцию, такую как __add__
который вызывается при использовании оператора +.
Подробнее читайте в БОДРОСТЬ ДУХА 8, особенно раздел "Соглашения об именовании".
Я думаю, что большинство просто получают к ним прямой доступ, нет необходимости в методах get / set.
>>> class myclass:
... x = 'hello'
...
>>>
>>> class_inst = myclass()
>>> class_inst.x
'hello'
>>> class_inst.x = 'world'
>>> class_inst.x
'world'
Кстати, вы можете использовать функцию dir(), чтобы увидеть, какие атрибуты / методы прикреплены к вашему экземпляру:
>>> dir(class_inst)
['__doc__', '__module__', 'x']
Две ведущие нижние панели, "__", используются для того, чтобы сделать атрибут или функцию закрытыми.Другие соглашения см. в PEP 08:http://www.python.org/dev/peps/pep-0008/
Python не нужно определять средства доступа с самого начала, поскольку преобразование атрибутов в свойства происходит быстро и безболезненно.Смотрите следующую наглядную демонстрацию:
В python нет реального смысла выполнять getter / setters, вы все равно не сможете защитить материал, и если вам нужно выполнить какой-то дополнительный код при получении / настройке свойства, посмотрите на встроенную функцию property() (python -c 'help(property)')
Некоторые люди используют геттеры и сеттеры.В зависимости от того, какой стиль кодирования вы используете, вы можете назвать их getSpam и seteggs.Но вы также можете сделать свои атрибуты доступными только для чтения или назначить только им.Делать это немного неловко.Одним из способов является переопределение
> __getattr__
и
> __setattr__
методы.
Редактировать:
Хотя мой ответ по-прежнему верен, как я начал понимать, он неправильный.Есть лучшие способы создания средств доступа в python, и они не очень неудобны.