Dlaczego „prywatne” metody Pythona nie są prywatne?

657

Python daje nam możliwość tworzenia „prywatny” metod i zmiennych w klasie przez poprzedzenie podwójne podkreślenia do nazwy, na przykład: __myPrivateMethod(). Jak zatem to wyjaśnić

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

O co chodzi?!

Wytłumaczę to trochę tym, którzy tego nie rozumieją.

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

To, co tam zrobiłem, to utworzenie klasy za pomocą metody publicznej i prywatnej oraz utworzenie jej instancji.

Następnie nazywam tę metodę publiczną.

>>> obj.myPublicMethod()
public method

Następnie próbuję wywołać jego prywatną metodę.

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

Tutaj wszystko wygląda dobrze; nie możemy tego nazwać. W rzeczywistości jest „prywatny”. Właściwie to nie jest. Uruchomienie dir () na obiekcie ujawnia nową magiczną metodę, którą Python tworzy magicznie dla wszystkich twoich „prywatnych” metod.

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

Nazwa tej nowej metody jest zawsze znakiem podkreślenia, po której następuje nazwa klasy, a następnie nazwa metody.

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

Tyle o enkapsulacji, co?

W każdym razie zawsze słyszałem, że Python nie obsługuje enkapsulacji, więc po co w ogóle próbować? Co daje?

Willurd
źródło
18
To samo dotyczy Java lub C #, jeśli używasz refleksji (która jest czymś, co tam robisz).
0x434D53,
4
Został zbudowany dla celów testów jednostkowych, więc możesz użyć tego „hacka”, aby przetestować prywatne metody twojej klasy z zewnątrz.
waas1919
16
Czy testowanie prywatnych metod nie jest anty-wzorcem? Prywatne metody będą używane w niektórych publicznych metodach, na pewno nie będą używane na zawsze. A właściwym sposobem na przetestowanie metod prywatnych (w oparciu o moją naukę dotychczasową od ThoughtWorks) jest napisanie testów tylko dla metod publicznych, które obejmują wszystkie przypadki. Jeśli to działa dobrze, wcale nie musisz testować prywatnych metod z zewnątrz.
Wisznu Narang
3
@VishnuNarang: Tak, tego często nauczano. Ale jak zawsze, niemal „religijne” podejście „ zawsze rób to, nigdy nie rób tego” to jedyna rzecz, która „nigdy” jest dobra. Jeśli testy jednostkowe są „tylko” używane do testów regresji lub testowania publicznego interfejsu API, nie trzeba testować prywatnych. Ale jeśli wykonujesz programowanie oparte na testach jednostkowych, istnieją dobre powody, by testować prywatne metody podczas programowania (na przykład, gdy trudno jest wyśmiewać pewne niezwykłe / ekstremalne parametry za pośrednictwem publicznego interfejsu). Niektóre środowiska testowe języków / jednostek nie pozwalają na to, co IMHO nie jest dobre.
Marco Freudenberger
5
@MarcoFreudenberger Rozumiem twój punkt widzenia. Mam doświadczenie w rozwoju opartym na testach jednostkowych. Często, gdy trudno wyśmiewać parametry, najczęściej rozwiązuje się to poprzez zmianę i ulepszenie projektu. Jeszcze nie spotkałem się ze scenariuszem, w którym konstrukcja jest idealna, a testowanie jednostkowe jest niezwykle trudne, aby uniknąć testowania metod prywatnych. Będę uważał na takie przypadki. Dzięki. Byłbym wdzięczny, gdybyś mógł podzielić się jednym scenariuszem z głowy, aby pomóc mi zrozumieć.
Wisznu Narang,

Odpowiedzi:

592

Szyfrowanie nazw służy do tego, aby podklasy przypadkowo nie zastępowały prywatnych metod i atrybutów ich nadklas. Nie jest zaprojektowany, aby zapobiegać celowemu dostępowi z zewnątrz.

Na przykład:

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

Oczywiście psuje się, jeśli dwie różne klasy mają tę samą nazwę.

Alya
źródło
12
docs.python.org/2/tutorial/classes.html . Sekcja: 9.6 na temat zmiennych prywatnych i lokalnych odwołań do klas.
gjain
72
Dla tych z nas, zbyt leniwy, aby przewinąć / szukaj: Sekcja 9.6 Link bezpośredni
cod3monk3y
3
Powinieneś umieścić pojedynczy znak podkreślenia, aby określić, że zmienna powinna być traktowana jako prywatna. Ponownie, nie uniemożliwia to komuś dostępu do tego.
igon
14
Guido odpowiedział na to pytanie - „głównym powodem uczynienia (prawie) wszystkiego, co można było odkryć, było debugowanie: podczas debugowania często trzeba przedzierać się przez abstrakty” - dodałem to jako komentarz, ponieważ jest za późno - zbyt wiele odpowiedzi.
Peter M. - oznacza Monikę
1
Jeśli zastosujesz kryterium „zapobiegaj celowemu dostępowi”, większość języków OOP nie obsługuje prawdziwie prywatnych członków. Na przykład w C ++ masz surowy dostęp do pamięci, aw zaufanym kodzie C # można użyć prywatnego odzwierciedlenia.
CodesInChaos
207

Przykład funkcji prywatnej

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 ###
bieg
źródło
12
self = MyClass() self.private_function(). : D Oczywiście, że to nie działa w klasach, ale wystarczy zdefiniować niestandardową funkcję: def foo(self): self.private_function()
Casey Kuball
161
Na wszelki wypadek nie było jasne: nigdy nie rób tego w prawdziwym kodzie;)
Sudo Bash
10
@ThorSummoner Lub po prostu function_call.startswith('self.').
nyuszika7h
13
inspect.stack()[1][4][0].strip()<- jakie są te 1, 4 i 0 magicznych liczb?
akhy
5
Można to dość łatwo pokonać, wykonując polecenie, self = MyClass(); self.private_function()i zawiedzie, gdy zostanie wywołane za x = self.private_function()pomocą metody.
Czy
171

Kiedy po raz pierwszy przyszedłem z Javy do Pythona, nienawidziłem tego. Przestraszyło mnie to na śmierć.

Dzisiaj może to być jedna rzecz, którą najbardziej lubię w Pythonie.

Uwielbiam być na platformie, na której ludzie sobie ufają i nie czują, że muszą budować nieprzeniknione ściany wokół swojego kodu. W silnie zamkniętych językach, jeśli interfejs API zawiera błąd, a ty zorientowałeś się, co poszło nie tak, nadal możesz nie być w stanie go obejść, ponieważ potrzebna metoda jest prywatna. W Pythonie postawa brzmi: „pewnie”. Jeśli uważasz, że rozumiesz sytuację, być może nawet ją przeczytałeś, to wszystko, co możemy powiedzieć, to „powodzenia!”.

Pamiętaj, że kapsułkowanie nie jest nawet słabo związane z „bezpieczeństwem” lub utrzymywaniem dzieci z dala od trawnika. To tylko kolejny wzorzec, który powinien być użyty, aby podstawa kodu była łatwiejsza do zrozumienia.

Thomas Ahle
źródło
36
@CamJackson Javascript jest Twoim przykładem? Jedyny powszechnie używany język, który ma dziedziczenie oparte na prototybe i język faworyzujący programowanie funkcjonalne? Myślę, że JS jest znacznie trudniejszy do nauczenia się niż większość innych języków, ponieważ wymaga kilku ortogonalnych kroków od tradycyjnego OOP. Nie przeszkadza to idiotom pisać JS, po prostu tego nie wiedzą;)
K.Steff,
23
Interfejsy API są naprawdę dobrym przykładem tego, dlaczego enkapsulacja ma znaczenie i kiedy preferowane są metody prywatne. Metoda, która ma być prywatna, może zniknąć, zmienić podpis lub najgorsze ze wszystkich zachowań związanych ze zmianą - wszystko bez ostrzeżenia - w każdej nowej nowej wersji. Czy Twój inteligentny, dorosły członek zespołu naprawdę pamięta, że ​​za rok uzyskał dostęp do metody, która ma być prywatna? Czy ona w ogóle tam będzie dalej pracować?
einnocent
2
Nie zgadzam się z argumentem. W kodzie produkcyjnym najprawdopodobniej nigdy nie użyłbym interfejsu API, który zawiera błąd, który powoduje, że zmieniam członków publicznych, aby działał. Interfejs API powinien działać. Jeśli tak się nie stanie, sam bym zgłosił błąd lub sam stworzyłem ten sam interfejs API. Nie podoba mi się ta filozofia i nie przepadam za Pythonem, chociaż jego składnia sprawia, że ​​pisanie mniejszych skryptów w ...
Yngve Sneen Lindal
4
Java ma Method.setAccessible i Field.setAccessible. Też przerażające?
Tony,
15
Wymuszanie w Javie i C ++ nie jest spowodowane tym, że Java nie ufa użytkownikowi, gdy robi to Python. Wynika to z faktu, że kompilator i / lub vm ​​mogą przyjmować różne założenia, jeśli chodzi o sposób prowadzenia działalności, jeśli zna te informacje, na przykład C ++ może pominąć całą warstwę pośredniczenia, używając zwykłych starych wywołań C zamiast wywołań wirtualnych, i że ma znaczenie, gdy pracujesz nad materiałami o wysokiej wydajności lub precyzji. Python ze swej natury nie może naprawdę dobrze wykorzystać informacji bez naruszenia jej dynamiki. Oba języki dążą do różnych rzeczy, więc żadne z nich nie jest „złe”
Shayne
144

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

Ściśle mówiąc, prywatne metody są dostępne poza ich klasą, po prostu nie są łatwo dostępne. Nic w Pythonie nie jest naprawdę prywatne; wewnętrznie nazwy prywatnych metod i atrybutów są zniekształcone i rozszyfrowane w locie, aby wydawały się niedostępne pod ich nazwami. Możesz uzyskać dostęp do metody __parse klasy MP3FileInfo pod nazwą _MP3FileInfo__parse. Uznaj, że jest to interesujące, a następnie obiecaj, że nigdy, nigdy nie zrobisz tego w prawdziwym kodzie. Prywatne metody są prywatne z jakiegoś powodu, ale podobnie jak wiele innych rzeczy w Pythonie, ich prywatność jest ostatecznie kwestią konwencji, a nie siły.

xsl
źródło
202
lub, jak ujął to Guido van Rossum: „wszyscy jesteśmy dorośli”.
35
-1: to po prostu źle. Podwójne podkreślenia nigdy nie powinny być używane jako prywatne. Odpowiedź Alyi poniżej mówi o prawdziwej intencji składni zmieniającej nazwy. Prawdziwa konwencja to pojedynczy znak podkreślenia.
nosklo
2
Spróbuj z jednym podkreśleniem, a zobaczysz wynik, który otrzymasz. @nosklo
Billal Begueradj
93

Często używanym zwrotem jest „wszyscy tutaj zgadzamy się na dorosłych”. Przygotowując pojedynczy znak podkreślenia (nie ujawniaj) lub podwójny znak podkreślenia (ukryj), mówisz użytkownikowi swojej klasy, że zamierzasz być w jakiś sposób „prywatny”. Jednak ufasz wszystkim, którzy zachowują się odpowiedzialnie i szanują to, chyba że mają ku temu ważny powód (np. Debuggery, uzupełnianie kodu).

Jeśli naprawdę musisz mieć coś prywatnego, możesz to zaimplementować w rozszerzeniu (np. W C dla CPython). Jednak w większości przypadków po prostu uczysz się Pythońskiego sposobu robienia rzeczy.

Tony Meyer
źródło
więc czy istnieje jakiś protokół otoki, którego powinienem użyć, aby uzyskać dostęp do chronionej zmiennej?
intuicyjnie
3
Nie ma „chronionych” zmiennych bardziej niż „prywatnych”. Jeśli chcesz uzyskać dostęp do atrybutu rozpoczynającego się znakiem podkreślenia, możesz po prostu to zrobić (ale pamiętaj, że autor tego nie zaleca). Jeśli musisz uzyskać dostęp do atrybutu rozpoczynającego się od podwójnego podkreślenia, możesz samodzielnie nadać mu nazwę, ale prawie na pewno nie chcesz tego robić.
Tony Meyer
33

To nie tak, że absolutnie nie możesz ominąć prywatności członków w żadnym języku (arytmetyka wskaźników w C ++, Refleksje w .NET / Java).

Chodzi o to, że pojawia się błąd, jeśli przypadkowo wywołujesz metodę prywatną. Ale jeśli chcesz strzelić sobie w stopę, zrób to.

Edycja: Nie próbujesz zabezpieczyć swoich rzeczy za pomocą enkapsulacji OO, prawda?

Maksymilian
źródło
2
Ani trochę. Po prostu podkreślam, że dziwne jest, aby dać deweloperowi łatwy i, moim zdaniem, sposób na magiczny dostęp do „prywatnych” nieruchomości.
willurd
2
Tak, właśnie próbowałem zilustrować tę kwestię. Ustawienie go jako prywatnego mówi tylko „nie powinieneś uzyskiwać do niego bezpośredniego dostępu”, powodując, że kompilator narzeka. Ale naprawdę chce się to naprawdę zrobić. Ale tak, w Pythonie jest łatwiej niż w większości innych języków.
Maximilian
7
W Javie możesz zabezpieczyć rzeczy poprzez enkapsulację, ale to wymaga od ciebie bycia inteligentnym i uruchamiania niezaufanego kodu w SecurityManager, i bądź bardzo ostrożny. Nawet Oracle czasami się myli.
Antymon
12

The class.__stuffKonwencja nazewnictwa pozwala programista wie, że nie ma dostępu __stuffz zewnątrz. Nazwa „mangling” sprawia, że ​​jest mało prawdopodobne, że ktoś zrobi to przypadkowo.

To prawda, że ​​nadal możesz to obejść, jest to nawet łatwiejsze niż w innych językach (które BTW również pozwala ci to zrobić), ale żaden programista Python nie zrobiłby tego, jeśli zależy mu na enkapsulacji.

Nickolay
źródło
12

Podobne zachowanie występuje, gdy nazwy atrybutów modułów zaczynają się od pojedynczego podkreślenia (np. _Foo).

Atrybuty modułu nazwane jako takie nie zostaną skopiowane do modułu importującego podczas korzystania z from*metody, np .:

from bar import *

Jest to jednak konwencja, a nie ograniczenie językowe. To nie są prywatne atrybuty; mogą być przywoływane i manipulowane przez dowolnego importera. Niektórzy twierdzą, że z tego powodu Python nie może zaimplementować prawdziwej enkapsulacji.

Ross
źródło
12

To tylko jedna z opcji wyboru języka. Na pewnym poziomie są uzasadnione. Robią to, więc musisz zrobić wszystko, co w twojej mocy, aby wywołać metodę, a jeśli naprawdę potrzebujesz jej tak bardzo, musisz mieć całkiem dobry powód!

Debugowanie haków i testowanie przychodzą na myśl jako możliwe aplikacje, oczywiście używane w sposób odpowiedzialny.

ctcherry
źródło
4

W Pythonie 3.4 jest to zachowanie:

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

Należy pamiętać, że reguły zniekształcania mają na celu przede wszystkim uniknięcie wypadków; nadal można uzyskać dostęp lub zmodyfikować zmienną uważaną za prywatną. Może to być przydatne nawet w szczególnych okolicznościach, takich jak debugger.

Nawet jeśli pytanie jest stare, mam nadzieję, że mój fragment może być pomocny.

Alberto
źródło
2

Najważniejszą kwestią związaną z prywatnymi metodami i atrybutami jest poinformowanie programistów, aby nie nazywali go poza klasą, a to jest hermetyzacja. można źle zrozumieć bezpieczeństwo wynikające z hermetyzacji. kiedy ktoś celowo używa takiej składni (poniżej), o której wspomniałeś, nie chcesz enkapsulacji.

obj._MyClass__myPrivateMethod()

Przeprowadziłem migrację z C # i na początku też było to dla mnie dziwne, ale po pewnym czasie doszedłem do wniosku, że tylko sposób, w jaki projektanci kodu Python myślą o OOP, jest inny.

Afshin Amiri
źródło
1

Dlaczego „prywatne” metody Pythona nie są prywatne?

Jak rozumiem, nie mogą być prywatni. Jak można egzekwować prywatność?

Oczywista odpowiedź brzmi: „członkowie prywatni są dostępni tylko przez self”, ale to nie zadziałałoby - selfnie jest to szczególne w Pythonie, jest niczym więcej niż powszechnie używaną nazwą pierwszego parametru funkcji.

użytkownik200783
źródło