Почему «приватные» методы Python на самом деле не приватны? - PullRequest
603 голосов
/ 16 сентября 2008

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 не поддерживает инкапсуляцию, так зачем даже пытаться? Что дает?

Ответы [ 12 ]

551 голосов
/ 16 сентября 2008

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

Например:

>>> 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}

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

205 голосов
/ 30 июня 2010

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

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 ###
148 голосов
/ 23 декабря 2009

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

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

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

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

139 голосов
/ 16 сентября 2008

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

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

87 голосов
/ 16 сентября 2008

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

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

32 голосов
/ 16 сентября 2008

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

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

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

12 голосов
/ 17 сентября 2008

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

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

from bar import *

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

12 голосов
/ 16 сентября 2008

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

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

12 голосов
/ 16 сентября 2008

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

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

4 голосов
/ 24 августа 2016

В 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

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

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

...