Quelqu'un peut-il s'il vous plaît expliquer le sens exact d'avoir des traits de soulignement avant le nom d'un objet en Python? Expliquez également la différence entre un trait de soulignement simple et double. En outre, cette signification reste-t-elle la même, que l'objet en question soit une variable, une fonction, une méthode, etc.?
Les noms, dans une classe, avec un tiret bas important 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: indicateur faible "d'utilisation interne". Par exemple.
from M import *
n'importe pas d'objets dont le nom commence par un trait de soulignement.
De le Python docs :
Tout identificateur de la forme
__spam
(au moins deux traits de soulignement principaux, au plus un trait de soulignement de fin) est remplacé textuellement par_classname__spam
, oùclassname
est le nom actuel de la classe avec le ou les traits de soulignement. dépouillé. Cette modification est effectuée sans tenir compte de la position syntaxique de l'identificateur. Elle peut donc être utilisée pour définir des variables d'instance et de classe privées, des méthodes, des variables stockées dans des globales et même des variables stockées dans des instances. privé à cette classe sur des instances d'autres classes.
Et un avertissement de la même page:
La gestion des noms a pour but de donner aux classes un moyen simple de définir des variables et des méthodes d’instance "privées", sans avoir à s’inquiéter des variables d’instance définies par des classes dérivées, ni du fauchage de variables d’instance par du code en dehors de la classe. Notez que les règles de manipulation sont principalement conçues 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.
>>> 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!'}
Excellentes réponses jusqu'à présent, mais il manque quelques détails. Un trait de soulignement simple n'est pas exactement juste une convention : si vous utilisez from foobar import *
et que le module foobar
ne définit pas de liste __all__
, les noms importés du module n’incluent pas ceux qui sont précédés d’un trait de soulignement. Disons que c'est surtout une convention, car cette affaire est un coin assez obscur ;-).
La convention de conduite de soulignement est largement utilisée non seulement pour noms privés , mais aussi pour ce que C++ appellerait protected - pour Par exemple, les noms des méthodes qui sont entièrement destinées à être écrasées par des sous-classes (même celles qui doivent être écrasées car dans la classe de base elles raise NotImplementedError
! -) sont souvent des noms à tiret simple soulignant d'indiquer le code en utilisant des instances de cette classe (ou sous-classes) pour lesquelles lesdites méthodes ne sont pas destinées. être appelé directement.
Par exemple, pour créer une file d'attente sécurisée pour les threads avec une discipline de file d'attente différente de celle de FIFO, on importe Queue, sous-classe Queue.Queue et remplace les méthodes telles que _get
et _put
; "code client" n'appelle jamais ces méthodes ("hook"), mais plutôt les méthodes publiques ("organisatrices") telles que put
et get
(c'est ce qu'on appelle la méthode du modèle modèle de conception - voir par exemple ici pour une présentation intéressante basée sur une vidéo d'un de mes discours sur le sujet, avec l'ajout de synopsis de la transcription).
__foo__
: il ne s'agit que d'une convention, d'une manière pour le système Python d'utiliser des noms qui ne seront pas en conflit avec les noms d'utilisateur.
_foo
: il ne s'agit que d'une convention, un moyen pour le programmeur d'indiquer que la variable est privée (peu importe ce que cela signifie en Python).
__foo
: cela a un sens réel: l'interpréteur remplace ce nom par _classname__foo
afin de s'assurer que le nom ne chevauchera pas avec un nom similaire dans une autre classe.
Aucune autre forme de caractère de soulignement n'a de signification dans le monde Python.
Il n'y a pas de différence entre classe, variable, global, etc. dans ces conventions.
._variable
est semi-privé et destiné uniquement à la convention
.__variable
est souvent considéré à tort comme superprivé, alors que sa signification réelle est simplement de nommer pour lier empêcher un accès accidentel [1] =
.__variable__
est généralement réservé aux méthodes intégrées ou aux variables
Vous pouvez toujours accéder aux variables .__mangled
si vous le souhaitez. Les doubles soulignés ne font que nommer ou renommer la variable à quelque chose comme instance._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 est seulement caché par convention
>>> t.__a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__a'
t .__a n'est pas trouvé car il n'existe plus à cause d'un changement de nom
>>> t._Test__a
'a'
En accédant à instance._className__variable
au lieu du nom de soulignement double, vous pouvez accéder à la valeur cachée.
Soulignement simple au début:
Python n'a pas de vraies méthodes privées. Au lieu de cela, un soulignement au début d'une méthode ou d'un nom d'attribut signifie que vous ne devriez 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 provient de Django code source: Django/forms/forms.py). Dans ce code, errors
est 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 traits de soulignement au début:
Cela cause beaucoup de confusion. Il ne doit pas être utilisé pour créer une méthode privée. Cela devrait être utilisé pour éviter que votre méthode ne 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!
Sortie:
$ 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 pouvait 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 sont pas écrasées par inadvertance. Lorsque vous créez une méthode commençant par __, cela signifie que vous ne souhaitez que personne ne puisse la remplacer, et que vous souhaitez uniquement y accéder depuis sa propre classe.
Deux traits de soulignement au début et à la fin:
Lorsque nous voyons une méthode comme __this__
, ne l'appelle 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, il ne s'agit que d'un hook python dans certaines situations. Par exemple, __init__()
est appelée lorsque l'objet est créé après que __new__()
soit appelé pour créer 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, voir le guide PEP-8 . Pour plus de méthodes magiques, voir ce PDF .
Parfois, vous avez ce qui semble être un tuple avec un trait de soulignement principal 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 attribué comme alias d’une fonction de localisation.
Si on veut vraiment créer une variable en lecture seule, à mon humble avis, le meilleur moyen serait d'utiliser property () avec seulement le getter qui lui est 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 puisque j’ai trouvé une autre question demandant "Comment définir des variables privées" marquée en double avec celle-ci, j’ai pensé à ajouter cette information supplémentaire ici.
Le soulignement simple est une convention. le point de vue de l'interprète ne fait aucune différence si les noms commencent par un simple soulignement ou non.
Les traits de soulignement doubles et suivants sont utilisés pour les méthodes intégrées, telles que __init__
, __bool__
, etc.
Les soulignements en double interligne sans contrepartie en queue sont également une convention, cependant, les méthodes de classe seront mutilées par l’interprète. Pour les variables ou les noms de fonctions de base, il n'y a pas de différence.
Votre question est bonne, il ne s'agit pas uniquement de méthodes. Les fonctions et les objets des modules sont généralement préfixés par un trait de soulignement et peuvent également être préfixés par deux.
Mais les noms __double_underscore ne sont pas nommés mutilés 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 module import *), pas plus que les noms affichés dans l'aide (module).
Les variables d'instance "privées" auxquelles il est impossible d'accéder sauf à l'intérieur d'un objet n'existent pas en Python. Cependant, il existe une convention suivie par la plupart des codes Python: un nom précédé d'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 un membre de données). Il doit être considéré comme un détail de la mise en œuvre et sujet à modification sans préavis.
référence https://docs.python.org/2/tutorial/classes.html#private-variables-and-class-local-references
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 qui indique que private ... warning aucune application n'est effectuée par Python.
__some_varaible --► Python remplace le nom de la variable par _classname__some_varaible (nom AKA), ce qui réduit/masque sa visibilité et ressemble davantage à une variable privée.
Juste pour être honnête ici selon Python documentation
"Les variables d'instances" privées "auxquelles il est impossible d'accéder, sauf de 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)
Voici un exemple illustratif simple sur la manière dont les propriétés de double soulignement 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 ce qui suit
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 sembler évident à certains, mais cela m’a pris au dépourvu dans un environnement beaucoup plus complexe.
Puisque beaucoup de gens se réfèrent au discours de Raymond , je faciliterai un peu la tâche en écrivant ce qu'il a dit:
L'intention des doubles soulignements ne concernait pas la vie privée. 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 le contraire de la vie privée, tout est question de liberté. Vos sous-classes sont libres de remplacer une méthode sans casser les autres .
Dites que vous ne gardez pas une référence locale de perimeter
dans Circle
. Maintenant, une classe dérivée Tire
annule l'implémentation de perimeter
, sans toucher area
. Lorsque vous appelez Tire(5).area()
, il devrait théoriquement continuer à utiliser Circle.perimeter
pour le calcul, mais en réalité, il utilise Tire.perimeter
, qui n'est pas le comportement souhaité. C'est pourquoi nous avons besoin d'une référence locale dans Circle.
Mais pourquoi __perimeter
au lieu de _perimeter
? Parce que _perimeter
donne 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 une altération de nom. Il y a donc très peu de chance que la référence locale dans la classe parente soit remplacée par une classe dérivée. ainsi " permet à vos sous-classes de remplacer n'importe quelle méthode sans casser les autres ".
Si votre classe ne sera pas héritée, ou que la méthode redéfinie ne casse rien, vous n'avez simplement pas besoin de __double_leading_underscore
.
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 à l'usage du public, comme par exemple une API. Ceci et la restriction à l’importation font qu’il se comporte beaucoup comme internal
en c #.
__
Devrait être utilisé pour éviter la collision de noms dans la hiérarchie héréditaire et pour éviter la liaison tardive. Un peu comme privé en c #.
==>
Si vous voulez indiquer que quelque chose n'est pas à usage public, mais que cela doit se passer comme protected
, utilisez _
. Si vous voulez indiquer que quelque chose n'est pas à usage public, mais que cela doit se passer comme private
, utilisez __
.
C'est aussi une citation que j'aime beaucoup:
Le problème est que l'auteur d'une classe peut légitimement penser que "cet attribut/nom de méthode doit être privé, uniquement accessible 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 super-classe doit être modifiée (ce qui peut être difficile ou impossible), soit le code de la sous-classe doit utiliser des noms mutilés manuellement (ce qui est au mieux laid et fragile).
Mais le problème avec cela est à mon avis que s'il n'y a pas IDE qui vous avertit lorsque vous substituez des méthodes, la recherche de l'erreur peut prendre un certain temps si vous avez accidentellement écrasé une méthode d'une classe de base.