Qual è il significato del carattere di sottolineatura singolo e doppio prima del nome di un oggetto?


1292

Qualcuno può spiegare l'esatto significato di avere i caratteri di sottolineatura iniziali prima del nome di un oggetto in Python e la differenza tra entrambi?

Inoltre, quel significato rimane lo stesso se l'oggetto in questione è una variabile, una funzione, un metodo, ecc.?


2
Una grande risposta breve da un altro thread: stackoverflow.com/a/8689983/911945
Anton Tarasenko

Risposte:


1156

Sottotitolo singolo

I nomi, in una classe, con un carattere di sottolineatura iniziale indicano semplicemente ad altri programmatori che l'attributo o il metodo sono intesi come privati. Tuttavia, non viene fatto nulla di speciale con il nome stesso.

Per citare PEP-8 :

_single_leading_underscore: indicatore di "uso interno" debole. Ad esempio from M import *, non importa oggetti il ​​cui nome inizia con un trattino basso.

Doppio carattere di sottolineatura (modifica del nome)

Dai documenti di Python :

Qualsiasi identificatore del modulo __spam(almeno due caratteri di sottolineatura iniziali, al massimo un carattere di sottolineatura finale) viene sostituito testualmente con _classname__spam, dove si classnametrova il nome della classe corrente con carattere di sottolineatura principale rimosso. Questa modifica viene effettuata indipendentemente dalla posizione sintattica dell'identificatore, quindi può essere utilizzata per definire istanze di classe privata e variabili di classe, metodi, variabili archiviate in globi e persino variabili archiviate in istanze. privato di questa classe su istanze di altre classi.

E un avviso dalla stessa pagina:

La modifica del nome ha lo scopo di fornire alle classi un modo semplice per definire variabili e metodi di istanza "privati", senza doversi preoccupare delle variabili di istanza definite dalle classi derivate, o confondere le variabili di istanza con un codice esterno alla classe. Si noti che le regole di demolizione sono progettate principalmente per evitare incidenti; è ancora possibile per un'anima determinata accedere o modificare una variabile considerata privata.

Esempio

>>> class MyClass():
...     def __init__(self):
...             self.__superprivate = "Hello"
...             self._semiprivate = ", world!"
...
>>> mc = MyClass()
>>> print mc.__superprivate
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
>>> print mc._semiprivate
, world!
>>> print mc.__dict__
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}

17
Cosa succede se esiste un nome di variabile dichiarato con 2 caratteri di sottolineatura che non è nella classe? È solo una variabile normale, giusto?
Dhruv Ramani,

5
qual è il significato di un __doppio trattino basso come nome variabile? comea, __ = foo()
AJ il

104
Questa risposta è estremamente fuorviante, in quanto porta il lettore a credere che dunderscore sia usato per rendere gli attributi di istanza "superprivati". Questo non è il caso, come spiegato qui da Raymond Hettinger, che afferma esplicitamente che dunderscore è usato in modo errato per contrassegnare i membri come privati, mentre è stato progettato per essere l'opposto del privato.
Markus Meskanen,

13
@MarkusMeskanen Non sono d'accordo, la risposta afferma esplicitamente l'uso di un dunderscore per creare istanze di variabili e metodi di classe privata. Mentre il dunderscore è stato progettato per rendere questi metodi e variabili facilmente sovrascritti da sottoclassi (rendendoli pubblici), l'uso di un dunderscore conserva un'istanza privata da usare all'interno di quella classe.
Arewm,

6
@MarkusMeskanen: La libertà è per le sottoclassi di usare gli stessi nomi della superclasse senza ostruire la superclasse - in altre parole, i nomi secondari delle superclassi diventano privati ​​a se stessi.
Ethan Furman,

311

Finora risposte eccellenti, ma mancano alcuni bocconcini. Un singolo carattere di sottolineatura iniziale non è esattamente solo una convenzione: se si utilizza from foobar import *e il modulo foobarnon definisce un __all__elenco, i nomi importati dal modulo non includono quelli con un carattere di sottolineatura iniziale. Diciamo che è principalmente una convenzione, poiché questo caso è un angolo piuttosto oscuro ;-).

La convenzione di carattere di sottolineatura viene ampiamente utilizzata non solo per i nomi privati , ma anche per quelli che il C ++ chiamerebbe quelli protetti , ad esempio nomi di metodi che sono completamente intesi per essere sovrascritti da sottoclassi (anche quelli che devono essere sovrascritti dal la classe base loro raise NotImplementedError! -) sono spesso nomi di sottolineatura a capo singolo per indicare al codice usando istanze di quella classe (o sottoclassi) che tali metodi non sono pensati per essere chiamati direttamente.

Ad esempio, per creare una coda thread-safe con una disciplina di accodamento diversa rispetto a FIFO, si importa una Coda, sottoclasse Queue.Queue e si sovrascrive su metodi come _gete _put; "codice client" non chiama mai quei metodi ("hook"), ma piuttosto i metodi pubblici ("organizzativi") come pute get(questo è noto come modello di progettazione del metodo Template - vedi ad esempio qui per una presentazione interessante basata su un video di un mio discorso sull'argomento, con l'aggiunta di sinossi della trascrizione).

Modifica: i collegamenti video nella descrizione dei discorsi ora sono interrotti. Puoi trovare i primi due video qui e qui .


1
Quindi, come si decide se utilizzare _var_nameo utilizzare var_name+ escludendolo da __all__?
endolith

3
@endolith Usa il trattino basso per segnalare al lettore del tuo codice che probabilmente non dovrebbero usarlo (ad es. perché potresti cambiarlo nella versione 2.0 o anche 1.1); usa esplicito __all__ogni volta che vuoi rendere from spam import *amichevole il modulo (incluso nell'interprete interattivo). Quindi la maggior parte delle volte, la risposta è entrambe .
abarnert,

@AlexMartelli Questa regola relativa all'importazione è discussa legalmente da qualche parte nei documenti o altrove?
Vicrobot,

1
Mi piace l'analogia C ++. In primo luogo, non mi piace quando la gente chiama il _ privato . Evidentemente sto parlando di analogie, dal momento che nulla è veramente privato in Python. Quando mi immergo nella semantica, direi che possiamo legare _a Java protetto poiché procurato in Java significa "classi derivate e / o all'interno dello stesso pacchetto". Sostituisci il pacchetto con il modulo poiché PEP8 ci dice già che _non è solo una convenzione quando si parla di *importazioni e il gioco è fatto. E sicuramente __sarebbe equivalente al privato di Java quando si parla di identificatori all'interno di una classe.
Marius Mucenicu,

2
Mentre una risposta decente, è anche fortemente auto promozionale.
Dev web ibrido

299

__foo__: questa è solo una convenzione, un modo per il sistema Python di usare nomi che non entreranno in conflitto con i nomi degli utenti.

_foo: questa è solo una convenzione, un modo per il programmatore di indicare che la variabile è privata (qualunque cosa ciò significhi in Python).

__foo: questo ha un significato reale: l'interprete sostituisce questo nome con _classname__foo un modo per assicurarsi che il nome non si sovrapponga a un nome simile in un'altra classe.

Nessun'altra forma di trattini bassi ha significato nel mondo Python.

Non c'è alcuna differenza tra classe, variabile, globale, ecc. In queste convenzioni.


6
Mi sono appena imbattuto __fooe curioso. Come può sovrapporsi con nomi di metodi simili con altre classi? Voglio dire, devi ancora accedervi come instance.__foo()(se non fosse rinominato dall'interprete), giusto?
Bibhas Debnath

81
Questo ragazzo afferma che from module import *non importa oggetti con prefisso di sottolineatura. Pertanto, _fooè più di una semplice convenzione.
dotancohen,

4
@Bibhas: se la classe Bsubclasse la classe A, ed entrambe implementano foo(), B.foo()sovrascrive l' .foo()ereditato da A. Un'istanza di Bsarà in grado di accedere B.foo(), tranne tramite super(B).foo().
naught101

3
Per i __dunder__nomi, le invocazioni implicite saltano il dizionario dell'istanza, quindi in alcuni casi è forse un po 'più di una semplice convenzione di denominazione (vedere la sezione di ricerca del metodo speciale nel modello di dati).
mercoledì

206

._variable è semiprivato e inteso solo per convenzione

.__variableè spesso erroneamente considerato superprivato, mentre il suo vero significato è solo quello di impedire un accesso accidentale [1]

.__variable__ è in genere riservato a metodi o variabili incorporati

Puoi ancora accedere alle .__mangledvariabili se lo desideri disperatamente. I doppi caratteri di sottolineatura sono solo namemangles, o rinomina, la variabile in qualcosa del genereinstance._className__mangled

Esempio:

class Test(object):
    def __init__(self):
        self.__a = 'a'
        self._b = 'b'

>>> t = Test()
>>> t._b
'b'

t._b è accessibile perché nascosto solo per convenzione

>>> t.__a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__a'

t .__ a non viene trovato perché non esiste più a causa della modifica dei nomi

>>> t._Test__a
'a'

Accedendo instance._className__variableinvece al solo doppio carattere di sottolineatura, è possibile accedere al valore nascosto


ma che ne dite se "__a" fosse una variabile di classe, quindi non è possibile accedervi anche con le istruzioni dai documenti di Python ..
Vitaliy Terziev,

Puoi aggiornare la tua risposta con un esempio di doppio trattino basso rispetto all'eredità?
variabile

116

Sottolineatura singola all'inizio:

Python non ha metodi privati ​​reali. Al contrario, un carattere di sottolineatura all'inizio di un metodo o di un nome di attributo significa che non dovresti accedere a questo metodo, perché non fa parte dell'API.

class BaseForm(StrAndUnicode):

    def _get_errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()
        return self._errors

    errors = property(_get_errors)

(Questo frammento di codice è stato preso dal codice sorgente di django: django / forme / forme.py). In questo codice,errors è una proprietà pubblica, ma il metodo chiamato da questa proprietà, _get_errors, è "privato", quindi non dovresti accedervi.

Due trattini bassi all'inizio:

Questo provoca molta confusione. Non dovrebbe essere usato per creare un metodo privato. Dovrebbe essere usato per evitare che il tuo metodo venga sovrascritto da una sottoclasse o acceduto accidentalmente. Vediamo un esempio:

class A(object):
    def __test(self):
        print "I'm a test method in class A"

    def test(self):
        self.__test()

a = A()
a.test()
# a.__test() # This fails with an AttributeError
a._A__test() # Works! We can access the mangled name directly!

Produzione:

$ python test.py
I'm test method in class A
I'm test method in class A

Ora crea una sottoclasse B ed esegui la personalizzazione per il metodo __test

class B(A):
    def __test(self):
        print "I'm test method in class B"

b = B()
b.test()

L'output sarà ....

$ python test.py
I'm test method in class A

Come abbiamo visto, A.test () non ha chiamato i metodi B .__ test (), come potremmo aspettarci. In realtà, questo è il comportamento corretto per __. I due metodi chiamati __test () vengono automaticamente rinominati (alterati) in _A__test () e _B__test (), quindi non sovrascrivono accidentalmente. Quando crei un metodo che inizia con __ significa che non vuoi che nessuno sia in grado di sovrascriverlo e intendi accedervi solo all'interno della sua stessa classe.

Due trattini bassi all'inizio e alla fine:

Quando vediamo un metodo simile __this__, non chiamarlo. Questo è un metodo che Python dovrebbe chiamare, non tu. Diamo un'occhiata:

>>> name = "test string"
>>> name.__len__()
11
>>> len(name)
11

>>> number = 10
>>> number.__add__(40)
50
>>> number + 50
60

C'è sempre un operatore o una funzione nativa che chiama questi metodi magici. A volte è solo un hook python che chiama in situazioni specifiche. Ad esempio, __init__()viene chiamato quando l'oggetto viene creato dopo che __new__()viene chiamato per creare l'istanza ...

Facciamo un esempio ...

class FalseCalculator(object):

    def __init__(self, number):
        self.number = number

    def __add__(self, number):
        return self.number - number

    def __sub__(self, number):
        return self.number + number

number = FalseCalculator(20)
print number + 10      # 10
print number - 20      # 40

Per maggiori dettagli, consultare la guida PEP-8 . Per altri metodi magici, vedi questo PDF .


1
Dopo aver modificato questa risposta, preferisco stackoverflow.com/a/8689983/1048186
Josiah Yoder,

Cosa intendi con "Come abbiamo visto, A.test () non ha chiamato i metodi B .__ test ()" - dove hai chiamato A.test ()?
variabile

18

A volte hai quella che sembra essere una tupla con un trattino basso come in

def foo(bar):
    return _('my_' + bar)

In questo caso, ciò che sta succedendo è che _ () è un alias per una funzione di localizzazione che opera sul testo per metterlo nella lingua corretta, ecc. In base alla locale. Ad esempio, Sphinx fa questo e troverai tra le importazioni

from sphinx.locale import l_, _

e in sphinx.locale, _ () è assegnato come alias di alcune funzioni di localizzazione.


11

Dal momento che così tante persone si riferiscono al discorso di Raymond , lo renderò un po 'più semplice scrivendo quello che ha detto:

L'intenzione del doppio trattino basso non riguardava la privacy. L'intenzione era di usarlo esattamente così

class Circle(object):

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        p = self.__perimeter()
        r = p / math.pi / 2.0
        return math.pi * r ** 2.0

    def perimeter(self):
        return 2.0 * math.pi * self.radius

    __perimeter = perimeter  # local reference


class Tire(Circle):

    def perimeter(self):
        return Circle.perimeter(self) * 1.25

In realtà è l'opposto della privacy, è tutta una questione di libertà. Rende le tue sottoclassi libere di sovrascrivere qualsiasi metodo senza interrompere gli altri .

Supponi di non mantenere un riferimento locale di perimeterin Circle. Ora, una classe derivata Tiresovrascrive l'implementazione di perimeter, senza toccarla area. Quando chiami Tire(5).area(), in teoria dovrebbe comunque essere utilizzato Circle.perimeterper il calcolo, ma in realtà sta usando Tire.perimeter, che non è il comportamento previsto. Ecco perché abbiamo bisogno di un riferimento locale in Circle.

Ma perché __perimeterinvece di _perimeter? Perché _perimeterdà ancora la possibilità alla classe derivata di ignorare:

class Tire(Circle):

    def perimeter(self):
        return Circle.perimeter(self) * 1.25

    _perimeter = perimeter

I caratteri di sottolineatura doppia hanno una modifica del nome, quindi ci sono poche possibilità che il riferimento locale nella classe genitore venga sostituito nella classe derivata. quindi " rende le tue sottoclassi libere di sovrascrivere qualsiasi metodo senza rompere gli altri ".

Se la tua classe non verrà ereditata o la sostituzione del metodo non romperà nulla, semplicemente non avrai bisogno __double_leading_underscore.


1
Grazie, la diapositiva non è stata visualizzata correttamente, quindi ho finito per non disturbare il motivo per cui il mio codice avrebbe fallito.
CG

8

Se si vuole davvero creare una variabile di sola lettura, IMHO il modo migliore sarebbe usare property () con solo getter passato ad essa. Con property () possiamo avere il controllo completo sui dati.

class PrivateVarC(object):

    def get_x(self):
        pass

    def set_x(self, val):
        pass

    rwvar = property(get_p, set_p)  

    ronly = property(get_p) 

Comprendo che OP ha posto una domanda leggermente diversa, ma poiché ho trovato un'altra domanda che chiedeva "come impostare le variabili private" contrassegnata come duplicata con questa, ho pensato di aggiungere queste informazioni aggiuntive qui.


8

Accodando a https://dbader.org/blog/meaning-of-underscores-in-python

  • Single Leading Underscore (_var) : convenzione di denominazione che indica un nome è destinata all'uso interno. Generalmente non imposto dall'interprete Python (tranne nelle importazioni di caratteri jolly) e inteso come un suggerimento solo per il programmatore.
  • Singolo carattere di sottolineatura finale (var_) : utilizzato per convenzione per evitare conflitti di denominazione con parole chiave Python.
  • Doppio carattere di sottolineatura iniziale (__ var) : attiva la modifica del nome quando utilizzato in un contesto di classe. Applicato dall'interprete Python.
  • Doppio trattino iniziale e finale (__ var__) : indica metodi speciali definiti dal linguaggio Python. Evita questo schema di denominazione per i tuoi attributi.
  • Singolo carattere di sottolineatura (_) : a volte usato come nome per variabili temporanee o insignificanti ("non importa"). Inoltre: il risultato dell'ultima espressione in un REPL Python.

5

Grandi risposte e tutte sono corrette. Ho fornito un semplice esempio insieme a una semplice definizione / significato.

Senso:

some_variable --► è pubblico chiunque può vederlo.

_some_variable --► è pubblico che chiunque può vederlo, ma è una convenzione per indicare privato ... avviso che Python non applica alcuna applicazione.

__some_varaible --► Python sostituisce il nome della variabile con _classname__some_varaible (modifica del nome AKA) e riduce / nasconde la sua visibilità ed è più simile alla variabile privata.

Giusto per essere sincero qui Secondo la documentazione di Python

"Le variabili di istanza" private "a cui non è possibile accedere se non dall'interno di un oggetto non esistono in Python"

L'esempio:

class A():
    here="abc"
    _here="_abc"
    __here="__abc"


aObject=A()
print(aObject.here) 
print(aObject._here)
# now if we try to print __here then it will fail because it's not public variable 
#print(aObject.__here)

_ _some_varaible - .... e riduce / nasconde la sua visibilità ed è più simile alla variabile privata. No, il nome è il problema, non nasconde il metodo.
AMC,

4

Le sottolineature principali singole sono una convenzione. non vi è alcuna differenza dal punto di vista dell'interprete se i nomi iniziano con un singolo trattino basso o meno.

Doppie iniziali e finali sottolineatura sono utilizzati per incasso metodi, come __init__, __bool__ecc

Anche le doppie sottolineature principali senza controparti finali sono una convenzione, tuttavia, i metodi di classe saranno alterati dall'interprete. Per variabili o nomi di funzioni di base non esiste alcuna differenza.


3

La tua domanda è buona, non riguarda solo i metodi. Le funzioni e gli oggetti nei moduli sono comunemente preceduti anche da un carattere di sottolineatura e possono essere preceduti da due.

Ad esempio, i nomi __double_underscore non sono alterati nei moduli. Ciò che accade è che i nomi che iniziano con uno (o più) caratteri di sottolineatura non vengono importati se si importano tutti da un modulo (dall'importazione del modulo *), né i nomi visualizzati nella guida (modulo).


1
Inoltre, i nomi che iniziano con uno o più caratteri di sottolineatura con due o più caratteri di sottolineatura finali si comportano nuovamente come qualsiasi altro nome.
Bentley4,

3

Ecco un semplice esempio illustrativo di come le proprietà del doppio trattino basso possano influenzare una classe ereditata. Quindi con la seguente configurazione:

class parent(object):
    __default = "parent"
    def __init__(self, name=None):
        self.default = name or self.__default

    @property
    def default(self):
        return self.__default

    @default.setter
    def default(self, value):
        self.__default = value


class child(parent):
    __default = "child"

se quindi crei un'istanza figlio in REPL python, vedrai quanto segue

child_a = child()
child_a.default            # 'parent'
child_a._child__default    # 'child'
child_a._parent__default   # 'parent'

child_b = child("orphan")
## this will show 
child_b.default            # 'orphan'
child_a._child__default    # 'child'
child_a._parent__default   # 'orphan'

Questo può essere ovvio per alcuni, ma mi ha colto di sorpresa in un ambiente molto più complesso


3

In Python non esistono variabili di istanza "private" a cui non è possibile accedere se non dall'interno di un oggetto. Tuttavia, esiste una convenzione seguita dalla maggior parte del codice Python: un nome preceduto da un trattino basso (ad esempio _spam) deve essere trattato come una parte non pubblica dell'API (che si tratti di una funzione, un metodo o un membro di dati) . Dovrebbe essere considerato un dettaglio di implementazione e soggetto a modifiche senza preavviso.

riferimento https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references


1
_ è molto più simile ad esempio a quello interno in c # che a quello privato. Il doppio carattere di sottolineatura è molto più simile al privato, quindi il carattere di sottolineatura è privato, direi.
Ini

2

Ottenere i fatti di _ e __ è abbastanza facile; le altre risposte le esprimono piuttosto bene. L'uso è molto più difficile da determinare.

Ecco come lo vedo io:

_

Dovrebbe essere usato per indicare che una funzione non è per uso pubblico come ad esempio un'API. Questo e le restrizioni all'importazione lo fanno comportare come internalin c #.

__

Dovrebbe essere usato per evitare la collisione dei nomi nella gerarchia ereditaria e per evitare il ritardo. Proprio come privato in c #.

==>

Se vuoi indicare che qualcosa non è per uso pubblico, ma dovrebbe comportarsi come un protecteduso _. Se vuoi indicare che qualcosa non è per uso pubblico, ma dovrebbe comportarsi come un privateuso __.

Questa è anche una citazione che mi piace molto:

Il problema è che l'autore di una classe può legittimamente pensare "questo nome di attributo / metodo deve essere privato, accessibile solo all'interno di questa definizione di classe" e utilizzare la convenzione __private. Ma in seguito, un utente di quella classe potrebbe creare una sottoclasse che necessita legittimamente dell'accesso a quel nome. Quindi o la superclasse deve essere modificata (che può essere difficile o impossibile), oppure il codice della sottoclasse deve usare nomi alterati manualmente (che è brutto e fragile nella migliore delle ipotesi).

Ma il problema è a mio avviso che se non esiste un IDE che ti avverte quando si sostituiscono i metodi, la ricerca dell'errore potrebbe richiedere del tempo se si è accidentalmente scavalcato un metodo da una classe base.

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.