Mangling del nome Python


109

In altre lingue, una linea guida generale che aiuta a produrre un codice migliore è sempre rendere tutto il più nascosto possibile. In caso di dubbi sul fatto che una variabile debba essere privata o protetta, è meglio utilizzare private.

Lo stesso vale per Python? Dovrei usare due trattini bassi iniziali su tutto all'inizio e renderli solo meno nascosti (solo un trattino basso) in quanto ne ho bisogno?

Se la convenzione prevede di utilizzare un solo trattino basso, mi piacerebbe conoscere anche la logica.

Ecco un commento che ho lasciato sulla risposta di JBernardo . Spiega perché ho posto questa domanda e anche perché mi piacerebbe sapere perché Python è diverso dagli altri linguaggi:

Vengo da lingue che ti insegnano a pensare che tutto dovrebbe essere pubblico solo se necessario e non di più. Il ragionamento è che questo ridurrà le dipendenze e renderà il codice più sicuro da modificare. Il modo in cui Python fa le cose al contrario - partendo dal pubblico e andando verso il nascosto - è strano per me.

Risposte:


182

In caso di dubbio, lascialo "pubblico" - Voglio dire, non aggiungere nulla per oscurare il nome del tuo attributo. Se hai una classe con qualche valore interno, non preoccupartene. Invece di scrivere:

class Stack(object):

    def __init__(self):
        self.__storage = [] # Too uptight

    def push(self, value):
        self.__storage.append(value)

scrivi questo per impostazione predefinita:

class Stack(object):

    def __init__(self):
        self.storage = [] # No mangling

    def push(self, value):
        self.storage.append(value)

Questo è sicuramente un modo controverso di fare le cose. I neofiti di Python lo odiano e anche alcuni vecchi ragazzi di Python disprezzano questa impostazione predefinita, ma è comunque l'impostazione predefinita, quindi ti consiglio davvero di seguirla, anche se ti senti a disagio.

Se vuoi davvero inviare il messaggio "Non posso toccarlo!" per i tuoi utenti, il modo usuale è far precedere la variabile con un trattino basso. Questa è solo una convenzione, ma le persone la capiscono e si prendono una doppia cura quando si tratta di cose del genere:

class Stack(object):

    def __init__(self):
        self._storage = [] # This is ok but pythonistas use it to be relaxed about it

    def push(self, value):
        self._storage.append(value)

Questo può essere utile anche per evitare conflitti tra nomi di proprietà e nomi di attributi:

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

E il doppio trattino basso? Ebbene, la magia del doppio trattino basso viene utilizzata principalmente per evitare il sovraccarico accidentale dei metodi e conflitti di nome con gli attributi delle superclassi . Può essere molto utile se scrivi una classe che dovrebbe essere estesa molte volte.

Se vuoi usarlo per altri scopi, puoi, ma non è né usuale né consigliato.

EDIT : Perché è così? Bene, il solito stile Python non enfatizza il fatto di rendere le cose private, al contrario! Ci sono molte ragioni per questo - la maggior parte controversa ... Vediamone alcune.

Python ha proprietà

La maggior parte delle lingue OO oggi usa l'approccio opposto: ciò che non dovrebbe essere usato non dovrebbe essere visibile, quindi gli attributi dovrebbero essere privati. Teoricamente, ciò produrrebbe classi più gestibili e meno accoppiate, perché nessuno cambierebbe i valori all'interno degli oggetti in modo sconsiderato.

Tuttavia, non è così semplice. Ad esempio, le classi Java hanno molti attributi e getter che ottengono solo i valori e setter che impostano semplicemente i valori. Sono necessarie, diciamo, sette righe di codice per dichiarare un singolo attributo, che un programmatore Python direbbe che è inutilmente complesso. Inoltre, in pratica, devi solo scrivere tutto questo codice per ottenere un campo pubblico, poiché puoi modificarne il valore usando getter e setter.

Allora perché seguire questa politica privata per impostazione predefinita? Rendi pubblici i tuoi attributi per impostazione predefinita. Ovviamente, questo è problematico in Java, perché se decidi di aggiungere una convalida al tuo attributo, ti richiederebbe di cambiare tutto

person.age = age;

nel tuo codice per, diciamo,

person.setAge(age);

setAge() essere:

public void setAge(int age) {
    if (age >= 0) {
        this.age = age;
    } else {
        this.age = 0;
    }
}

Quindi in Java (e in altri linguaggi) l'impostazione predefinita è usare comunque getter e setter, perché possono essere fastidiosi da scrivere ma possono farti risparmiare molto tempo se ti trovi nella situazione che ho descritto.

Tuttavia, non è necessario farlo in Python, poiché Python ha proprietà. Se hai questa classe:

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self.age = age

e poi decidi di convalidare le età, non è necessario modificare i person.age = agepezzi del tuo codice. Basta aggiungere una proprietà (come mostrato di seguito)

 class Person(object):
     def __init__(self, name, age):
         self.name = name
         self._age = age if age >= 0 else 0

     @property
     def age(self):
         return self._age

     @age.setter
     def age(self, age):
         if age >= 0:
             self._age = age
         else:
             self._age  = 0

Se puoi farlo e continuare a usarlo person.age = age, perché dovresti aggiungere campi privati ​​e getter e setter?

(Inoltre, vedere Python non è Java e questo articolo sui danni derivanti dall'uso di getter e setter .).

Tutto è comunque visibile e cercare di nascondersi complica il tuo lavoro

Anche nelle lingue in cui sono presenti attributi privati, è possibile accedervi tramite una sorta di libreria di riflessione / introspezione. E le persone lo fanno molto, in quadri e per risolvere bisogni urgenti. Il problema è che le librerie di introspezione sono solo un modo difficile per fare ciò che potresti fare con gli attributi pubblici.

Poiché Python è un linguaggio molto dinamico, è semplicemente controproducente aggiungere questo peso alle tue classi.

Il problema non è essere possibile vedere, è necessario vedere

Per un Pythonista, l'incapsulamento non è l'incapacità di vedere l'interno delle classi, ma la possibilità di evitare di guardarlo. Quello che voglio dire è che l'incapsulamento è la proprietà di un componente che ne consente l'utilizzo senza che l'utente si preoccupi dei dettagli interni. Se puoi usare un componente senza preoccuparti della sua implementazione, allora è incapsulato (secondo l'opinione di un programmatore Python).

Ora, se hai scritto la tua classe in questo modo puoi usarla senza dover pensare ai dettagli di implementazione, non ci sono problemi se vuoi guardare dentro la classe per qualche motivo. Il punto è: la tua API dovrebbe essere buona e il resto sono dettagli.

L'ha detto Guido

Ebbene, questo non è controverso: lo ha detto, in realtà . (Cerca "kimono aperto".)

Questa è cultura

Sì, ci sono alcune ragioni, ma nessuna ragione critica. Questo è principalmente un aspetto culturale della programmazione in Python. Francamente, potrebbe essere anche il contrario, ma non lo è. Inoltre, potresti facilmente chiedere il contrario: perché alcune lingue utilizzano attributi privati ​​per impostazione predefinita? Per lo stesso motivo principale della pratica Python: perché è la cultura di questi linguaggi, e ogni scelta ha vantaggi e svantaggi.

Poiché esiste già questa cultura, ti consigliamo di seguirla. Altrimenti, sarai infastidito dai programmatori Python che ti dicono di rimuovere __dal codice quando fai una domanda in Stack Overflow :)


1. L'incapsulamento serve a proteggere le invarianti di classe. Non nascondere dettagli non necessari al mondo esterno perché sarebbe un fastidio. 2. "Il punto è: la tua API dovrebbe essere buona e il resto sono dettagli." Questo è vero. E gli attributi pubblici fanno parte della tua API. Inoltre, a volte i setter pubblici sono appropriati (riguardo alle invarianti di classe) ea volte no. Un'API che ha setter pubblici che non dovrebbero essere pubblici (rischio di violazione di invarianti) è una cattiva API. Ciò significa che devi comunque pensare alla visibilità di ogni setter e avere un "default" significa meno.
Giove

21

Primo: qual è il nome che altera?

La modifica del nome viene invocata quando ci si trova in una definizione di classe e si utilizza __any_nameo __any_name_, ovvero, due (o più) trattini bassi iniziali e al massimo un trattino basso finale.

class Demo:
    __any_name = "__any_name"
    __any_other_name_ = "__any_other_name_"

E adesso:

>>> [n for n in dir(Demo) if 'any' in n]
['_Demo__any_name', '_Demo__any_other_name_']
>>> Demo._Demo__any_name
'__any_name'
>>> Demo._Demo__any_other_name_
'__any_other_name_'

In caso di dubbio, cosa fare?

L'uso apparente è quello di impedire ai sottoclassi di utilizzare un attributo utilizzato dalla classe.

Un potenziale valore consiste nell'evitare collisioni di nomi con le sottoclassi che desiderano sovrascrivere il comportamento, in modo che la funzionalità della classe padre continui a funzionare come previsto. Tuttavia, l' esempio nella documentazione di Python non è sostituibile con Liskov e non mi vengono in mente esempi in cui l'ho trovato utile.

Gli svantaggi sono che aumenta il carico cognitivo per la lettura e la comprensione di una base di codice, e specialmente durante il debug in cui si vede il doppio trattino basso nel codice sorgente e un nome alterato nel debugger.

Il mio approccio personale è evitarlo intenzionalmente. Lavoro su una base di codice molto ampia. I rari usi di esso sporgono come un pollice irritato e non sembrano giustificati.

Devi esserne consapevole in modo da saperlo quando lo vedi.

PEP 8

PEP 8 , la guida allo stile della libreria standard di Python, attualmente dice (abbreviato):

C'è qualche controversia sull'uso di __names .

Se la tua classe è destinata a essere sottoclasse e hai attributi che non vuoi che le sottoclassi utilizzino, valuta la possibilità di denominarle con doppi trattini bassi iniziali e senza trattini bassi finali.

  1. Nota che solo il nome della classe semplice viene utilizzato nel nome alterato, quindi se una sottoclasse sceglie sia lo stesso nome di classe che il nome di attributo, puoi comunque ottenere collisioni di nomi.

  2. La modifica del nome può rendere alcuni usi, come il debug e __getattr__(), meno conveniente. Tuttavia, l'algoritmo di modifica del nome è ben documentato e facile da eseguire manualmente.

  3. Non a tutti piace alterare i nomi. Cerca di bilanciare la necessità di evitare conflitti di nomi accidentali con il potenziale utilizzo da parte di chiamanti esperti.

Come funziona?

Se anteponi due trattini bassi (senza terminare i doppi trattini bassi) in una definizione di classe, il nome verrà alterato e sull'oggetto verrà anteposto un trattino basso seguito dal nome della classe:

>>> class Foo(object):
...     __foobar = None
...     _foobaz = None
...     __fooquux__ = None
... 
>>> [name for name in dir(Foo) if 'foo' in name]
['_Foo__foobar', '__fooquux__', '_foobaz']

Nota che i nomi verranno alterati solo quando viene analizzata la definizione della classe:

>>> Foo.__test = None
>>> Foo.__test
>>> Foo._Foo__test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute '_Foo__test'

Inoltre, i nuovi utenti di Python a volte hanno difficoltà a capire cosa sta succedendo quando non possono accedere manualmente a un nome che vedono definito in una definizione di classe. Questo non è un motivo forte contro di esso, ma è qualcosa da considerare se hai un pubblico che apprende.

Un carattere di sottolineatura?

Se la convenzione prevede di utilizzare un solo trattino basso, mi piacerebbe conoscere anche la logica.

Quando la mia intenzione è che gli utenti tengano le mani lontane da un attributo, tendo a usare solo un carattere di sottolineatura, ma è perché nel mio modello mentale, i sottoclassisti avrebbero accesso al nome (che hanno sempre, poiché possono facilmente individuare il nome alterato comunque).

Se dovessi rivedere il codice che utilizza il __prefisso, chiederei perché stanno invocando il nome alterato e se non potevano fare altrettanto bene con un singolo trattino basso, tenendo presente che se i sottoclassi scelgono gli stessi nomi per la classe e attributo di classe ci sarà una collisione di nomi nonostante ciò.


15

Non direi che la pratica produce un codice migliore. I modificatori di visibilità ti distraggono solo dall'attività in corso e, come effetto collaterale, costringono la tua interfaccia a essere utilizzata come previsto. In generale, l'applicazione della visibilità impedisce ai programmatori di rovinare le cose se non hanno letto correttamente la documentazione.

Una soluzione di gran lunga migliore è il percorso che Python incoraggia: le tue classi e variabili dovrebbero essere ben documentate e il loro comportamento chiaro. La fonte dovrebbe essere disponibile. Questo è un modo molto più estensibile e affidabile per scrivere codice.

La mia strategia in Python è questa:

  1. Basta scrivere la dannata cosa, non fare supposizioni su come i tuoi dati dovrebbero essere protetti. Ciò presuppone che tu scriva per creare le interfacce ideali per i tuoi problemi.
  2. Usa un trattino basso iniziale per cose che probabilmente non saranno usate esternamente e non fanno parte della normale interfaccia "codice client".
  3. Usa il doppio trattino basso solo per cose che sono puramente utili all'interno della classe, o causeranno danni considerevoli se scoperte accidentalmente.

Soprattutto, dovrebbe essere chiaro cosa fa tutto. Documentalo se qualcun altro lo utilizzerà. Documentalo se vuoi che sia utile tra un anno.

Come nota a margine, dovresti effettivamente andare con protected in quelle altre lingue: non sai mai che la tua classe potrebbe essere ereditata in seguito e per cosa potrebbe essere usata. È meglio proteggere solo quelle variabili che si è certi non possano o non debbano essere usate da codice esterno.


9

Non dovresti iniziare con dati privati ​​e renderli pubblici se necessario. Piuttosto, dovresti iniziare a capire l'interfaccia del tuo oggetto. Cioè dovresti iniziare a capire cosa vede il mondo (le cose pubbliche) e poi capire quali cose private sono necessarie affinché ciò accada.

Altre lingue rendono difficile rendere privato ciò che una volta era pubblico. Cioè interromperò un sacco di codice se rendo la mia variabile privata o protetta. Ma con le proprietà in Python questo non è il caso. Piuttosto, posso mantenere la stessa interfaccia anche riorganizzando i dati interni.

La differenza tra _ e __ è che python fa effettivamente un tentativo di applicare quest'ultimo. Certo, non si sforza davvero, ma lo rende difficile. Avere _ semplicemente dice agli altri programmatori qual è l'intenzione, sono liberi di ignorare a loro rischio e pericolo. Ma ignorare questa regola a volte è utile. Gli esempi includono il debug, gli hack temporanei e il lavoro con codice di terze parti che non era destinato a essere utilizzato nel modo in cui lo usi.


6

Ci sono già molte buone risposte a questo, ma ne offrirò un'altra. Questa è anche in parte una risposta alle persone che continuano a dire che il doppio trattino basso non è privato (lo è davvero).

Se guardi Java / C #, entrambi hanno privato / protetto / pubblico. Tutti questi sono costrutti in fase di compilazione . Sono applicati solo al momento della compilazione. Se dovessi usare la reflection in Java / C #, potresti accedere facilmente al metodo privato.

Ora ogni volta che chiami una funzione in Python, stai intrinsecamente usando la riflessione. Questi pezzi di codice sono gli stessi in Python.

lst = []
lst.append(1)
getattr(lst, 'append')(1)

La sintassi "punto" è solo zucchero sintattico per quest'ultimo pezzo di codice. Principalmente perché usare getattr è già brutto con una sola chiamata di funzione. Da lì diventa solo peggio.

Quindi con quello, non è possibile esserci una versione Java / C # di private, poiché Python non compila il codice. Java e C # non possono verificare se una funzione è privata o pubblica in fase di esecuzione, poiché tale informazione è scomparsa (e non ha alcuna conoscenza da dove viene chiamata la funzione).

Ora, con queste informazioni, la modifica del nome del doppio trattino basso ha più senso per raggiungere la "riservatezza". Ora, quando una funzione viene chiamata dall'istanza "self" e nota che inizia con "__", esegue semplicemente la modifica del nome proprio lì. È solo più zucchero sintattico. Questo zucchero sintattico consente l'equivalente di "privato" in un linguaggio che utilizza solo la riflessione per l'accesso ai membri dei dati.

Disclaimer: non ho mai sentito nessuno dello sviluppo di Python dire qualcosa del genere. La vera ragione per la mancanza di "privato" è culturale, ma noterai anche che la maggior parte delle lingue di scripting / interpretate non ha un privato. Un privato rigorosamente applicabile non è pratico per niente tranne che per il tempo di compilazione.


4

Primo: perché vuoi nascondere i tuoi dati? Perché questo è così importante?

La maggior parte delle volte non vuoi farlo davvero, ma lo fai perché lo fanno gli altri.

Se davvero davvero non vuoi che le persone usino qualcosa, aggiungine uno trattino basso davanti ad esso. Ecco fatto ... I Pythonisti sanno che non è garantito che le cose con un carattere di sottolineatura funzionino ogni volta e potrebbero cambiare senza che tu lo sappia.

Questo è il modo in cui viviamo e ci sta bene.

L'uso di due trattini bassi renderà la tua classe così cattiva da sottoclasse che nemmeno tu vorrai lavorare in quel modo.


2
Hai omesso il motivo per cui il doppio trattino basso è dannoso per la sottoclasse ... questo migliorerebbe la tua risposta.
Matt Joiner

2
Dato che i doppi trattini bassi servono solo a prevenire collisioni di nomi con le sottoclassi (come un modo per dire "senza mani" ai sottoclassi), non vedo come la modifica dei nomi crei un problema.
Aaron Hall

4

La risposta scelta fa un buon lavoro nello spiegare come le proprietà rimuovano la necessità di attributi privati , ma aggiungerei anche che le funzioni a livello di modulo rimuovono la necessità di metodi privati .

Se trasformi un metodo in una funzione a livello di modulo, rimuovi l'opportunità per le sottoclassi di sovrascriverlo. Spostare alcune funzionalità a livello di modulo è più Pythonic che cercare di nascondere metodi con alterazione del nome.


3

Il seguente frammento di codice spiegherà tutti i diversi casi:

  • due trattini bassi iniziali (__a)
  • singolo trattino basso iniziale (_a)
  • nessun trattino basso (a)

    class Test:
    
    def __init__(self):
        self.__a = 'test1'
        self._a = 'test2'
        self.a = 'test3'
    
    def change_value(self,value):
        self.__a = value
        return self.__a

stampa di tutti gli attributi validi dell'oggetto di prova

testObj1 = Test()
valid_attributes = dir(testObj1)
print valid_attributes

['_Test__a', '__doc__', '__init__', '__module__', '_a', 'a', 
'change_value']

Qui, puoi vedere che il nome di __a è stato cambiato in _Test__a per evitare che questa variabile venga sovrascritta da una qualsiasi delle sottoclassi. Questo concetto è noto come "Name Mangling" in python. Puoi accedere a questo in questo modo:

testObj2 = Test()
print testObj2._Test__a

test1

Allo stesso modo, nel caso di _a, la variabile serve solo a notificare allo sviluppatore che dovrebbe essere usata come variabile interna di quella classe, l'interprete python non farà nulla anche se vi accedete, ma non è una buona pratica.

testObj3 = Test()
print testObj3._a

test2

una variabile può essere accessibile da qualsiasi luogo è come una variabile di classe pubblica.

testObj4 = Test()
print testObj4.a

test3

Spero che la risposta ti abbia aiutato :)


2

A prima vista dovrebbe essere lo stesso degli altri linguaggi (sotto "altro" intendo Java o C ++), ma non lo è.

In Java hai reso private tutte le variabili che non dovrebbero essere accessibili dall'esterno. Allo stesso tempo in Python non è possibile ottenere questo risultato poiché non esiste "privacy" (come dice uno dei principi di Python - "Siamo tutti adulti"). Quindi il doppio trattino basso significa solo "Ragazzi, non usate direttamente questo campo". Lo stesso significato ha un solo carattere di sottolineatura, che nello stesso tempo non causa alcun mal di testa quando si deve ereditare dalla classe considerata (solo un esempio di possibile problema causato dal doppio trattino basso).

Quindi, ti consiglio di utilizzare un singolo trattino basso per impostazione predefinita per i membri "privati".


Usa il doppio trattino basso per "privato" e il carattere di sottolineatura singolo per "protetto". Di solito, le persone usano solo un singolo trattino basso per ogni cosa (il doppio trattino basso aiuterà a rafforzare la privacy, che di solito è contro lo stile Python).
Jonathan Sternberg

1
Ma questo non rende due trattini bassi simili a privato e uno simile a protetto? Perché non iniziare semplicemente da "privato"?
Paul Manta

@ Paul No, non è così. Non esiste un privato in Python e non dovresti provare a raggiungerlo.
Roman Bodnarchuk,

@Roman Concettualmente parlando ... Notare le virgolette intorno a "private".
Paul Manta

1

"In caso di dubbi sul fatto che una variabile debba essere privata o protetta, è meglio scegliere private". - sì, lo stesso vale in Python.

Alcune risposte qui dicono di "convenzioni", ma non fornire i collegamenti a tali convenzioni. La guida autorevole per Python, PEP 8 afferma esplicitamente:

In caso di dubbio, scegli non pubblico; è più facile renderlo pubblico più tardi che rendere non pubblico un attributo pubblico.

La distinzione tra pubblico e privato e la manipolazione dei nomi in Python sono stati considerati in altre risposte. Dallo stesso collegamento,

Non usiamo il termine "privato" qui, poiché nessun attributo è realmente privato in Python (senza una quantità di lavoro generalmente non necessaria).

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.