Czy Python ma „prywatne” zmienne w klasach?

578

Pochodzę ze świata Java i czytam Bruce'a Eckelsa Wzory, przepisy i idiomy języku Python 3 .

Podczas czytania o klasach mówi się dalej, że w Pythonie nie ma potrzeby deklarowania zmiennych instancji. Po prostu używasz ich w konstruktorze i bum, są tam.

Na przykład:

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

Jeśli to prawda, to każdy obiekt klasy Simplemoże po prostu zmienić wartość zmiennejs poza klasą.

Na przykład:

if __name__ == "__main__":
    x = Simple("constructor argument")
    x.s = "test15" # this changes the value
    x.show()
    x.showMsg("A message")

W Javie uczono nas o zmiennych publicznych / prywatnych / chronionych. Te słowa kluczowe mają sens, ponieważ czasami chcesz zmiennych w klasie, do których nikt poza klasą nie ma dostępu.

Dlaczego nie jest to wymagane w Pythonie?

Wszechobecny
źródło
17
Miałeś na myśli zmienne instancji , a nie zmienne klasowe , prawda?
PaulMcG
13
Powinieneś sprawdzić właściwości: docs.python.org/library/functions.html#property . Wystarczy użyć gettera, a twoja zmienna będzie chroniona.
rubik
Krótka i wyraźna odpowiedź jest tutaj . Mam nadzieję, że to pomoże.
Premkumar chalmeti

Odpowiedzi:

961

To kulturalne. W Pythonie nie piszesz do instancji lub zmiennych klas innych klas. W Javie nic nie stoi na przeszkodzie, abyś zrobił to samo, jeśli naprawdę tego chcesz - w końcu zawsze możesz edytować źródło samej klasy, aby osiągnąć ten sam efekt. Python porzuca pozory bezpieczeństwa i zachęca programistów do odpowiedzialności. W praktyce działa to bardzo dobrze.

Jeśli z jakiegoś powodu chcesz emulować zmienne prywatne, zawsze możesz użyć __prefiksu z PEP 8 . Python Zmieniany nazwy zmiennych podoba __footak, że nie są łatwo widoczne dla kodu poza klasą, który zawiera je (choć można ominąć, jeśli jesteś zdecydowany na tyle, tak jak puszka obejść zabezpieczenia Java, jeśli pracujesz na nim ).

Zgodnie z tą samą konwencją _prefiks oznacza trzymanie się z dala, nawet jeśli nie jest to technicznie niemożliwe . Nie bawisz się zmiennymi innej klasy, które wyglądają jak __foolub _bar.

Kirk Strauser
źródło
14
To ma sens. Jednak nie sądzę, że istnieje jakikolwiek sposób w java na dostęp do zmiennych prywatnych poza klasą (z wyjątkiem faktycznej zmiany źródła klasy kursu). Jest tu?
Wszechobecny
168
Wolę sposób na python, ale nie sądzę, że sposób java jest tak bezcelowy, jak się wydaje. Deklaracja czegoś prywatnego szybko mówi komuś czytającemu kod coś bardzo przydatnego: to pole jest zawsze modyfikowane tylko w tej klasie.
Ned
67
@ Wszechobecny, możesz użyć odbicia.
rapadura
76
Wyjaśnię to wprost, aby Python nie implementował publicznych ani prywatnych atrybutów, ponieważ „jest to pozór bezpieczeństwa i zachęca programistów do odpowiedzialności”, jednak społeczność zachęca do używania „_” do oznaczania prywatnych zmiennych i metod? Może python powinien definitywnie mieć publiczny i prywatny nie? Ich głównym celem jest poinformowanie Cię, jakiego interfejsu API należy użyć do interakcji z klasą. Służą one jako dokumentacja informująca o korzystaniu z tych metod, a nie o ich używaniu. Nie są one „pozorem bezpieczeństwa”, są dokumentacją API, którą IDE może nawet użyć, aby cię poprowadzić!
PedroD
19
To dobra odpowiedź i twoje rozumowanie jest z pewnością słuszne, ale nie zgadzam się z jednym aspektem. Celem modyfikatorów dostępu nigdy nie było bezpieczeństwo . Są raczej sposobem na wyraźne rozgraniczenie (iw dużym stopniu egzekwowanie) tego, które części klasy są uważane za wewnętrzne, a które są narażone na użytkowanie przez zewnętrznych użytkowników tej klasy. Konwencje (kultura) są z pewnością ważną alternatywą dla modyfikatorów dostępu, a obie metody mają swoje zalety i wady, ale mylące jest dążenie do tego, aby modyfikatory dostępu na poziomie językowym były w jakikolwiek sposób „bezpieczne” w zwykłym znaczeniu słowo.
devios1
159

Prywatne zmienne w pythonie są mniej więcej włamaniem: interpreter celowo zmienia nazwę zmiennej.

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

Teraz, jeśli spróbujesz uzyskać dostęp __var poza definicją klasy, zakończy się ona niepowodzeniem:

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

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

Ale możesz łatwo uciec od tego:

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

Prawdopodobnie wiesz, że metody w OOP są wywoływane w następujący sposób: x.printVar() => A.printVar(x)jeśli A.printVar()można uzyskać dostęp do jakiegoś pola wewnątrz x, do tego pola można również uzyskać dostęp na zewnątrz A.printVar() ... w końcu funkcje są tworzone w celu ponownego użycia, nie ma żadnej szczególnej mocy dla instrukcji wewnątrz.

Gra różni się, gdy w grę wchodzi kompilator ( prywatność to koncepcja na poziomie kompilatora ). Wie o definicji klasy z modyfikatorami kontroli dostępu, więc może popełnić błąd, jeśli reguły nie będą przestrzegane w czasie kompilacji

watashiSHUN
źródło
3
w skrócie, to nie jest hermetyzacja
watashiSHUN
2
Zastanawiam się, czy PHP ma coś podobnego z głupimi zmiennymi prywatnymi - ponieważ zmienne prywatne nie mają tak naprawdę sensu w interpretowanym języku - mam na myśli jaką optymalizację może zrobić, wiedząc, że zmienna x jest prywatna, jeśli nie jest skompilowana?
NoBugs
1
Jak możemy randomizować wzór zmiennych prywatnych?
crisron
@crisron to samo pytanie
IanS
5
@watashiSHUN "w skrócie, to nie jest hermetyzacja" => tak to jest. Hermetyzacja polega tylko na użyciu publicznego interfejsu API, aby kod klienta był chroniony przed zmianami implementacyjnymi. Konwencje nazewnictwa są doskonale poprawnym sposobem na określenie, czym jest API, a co implementacja, a chodzi o to, że to po prostu działa.
bruno desthuilliers
30

Jak słusznie wspomniano w wielu powyższych komentarzach, nie zapominajmy o głównym celu Modyfikatorów dostępu: Pomaganiu użytkownikom kodu zrozumieć, co powinno się zmienić, a co nie. Kiedy zobaczysz prywatne pole, nie będziesz się z nim bawić. Więc jest to głównie cukier składniowy, który jest łatwo osiągalny w Pythonie przez _ i __.

Ardawan
źródło
4
Myślę, że jest to tak samo ważny punkt jak każdy inny. Podczas debugowania kodu (wiem, że jestem słabym wprowadzeniem błędów), wiedza, które klasy mogą zmienić zmienną składową, upraszcza proces debugowania. Przynajmniej jeśli zmienna jest chroniona przez pewien zakres. Podobną koncepcją są funkcje const w C ++. Ja wiem, że zmienne składowe nie zostały zmienione tam i tak nawet nie patrzeć na tę metodę jako potencjalną przyczynę złej zmiennym otoczeniu. Chociaż może to ułatwić dalszy rozwój rozszerzeń klas / dodawanie funkcji, ograniczenie widoczności kodu ułatwia debugowanie.
MrMas
18

Istnieje konwencja zmiennych prywatnych w konwencji podkreślenia.

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'

Istnieją pewne subtelne różnice, ale ze względu na programowanie wzoru ideologiczna czystość jest wystarczająca.

Istnieją przykłady dekoratorów @private, które ściślej wdrażają tę koncepcję, ale YMMV. Prawdopodobnie można również napisać definicję klasy, która używa meta

Shayne
źródło
14
Zdaję sobie sprawę, że jest już dość późno na imprezę, ale ten link pojawia się w Google, gdy googlujesz problem. To nie mówi całej historii. __xjako zmienna wewnątrz klasyA kompilator przepisał_A__x , nadal nie jest ona w pełni prywatna i nadal można uzyskać do niej dostęp.
Zorf
1
Oczywiście, jeśli zobaczę zmienną o nazwie _A__x, nie zamierzam jej dotykać. To może być zaraźliwe. Ucieknę od tego.
Mateen Ulhaq
Upewnij się, że to nie jest prawdziwy prywatny. Ale zasadnicze uzasadnienie dla mocno wymuszonego prywatnego w C ++ i Javie (itp.), Optymalizacji kompilatora, tak naprawdę nie istnieje w Pythonie, więc prywatność według konwencji jest wystarczająco dobra. Konwencja Pythona ogólnie polega na tym, że ufa, że ​​będziesz się zachowywał bez nadzoru. (I to pułapka dla nowicjuszy, ale wiesz, uważaj na klasowe wzornictwo i zużycie)
Shayne,
14

„W Javie uczono nas o zmiennych publicznych / prywatnych / chronionych”

„Dlaczego nie jest to wymagane w Pythonie?”

Z tego samego powodu nie jest to wymagane w Javie.

Możesz swobodnie korzystać - lub nie używać privateiprotected .

Jako programista Python i Java znalazłem to privatei protectedsą to bardzo, bardzo ważne koncepcje projektowe. Ale w praktyce, w dziesiątkach tysięcy linii Java i Pythona, tak naprawdę nigdy nie korzystałem z privateaniprotected .

Dlaczego nie?

Oto moje pytanie „chroniony przed kim?”

Inni programiści w moim zespole? Mają źródło. Co oznacza ochrona, kiedy mogą to zmienić?

Inni programiści z innych zespołów? Pracują dla tej samej firmy. Mogą - podczas rozmowy telefonicznej - uzyskać źródło.

Klienci? Jest to program do pracy najemnej (ogólnie). Klienci (ogólnie) są właścicielami kodu.

Więc przed kim dokładnie go chronię?

S.Lott
źródło
118
-1: Zgadzam się z Porculusem. Nie chodzi o zabronienie dostępu lub ukrycie czegoś, chodzi o niejawną dokumentację API. Programiści, a także kompilatory / interpreter / sprawdzanie kodu łatwo sprawdzają, których członków zaleca się używać, a których nie należy dotykać (a przynajmniej ostrożnie). W większości przypadków byłoby strasznym bałaganem, gdyby wszyscy członkowie klasy lub modułu byli publiczni. Rozważ rozróżnienie między prywatnymi / chronionymi / publicznymi członkami jako usługą, mówiąc: „Hej, ci członkowie są ważni, podczas gdy ci są wykorzystywani wewnętrznie i prawdopodobnie nie są dla ciebie przydatni”.
Oben Sonne,
7
@ S.Lott: Zgadzam się, że dokumenty API mają wyższy priorytet i często są jedynym sposobem komunikowania zamierzonych zastosowań API. Ale czasami nazwy członków i widoczność (w kategoriach prywatnych / publicznych) w wystarczającym stopniu mówią same za siebie. Widzę również twój punkt widzenia, że ​​idea niejawnej dokumentacji nie działa dobrze w edytorach bez kontroli API, ale jest naprawdę pomocna w IDE z uzupełnianiem kodu. Zakładając, że przeczytałeś dokumentację API już jakiś czas temu, pomaga ci zapamiętać, jak korzystać z klasy. Rzeczy nie działałyby tak mądrze, gdyby nie było rozróżnienia między członkami prywatnymi i publicznymi.
Oben Sonne,
21
Późno do dyskusji, ale wszystko, o co proszą Porculus i Oben, jest doskonale obsługiwane przez konwencję „przedrostek z podkreśleniem” (i bez szkody, jaką może spowodować egzekwowanie tej konwencji przez kompilator)
ncoghlan
38
@ S.Lott Nie jestem facetem pytona, więc nie będę komentował z tej perspektywy. Jednak jako programista Java jest to naprawdę przerażająca rada. -1
dbyrne
11
Łał. Tęsknisz całkowicie za punktem, udzielasz bardzo złej rady, obrażasz każdego, kto nie zgadza się z tobą w tej kwestii, ale wciąż otrzymujesz odznaki i ponad 1000 punktów reputacji za tę „odpowiedź”.
Eric Duminil,
12

Jak wspomniano wcześniej, można wskazać, że zmienna lub metoda jest prywatna, poprzedzając ją znakiem podkreślenia. Jeśli nie uważasz, że to wystarczy, zawsze możesz użyć propertydekoratora. Oto przykład:

class Foo:

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

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

W ten sposób ktoś lub coś, do czego się odwołuje, barfaktycznie odwołuje się do wartości zwracanej przez barfunkcję, a nie do samej zmiennej, a zatem można uzyskać do niej dostęp, ale nie można jej zmienić. Jednak jeśli ktoś naprawdę tego chce, może po prostu użyć _bari przypisać mu nową wartość. Nie ma pewnego sposobu, aby uniemożliwić komuś dostęp do zmiennych i metod, które chcesz ukryć, jak wielokrotnie powtarzano. Jednak użycie propertyjest najczystszym komunikatem, który można wysłać, że zmiennej nie można edytować. propertymoże być również używany do bardziej złożonych ścieżek dostępu do getter / setter / deleter, jak wyjaśniono tutaj: https://docs.python.org/3/library/functions.html#property

Isaac Saffold
źródło
Django też to docenia.
babygame0ver
10

Python ma ograniczoną obsługę prywatnych identyfikatorów, dzięki funkcji, która automatycznie dołącza nazwę klasy do dowolnych identyfikatorów rozpoczynających się od dwóch znaków podkreślenia. Jest to przeważnie przezroczyste dla programisty, ale efektem netto jest to, że dowolne zmienne nazwane w ten sposób mogą być używane jako zmienne prywatne.

Zobacz tutaj więcej na ten temat.

Ogólnie rzecz biorąc, implementacja orientacji obiektowej w Pythonie jest nieco prymitywna w porównaniu do innych języków. Ale tak naprawdę to lubię. Jest to bardzo koncepcyjnie prosta implementacja i dobrze pasuje do dynamicznego stylu języka.

Dan Olson
źródło
Tak. Piękno polega na tym, że możliwości metaprogramowania Pythona oznaczają, że możesz naprawdę zaimplementować fantazyjne rzeczy, jeśli chcesz (i są biblioteki, które implementują dekoratory @ prywatne / @ chronione / itp. Itp. Do diabła, widziałem nawet bibliotekę, która implementowała klasy prototypowe w stylu JS bez cholernego rozsądnego powodu), ale w praktyce nie jest to konieczne. Nienawidzę memu „python / js / what is a lisp”, ponieważ prawie nigdy nie jest prawdziwy, ale python dzieli „metaprogramowanie” w połączeniu z prostą składnią „w tym języku
Shayne,
8

Jedynym razem, kiedy korzystam ze zmiennych prywatnych, jest to, że muszę robić inne rzeczy podczas pisania lub odczytywania zmiennych i jako takie muszę wymusić użycie settera i / lub gettera.

Ponownie dotyczy to kultury, jak już wspomniano. Pracowałem nad projektami, w których czytanie i pisanie zmiennych innych klas było bezpłatne dla wszystkich. Kiedy jedna implementacja stała się przestarzała, identyfikacja wszystkich ścieżek kodu, które korzystały z tej funkcji, zajęła dużo więcej czasu. Gdy wymuszono użycie zestawów ustawiających i pobierających, można łatwo napisać instrukcję debugowania, aby zidentyfikować, że wywołano przestarzałą metodę i ścieżkę kodu, która ją wywołuje.

Kiedy pracujesz nad projektem, w którym każdy może napisać rozszerzenie, powiadamianie użytkowników o przestarzałych metodach, które mają zniknąć w kilku wydaniach, dlatego bardzo ważne jest, aby ograniczyć awarie modułów do minimum podczas aktualizacji.

Więc moja odpowiedź brzmi; jeśli ty i twoi koledzy macie prosty zestaw kodów, ochrona zmiennych klas nie zawsze jest konieczna. Jeśli piszesz system rozszerzalny, staje się konieczny, gdy wprowadzane są zmiany w rdzeniu, które muszą zostać przechwycone przez wszystkie rozszerzenia korzystające z kodu.

BlueEagle
źródło
7

Prywatne i chronione pojęcia są bardzo ważne. Ale python - tylko narzędzie do prototypowania i szybkiego rozwoju z ograniczonymi zasobami dostępnymi do programowania, dlatego niektóre poziomy ochrony nie są tak rygorystyczne w Pythonie. Możesz użyć „__” w klasie, działa poprawnie, ale nie wygląda wystarczająco dobrze - każdy dostęp do takiego pola zawiera te znaki.

Można również zauważyć, że koncepcja OOP w Pythonie nie jest idealna, smaltalk lub rubin jest znacznie bliższa czystej koncepcji OOP. Nawet C # lub Java są bliżej.

Python jest bardzo dobrym narzędziem. Ale jest to uproszczony język OOP. Uproszczone pod względem składniowym i koncepcyjnym. Głównym celem istnienia Pythona jest zapewnienie programistom możliwości szybkiego pisania czytelnego kodu o wysokim poziomie abstrakcji.

użytkownik711294
źródło
Uwaga Prywatne i chronione są ważne, że w statycznie skompilowanych językach kompilator może tworzyć dokładne wywołania metody prywatnej, ale musi polegać na tabeli wyszukiwania metod publicznych. To po prostu nie jest problem z dynamicznymi językami. Wreszcie języki takie jak C ++ mają wpływ na dziedziczenie i rozwiązywanie metod. Python i Ruby mają bardzo podobne implementacje OO, więc porównanie jest bez znaczenia. Smalltalk w rzeczywistości nie ma pojęcia wiadomości publicznych / prywatnych. Możesz dodawać prywatne jako kategorię, ale jest to wyłącznie wskazówka.
Shayne
Aby podnieść moje twierdzenie. Z punktu widzenia higieny kodowania, tak, są one ważne dla enkapsulacji, ale nie jest to konieczne , dlatego dekoratorzy @private (itp.) Są bardziej doradczy niż cokolwiek innego, ale ponieważ prywatna / publiczna nie dodaje niczego użytecznego do optymalizacji w język niestatyczny, nie jest zaimplementowany na głębokim poziomie, tak jak w skompilowanym języku, takim jak java lub c
Shayne
7

Przepraszam chłopaki za „wskrzeszenie” wątku, ale mam nadzieję, że to pomoże komuś:

W Python3, jeśli chcesz po prostu „obudować” atrybuty klasy, tak jak w Javie, możesz po prostu zrobić to samo:

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

Aby utworzyć instancję, wykonaj następujące czynności:

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

Uwaga: print(ss.__s) wyrzuci błąd.

W praktyce Python3 zaciemni globalną nazwę atrybutu. Zmieniając to na „prywatny” atrybut, jak w Javie. Nazwa atrybutu jest nadal globalna, ale niedostępna, podobnie jak atrybut prywatny w innych językach.

Ale nie bój się tego. To nie ma znaczenia To też działa. ;)

Ferrarezi
źródło
2
istnieje od czasu Python 1.5.2 IIRC i nadal nie uniemożliwia dostępu do atrybutu poprzez jego zniekształconą nazwę.
bruno desthuilliers
4

Python nie ma żadnych prywatnych zmiennych, takich jak C ++ lub Java. W razie potrzeby można uzyskać dostęp do dowolnej zmiennej składowej w dowolnym momencie. Jednak nie potrzebujesz prywatnych zmiennych w Pythonie, ponieważ w Pythonie nie jest źle eksponować zmienne składowe klas. Jeśli potrzebujesz enkapsulować zmienną składową, możesz to zrobić, używając później „@property” bez zerwania istniejącego kodu klienta.

W pythonie pojedynczy znak podkreślenia „_” służy do wskazania, że ​​metoda lub zmienna nie jest uważana za część publicznego interfejsu API klasy i że ta część interfejsu API może zmieniać się między różnymi wersjami. Możesz użyć tych metod / zmiennych, ale kod może się zepsuć, jeśli użyjesz nowszej wersji tej klasy.

Podwójny znak podkreślenia „__” nie oznacza „zmiennej prywatnej”. Używasz go do definiowania zmiennych, które są „klasami lokalnymi” i których nie można łatwo ominąć podklasami. Zmienia nazwę zmiennej.

Na przykład:

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 .__ nazwa foobar jest automatycznie zniekształcana do self._A__foobar w klasie A. W klasie B jest przekształcana w self._B__foobar. Każda podklasa może więc zdefiniować własną zmienną __foobar bez nadpisywania zmiennych nadrzędnych. Ale nic nie stoi na przeszkodzie, aby uzyskać dostęp do zmiennych zaczynających się od podwójnych znaków podkreślenia. Jednak zmiana nazwy uniemożliwia przypadkowe wywołanie tych zmiennych / metod.

Zdecydowanie polecam oglądać Raymonda Hettingersa rozmawiającego na temat „zestawu narzędzi rozwojowych klasy Python” z Pycon 2013 (powinien być dostępny na Youtube), który daje dobry przykład, dlaczego i jak należy używać @property i „__” - zmiennych instancji.

Hatatister
źródło
Sprawdzę to przemówienie. Czy @propertyrzecz jest częścią standardowego Pythona, czy jest specyficzna dla IDE?
bballdave025,
Jest to część standardu od Pythona 2.6. Jeśli powinieneś użyć starszej wersji, nadal istnieje możliwość skorzystania z propertywbudowanej funkcji, która jest dostępna od wersji Python 2.2
Hatatister
0

W rzeczywistości możesz symulować C#metodę pobierającą i ustawiającą za pomocą tej prostej sztuczki:

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

Następnie użyj go podobnie jak w C#:

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

Po prostu deklaruje statyczną zmienną lokalną w funkcji, która będzie odgrywać rolę get / set, ponieważ jest to jedyny sposób dzielenia się zmienną za pomocą metod get i set, bez uczynienia jej globalną dla klasy lub pliku.

Ilian Zapryanov
źródło