Чому "приватні" методи 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 () на об'єкт виявляє новий магічний метод, який пітон створює магічно для всіх ваших "приватних" методів.

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

Ім'я нового методу завжди є підкресленням, за ним - ім'я класу, а потім ім'я методу.

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

Стільки для інкапсуляції, так?

У будь-якому випадку, я завжди чув, що Python не підтримує інкапсуляцію, так навіщо навіть намагатися? Що дає?


18
Те саме стосується Java або C #, якщо ви використовуєте рефлексію (це якимось чином ви там робите).
0x434D53

4
Він був побудований з метою тестування Unit, так що ви можете використовувати цей "хак" для того, щоб перевірити приватні методи вашого класу ззовні.
waas1919

16
Чи не тестування приватних методів є антидією? Приватні методи будуть використані в якомусь публічному методі, напевно, він просто не використовується назавжди. І правильний спосіб перевірити приватні методи (засновані на моєму навчанні дотепер від ThoughtWorks) - це те, що ви пишете тести лише для публічних методів, які охоплюють усі випадки. Якщо це працює добре, вам взагалі не потрібно тестувати приватні методи.
Вишну Наранг

3
@VishnuNarang: Так, цього часто навчають. Але, як завжди, майже «релігійний» підхід « завжди роби це, ніколи цього не роби» - єдине, що «ніколи» не є добрим. Якщо одиничні тести "використовуються" лише для регресійних тестів або тестування загальнодоступних API, вам не потрібно тестувати приватних осіб. Але якщо ви працюєте з тестовою розробкою, є вагомі причини для тестування приватних методів під час розробки (наприклад, коли важко знущатися над певними незвичайними / екстремальними параметрами через публічний інтерфейс). Деякі мовні / одиничні тестові середовища не дозволяють вам це робити, що IMHO не годиться.
Марко Фрейденбергер

5
@MarcoFreudenberger Я бачу вашу думку. У мене є досвід розробки тестових підрозділів. Часто, коли стає важко знущатися над параметрами, найчастіше це вирішується шляхом зміни та вдосконалення дизайну. Я ще не зіткнувся зі сценарієм, коли дизайн ідеальний, і все-таки тестування одиниць надзвичайно складно уникнути тестування приватних методів. Я буду дивитися на такі випадки. Дякую. Буду вдячний, якщо ви могли б поділитися одним сценарієм у верхній частині голови, щоб допомогти мені зрозуміти.
Vishnu Narang

Відповіді:


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
Вам слід поставити єдине підкреслення, щоб вказати, що змінна повинна вважатися приватною. Знову ж таки, це не заважає комусь реально отримати доступ до цього.
igon

14
Гвідо відповів на це запитання - "основною причиною зробити (майже) все відкритим було налагодження: при налагодженні вам часто потрібно пробитися через абстракції", - я додав це як коментар, тому що вже пізно - занадто багато відповідей.
Пітер М. - виступає за Моніку

1
Якщо ви керуєтесь критерієм "запобігати навмисному доступу", більшість мов OOP не підтримують дійсно приватних членів. Наприклад, у 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
Про всяк випадок не було зрозуміло: ніколи цього не роби в реальному коді;)
Судо Баш

10
@ThorSummoner Або просто function_call.startswith('self.').
nyuszika7h

13
inspect.stack()[1][4][0].strip()<- що це магічні числа 1, 4 і 0?
akhy

5
Це можна легко перемогти, зробивши self = MyClass(); self.private_function()це, і це не вдасться, коли викликається використання x = self.private_function()методу всередині.
Буде

171

Коли я вперше приїхав з Java на Python, я ненавидів це. Це мене налякало на смерть.

Сьогодні це може бути саме те, що я найбільше люблю в Python.

Я люблю перебувати на платформі, де люди довіряють один одному і не відчувають, що їм потрібно будувати непрохідні стіни навколо свого коду. Якщо в сильно інкапсульованих мовах, якщо в API є помилка, і ви з’ясували, що піде не так, ви все ще не зможете обійти його, оскільки потрібний метод є приватним. У Python таке ставлення: "впевнений". Якщо ви думаєте, що розумієте ситуацію, можливо, ви навіть її прочитали, то все, що ми можемо сказати, - «удача!».

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


36
@CamJackson Javascript - ваш приклад ?? Єдина широко використовувана мова, яка має успадкування на основі прототипів і мова, яка надає перевагу функціональному програмуванню? Я думаю, що JS набагато складніше вивчити, ніж більшість інших мов, оскільки це робить кілька ортогональних кроків від традиційного OOP. Не те, що це заважає ідіотам писати 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 за своєю природою насправді не може добре використати інформацію, не порушуючи її динамізму. Обидві мови націлені на різні речі, тому жодна з них не є "неправильною"
Shayne

144

Від http://www.faqs.org/docs/diveintopython/fileinfo_private.html

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


202
або, як говорив Гідо ван Россум: "ми всі дорослі".

35
-1: це просто неправильно. Подвійні підкреслення ніколи не маються на увазі використовуватися як приватні в першу чергу. Відповідь Алі нижче розповідає про реальний намір керувати синтаксисом керування іменами. Справжня умова - це єдине підкреслення.
nosklo

2
Спробуйте лише з одним підкресленням, і ви побачите отриманий результат. @nosklo
Billal Begueradj

93

Загальновживана фраза - "ми тут усі згоди на дорослих". Заздалегідь випереджаючи окремий підкреслення (не розкривайте) або подвійне підкреслення (приховування), ви повідомляєте користувачеві свого класу, що ви маєте намір учасником певним чином бути "приватним". Однак ви довіряєте всім іншим поводитися відповідально і поважати це, якщо вони не мають вагомих причин цього не робити (наприклад, налагоджувачі, заповнення коду).

Якщо у вас справді має бути щось приватне, ви можете реалізувати це в розширенні (наприклад, в C для CPython). Однак у більшості випадків ви просто вивчаєте піфонічний спосіб робити речі.


так чи є якийсь протокол обгортки, який я повинен використовувати для доступу до захищеної змінної?
інтуїтоване

3
"Захищених" змінних більше немає, ніж є "приватні". Якщо ви хочете отримати доступ до атрибута, який починається з підкреслення, ви можете просто зробити це (але зауважте, що автор це відволікає). Якщо вам потрібно отримати доступ до атрибуту, який починається з подвійного підкреслення, ви можете зробити ім'я, керуючи собою, але ви майже точно не хочете цього робити.
Тоні Мейєр

33

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

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

Редагувати: Ви не намагаєтеся закріпити свої речі шляхом OO-інкапсуляції, чи не так?


2
Зовсім ні. Я просто зазначаю, що дивно надати розробнику простий і, на мій погляд, магічний спосіб доступу до "приватних" властивостей.
willurd

2
Так, я просто намагався проілюструвати суть. Здійснюючи це приватне, просто сказано, що "ви не повинні отримувати доступ до цього прямо", роблячи скарга компілятора. Але хочеться справді реально зробити це він може. Але так, у Python це простіше, ніж у більшості інших мов.
Максиміліан

7
На Java ви дійсно можете захистити речі за допомогою інкапсуляції, але для цього потрібно бути розумним та запускати ненадійний код у SecurityManager та бути дуже обережним. Навіть Oracle іноді помиляється.
Сурма

12

class.__stuffІменування дозволяє програмісту знати , що він не призначений для доступу __stuffззовні. Ім'я mangling робить малоймовірним, що хтось зробить це випадково.

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


12

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

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

from bar import *

Однак це конвенція, а не обмеження мови. Це не приватні атрибути; на них може посилатися та маніпулювати будь-який імпортер. Деякі стверджують, що через це Python не може реалізувати справжнє капсулювання.


12

Це просто один із таких варіантів мовного дизайну. На якомусь рівні вони виправдані. Вони роблять це так, що вам потрібно вийти досить далеко зі свого шляху, щоб спробувати і зателефонувати за методом, і якщо вам це справді дуже потрібно, у вас повинна бути досить вагома причина!

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


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

Зауважте, що правила керування людьми розроблені переважно для уникнення нещасних випадків; все ще можна отримати доступ або змінити змінну, яка вважається приватною. Це навіть може бути корисно в особливих обставинах, наприклад, у налагоджувачі.

Навіть якщо питання старе, я сподіваюся, що мій фрагмент може бути корисним.


2

Найважливіше занепокоєння щодо приватних методів та атрибутів - це сказати розробникам не називати його поза класом, і це інкапсуляція. можна неправильно зрозуміти безпеку від інкапсуляції. коли ви навмисно використовуєте такий синтаксис (нижче), який ви згадали, ви не хочете інкапсуляції.

obj._MyClass__myPrivateMethod()

Я мігрував з C #, і спочатку це теж було дивно для мене, але через деякий час я прийшов до думки, що тільки спосіб, який дизайнери коду Python думають про OOP, відрізняється.


1

Чому "приватні" методи Python насправді не є приватними?

Як я розумію, вони не можуть бути приватними. Як можна забезпечити конфіденційність?

Очевидна відповідь - "приватні члени можуть отримати доступ лише через self", але це не вийде - selfце не особливе значення в Python, це не що інше, як загальновживане ім'я першого параметра функції.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.