Почему «приватные» методы Python на самом деле не приватны?

658

Python дает нам возможность создавать «частные» методы и переменные в классе, предваряя двойные подчеркивания к имени, например: __myPrivateMethod(). Как же тогда это объяснить?

>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
... 
>>> obj = MyClass()
>>> obj.myPublicMethod()
public method
>>> obj.__myPrivateMethod()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: MyClass instance has no attribute '__myPrivateMethod'
>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']
>>> obj._MyClass__myPrivateMethod()
this is private!!

В чем дело?!

Я объясню это немного тем, кто не совсем понял.

>>> class MyClass:
...     def myPublicMethod(self):
...             print 'public method'
...     def __myPrivateMethod(self):
...             print 'this is private!!'
... 
>>> obj = MyClass()

Я создал класс с открытым и закрытым методами и создал его.

Далее я вызываю его публичный метод.

>>> obj.myPublicMethod()
public method

Далее я пытаюсь вызвать его закрытый метод.

>>> obj.__myPrivateMethod()
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: MyClass instance has no attribute '__myPrivateMethod'

Здесь все выглядит хорошо; мы не можем это назвать. На самом деле это «личное». Ну, на самом деле это не так. Запуск dir () на объекте открывает новый магический метод, который python создает магическим образом для всех ваших «приватных» методов.

>>> dir(obj)
['_MyClass__myPrivateMethod', '__doc__', '__module__', 'myPublicMethod']

Имя этого нового метода всегда является подчеркиванием, за которым следует имя класса, а затем имя метода.

>>> obj._MyClass__myPrivateMethod()
this is private!!

Так много для инкапсуляции, а?

В любом случае, я всегда слышал, что Python не поддерживает инкапсуляцию, так зачем даже пытаться? Что дает?

willurd
источник
18
То же самое верно для Java или C #, если вы используете отражение (что-то, что вы делаете там).
0x434D53
4
Он был построен для модульного тестирования, так что вы можете использовать этот «хак» для модульного тестирования частных методов вашего класса извне.
waas1919
16
Разве тестирование частных методов не является анти-паттерном? Частные методы будут использоваться в некоторых публичных методах, иначе они просто не будут использоваться вечно. И правильный способ тестирования приватных методов (основанный на моем обучении до ThoughtWorks) состоит в том, что вы пишете тесты только для открытых методов, которые охватывают все случаи. Если это работает нормально, вам вообще не нужно тестировать частные методы извне.
Вишну Наранг
3
@VishnuNarang: Да, это то, что часто учил. Но, как всегда, почти «религиозный» подход « всегда делай это, никогда не делай этого» - единственное, что «никогда» - это хорошо. Если модульные тесты «только» используются для регрессионных тестов или тестирования публичного API, вам не нужно тестировать рядовые. Но если вы разрабатываете модульное тестирование, есть веские причины для тестирования приватных методов во время разработки (например, когда сложно смоделировать некоторые необычные / экстремальные параметры через открытый интерфейс). Некоторые языки / модульные тестовые среды не позволяют вам этого делать, что имхо не хорошо.
Марко Фройденбергер,
5
@MarcoFreudenberger Я понимаю вашу точку зрения. У меня есть опыт разработки на основе модульных тестов. Часто, когда становится трудно смоделировать параметры, чаще всего это решается путем изменения и улучшения дизайна. Мне еще не приходилось сталкиваться со сценарием, в котором дизайн совершенен, и все же модульное тестирование крайне сложно избежать тестирования частных методов. Я буду следить за такими случаями. Спасибо. Я был бы признателен, если бы вы поделились одним сценарием, чтобы помочь мне понять.
Вишну Наранг

Ответы:

592

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

Например:

>>> class Foo(object):
...     def __init__(self):
...         self.__baz = 42
...     def foo(self):
...         print self.__baz
...     
>>> class Bar(Foo):
...     def __init__(self):
...         super(Bar, self).__init__()
...         self.__baz = 21
...     def bar(self):
...         print self.__baz
...
>>> x = Bar()
>>> x.foo()
42
>>> x.bar()
21
>>> print x.__dict__
{'_Bar__baz': 21, '_Foo__baz': 42}

Конечно, он ломается, если два разных класса имеют одинаковые имена.

Аля
источник
12
docs.python.org/2/tutorial/classes.html . Раздел: 9.6 на Частные переменные и классовые локальные ссылки.
Gjain
72
Для тех из нас, кому лень прокручивать / искать: прямая ссылка на раздел 9.6
cod3monk3y
3
Вы должны поставить одно подчеркивание, чтобы указать, что переменная должна рассматриваться как закрытая. Опять же, это не мешает кому-то на самом деле получить доступ к этому.
игон
14
Гвидо ответил на этот вопрос - «основной причиной создания (почти) всего обнаруживаемого была отладка: при отладке вам часто нужно пробивать абстракции» - я добавил это как комментарий, потому что слишком поздно - слишком много ответов.
Питер М. - выступает за Монику
1
Если следовать критерию «запретить преднамеренный доступ», большинство языков ООП не поддерживают действительно частных членов. Например, в C ++ у вас есть сырой доступ к памяти, а в C # доверенный код может использовать частное отражение.
CodesInChaos
207

Пример приватной функции

import re
import inspect

class MyClass :

    def __init__(self) :
        pass

    def private_function ( self ) :
        try :
            function_call = inspect.stack()[1][4][0].strip()

            # See if the function_call has "self." in the begining
            matched = re.match( '^self\.', function_call )
            if not matched :
                print 'This is Private Function, Go Away'
                return
        except :
            print 'This is Private Function, Go Away'
            return

        # This is the real Function, only accessible inside class #
        print 'Hey, Welcome in to function'

    def public_function ( self ) :
        # i can call private function from inside the class
        self.private_function()

### End ###
Арун
источник
12
self = MyClass() self.private_function(), : D Конечно, это не работает в классах, но вы просто должны определить пользовательскую функцию: def foo(self): self.private_function()
Кейси Кубалл
161
На всякий случай было непонятно: никогда не делайте этого в реальном коде;)
Sudo Bash
10
@ThorSummoner Или просто function_call.startswith('self.').
nyuszika7h
13
inspect.stack()[1][4][0].strip()<- что это за магические числа 1, 4 и 0?
Ахи
5
Это может быть довольно легко побеждено при выполнении действия, self = MyClass(); self.private_function()и оно вызывает ошибку при вызове x = self.private_function()внутри метода.
Будет
171

Когда я впервые пришел с Java на Python, я ненавидел это. Это напугало меня до смерти.

Сегодня это может быть одна вещь, которую я люблю больше всего в Python.

Мне нравится находиться на платформе, где люди доверяют друг другу и не чувствуют, что им нужно строить непроницаемые стены вокруг своего кода. В сильно инкапсулированных языках, если в API есть ошибка, и вы выяснили, что идет не так, вы все равно не сможете обойти ее, потому что необходимый метод является закрытым. В Python отношение: «конечно». Если вы думаете, что понимаете ситуацию, возможно, вы даже читали ее, тогда все, что мы можем сказать, - это «удачи!».

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

Томас Ахле
источник
36
@CamJackson Javascript это ваш пример ?? Единственный широко используемый язык, который имеет наследование на основе прототипов и язык, который предпочитает функциональное программирование? Я думаю, что JS изучать намного сложнее, чем большинство других языков, поскольку он требует нескольких ортогональных шагов от традиционного ООП. Не то чтобы это мешало идиотам писать JS, они просто этого не знают;)
K.Steff
23
API-интерфейсы - действительно хороший пример того, почему инкапсуляция имеет значение и когда предпочтительнее использовать частные методы. Метод, предназначенный для приватности, может исчезнуть, изменить сигнатуру или, что хуже всего, изменить поведение - все без предупреждения - в любой последующей новой версии. Будет ли ваша умная, взрослая участница команды действительно помнить, что она использовала метод, предназначенный для частного использования, через год после обновления? Будет ли она там еще работать?
einnocent
2
Я не согласен с аргументом. В рабочем коде я бы, скорее всего, никогда не использовал бы API, в котором есть ошибка, которая заставляет меня менять открытых членов, чтобы она работала. API должен работать. Если это не так, я бы подал отчет об ошибке или сам сделал бы тот же API. Мне не нравится философия, и я не очень люблю Python, хотя его синтаксис делает забавным написание небольших сценариев в ...
Ингве Снине Линдал
4
У Java есть Method.setAccessible и Field.setAccessible. Тоже страшно?
Тони
15
Применение в Java и C ++ происходит не потому, что Java не доверяет пользователю, в то время как Python делает. Это потому, что компилятор и / или vm могут делать различные предположения, имея дело с тем, как он ведет свою деятельность, если он знает эту информацию, например, C ++ может пропустить весь уровень косвенности, используя обычные старые вызовы C вместо виртуальных вызовов, и это имеет значение, когда вы работаете над высокой производительностью или высокой точностью. Python по своей природе не может эффективно использовать информацию, не ставя под угрозу ее динамизм. Оба языка нацелены на разные вещи, поэтому ни один не «неправильный»
Шейн
144

С http://www.faqs.org/docs/diveintopython/fileinfo_private.html

Строго говоря, частные методы доступны за пределами их класса, но их нелегко получить. Ничто в Python не является по-настоящему приватным; внутренне, имена частных методов и атрибутов искажаются и распадаются на лету, чтобы сделать их недоступными по заданным именам. Вы можете получить доступ к методу __parse класса MP3FileInfo с именем _MP3FileInfo__parse. Признайте, что это интересно, и пообещайте никогда, никогда не делайте это в реальном коде. Частные методы являются частными по какой-то причине, но, как и многие другие вещи в Python, их конфиденциальность в конечном итоге является условностью, а не силой.

XSL
источник
202
или, как сказал Гвидо ван Россум: «Мы все взрослые».
35
-1: это просто неправильно. Двойное подчеркивание никогда не предназначено для использования в качестве частного. Ответ от Аля ниже говорит о подлинном намерении искажения имени в синтаксисе. Настоящее соглашение - это одно подчеркивание.
nosklo
2
Попробуйте только с одним подчеркиванием, и вы увидите результат, который вы получите. @nosklo
Билла Бегерадж
93

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

Если вам действительно нужно что-то частное, то вы можете реализовать это в расширении (например, в C для CPython). В большинстве случаев, однако, вы просто изучаете Pythonic, как делать вещи.

Тони Мейер
источник
так есть ли какой-то протокол оболочки, который я должен использовать для доступа к защищенной переменной?
интуитивно
3
Защищенных переменных больше нет, чем частных. Если вы хотите получить доступ к атрибуту, который начинается с подчеркивания, вы можете просто сделать это (но учтите, что автор не одобряет это). Если вам нужен доступ к атрибуту, который начинается с двойного подчеркивания, вы можете сделать это самостоятельно, но вы почти наверняка не хотите этого делать.
Тони Мейер
33

Это не значит, что вы абсолютно не можете обойти конфиденциальность членов на любом языке (арифметика указателей в C ++, Reflections в .NET / Java).

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

Изменить: Вы не пытаетесь защитить свои вещи с помощью OO-инкапсуляции, не так ли?

Максимилиан
источник
2
Не за что. Я просто подчеркиваю, что странно дать разработчику простой и, по моему мнению, волшебный способ доступа к «частным» свойствам.
Willurd
2
Да, я просто попытался проиллюстрировать это. Если вы сделаете это закрытым, просто скажете: «Вы не должны обращаться к нему напрямую», заставив компилятор жаловаться. Но кто-то действительно хочет сделать это, он может. Но да, в Python это проще, чем в большинстве других языков.
Максимилиан
7
В Java вы действительно можете защитить вещи с помощью инкапсуляции, но для этого нужно быть умным, выполнять ненадежный код в SecurityManager и быть очень осторожным. Даже Oracle иногда ошибается.
Сурьма
12

Соглашение class.__stuffоб именах позволяет программисту знать, что он не предназначен для доступа __stuffизвне. Название искажает имя, поэтому вряд ли кто-нибудь сделает это случайно.

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

Nickolay
источник
12

Подобное поведение существует, когда имена атрибутов модуля начинаются с одного подчеркивания (например, _foo).

Атрибуты модуля, названные как таковые, не будут скопированы в импортирующий модуль при использовании from*метода, например:

from bar import *

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

Росс
источник
12

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

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

ctcherry
источник
4

В Python 3.4 это поведение:

>>> class Foo:
        def __init__(self):
                pass
        def __privateMethod(self):
                return 3
        def invoke(self):
                return self.__privateMethod()


>>> help(Foo)
Help on class Foo in module __main__:

class Foo(builtins.object)
 |  Methods defined here:
 |
 |  __init__(self)
 |
 |  invoke(self)
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)

 >>> f = Foo()
 >>> f.invoke()
 3
 >>> f.__privateMethod()
 Traceback (most recent call last):
   File "<pyshell#47>", line 1, in <module>
     f.__privateMethod()
 AttributeError: 'Foo' object has no attribute '__privateMethod'

https://docs.python.org/3/tutorial/classes.html#tut-private

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

Даже если вопрос старый, я надеюсь, что мой фрагмент может быть полезным.

Alberto
источник
2

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

obj._MyClass__myPrivateMethod()

Я перешел с C #, и сначала это тоже было странно для меня, но через некоторое время я пришел к мысли, что только то, как дизайнеры кода Python думают об ООП, отличается.

Афшин Амири
источник
1

Почему «приватные» методы Python на самом деле не приватны?

Насколько я понимаю, они не могут быть частными. Как обеспечить конфиденциальность?

Очевидный ответ: «Доступ к закрытым членам возможен только через self», но это не сработает - selfэто не является особенным в Python, это не более чем общеупотребительное имя для первого параметра функции.

user200783
источник