Quelle est la signification du trait de soulignement simple et double devant un nom d'objet?

1293

Quelqu'un peut-il expliquer la signification exacte d'avoir des traits de soulignement avant le nom d'un objet en Python, et la différence entre les deux?

En outre, ce sens reste-t-il le même que l'objet en question soit une variable, une fonction, une méthode, etc.?

ivanleoncz
la source
2
Une excellente réponse courte d'un autre thread: stackoverflow.com/a/8689983/911945
Anton Tarasenko

Réponses:

1157

Underscore simple

Les noms, dans une classe, avec un trait de soulignement principal sont simplement pour indiquer aux autres programmeurs que l'attribut ou la méthode est destiné à être privé. Cependant, rien de spécial n'est fait avec le nom lui-même.

Pour citer PEP-8 :

_single_leading_underscore: faible indicateur "usage interne". Par exemple, from M import *n'importe pas les objets dont le nom commence par un soulignement.

Double soulignement (nom Mangling)

Depuis les documents Python :

Tout identifiant du formulaire __spam(au moins deux traits de soulignement de tête, au plus un trait de soulignement de fin) est textuellement remplacé par _classname__spam, où classnameest le nom de classe actuel avec le ou les traits de soulignement de tête supprimés. Cette manipulation est effectuée sans tenir compte de la position syntaxique de l'identifiant, de sorte qu'il peut être utilisé pour définir des instances de classe privée et des variables de classe, des méthodes, des variables stockées dans des globaux et même des variables stockées dans des instances. privé à cette classe sur les instances d'autres classes.

Et un avertissement de la même page:

Le mangling de nom est destiné à donner aux classes un moyen facile de définir des variables et des méthodes d'instance «privées», sans avoir à se soucier des variables d'instance définies par des classes dérivées, ou à nettoyer les variables d'instance par du code en dehors de la classe. Notez que les règles de mutilation sont conçues principalement pour éviter les accidents; il est toujours possible pour une âme déterminée d'accéder ou de modifier une variable considérée comme privée.

Exemple

>>> 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!'}
Andrew Keeton
la source
17
Que faire s'il y a un nom de variable déclaré avec 2 traits de soulignement qui n'est pas dans la classe? C'est juste une variable normale alors non?
Dhruv Ramani
5
Quelle est la signification du simple __double soulignement en tant que nom de variable? commea, __ = foo()
AJ
104
Cette réponse est extrêmement trompeuse, car elle conduit le lecteur à croire que le dunderscore est utilisé pour rendre les attributs d'instance "superprivates". Ce n'est pas le cas, comme l' explique ici Raymond Hettinger, qui déclare explicitement que le dunderscore est incorrectement utilisé pour marquer les membres comme privés, alors qu'il a été conçu pour être l'opposé de privé.
Markus Meskanen
14
@MarkusMeskanen Je ne suis pas d'accord, la réponse indique explicitement l'utilisation d'un dunderscore pour créer des instances de variables et méthodes de classe privée. Alors que le dunderscore a été conçu pour rendre ces méthodes et variables facilement remplacées par des sous-classes (les rendant publiques), l'utilisation d'un dunderscore préserve une instance privée à utiliser dans cette classe.
arewm
7
@MarkusMeskanen: La liberté est pour les sous-classes d'utiliser les mêmes noms que la superclasse sans encombrer la superclasse - en d'autres termes, les noms abrutis des superclasses deviennent privés pour eux-mêmes.
Ethan Furman
311

D'excellentes réponses à ce jour, mais il manque des petits morceaux. Un trait de soulignement premier single est pas exactement juste une convention: si vous utilisez from foobar import *, et le module foobarne définit pas une __all__liste, les noms importés du module ne comprennent ceux qui ont un trait de soulignement de premier plan. Disons que c'est surtout une convention, car ce cas est un coin assez obscur ;-).

La convention de soulignement principal est largement utilisée non seulement pour les noms privés , mais aussi pour ce que C ++ appellerait ceux protégés - par exemple, les noms de méthodes qui sont entièrement destinées à être remplacées par des sous-classes (même celles qui doivent être remplacées depuis dans la classe de base qu'ils raise NotImplementedError! -) sont souvent des noms à soulignement simple pour indiquer au code à l' aide d' instances de cette classe (ou sous-classes) que lesdites méthodes ne sont pas censées être appelées directement.

Par exemple, pour créer une file d'attente thread-safe avec une discipline de mise en file d'attente différente de FIFO, on importe Queue, sous-classe Queue.Queue et remplace les méthodes comme _getet _put; "code client" n'appelle jamais ces méthodes ("hook"), mais plutôt les méthodes publiques ("organisantes") telles que putet get(c'est ce qu'on appelle le modèle de conception de la méthode modèle - voir par exemple ici pour une présentation intéressante basée sur une vidéo d'un entretien sur le sujet, avec l'ajout de synopsis de la transcription).

Edit: Les liens vidéo dans la description des discussions sont maintenant rompus. Vous pouvez trouver les deux premières vidéos ici et ici .

Alex Martelli
la source
1
Alors, comment décidez-vous d'utiliser _var_nameou d'utiliser var_name+ l'exclusion de __all__?
endolith
3
@endolith Utilisez le trait de soulignement principal pour signaler au lecteur de votre code qu'il ne devrait probablement pas l'utiliser (par exemple, car vous pourriez le changer dans la version 2.0, ou même 1.1); utilisez explicit __all__chaque fois que vous voulez rendre le module from spam import *convivial (y compris à l'interpréteur interactif). La plupart du temps, la réponse est donc les deux .
abarnert
@AlexMartelli Cette règle relative à l'importation est-elle discutée légalement quelque part dans la documentation ou ailleurs?
Vicrobot
1
J'aime l'analogie C ++. Tout d'abord, je n'aime pas quand les gens appellent le _ privé . Évidemment, je parle d'analogies, car rien n'est vraiment privé en Python. Lorsque la plongée en sémantique , je dirais que nous pouvons attacher le _à de Java protégé depuis proctected dans les moyens Java « classes dérivées et / ou dans un même emballage ». Remplacez le package par le module car PEP8 nous dit déjà que ce _n'est pas seulement une convention lorsque vous parlez d' *importations et que vous l'avez. Et __serait certainement équivalent à privé de Java lorsque l'on parle d'identifiants au sein d'une classe.
Marius Mucenicu
2
Bien qu'une réponse décente, elle est également fortement auto-promotionnelle.
Développeur web hybride le
299

__foo__: c'est juste une convention, un moyen pour le système Python d'utiliser des noms qui n'entreront pas en conflit avec les noms d'utilisateurs.

_foo: c'est juste une convention, un moyen pour le programmeur d'indiquer que la variable est privée (quoi que cela signifie en Python).

__foo: cela a un sens réel: l'interpréteur remplace ce nom par _classname__foocomme un moyen de s'assurer que le nom ne chevauchera pas avec un nom similaire dans une autre classe.

Aucune autre forme de soulignement n'a de sens dans le monde Python.

Il n'y a aucune différence entre classe, variable, global, etc. dans ces conventions.

Ned Batchelder
la source
6
Je suis juste tombé __fooet curieux. Comment peut-il chevaucher des noms de méthode similaires avec d'autres classes? Je veux dire que vous devez toujours y accéder comme instance.__foo()(s'il n'a pas été renommé par interprète), non?
Bibhas Debnath
81
Ce type déclare qu'il from module import *n'importe pas d'objets avec préfixe de soulignement. Par conséquent, _fooc'est plus qu'une simple convention.
dotancohen
4
@Bibhas: si la classe Bsous - classe la classe A, et les deux implémentent foo(), B.foo()remplace alors le .foo()hérité de A. Une instance de Bne pourra y accéder B.foo(), sauf via super(B).foo().
naught101
3
Pour les __dunder__noms, les invocations implicites ignorent le dictionnaire d'instances, c'est donc peut-être un peu plus qu'une simple convention de dénomination dans certains cas (voir la section spéciale de recherche de méthode dans le modèle de données).
wim
206

._variable est semi-privé et destiné uniquement à la convention

.__variableest souvent considéré à tort comme superprivé, alors que sa signification réelle est simplement de nommer un angle pour empêcher un accès accidentel [1]

.__variable__ est généralement réservé aux méthodes ou variables intégrées

Vous pouvez toujours accéder aux .__mangledvariables si vous le souhaitez. Le double souligne simplement les noms de noms, ou renomme la variable en quelque chose commeinstance._className__mangled

Exemple:

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

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

t._b est accessible car il n'est masqué que par convention

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

t .__ a est introuvable car il n'existe plus en raison de la confusion des noms

>>> t._Test__a
'a'

En accédant instance._className__variableau lieu du simple double soulignement, vous pouvez accéder à la valeur cachée

NickCSE
la source
mais que diriez-vous si "__a" était une variable de classe, alors vous ne pouvez pas y accéder même avec les instructions des documents python ..
Vitaliy Terziev
Pouvez-vous mettre à jour votre réponse avec un exemple de double soulignement en ce qui concerne l'héritage?
variable
116

Souligné unique au début:

Python n'a pas de véritables méthodes privées. Au lieu de cela, un trait de soulignement au début d'un nom de méthode ou d'attribut signifie que vous ne devez pas accéder à cette méthode, car elle ne fait pas partie de l'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)

(Cet extrait de code a été extrait du code source de django: django / forms / forms.py). Dans ce code, errorsest une propriété publique, mais la méthode que cette propriété appelle, _get_errors, est "privée", vous ne devez donc pas y accéder.

Deux soulignements au début:

Cela provoque beaucoup de confusion. Il ne doit pas être utilisé pour créer une méthode privée. Il doit être utilisé pour éviter que votre méthode soit remplacée par une sous-classe ou accédée accidentellement. Voyons un exemple:

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!

Production:

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

Créez maintenant une sous-classe B et personnalisez la méthode __test

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

b = B()
b.test()

La sortie sera ....

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

Comme nous l'avons vu, A.test () n'a pas appelé les méthodes B .__ test (), comme on pourrait s'y attendre. Mais en fait, c'est le comportement correct pour __. Les deux méthodes appelées __test () sont automatiquement renommées (mutilées) en _A__test () et _B__test (), de sorte qu'elles ne remplacent pas accidentellement. Lorsque vous créez une méthode commençant par __, cela signifie que vous ne voulez pas que quiconque puisse la remplacer, et vous avez uniquement l'intention d'y accéder depuis l'intérieur de sa propre classe.

Deux soulignements au début et à la fin:

Quand nous voyons une méthode comme __this__, ne l'appelez pas. C'est une méthode que python est censé appeler, pas vous. Nous allons jeter un coup d'oeil:

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

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

Il y a toujours un opérateur ou une fonction native qui appelle ces méthodes magiques. Parfois, c'est juste un appel de python dans des situations spécifiques. Par exemple, __init__()est appelé lorsque l'objet est créé après __new__()est appelé pour construire l'instance ...

Prenons un exemple ...

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

Pour plus de détails, consultez le guide PEP-8 . Pour plus de méthodes magiques, voir ce PDF .

PythonDev
la source
1
Après avoir édité cette réponse moi-même, je préfère stackoverflow.com/a/8689983/1048186
Josiah Yoder
Que voulez-vous dire par "Comme nous l'avons vu, A.test () n'a pas appelé les méthodes B .__ test ()" - où avez-vous appelé A.test ()?
variable
18

Parfois, vous avez ce qui semble être un tuple avec un trait de soulignement comme dans

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

Dans ce cas, ce qui se passe, c'est que _ () est un alias pour une fonction de localisation qui opère sur du texte pour le mettre dans la langue appropriée, etc. en fonction des paramètres régionaux. Par exemple, Sphinx fait cela, et vous trouverez parmi les importations

from sphinx.locale import l_, _

et dans sphinx.locale, _ () est assigné comme alias d'une fonction de localisation.

Tim D
la source
11

Étant donné que tant de gens font référence à la conférence de Raymond , je vais simplement faciliter les choses en écrivant ce qu'il a dit:

L'intention des doubles soulignements n'était pas une question d'intimité. L'intention était de l'utiliser exactement comme ça

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

C'est en fait l'opposé de la vie privée, c'est une question de liberté. Cela rend vos sous-classes libres de remplacer n'importe quelle méthode sans casser les autres .

Supposons que vous ne conserviez pas une référence locale de perimeterin Circle. Maintenant, une classe dérivée Tireremplace l'implémentation de perimeter, sans toucher area. Lorsque vous appelez Tire(5).area(), en théorie, il devrait toujours être utilisé Circle.perimeterpour le calcul, mais en réalité, il utilise Tire.perimeter, ce qui n'est pas le comportement prévu. C'est pourquoi nous avons besoin d'une référence locale dans Circle.

Mais pourquoi __perimeterau lieu de _perimeter? Parce que _perimeterdonne toujours à la classe dérivée la possibilité de remplacer:

class Tire(Circle):

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

    _perimeter = perimeter

Les doubles soulignements ont un changement de nom, il y a donc très peu de chances que la référence locale dans la classe parent soit remplacée dans la classe dérivée. ainsi " rend vos sous-classes libres de remplacer n'importe quelle méthode sans casser les autres ".

Si votre classe ne sera pas héritée, ou si la substitution de méthode ne casse rien, alors vous n'avez tout simplement pas besoin __double_leading_underscore.

laike9m
la source
1
Merci, la diapositive ne s'affichait pas correctement, j'ai donc fini par comprendre pourquoi mon code échouerait.
cgte
8

Si l'on veut vraiment faire une variable en lecture seule, à mon humble avis la meilleure façon serait d'utiliser la propriété () avec seulement getter passé. Avec property (), nous pouvons avoir un contrôle complet sur les données.

class PrivateVarC(object):

    def get_x(self):
        pass

    def set_x(self, val):
        pass

    rwvar = property(get_p, set_p)  

    ronly = property(get_p) 

Je comprends que OP a posé une question un peu différente mais comme j'ai trouvé une autre question demandant "comment définir des variables privées" marquées en double avec celle-ci, j'ai pensé ajouter cette information supplémentaire ici.

Dev Maha
la source
8

Accroding à https://dbader.org/blog/meaning-of-underscores-in-python

  • Single Leading Underscore (_var) : convention de dénomination indiquant un nom est destinée à un usage interne. Généralement non appliqué par l'interpréteur Python (sauf dans les importations de caractères génériques) et signifié comme un indice pour le programmeur uniquement.
  • Single Trailing Underscore (var_) : utilisé par convention pour éviter les conflits de dénomination avec les mots clés Python.
  • Underscore double interligne (__ var) : déclenche la modification du nom lorsqu'il est utilisé dans un contexte de classe. Appliqué par l'interpréteur Python.
  • Souligné double de début et de fin (__ var__) : indique des méthodes spéciales définies par le langage Python. Évitez ce schéma de dénomination pour vos propres attributs.
  • Souligné simple (_) : Parfois utilisé comme nom pour des variables temporaires ou insignifiantes («ne s'en soucie pas»). Aussi: Le résultat de la dernière expression dans un REPL Python.
Feuda
la source
5

Excellentes réponses et toutes sont correctes. J'ai fourni un exemple simple avec une définition / signification simple.

Sens:

some_variable --► c'est public, tout le monde peut le voir.

_some_variable --► c'est public tout le monde peut le voir mais c'est une convention pour indiquer privé ... avertissement aucune application n'est effectuée par Python.

__some_varaible --► Python remplace le nom de variable par _classname__some_varaible (AKA name mangling) et il réduit / masque sa visibilité et ressemble plus à une variable privée.

Juste pour être honnête ici selon la documentation Python

"Les variables d'instance" privées "auxquelles on ne peut accéder que depuis l'intérieur d'un objet n'existent pas en Python"

L'exemple:

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)
grepit
la source
_ _some_varaible - .... et cela réduit / masque sa visibilité et ressemble plus à une variable privée. Non, le changement de nom est le point, il ne cache pas la méthode.
AMC
4

Les traits de soulignement simples sont une convention. il n'y a aucune différence du point de vue de l'interprète si le nom commence ou non par un seul trait de soulignement.

Double avant et arrière underscores sont utilisés pour des méthodes intégrées, telles que __init__, __bool__, etc.

Les doubles soulignements principaux sans homologues de fin sont également une convention, cependant, les méthodes de classe seront modifiées par l'interpréteur. Pour les variables ou les noms de fonction de base, aucune différence n'existe.

SilentGhost
la source
3

Votre question est bonne, elle ne concerne pas seulement les méthodes. Les fonctions et les objets dans les modules sont généralement préfixés d'un trait de soulignement et peuvent être préfixés de deux.

Mais les noms __double_underscore ne sont pas modifiés par nom dans les modules, par exemple. Ce qui se passe, c'est que les noms commençant par un (ou plusieurs) traits de soulignement ne sont pas importés si vous importez tous à partir d'un module (à partir de l'importation de module *), ni les noms affichés dans l'aide (module).

u0b34a0f6ae
la source
1
En outre, les noms commençant par un ou plusieurs traits de soulignement qui ont deux ou plusieurs traits de soulignement finaux se comportent à nouveau comme tout autre nom.
Bentley4
3

Voici un exemple illustratif simple sur la façon dont les propriétés de soulignement double peuvent affecter une classe héritée. Donc, avec la configuration suivante:

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"

si vous créez ensuite une instance enfant dans le python REPL, vous verrez ci-dessous

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'

Cela peut être évident pour certains, mais cela m'a pris au dépourvu dans un environnement beaucoup plus complexe

Marc
la source
3

Les variables d'instance «privées» auxquelles on ne peut accéder que depuis l'intérieur d'un objet n'existent pas en Python. Cependant, il existe une convention qui est suivie par la plupart du code Python: un nom préfixé avec un trait de soulignement (par exemple _spam) doit être traité comme une partie non publique de l'API (qu'il s'agisse d'une fonction, d'une méthode ou d'un membre de données) . Il doit être considéré comme un détail d'implémentation et sujet à changement sans préavis.

référence https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references

aptro
la source
1
_ est beaucoup plus similaire par exemple à internal en c # puis à private. Double soulignement, il ressemble beaucoup plus à privé que le soulignement est privé, je dirais.
Ini
2

Obtenir les faits de _ et __ est assez facile; les autres réponses les expriment assez bien. L'utilisation est beaucoup plus difficile à déterminer.

Voici comment je le vois:

_

Doit être utilisé pour indiquer qu'une fonction n'est pas destinée à un usage public comme par exemple une API. Ceci et la restriction d'importation le font se comporter comme internalen c #.

__

Doit être utilisé pour éviter la collision de noms dans l'hirarchie d'héritage et pour éviter la liaison tardive. Tout comme privé en c #.

==>

Si vous voulez indiquer que quelque chose n'est pas destiné à un usage public, mais qu'il devrait agir comme un protectedusage _. Si vous voulez indiquer que quelque chose n'est pas destiné à un usage public, mais qu'il devrait agir comme un privateusage __.

C'est aussi une citation que j'aime beaucoup:

Le problème est que l'auteur d'une classe peut légitimement penser que "ce nom d'attribut / méthode doit être privé, accessible uniquement depuis cette définition de classe" et utiliser la convention __private. Mais plus tard, un utilisateur de cette classe peut créer une sous-classe qui a légitimement besoin d'accéder à ce nom. Donc, soit la superclasse doit être modifiée (ce qui peut être difficile ou impossible), soit le code de la sous-classe doit utiliser des noms modifiés manuellement (ce qui est laid et fragile au mieux).

Mais le problème avec cela est à mon avis que s'il n'y a pas d'IDE qui vous avertit lorsque vous substituez des méthodes, la recherche de l'erreur peut vous prendre un certain temps si vous avez accidentellement remplacé une méthode d'une classe de base.

2 tours
la source