Имеет ли Python "приватные" переменные в классах?
Вопрос о доступности переменных экземпляра в Python
Я пришел из мира Java и читаю книгу Брюса Эккелла Python 3 Patterns, Recipes and Idioms.
Во время чтения о классах я узнал, что в Python не нужно объявлять переменные экземпляра. Вы просто используете их в конструкторе, и они автоматически создаются.
Например:
class Simple:
def __init__(self, s):
print("inside the simple constructor")
self.s = s
def show(self):
print(self.s)
def showMsg(self, msg):
print(msg + ':', self.show())
Если это правда, тогда любой объект класса Simple
может просто изменять значение переменной s
снаружи класса.
Например:
if __name__ == "__main__":
x = Simple("constructor argument")
x.s = "test15" # это изменяет значение
x.show()
x.showMsg("A message")
В Java нас учили о публичных/приватных/защищённых переменных. Эти ключевые слова имеют смысл, потому что иногда вам нужен доступ к переменным внутри класса, но вы хотите запретить доступ к ним извне.
Почему в Python это не требуется?
5 ответ(ов)
В Python приватные переменные — это скорее хитрость: интерпретатор намеренно переименовывает переменные.
class A:
def __init__(self):
self.__var = 123
def printVar(self):
print(self.__var)
Теперь, если вы попытаетесь получить доступ к __var
вне определения класса, это приведет к ошибке:
>>> x = A()
>>> x.__var # это вызовет ошибку: "A has no attribute __var"
>>> x.printVar() # это вернет 123
Но вы можете легко обойти это ограничение:
>>> x.__dict__ # это покажет все, что содержится в объекте x
# что в этом случае будет чем-то вроде {'_A__var' : 123}
>>> x._A__var = 456 # теперь вы знаете скрытое имя приватной переменной
>>> x.printVar() # это вернет 456
Вы, вероятно, знаете, что методы в ООП вызываются так: x.printVar() => A.printVar(x)
. Если A.printVar()
может получить доступ к какому-либо полю в x
, это поле можно также получить вне A.printVar()
... В конце концов, функции создаются для переиспользуемости, и внутри них нет никакой особой привилегии для выполнения инструкций.
Единственный случай, когда я использую приватные переменные, — это когда мне нужно выполнять дополнительные действия при записи или чтении из переменной, и поэтому я вынужден использовать сеттеры и геттеры.
Это снова связано с культурой разработки, как уже упоминалось. Я работал над проектами, где доступ к переменным других классов был открыт для редактирования. Когда одна из реализаций становилась устаревшей, выявление всех путей кода, использующих эту функцию, занимало гораздо больше времени. Если использование сеттеров и геттеров было обязательным, то можно было легко добавить оператор отладки, чтобы определить, что устаревший метод был вызван, и узнать, какой код его вызвал.
На проекте, где любой может написать расширение, информирование пользователей о методах, которые будут удалены в ближайших релизах, критически важно, чтобы минимизировать поломку модулей при обновлениях.
Поэтому мой ответ таков: если вы и ваши коллеги работаете с простым набором кода, то защита переменных класса не всегда необходима. Если вы разрабатываете расширяемую систему, то становится крайне важно, чтобы изменения в ядре кода отслеживались всеми расширениями, его использующими.
Как правильно упомянуто во многих комментариях выше, не стоит забывать о главной цели модификаторов доступа: помочь пользователям кода понять, что именно можно изменять, а что не следует трогать. Когда вы видите приватное поле, вы не должны с ним «шоколадить». Таким образом, это в основном синтаксический сахар, который легко достигается в Python с помощью символов _ и __.
В языке Python существует соглашение об использовании символа подчеркивания для обозначения приватных переменных и методов.
Рассмотрим следующий пример:
class Test(object):
def __private_method(self):
return "Boo"
def public_method(self):
return self.__private_method()
В этом классе метод __private_method
обозначен как приватный и не может быть напрямую вызван из экземпляра класса:
x = Test()
print(x.public_method()) # Вывод: 'Boo'
Однако, если попытаться вызвать его напрямую:
x.__private_method()
Мы получим ошибку AttributeError
, указывающую на то, что объект Test
не имеет атрибута __private_method
.
Это происходит из-за механизма именования в Python, который делает имя метода не просто __private_method
, а "скрывает" его, преобразуя в _Test__private_method
, чтобы избежать конфликтов имен при наследовании.
Существуют и более строгие реализации концепции приватности, такие как использование декораторов @private
, которые могут предлагать более четкий контроль доступа к методам и переменным. Тем не менее, уровень инкапсуляции, обеспечиваемый стандартным соглашением с подчеркиванием, достаточно хорош для большинства случаев.
Также стоит отметить, что можно использовать метаклассы для создания более сложной логики определения приватных методов и атрибутов, но это будет выходить за рамки простого использования классов.
Как уже упоминалось, вы можете указать, что переменная или метод являются приватными, префиксируя их нижним подчеркиванием. Если этого вам недостаточно, вы всегда можете использовать декоратор property
. Вот пример:
class Foo:
def __init__(self, bar):
self._bar = bar
@property
def bar(self):
"""Геттер для '_bar'."""
return self._bar
Таким образом, когда кто-то ссылается на bar
, он на самом деле обращается к возвращаемому значению функции bar
, а не к самой переменной, и поэтому доступ к ней возможен, но изменение – нет. Однако, если кто-то действительно захочет, он может просто использовать _bar
и присвоить ему новое значение. Нет надежного способа предотвратить доступ к переменным и методам, которые вы хотите скрыть, как неоднократно уже говорилось. Тем не менее, использование property
– это самый четкий сигнал о том, что переменная не должна изменяться. property
также может использоваться для более сложных путей доступа к геттерам/сеттерам/удалителям, как объясняется здесь: https://docs.python.org/3/library/functions.html#property
Понимание Python super() с методами __init__()
Почему классы в Python наследуют от object?
Разница между старыми и новыми классами в Python?
Как красиво и "питонично" реализовать несколько конструкторов?
Как отсортировать список/кортеж списков/кортежей по элементу на заданном индексе