Есть ли в Python «закрытые» переменные в классах?

578

Я из мира Java и читаю паттерны, рецепты и идиомы Брюса Экелса « Python 3» .

Читая о классах, можно сказать, что в 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" # this changes the value
    x.show()
    x.showMsg("A message")

В Java нас учили о публичных / приватных / защищенных переменных. Эти ключевые слова имеют смысл, потому что иногда вам нужны переменные в классе, к которым никто за пределами класса не имеет доступа.

Почему это не требуется в Python?

вездесущий
источник
17
Вы имели в виду переменные экземпляра , а не переменные класса , верно?
PaulMcG
13
Вы должны проверить свойства: docs.python.org/library/functions.html#property . Просто используйте геттер и ваша переменная будет защищена.
Рубик
Короткий и четкий ответ здесь . Я надеюсь, это поможет.
Премкумар Чалмети

Ответы:

962

Это культурно. В Python вы не пишете в экземпляр класса или переменные класса. В Java ничто не мешает вам делать то же самое, если вы действительно этого хотите - в конце концов, вы всегда можете отредактировать исходный код самого класса для достижения того же эффекта. Python отказывается от претензий на безопасность и призывает программистов быть ответственными. На практике это работает очень хорошо.

Если по какой-то причине вы хотите эмулировать закрытые переменные, вы всегда можете использовать __префикс из PEP 8 . Python изменяет имена переменных, например, __fooтак, чтобы они не были легко видны для кода вне класса, который их содержит (хотя вы можете обойти это, если вы достаточно решительны, точно так же, как вы можете обойти защиту Java, если работаете над этим ).

В соответствии с тем же соглашением _префикс означает « держаться подальше», даже если вы технически не защищены от этого . Вы не играете с переменными другого класса, которые выглядят как __fooили _bar.

Кирк Штраузер
источник
14
В этом есть смысл. Тем не менее, я не думаю, что в java есть какой-либо способ доступа к закрытым переменным вне класса (кроме фактического изменения источника класса). Есть?
Вездесущий
168
Я склонен предпочесть путь на питоне, но я не думаю, что путь на Java так же бессмыслен, как вы это представляете. Объявление чего-то закрытого быстро говорит кому-то, читающему код, что-то очень полезное: это поле только когда-либо изменяется внутри этого класса.
Нед
67
@ Вездесущий, вы можете использовать отражение.
рападура
77
Позвольте мне сказать прямо, поэтому Python не реализует публичные или частные атрибуты, потому что «это притворство безопасности и поощряет программистов к ответственности», однако сообщество поощряет использование «_» для обозначения частных переменных и методов? Может быть, Python определенно должен иметь публичного и частного нет? Их основная цель - рассказать вам, какой API вы должны использовать для взаимодействия с классом. Они служат документацией, говорящей вам использовать эти методы, а не использовать их. Они не являются «притворством безопасности», они представляют собой документацию по API, которая может даже использоваться IDE для руководства вами!
PedroD
19
Это хороший ответ, и ваши аргументы, безусловно, верны, но я не согласен с одним аспектом. Целью модификаторов доступа никогда не была безопасность . Скорее, они являются средством явного разграничения (и в значительной степени обеспечения) того, какие части класса считаются внутренними, а какие открыты для внешних пользователей этого класса. Условные обозначения (культура), безусловно, являются действительной альтернативой модификаторам доступа, и оба метода имеют свои плюсы и минусы, но вводит в заблуждение цель того, что модификаторы доступа на уровне языка каким-либо образом предназначены для «обеспечения безопасности» в обычном смысле этого слова. слово.
devios1
159

Закрытые переменные в python - это более или менее хак: интерпретатор намеренно переименовывает переменную.

class A:
    def __init__(self):
        self.__var = 123
    def printVar(self):
        print self.__var

Теперь, если вы попытаетесь получить доступ __varвне определения класса, произойдет сбой:

 >>>x = A()
 >>>x.__var # this will return error: "A has no attribute __var"

 >>>x.printVar() # this gives back 123

Но вы можете легко сойти с рук:

 >>>x.__dict__ # this will show everything that is contained in object x
               # which in this case is something like {'_A__var' : 123}

 >>>x._A__var = 456 # you now know the masked name of private variables
 >>>x.printVar() # this gives back 456

Вы, вероятно, знаете, что методы в ООП вызываются следующим образом: x.printVar() => A.printVar(x)если вы A.printVar()можете получить доступ к какому-либо полю внутри x, к этому полю также можно получить доступ извне A.printVar() ... в конце концов, функции созданы для повторного использования, внутри операторов нет особой власти.

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

watashiSHUN
источник
3
короче говоря, это не инкапсуляция
watashiSHUN
2
Интересно, есть ли в PHP что-то похожее с его тупыми приватными переменными - поскольку приватные переменные не имеют смысла в интерпретируемом языке - я имею в виду, какую оптимизацию можно добиться, зная, что переменная x является приватной, если она не скомпилирована?
NoBugs
1
Как мы можем рандомизировать шаблон частных переменных?
crisron
@crisron тот же вопрос
IanS
5
@watashiSHUN "короче говоря, это не инкапсуляция" => да, это так. Инкапсуляция подразумевает только использование общедоступного API, поэтому код клиента защищен от изменений реализации. Соглашения об именах - это совершенно верный способ сказать, что такое API и что такое реализация, и дело в том, что он просто работает.
Bruno Desthuilliers
30

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

Ардаване
источник
4
Я думаю, что это такой же важный момент, как и любой другой. При отладке кода (я знаю, я слабак, чтобы вводить ошибки), знать, какие классы могут изменять переменную-член, упрощает процесс отладки. По крайней мере, если переменная защищена некоторой областью действия. Схожая концепция - это константные функции в C ++. Я знаю, что переменные-члены там не были изменены, и поэтому я даже не рассматриваю этот метод как потенциальную причину неправильной установки переменных. Хотя это может сделать последующую разработку расширений классов / добавление функций, ограничение видимости кода облегчает отладку.
MrMas
18

В соглашении подчеркивания есть вариация приватных переменных.

In [5]: class Test(object):
   ...:     def __private_method(self):
   ...:         return "Boo"
   ...:     def public_method(self):
   ...:         return self.__private_method()
   ...:     

In [6]: x = Test()

In [7]: x.public_method()
Out[7]: 'Boo'

In [8]: x.__private_method()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-8-fa17ce05d8bc> in <module>()
----> 1 x.__private_method()

AttributeError: 'Test' object has no attribute '__private_method'

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

Существуют примеры декораторов @private, которые более близко реализуют эту концепцию, но YMMV. Можно также написать определение класса, которое использует мета

Шэйн
источник
14
Я понимаю, что это довольно поздно для вечеринки, но эта ссылка появляется в Google, когда гуглит проблему. Это не говорит всю историю. __xпоскольку переменная внутри класса Aна самом деле переписывается компилятором _A__x, она все еще не является полностью закрытой и может быть доступна.
Цорф
1
Конечно, если я увижу переменную с именем _A__x, я не буду ее трогать. Это может быть заразно. Я убегу к черту от этого.
Матин Улхак
Конечно, это не настоящая личность. Но основной аргумент в пользу жестко принудительной приватности в C ++ и Java (и т. Д.), Оптимизации компилятора, в действительности не существует в Python, поэтому общепринятая приватность достаточно хороша. Соглашение Python, как правило, заключается в том, что вы полагаете, что будете вести себя самостоятельно без присмотра. (И это ловушка для новичков, но, знаете, просто подумайте о дизайне и потреблении классов)
Шейн
14

«В Java нас учили о публичных / частных / защищенных переменных»

"Почему это не требуется в Python?"

По той же причине это не требуется в Java.

Вы можете использовать - или не использовать privateи protected.

Как программист Python и Java, я обнаружил , что privateи protectedочень, очень важные концепции дизайна. Но на практике, в десятки тысяч строк Java и Python, я никогда на самом деле использовали privateили protected.

Почему бы нет?

Вот мой вопрос "защищен от кого?"

Другие программисты в моей команде? У них есть источник. Что означает защищенный, когда они могут изменить это?

Другие программисты в других командах? Они работают в одной компании. Они могут - с помощью телефонного звонка - получить источник.

Клиенты? Это программирование по найму (как правило). Клиенты (как правило) владеют кодом.

Так от кого именно я это защищаю?

С. Лотт
источник
119
-1: я согласен с Porculus. Речь идет не о запрете доступа или скрытии чего-либо, а о неявной документации API. Разработчики, а также компиляторы / интерпретаторы / средства проверки кода легко видят, какие элементы рекомендуется использовать, а какие не следует трогать (или хотя бы с осторожностью). В большинстве случаев было бы ужасно, если бы все члены класса или модуля были публичными. Подумайте о разграничении частных / защищенных / общедоступных членов как услуги, сказав: «Эй, эти участники важны, хотя они используются внутри и, вероятно, бесполезны для вас».
Обен Сонне
7
@ S.Lott: Я согласен, что документы API имеют более высокий приоритет и часто являются единственным способом сообщить о предполагаемом использовании API. Но иногда имена членов и видимость (с точки зрения частного / публичного) достаточно говорят сами по себе. Кроме того, я понимаю вашу точку зрения, что идея неявной документации плохо работает в редакторах без проверки API, но она действительно полезна в IDE с дополнением кода. Предполагая, что вы уже читали документацию по API, это поможет вам вспомнить, как использовать класс. Все бы не получилось так умно, если бы не было различий между частными и публичными членами.
Обен Сонн
21
Поздно к обсуждению, но все, о чем здесь просят Porculus и Oben, вполне адекватно обрабатывается соглашением «префикс с подчеркиванием» (и без вреда, который может принести соблюдение компилятором этого соглашения)
ncoghlan
38
@ S.Lott Я не парень с питоном, поэтому я не буду комментировать с этой точки зрения. Однако, как Java-разработчик, это действительно ужасающий совет. -1
dbyrne
11
Ух ты. Вы полностью упускаете суть, вы даете очень плохой совет, оскорбляете любого, кто не согласен с вами по этому вопросу, но вы все равно получаете значки и более 1000 очков репутации за этот «ответ».
Эрик
12

Как упомянуто ранее, вы можете указать, что переменная или метод являются закрытыми, добавив префикс подчеркивания. Если вы чувствуете, что этого недостаточно, вы всегда можете использовать propertyдекоратор. Вот пример:

class Foo:

    def __init__(self, bar):
        self._bar = bar

    @property
    def bar(self):
        """Getter for '_bar'."""
        return self._bar

Таким образом, кто-то или что-то, на что ссылается, barна самом деле ссылается на возвращаемое значение barфункции, а не на саму переменную, и поэтому к нему можно получить доступ, но нельзя изменить. Однако, если кто-то действительно хочет, он может просто использовать _barи присвоить ему новое значение. Не существует надежного способа запретить кому-либо доступ к переменным и методам, которые вы хотите скрыть, как уже неоднократно говорилось. Тем не менее, использование property- это самое ясное сообщение, которое вы можете отправить, что переменная не подлежит редактированию. propertyтакже может использоваться для более сложных путей доступа для получения / установки / удаления, как описано здесь: https://docs.python.org/3/library/functions.html#property

Исаак Саффолд
источник
Джанго также ценит это.
babygame0ver
10

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

Смотрите здесь для получения дополнительной информации об этом.

В общем, реализация объектной ориентации в Python немного примитивна по сравнению с другими языками. Но на самом деле мне это нравится. Это очень концептуально простая реализация и хорошо вписывается в динамический стиль языка.

Дэн Олсон
источник
Ага. Прелесть в том, что возможности python для метапрограммирования означают, что вы действительно можете реализовывать причудливые вещи, если хотите (и есть библиотеки, которые реализуют декораторы @ private / @ protected / etc и прочее. Черт, я даже видел библиотеку, в которой реализованы классы прототипов в стиле JS без всякой проклятой здравой причины), но на практике это просто не то, что нужно .. Я ненавижу мем "python / js / what is lisp", потому что он почти никогда не верен, но python действительно разделяет 'метапрограммирующие отбросы в сочетании с простым синтаксисом с этим языком
Шейн
8

Единственный раз, когда я использую закрытые переменные, это когда мне нужно делать что-то другое при записи или чтении из переменной, и поэтому мне нужно принудительно использовать установщик и / или получатель.

Опять же, это относится к культуре, как уже говорилось. Я работал над проектами, где чтение и запись переменных других классов были бесплатными для всех. Когда одна реализация устарела, потребовалось намного больше времени для определения всех путей кода, которые использовали эту функцию. Когда использование сеттеров и геттеров было принудительным, можно было легко написать оператор отладки, чтобы идентифицировать, что устаревший метод был вызван, и путь к коду, который его вызывает.

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

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

BlueEagle
источник
8

Извините, ребята, за "воскрешение" темы, но, надеюсь, это кому-нибудь поможет:

В Python3, если вы просто хотите «инкапсулировать» атрибуты класса, как в Java, вы можете просто сделать то же самое, как это:

class Simple:
    def __init__(self, str):
        print("inside the simple constructor")
        self.__s = str

    def show(self):
        print(self.__s)

    def showMsg(self, msg):
        print(msg + ':', self.show())

Чтобы создать это, сделайте:

ss = Simple("lol")
ss.show()

Обратите внимание, что: print(ss.__s)выдаст ошибку.

На практике Python3 будет скрывать глобальное имя атрибута. Превращая это как "приватный" атрибут, как в Java. Имя атрибута по-прежнему глобально, но недоступно, как частный атрибут в других языках.

Но не бойся этого. Это не важно Это тоже делает работу. ;)

Ferrarezi
источник
2
он существует со времен Python 1.5.2 IIRC и до сих пор не препятствует доступу к атрибуту через его искаженное имя.
Bruno Desthuilliers
7

частные и защищенные концепции очень важны. Но python - всего лишь инструмент для создания прототипов и быстрой разработки с ограниченными ресурсами, доступными для разработки, поэтому некоторые уровни защиты не так строги в Python. Вы можете использовать «__» в классе, он работает правильно, но выглядит недостаточно хорошо - каждый доступ к такому полю содержит эти символы.

Кроме того, вы можете заметить, что концепция ООП Python не идеальна, смальта или рубин намного ближе к концепции чистого ООП. Даже C # или Java ближе.

Python очень хороший инструмент. Но это упрощенный язык ООП. Синтаксически и концептуально упрощено. Основная цель существования Python - предоставить разработчикам возможность быстро и легко писать легко читаемый код с высоким уровнем абстракции.

user711294
источник
Причины Private и Protected важны потому, что в статически скомпилированных языках компилятор может создавать косвенные вызовы для закрытого метода, но для открытых методов он должен полагаться на таблицу поиска. Это просто не проблема с динамическими языками. Наконец, языки, такие как C ++, имеют значение для наследования и разрешения методов. Python и Ruby имеют очень похожие реализации ОО, поэтому сравнение бессмысленно. Smalltalk на самом деле не имеет понятия публичных / личных сообщений. Вы можете добавить личное как категорию, но это чисто консультативное.
Шейн
Для дальнейшего моего утверждения. С точки зрения гигиены кодирования, да, они важны для инкапсуляции, но в этом нет необходимости , и поэтому декораторы @private (и т. Д.) Более полезны, чем что-либо другое, но поскольку private / public не добавляет ничего полезного для оптимизации в нестатический язык, он не реализован на глубоком уровне, как это было бы в скомпилированном языке, таком как java или c
Shayne
4

В Python нет личных переменных, таких как C ++ или Java. Вы можете получить доступ к любой переменной-члену в любое время, если хотите. Тем не менее, вам не нужны закрытые переменные в Python, потому что в Python неплохо представить переменные-члены ваших классов. Если у вас есть необходимость инкапсулировать переменную-член, вы можете сделать это позже, используя "@property", не нарушая существующий клиентский код.

В python одиночное подчеркивание «_» используется для указания того, что метод или переменная не рассматриваются как часть публичного API класса и что эта часть API может меняться в разных версиях. Вы можете использовать эти методы / переменные, но ваш код может сломаться, если вы используете более новую версию этого класса.

Двойное подчеркивание «__» не означает «частная переменная». Вы используете его для определения переменных, которые являются «локальными по классу» и которые не могут быть легко переопределены подклассами. Это искажает имя переменных.

Например:

class A(object):
    def __init__(self):
        self.__foobar = None # will be automatically mangled to self._A__foobar

class B(A):
    def __init__(self):
        self.__foobar = 1 # will be automatically mangled to self._B__foobar

Имя self .__ foobar автоматически искажается до self._A__foobar в классе A. В классе B оно искажается до self._B__foobar. Таким образом, каждый подкласс может определять свою собственную переменную __foobar без переопределения своих родительских переменных. Но ничто не мешает вам получить доступ к переменным, начинающимся с двойного подчеркивания. Однако искажение имен не позволяет вам вызывать эти переменные / методы случайно.

Я настоятельно рекомендую посмотреть выступление Рэймонда Хеттингера «Инструментарий разработки классов Pythons» из Pycon 2013 (должен быть доступен на Youtube), в котором приведен хороший пример, почему и как следует использовать переменные экземпляра @property и «__».

Hatatister
источник
Я собираюсь проверить этот разговор. Является ли @propertyэто частью стандартного Python, или это специфично для IDE?
bballdave025
Его часть стандарта начиная с Python 2.6. Если вам следует использовать более старую версию, все еще есть возможность использовать propertyвстроенную функцию, которая доступна с python 2.2
Hatatister
0

На самом деле вы можете смоделировать C#геттер и сеттер, используя этот простой трюк:

class Screen(object):

    def getter_setter_y(self, y, get=True):
        if get is True:
            Screen.getter_setter_y.value = y
        else:
            return Screen.getter_setter_y.value

     def getter_setter_x(self, x, get=True):
         if get is True:
             Screen.getter_setter_x.value = x
         else:
             return Screen.getter_setter_x.value

Затем используйте его как в C#:

scr = Screen()
scr.getter_setter_x(100)
value =  scr.getter_setter_x(0, get=False)
print (value)

Это просто объявление статической локальной переменной в функции, которая будет играть роль get / set, поскольку это единственный способ поделиться переменной с помощью методов get и set, не делая ее глобальной для класса или файла.

Илиан Запрянов
источник