Атрибуты объекта Python — методология доступа

Предположим, у меня есть класс с некоторыми атрибутами. Как лучше всего (с точки зрения Pythonic-OOP) получить доступ к этим атрибутам? Так же, как obj.attr ? Или, возможно, написать get accessors ? Каковы принятые стили именования таких вещей?

Изменить. Можете ли вы рассказать о лучших методах именования атрибутов с одинарным или двойным подчеркиванием в начале? Я вижу, что в большинстве модулей используется одно подчеркивание.


Если этот вопрос уже задавался (а я подозреваю, что он есть, хотя поиск не дал результатов), пожалуйста, укажите на него - и я закрою этот вопрос.


person Eli Bendersky    schedule 03.10.2008    source источник
comment
Вы говорите об атрибутах уровня класса, которые принадлежат классу (и всем объектам), или об атрибутах уровня экземпляра, определенных в классе? Я думаю, что ваш вопрос путает класс и объект.   -  person S.Lott    schedule 03.10.2008
comment
Я имею в виду атрибуты объекта (на уровне экземпляра), а не атрибуты класса   -  person Eli Bendersky    schedule 03.10.2008


Ответы (7)


Общепринятый способ сделать что-то — просто использовать простые атрибуты, например

>>> class MyClass:
...     myAttribute = 0
... 
>>> c = MyClass()
>>> c.myAttribute 
0
>>> c.myAttribute = 1
>>> c.myAttribute
1

Если вам действительно нужно иметь возможность писать геттеры и сеттеры, то вам нужно искать «свойства класса python» и Статья Райана Томайко о геттерах/сеттерах/фуксорах — отличное место для начала (хотя и немного длинное).

person willurd    schedule 03.10.2008
comment
Спасибо за ссылку - это отличная статья, очень актуальная для моего вопроса. - person Eli Bendersky; 03.10.2008

Что касается одинарного и двойного нижнего подчеркивания: оба указывают на одно и то же понятие «конфиденциальность». Другими словами, люди будут знать, что атрибут (будь то метод, «обычный» атрибут данных или что-то еще) не является частью общедоступного 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, не включенному в код для краткости, он не затронул бы __double ClassA, а создал бы новый атрибут.) Никакой другой защиты этого атрибута нет, поэтому вы все равно можете получить к нему прямой доступ. если вы знаете правильное имя:

>>> a._ClassA__double, b._ClassA__double
('Double', 'Double')

Так почему это проблема?

Ну, это проблема каждый раз, когда вы хотите наследовать и изменить поведение любого кода, имеющего дело с этим атрибутом. Вы либо должны переопределить все, что напрямую касается этого атрибута двойного подчеркивания, либо вам нужно угадать имя класса и изменить имя вручную. Проблема усугубляется, когда этот атрибут двойного подчеркивания на самом деле является методом: переопределение метода или вызов метода в подклассе означает изменение имени вручную или повторную реализацию всего кода, вызывающего метод, для не используйте имя с двойным подчеркиванием. Не говоря уже о динамическом доступе к атрибуту с помощью getattr(): здесь вам тоже придется манипулировать вручную.

С другой стороны, поскольку атрибут лишь тривиально переписывается, он обеспечивает лишь поверхностную «защиту». Любой фрагмент кода по-прежнему может получить атрибут, изменив его вручную, хотя это сделает код их зависимым от имени вашего класса и усилий с вашей стороны по рефакторингу вашего code или переименовать свой класс (при сохранении того же имени, видимого пользователю, что является обычной практикой в ​​​​Python) без необходимости нарушило бы их код. Они также могут «обмануть» Python, заставив его изменить имя за них, назвав свой класс таким же, как ваш: обратите внимание, что имя модуля не включено в измененное имя атрибута. И, наконец, атрибут двойного подчеркивания по-прежнему виден во всех списках атрибутов и во всех формах самоанализа, которые не заботятся о пропуске атрибутов, начинающихся с (одинарного) подчеркивания.

Итак, если вы используете имена с двойным подчеркиванием, используйте их очень экономно, так как они могут оказаться весьма неудобными, и никогда не используйте их для методов или чего-либо еще, что подкласс может когда-либо захотеть перереализовать, переопределить или получить доступ напрямую. И помните, что искажение имен с двойным подчеркиванием не дает настоящей защиты. В конце концов, использование одного начального подчеркивания принесет вам столько же пользы и принесет вам меньшую (потенциальную, будущую) боль. Используйте одно ведущее подчеркивание.

person Thomas Wouters    schedule 03.10.2008
comment
Спасибо за отличный ответ. Хотел бы я дважды проголосовать за вас :-) - person Eli Bendersky; 03.10.2008
comment
Это отличное объяснение. Спасибо за совет! - person imiric; 21.07.2009
comment
отличный ответ, за исключением того, что геттер и сеттер - зло в питоне. - person Lie Ryan; 15.04.2010
comment
Я не уверен, что вы говорите. геттеры и сеттеры имеют свои цели; вот почему у нас есть property(). Контроль доступа просто не является одним из них. - person Thomas Wouters; 15.04.2010

Изменить. Можете ли вы рассказать о лучших методах именования атрибутов с одинарным или двойным подчеркиванием? Я вижу, что в большинстве модулей используется одно подчеркивание.

Одиночное подчеркивание не означает ничего особенного для python, это просто лучшая практика, чтобы сказать: «Эй, вы, вероятно, не хотите получать к этому доступ, если не знаете, что делаете». Однако двойное подчеркивание заставляет python внутренне искажать имя, делая его доступным только из класса, в котором оно определено.

Двойное подчеркивание в начале И в конце обозначает специальную функцию, такую ​​как __add__, которая вызывается при использовании оператора +.

Подробнее читайте в PEP 8, особенно в разделе "Соглашения об именах".

person Anders Waldenborg    schedule 03.10.2008

Я думаю, что большинство просто обращается к ним напрямую, нет необходимости в методах 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/

person monkut    schedule 03.10.2008

Python не нужно определять методы доступа с самого начала, поскольку преобразование атрибутов в свойства происходит быстро и безболезненно. Смотрите следующее для наглядной демонстрации:

Избавление от зависимости

person Ignacio Vazquez-Abrams    schedule 03.10.2008

Нет никакого реального смысла использовать геттеры/сеттеры в python, вы все равно не можете защитить вещи, и если вам нужно выполнить какой-то дополнительный код при получении/установке свойства, посмотрите на встроенную функцию property() (python -c 'help(property )')

person Anders Waldenborg    schedule 03.10.2008

Некоторые люди используют геттеры и сеттеры. В зависимости от того, какой стиль кодирования вы используете, вы можете назвать их getSpam и seteggs. Но вы также можете сделать атрибуты только для чтения или только для назначения. Это немного неудобно делать. Один из способов переопределить

> __getattr__

и

> __setattr__

методы.

Редактировать:

Хотя мой ответ по-прежнему верен, он неверен, как я понял. Есть лучшие способы создания аксессоров в python, и они не очень неудобны.

person Vasil    schedule 03.10.2008
comment
getattr и setattr на самом деле наименее удобный способ сделать это. свойства и слоты обычно гораздо предпочтительнее. - person Thomas Wouters; 03.10.2008
comment
Я полагаю, что использовать property еще проще - person Eli Bendersky; 03.10.2008