Perché i metodi "privati" di Python non sono in realtà privati?


658

Python ci dà la possibilità di creare metodi di 'privato' e le variabili all'interno di una classe anteponendo doppia sottolineatura per il nome, in questo modo: __myPrivateMethod(). Come, quindi, si può spiegare questo

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

Qual è l'accordo?!

Lo spiegherò un po 'per coloro che non l'hanno capito.

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

Quello che ho fatto lì è creare una classe con un metodo pubblico e un metodo privato e istanziarlo.

Successivamente, chiamo il suo metodo pubblico.

>>> obj.myPublicMethod()
public method

Successivamente, provo a chiamare il suo metodo privato.

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

Qui tutto sembra a posto; non siamo in grado di chiamarlo. È, infatti, "privato". Bene, in realtà non lo è. L'esecuzione di dir () sull'oggetto rivela un nuovo metodo magico che Python crea magicamente per tutti i tuoi metodi "privati".

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

Il nome di questo nuovo metodo è sempre un carattere di sottolineatura, seguito dal nome della classe, seguito dal nome del metodo.

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

Tanto per l'incapsulamento, eh?

In ogni caso, ho sempre sentito che Python non supporta l'incapsulamento, quindi perché provarci? Cosa dà?


18
Lo stesso vale per Java o C # se si utilizza la riflessione (che è in qualche modo ciò che si sta facendo lì).
0x434D53,

4
È stato creato per scopi di unit test, quindi puoi usare questo "hack" per testare i metodi privati ​​della tua classe dall'esterno.
waas1919,

16
Testare metodi privati ​​non è un anti-pattern? I metodi privati ​​verranno utilizzati in alcuni metodi pubblici, altrimenti è inutilizzato per sempre. E il modo giusto di testare metodi privati ​​(basato sul mio apprendimento così lontano da ThoughtWorks) è che tu scriva test solo per metodi pubblici che coprono tutti i casi. Se funziona bene, non è necessario testare metodi privati ​​dall'esterno.
Vishnu Narang,

3
@VishnuNarang: Sì, è ciò che viene spesso insegnato. Ma come sempre, un approccio quasi "religioso" di " fai sempre questo, non farlo mai " è l'unica cosa che "mai" è buono. Se i test unitari sono "solo" utilizzati per i test di regressione o per testare le API pubbliche, non è necessario testare i privati. Ma se si esegue lo sviluppo guidato da unit test, ci sono buoni motivi per testare i metodi privati ​​durante lo sviluppo (ad esempio quando è difficile deridere alcuni parametri insoliti / estremi attraverso l'interfaccia pubblica). Alcuni linguaggi / ambienti di test unitari non ti consentono di farlo, il che IMHO non è buono.
Marco Freudenberger,

5
@MarcoFreudenberger Vedo il tuo punto. Ho esperienza nello sviluppo guidato da unit test. Spesso quando diventa difficile deridere i parametri, il più delle volte viene risolto cambiando e migliorando il design. Devo ancora imbattermi in uno scenario in cui il design è perfetto e ancora i test unitari sono estremamente difficili da evitare per testare metodi privati. Cercherò questi casi. Grazie. Apprezzerei se potessi forse condividere uno scenario dalla cima della tua testa per aiutarmi a capire.
Vishnu Narang,

Risposte:


592

Il nome scrambling viene utilizzato per garantire che le sottoclassi non sovrascrivano accidentalmente i metodi e gli attributi privati ​​delle loro superclassi. Non è progettato per impedire l'accesso deliberato dall'esterno.

Per esempio:

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

Naturalmente, si rompe se due classi diverse hanno lo stesso nome.


12
docs.python.org/2/tutorial/classes.html . Sezione: 9.6 su Variabili private e riferimenti di classe locale.
gjain,

72
Per quelli di noi troppo pigri per scorrere / cercare: Sezione 9.6 link diretto
cod3monk3y

3
Dovresti inserire un singolo trattino basso per specificare che la variabile deve essere considerata privata. Ancora una volta, ciò non impedisce a qualcuno di accedervi effettivamente.
igon,

14
Guido ha risposto a questa domanda : "La ragione principale per rendere (quasi) tutto ciò che è stato scoperto era il debug: quando si esegue il debug spesso è necessario superare le astrazioni" - L'ho aggiunto come commento perché è troppo tardi - troppe risposte.
Peter M. - sta per Monica il

1
Se segui il criterio "impedisce l'accesso deliberato", la maggior parte delle lingue OOP non supporta i membri veramente privati. Ad esempio in C ++ hai accesso non elaborato alla memoria e in codice attendibile C # puoi usare la riflessione privata.
CodesInChaos,

207

Esempio di funzione privata

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 Ovviamente non funziona in classe, ma devi solo definire una funzione personalizzata: def foo(self): self.private_function()
Casey Kuball

161
Nel caso non fosse chiaro: non farlo mai in codice reale;)
Sudo Bash

10
@ThorSummoner O semplicemente function_call.startswith('self.').
nyuszika7h,

13
inspect.stack()[1][4][0].strip()<- che cosa sono quei numeri magici 1, 4 e 0?
Akhy il

5
Questo può essere sconfitto abbastanza facilmente facendo self = MyClass(); self.private_function()e fallisce quando viene chiamato usando x = self.private_function()all'interno di un metodo.
Sarà il

171

Quando sono arrivato per la prima volta da Java a Python, odiavo questo. Mi ha spaventato a morte.

Oggi potrebbe essere l'unica cosa che amo di più di Python.

Adoro stare su una piattaforma, dove le persone si fidano l'una dell'altra e non si sentono come se dovessero costruire muri impenetrabili attorno al loro codice. In linguaggi fortemente incapsulati, se un'API ha un bug e hai capito cosa non va, potresti non essere ancora in grado di aggirare il problema perché il metodo necessario è privato. In Python l'atteggiamento è: "sicuro". Se pensi di capire la situazione, forse l'hai persino letta, allora tutto ciò che possiamo dire è "buona fortuna!".

Ricorda, l'incapsulamento non è nemmeno debolmente correlato alla "sicurezza", o alla custodia dei bambini dal prato. È solo un altro modello che dovrebbe essere usato per rendere più semplice la comprensione di una base di codice.


36
@CamJackson Javascript è il tuo esempio ?? L'unico linguaggio ampiamente utilizzato che ha eredità basata su prototybe e un linguaggio che favorisce la programmazione funzionale? Penso che JS sia molto più difficile da imparare rispetto alla maggior parte delle altre lingue, poiché richiede diversi passaggi ortogonali rispetto alla OOP tradizionale. Non che questo impedisca agli idioti di scrivere JS, semplicemente non lo sanno;)
K.Steff

23
Le API sono in realtà un ottimo esempio del perché l'incapsulamento è importante e quando si preferirebbero i metodi privati. Un metodo destinato a essere privato può scomparire, cambiare firma o, peggio ancora, cambiare comportamento - tutti senza preavviso - su qualsiasi nuova versione successiva. Il tuo membro del team intelligente e adulto ricorderà davvero di aver avuto accesso a un metodo destinato a essere privato tra un anno a partire da quando effettuerai l'aggiornamento? Lavorerà ancora più lì?
Einnocent,

2
Non sono d'accordo con l'argomento. Nel codice di produzione, molto probabilmente non userei mai un'API che ha un bug che mi fa cambiare membri pubblici per farlo "funzionare". Un'API dovrebbe funzionare. In caso contrario, vorrei presentare una segnalazione di bug o creare la stessa API da solo. Non mi piace la filosofia e non amo molto Python, anche se la sua sintassi rende divertente scrivere script più piccoli in ...
Yngve Sneen Lindal,

4
Java ha Method.setAccessible e Field.setAccessible. Anche spaventoso?
Tony,

15
L'applicazione in Java e C ++ non è dovuta al fatto che Java non diffida dell'utente mentre lo fa Python. È perché il compilatore e / o vm possono fare varie ipotesi quando si occupano di come vanno le sue attività se conoscono queste informazioni, per esempio il C ++ può saltare un intero livello di indiretta usando le normali chiamate C anzichè le chiamate virtuali, e che è importante quando lavori su oggetti ad alte prestazioni o alta precisione. Python per sua natura non può davvero fare buon uso delle informazioni senza comprometterne il dinamismo. Entrambe le lingue puntano su cose diverse, quindi nessuna delle due è "sbagliata"
Shayne,

144

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

A rigor di termini, i metodi privati ​​sono accessibili al di fuori della loro classe, semplicemente non facilmente accessibili. Niente in Python è veramente privato; internamente, i nomi di metodi e attributi privati ​​sono alterati e non frastagliati al volo per farli sembrare inaccessibili con i loro nomi. Puoi accedere al metodo __parse della classe MP3FileInfo con il nome _MP3FileInfo__parse. Riconosci che questo è interessante, quindi prometti di non farlo mai, mai nel vero codice. I metodi privati ​​sono privati ​​per una ragione, ma come molte altre cose in Python, la loro riservatezza è in definitiva una questione di convenzione, non di forza.


202
o come diceva Guido van Rossum: "siamo tutti adulti".

35
-1: questo è solo sbagliato. I doppi trattini bassi non devono mai essere usati come privati ​​in primo luogo. La risposta di Alya in basso racconta la vera intenzione di sintonizzare il nome mangling. La vera convenzione è un singolo carattere di sottolineatura.
nosklo,

2
Prova con un solo trattino basso e vedrai il risultato che otterrai. @nosklo
Billal Begueradj,

93

La frase comunemente usata è "siamo tutti adulti consenzienti qui". Anticipando un singolo carattere di sottolineatura (non esporre) o doppio carattere di sottolineatura (nascondi), stai dicendo all'utente della tua classe che in qualche modo intendi che il membro sia "privato". Tuttavia, ti fidi di tutti gli altri di comportarsi in modo responsabile e di rispettarlo, a meno che non abbiano un motivo convincente di non farlo (ad esempio debugger, completamento del codice).

Se devi davvero avere qualcosa di privato, puoi implementarlo in un'estensione (ad es. In C per CPython). Nella maggior parte dei casi, tuttavia, impari semplicemente il modo pitone di fare le cose.


quindi c'è una sorta di protocollo wrapper che dovrei usare per accedere a una variabile protetta?
intuito

3
Non ci sono variabili "protette" più di quanto non ci siano "private". Se vuoi accedere a un attributo che inizia con un carattere di sottolineatura, puoi semplicemente farlo (ma nota che l'autore lo scoraggia). Se devi accedere a un attributo che inizia con un doppio trattino di sottolineatura, puoi fare il nome a rovesciando te stesso, ma quasi sicuramente non vuoi farlo.
Tony Meyer,

33

Non è che tu non riesca assolutamente a evitare la riservatezza dei membri in nessun linguaggio (aritmetica del puntatore in C ++, Reflections in .NET / Java).

Il punto è che viene visualizzato un errore se si tenta di chiamare il metodo privato per errore. Ma se vuoi spararti al piede, vai avanti e fallo.

Modifica: non provi a proteggere le tue cose con l'incapsulamento OO, vero?


2
Affatto. Sto semplicemente sottolineando che è strano dare allo sviluppatore un modo semplice e, secondo me, magico, di accedere a proprietà "private".
Willurd,

2
Sì, ho appena provato a illustrare il punto. Rendendolo privato dice semplicemente "non dovresti accedervi direttamente" facendo lamentare il compilatore. Ma uno vuole davvero farlo davvero che può. Ma sì, è più facile in Python che nella maggior parte delle altre lingue.
Massimiliano,

7
In Java, in realtà puoi proteggere cose tramite incapsulamento, ma ciò richiede di essere intelligente ed eseguire il codice non attendibile in un SecurityManager e fare molta attenzione. Anche Oracle a volte sbaglia.
Antimonio

12

La class.__stuffconvenzione di denominazione fa sapere al programmatore che non è destinato ad accedere __stuffdall'esterno. Il nome mangling rende improbabile che qualcuno lo faccia per caso.

È vero, puoi ancora aggirare questo, è ancora più facile che in altre lingue (che BTW ti consente anche di fare questo), ma nessun programmatore Python lo farebbe se si preoccupasse dell'incapsulamento.


12

Un comportamento simile esiste quando i nomi degli attributi del modulo iniziano con un singolo trattino basso (ad es. _Foo).

Gli attributi del modulo nominati come tali non verranno copiati in un modulo di importazione quando si utilizza il from*metodo, ad esempio:

from bar import *

Tuttavia, questa è una convenzione e non un vincolo linguistico. Questi non sono attributi privati; possono essere referenziati e manipolati da qualsiasi importatore. Alcuni sostengono che per questo motivo, Python non può implementare l'incapsulamento reale.


12

È solo una di quelle scelte di design del linguaggio. Ad un certo livello sono giustificati. Lo fanno quindi devi andare abbastanza lontano dal tuo modo di provare a chiamare il metodo e se ne hai davvero bisogno, devi avere una ragione abbastanza buona!

Hook di debug e test vengono in mente come possibili applicazioni, utilizzate in modo responsabile ovviamente.


4

Con Python 3.4 questo è il comportamento:

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

Si noti che le regole di demolizione sono progettate principalmente per evitare incidenti; è ancora possibile accedere o modificare una variabile considerata privata. Questo può essere utile anche in circostanze speciali, come nel debugger.

Anche se la domanda è vecchia, spero che il mio frammento possa essere utile.


2

La preoccupazione più importante per i metodi e gli attributi privati ​​è di dire agli sviluppatori di non chiamarlo al di fuori della classe e questo è l'incapsulamento. si può fraintendere la sicurezza dall'incapsulamento. quando uno usa deliberatamente la sintassi come quella (qui sotto) che hai menzionato, non vuoi incapsulare.

obj._MyClass__myPrivateMethod()

Sono migrato da C # e all'inizio è stato strano anche per me, ma dopo un po 'sono arrivato all'idea che solo il modo in cui i progettisti di codice Python pensano a OOP è diverso.


1

Perché i metodi "privati" di Python non sono in realtà privati?

A quanto ho capito, non possono essere privati. Come si può far rispettare la privacy?

La risposta ovvia è che "i membri privati ​​sono accessibili solo attraverso self", ma che non funzionerebbe - selfnon è speciale in Python, non è altro che un nome comunemente usato per il primo parametro di una funzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.