Mangling de nom de Python

110

Dans d'autres langues, une directive générale qui aide à produire un meilleur code est toujours de rendre tout aussi caché que possible. En cas de doute quant à savoir si une variable doit être privée ou protégée, il est préférable d'utiliser private.

Est-ce qu'il en va de même pour Python? Dois-je utiliser deux traits de soulignement sur tout au début, et les rendre moins cachés (un seul trait de soulignement) car j'en ai besoin?

Si la convention n'utilise qu'un seul trait de soulignement, j'aimerais également en connaître la raison.

Voici un commentaire que j'ai laissé sur la réponse de JBernardo . Cela explique pourquoi j'ai posé cette question et aussi pourquoi j'aimerais savoir pourquoi Python est différent des autres langages:

Je viens de langues qui vous apprennent à penser que tout doit être aussi public que nécessaire et pas plus. Le raisonnement est que cela réduira les dépendances et rendra le code plus sûr à modifier. La manière Python de faire les choses à l'envers - en partant du public et en allant vers caché - m'est étrange.

Paul Manta
la source

Réponses:

182

En cas de doute, laissez-le "public" - je veux dire, n'ajoutez rien pour masquer le nom de votre attribut. Si vous avez une classe avec une valeur interne, ne vous inquiétez pas. Au lieu d'écrire:

class Stack(object):

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

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

écrivez ceci par défaut:

class Stack(object):

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

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

C'est à coup sûr une manière controversée de faire les choses. Les débutants en Python le détestent et même certains vieux gars de Python méprisent cette valeur par défaut - mais c'est quand même la valeur par défaut, donc je vous recommande vraiment de la suivre, même si vous vous sentez mal à l'aise.

Si vous voulez vraiment envoyer le message "Je ne peux pas y toucher!" pour vos utilisateurs, la méthode habituelle consiste à faire précéder la variable d' un trait de soulignement. Ce n'est qu'une convention, mais les gens la comprennent et font double attention lorsqu'ils traitent de telles choses:

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)

Cela peut également être utile pour éviter les conflits entre les noms de propriétés et les noms d'attributs:

 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

Qu'en est-il du double trait de soulignement? Eh bien, la magie du double trait de soulignement est principalement utilisée pour éviter la surcharge accidentelle des méthodes et les conflits de noms avec les attributs des superclasses . Cela peut être très utile si vous écrivez une classe qui devrait être étendue plusieurs fois.

Si vous souhaitez l'utiliser à d'autres fins, vous pouvez, mais ce n'est ni habituel ni recommandé.

EDIT : Pourquoi est-ce ainsi? Eh bien, le style Python habituel ne met pas l'accent sur le fait de rendre les choses privées - au contraire! Il y a de nombreuses raisons à cela - la plupart sont controversées ... Voyons quelques-unes d'entre elles.

Python a des propriétés

La plupart des langages OO utilisent aujourd'hui l'approche opposée: ce qui ne doit pas être utilisé ne doit pas être visible, les attributs doivent donc être privés. Théoriquement, cela donnerait des classes plus faciles à gérer et moins couplées, car personne ne changerait imprudemment les valeurs à l'intérieur des objets.

Cependant, ce n'est pas si simple. Par exemple, les classes Java ont de nombreux attributs et getters qui n'obtiennent que les valeurs et les setters qui ne font que définir les valeurs. Vous avez besoin, disons, de sept lignes de code pour déclarer un seul attribut - qui, selon un programmeur Python, est inutilement complexe. De plus, en pratique, vous écrivez simplement tout ce code pour obtenir un champ public, car vous pouvez modifier sa valeur en utilisant les getters et les setters.

Alors, pourquoi suivre cette politique privée par défaut? Rendez simplement vos attributs publics par défaut. Bien sûr, cela pose un problème en Java, car si vous décidez d'ajouter une validation à votre attribut, il vous faudrait tout changer

person.age = age;

dans votre code pour, disons,

person.setAge(age);

setAge() étant:

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

Donc en Java (et dans d'autres langages), la valeur par défaut est d'utiliser les getters et les setters de toute façon, car ils peuvent être ennuyeux à écrire mais peuvent vous faire gagner beaucoup de temps si vous vous trouvez dans la situation que j'ai décrite.

Cependant, vous n'avez pas besoin de le faire en Python, car Python a des propriétés. Si vous avez ce cours:

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

et puis vous décidez de valider les âges, vous n'avez pas besoin de changer les person.age = agemorceaux de votre code. Ajoutez simplement une propriété (comme indiqué ci-dessous)

 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

Si vous pouvez le faire et continuer à l'utiliser person.age = age, pourquoi ajouteriez-vous des champs privés, des getters et des setters?

(En outre, voir Python n'est pas Java et cet article sur les inconvénients liés à l'utilisation des getters et des setters .).

Tout est visible de toute façon - et essayer de se cacher complique simplement votre travail

Même dans les langues où il existe des attributs privés, vous pouvez y accéder via une sorte de bibliothèque de réflexion / introspection. Et les gens le font beaucoup, dans des cadres et pour résoudre des besoins urgents. Le problème est que les bibliothèques d'introspection ne sont qu'un moyen difficile de faire ce que vous pourriez faire avec les attributs publics.

Puisque Python est un langage très dynamique, il est tout simplement contre-productif d'ajouter ce fardeau à vos classes.

Le problème n'est pas possible de voir - il est nécessaire de voir

Pour un Pythonista, l'encapsulation n'est pas l'incapacité de voir les internes des classes, mais la possibilité d'éviter de la regarder. Ce que je veux dire, c'est que l'encapsulation est la propriété d'un composant qui lui permet d'être utilisé sans que l'utilisateur se soucie des détails internes. Si vous pouvez utiliser un composant sans vous soucier de son implémentation, alors il est encapsulé (de l'avis d'un programmeur Python).

Maintenant, si vous avez écrit votre classe de telle manière que vous pouvez l'utiliser sans avoir à penser aux détails d'implémentation, il n'y a aucun problème si vous voulez regarder à l'intérieur de la classe pour une raison quelconque. Le fait est que votre API doit être bonne et le reste, ce sont les détails.

Guido l'a dit

Eh bien, ce n'est pas controversé: il l'a dit, en fait . (Cherchez «kimono ouvert».)

C'est la culture

Oui, il y a quelques raisons, mais aucune raison critique. Il s'agit principalement d'un aspect culturel de la programmation en Python. Franchement, cela pourrait aussi être l'inverse - mais ce n'est pas le cas. De plus, vous pourriez tout aussi facilement demander l'inverse: pourquoi certaines langues utilisent-elles des attributs privés par défaut? Pour la même raison principale que pour la pratique Python: parce que c'est la culture de ces langages, et chaque choix a des avantages et des inconvénients.

Puisqu'il existe déjà cette culture, il est conseillé de la suivre. Sinon, vous serez ennuyé par les programmeurs Python vous disant de supprimer le __de votre code lorsque vous posez une question dans Stack Overflow :)

brandizzi
la source
1. L'encapsulation sert à protéger les invariants de classe. Ne pas cacher des détails inutiles au monde extérieur car ce serait un ennui. 2. "Le point est: votre API doit être bonne et le reste est des détails." C'est vrai. Et les attributs publics font partie de votre API. Aussi, parfois les setters publics sont appropriés (concernant vos invariants de classe) et parfois ils ne le sont pas. Une API qui a des setters publics qui ne devraient pas être publics (risque de violation des invariants) est une mauvaise API. Cela signifie que vous devez de toute façon penser à la visibilité de chaque setter et avoir un «défaut» signifie moins.
Jupiter le
21

Premièrement - Qu'est-ce que la mutilation des noms?

Le changement de nom est appelé lorsque vous êtes dans une définition de classe et que vous utilisez __any_nameou __any_name_, c'est-à-dire deux (ou plus) traits de soulignement de début et au plus un trait de soulignement de fin.

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

Et maintenant:

>>> [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_'

En cas de doute, faire quoi?

L'utilisation apparente est d'empêcher les sous-classeurs d'utiliser un attribut que la classe utilise.

Une valeur potentielle consiste à éviter les collisions de noms avec les sous-classes qui souhaitent remplacer le comportement, afin que la fonctionnalité de classe parent continue de fonctionner comme prévu. Cependant, l' exemple de la documentation Python n'est pas substituable à Liskov, et aucun exemple ne me vient à l'esprit où j'ai trouvé cela utile.

Les inconvénients sont que cela augmente la charge cognitive pour la lecture et la compréhension d'une base de code, et en particulier lors du débogage où vous voyez le double nom de soulignement dans la source et un nom mutilé dans le débogueur.

Mon approche personnelle est de l'éviter intentionnellement. Je travaille sur une très grande base de code. Les rares utilisations de celui-ci ressortent comme un pouce endolori et ne semblent pas justifiées.

Vous devez en être conscient afin de le savoir quand vous le voyez.

PEP 8

PEP 8 , le guide de style de la bibliothèque standard Python, dit actuellement (abrégé):

Il y a une certaine controverse sur l'utilisation de __names.

Si votre classe est destinée à être sous-classée et que vous avez des attributs que vous ne voulez pas que les sous-classes utilisent, envisagez de les nommer avec des traits de soulignement doubles et sans traits de soulignement à la fin.

  1. Notez que seul le nom de classe simple est utilisé dans le nom mutilé, donc si une sous-classe choisit à la fois le même nom de classe et le même nom d'attribut, vous pouvez toujours obtenir des collisions de noms.

  2. Le changement de nom peut rendre certaines utilisations, telles que le débogage et __getattr__(), moins pratique. Cependant, l'algorithme de transformation des noms est bien documenté et facile à exécuter manuellement.

  3. Tout le monde n'aime pas la mutilation des noms. Essayez d'équilibrer la nécessité d'éviter les conflits de noms accidentels avec une utilisation potentielle par les appelants avancés.

Comment ça marche?

Si vous ajoutez deux traits de soulignement (sans terminer les doubles traits de soulignement) dans une définition de classe, le nom sera mutilé et un trait de soulignement suivi du nom de la classe sera ajouté au début de l'objet:

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

Notez que les noms ne seront mutilés que lorsque la définition de classe est analysée:

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

De plus, les nouveaux utilisateurs de Python ont parfois du mal à comprendre ce qui se passe lorsqu'ils ne peuvent pas accéder manuellement à un nom qu'ils voient défini dans une définition de classe. Ce n'est pas une bonne raison contre cela, mais c'est quelque chose à considérer si vous avez un public apprenant.

Un soulignement?

Si la convention n'utilise qu'un seul trait de soulignement, j'aimerais également en connaître la raison.

Lorsque mon intention est que les utilisateurs gardent la main sur un attribut, j'ai tendance à n'utiliser qu'un seul trait de soulignement, mais c'est parce que dans mon modèle mental, les sous-classeurs auraient accès au nom (qu'ils ont toujours, car ils peuvent facilement repérer le nom mutilé de toute façon).

Si je __passais en revue le code qui utilise le préfixe, je demanderais pourquoi ils invoquent le changement de nom, et s'ils ne pourraient pas faire aussi bien avec un seul trait de soulignement, en gardant à l'esprit que si les sous-classes choisissent les mêmes noms pour la classe et attribut de classe il y aura une collision de nom malgré cela.

Salle Aaron
la source
16

Je ne dirais pas que la pratique produit un meilleur code. Les modificateurs de visibilité ne font que vous distraire de la tâche à accomplir et, en tant qu'effet secondaire, forcent votre interface à être utilisée comme vous le souhaitez. De manière générale, l'application de la visibilité empêche les programmeurs de gâcher les choses s'ils n'ont pas lu correctement la documentation.

Une bien meilleure solution est la route que Python encourage: vos classes et variables doivent être bien documentées et leur comportement clair. La source doit être disponible. C'est un moyen beaucoup plus extensible et fiable d'écrire du code.

Ma stratégie en Python est la suivante:

  1. Écrivez simplement la fichue chose, ne faites aucune supposition sur la façon dont vos données devraient être protégées. Cela suppose que vous écrivez pour créer les interfaces idéales pour vos problèmes.
  2. Utilisez un trait de soulignement pour les éléments qui ne seront probablement pas utilisés en externe et qui ne font pas partie de l'interface normale du «code client».
  3. Utilisez le double trait de soulignement uniquement pour les choses qui sont purement pratiques à l'intérieur de la classe, ou qui causeront des dommages considérables en cas d'exposition accidentelle.

Surtout, ce que tout fait doit être clair. Documentez-le si quelqu'un d'autre l'utilise. Documentez-le si vous voulez qu'il soit utile dans un an.

En remarque, vous devriez en fait utiliser protected dans ces autres langues: vous ne savez jamais que votre classe pourrait être héritée plus tard et pour ce qu'elle pourrait être utilisée. Il est préférable de ne protéger que les variables dont vous êtes certain qu'elles ne peuvent ou ne doivent pas être utilisées par du code étranger.

Matt Joiner
la source
9

Vous ne devriez pas commencer avec des données privées et les rendre publiques si nécessaire. Au contraire, vous devriez commencer par déterminer l'interface de votre objet. C'est-à-dire que vous devriez commencer par déterminer ce que le monde voit (les trucs publics) et ensuite déterminer quels trucs privés sont nécessaires pour que cela se produise.

D'autres termes rendent difficile de rendre privé ce qui était autrefois public. Ie je vais casser beaucoup de code si je rend ma variable privée ou protégée. Mais avec les propriétés en python, ce n'est pas le cas. Au contraire, je peux conserver la même interface même en réorganisant les données internes.

La différence entre _ et __ est que python tente en fait d'appliquer ce dernier. Bien sûr, cela n'essaie pas vraiment mais cela rend les choses difficiles. Avoir _ dit simplement aux autres programmeurs quelle est l'intention, ils sont libres de l'ignorer à leurs risques et périls. Mais ignorer cette règle est parfois utile. Les exemples incluent le débogage, les hacks temporaires et l'utilisation de code tiers qui n'était pas destiné à être utilisé comme vous l'utilisez.

Winston Ewert
la source
6

Il y a déjà beaucoup de bonnes réponses à cela, mais je vais en proposer une autre. C'est aussi en partie une réponse aux gens qui n'arrêtent pas de dire que le double soulignement n'est pas privé (c'est vraiment le cas).

Si vous regardez Java / C #, les deux ont private / protected / public. Tous ces éléments sont des constructions au moment de la compilation . Ils ne sont appliqués qu'au moment de la compilation. Si vous utilisiez la réflexion en Java / C #, vous pourriez facilement accéder à la méthode privée.

Désormais, chaque fois que vous appelez une fonction en Python, vous utilisez intrinsèquement la réflexion. Ces morceaux de code sont les mêmes en Python.

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

La syntaxe "dot" n'est que du sucre syntaxique pour ce dernier morceau de code. Surtout parce que l'utilisation de getattr est déjà moche avec un seul appel de fonction. Cela empire à partir de là.

Donc, avec cela, il ne peut pas y avoir de version Java / C # de private, car Python ne compile pas le code. Java et C # ne peuvent pas vérifier si une fonction est privée ou publique au moment de l'exécution, car ces informations ont disparu (et il ne sait pas d'où la fonction est appelée).

Maintenant, avec cette information, le nom de la déformation du double trait de soulignement est le plus logique pour atteindre le "caractère privé". Maintenant, lorsqu'une fonction est appelée à partir de l'instance 'self' et qu'elle remarque qu'elle commence par '__', elle exécute simplement le changement de nom juste là. C'est juste plus de sucre syntaxique. Ce sucre syntaxique permet l'équivalent de «privé» dans un langage qui n'utilise que la réflexion pour l'accès aux données membres.

Avertissement: je n'ai jamais entendu personne du développement Python dire quelque chose de tel. La vraie raison du manque de «privé» est culturelle, mais vous remarquerez également que la plupart des langages de script / interprétés n'ont pas de privé. Un privé strictement exécutoire n'est pratique à rien, sauf au moment de la compilation.

Jonathan Sternberg
la source
4

Premièrement: pourquoi souhaitez-vous masquer vos données? Pourquoi est-ce si important?

La plupart du temps, vous ne voulez pas vraiment le faire, mais vous le faites parce que les autres le font.

Si vous ne voulez vraiment vraiment pas que les gens utilisent quelque chose, ajoutez un trait de soulignement devant. Voilà ... Les pythonistes savent que les choses avec un seul trait de soulignement ne sont pas garanties de fonctionner à chaque fois et peuvent changer sans que vous le sachiez.

C'est comme ça que nous vivons et cela nous convient.

L'utilisation de deux traits de soulignement rendra votre classe si mauvaise en sous-classe que même vous ne voudrez pas travailler de cette façon.

JBernardo
la source
2
Vous avez omis la raison pour laquelle le double soulignement est mauvais pour le sous-classement ... cela améliorerait votre réponse.
Matt Joiner le
2
Etant donné que les doubles traits de soulignement sont en fait juste pour éviter les collisions de noms avec les sous-classes (comme une façon de dire "mains libres" aux sous-classes), je ne vois pas comment la transformation des noms crée un problème.
Aaron Hall
4

La réponse choisie explique bien comment les propriétés suppriment le besoin d' attributs privés , mais j'ajouterais également que les fonctions au niveau du module suppriment le besoin de méthodes privées .

Si vous transformez une méthode en fonction au niveau du module, vous supprimez la possibilité pour les sous-classes de la remplacer. Déplacer certaines fonctionnalités au niveau du module est plus pythonique que d'essayer de masquer des méthodes avec une modification des noms.

Tanner_Wauchope
la source
3

L'extrait de code suivant expliquera tous les différents cas:

  • deux traits de soulignement (__a)
  • trait de soulignement unique (_a)
  • pas de trait de soulignement (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

impression de tous les attributs valides de l'objet de test

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

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

Ici, vous pouvez voir que le nom de __a a été changé en _Test__a pour éviter que cette variable ne soit remplacée par l'une des sous-classes. Ce concept est connu sous le nom de "Name Mangling" en python. Vous pouvez accéder à ceci comme ceci:

testObj2 = Test()
print testObj2._Test__a

test1

De même, dans le cas de _a, la variable sert simplement à informer le développeur qu'elle doit être utilisée comme variable interne de cette classe, l'interpréteur python ne fera rien même si vous y accédez, mais ce n'est pas une bonne pratique.

testObj3 = Test()
print testObj3._a

test2

une variable peut être accessible de n'importe où, c'est comme une variable de classe publique.

testObj4 = Test()
print testObj4.a

test3

J'espère que la réponse vous a aidé :)

Nitish Chauhan
la source
2

À première vue, cela devrait être le même que pour les autres langages (sous «autre», je veux dire Java ou C ++), mais ce n'est pas le cas.

En Java, vous avez rendu privées toutes les variables qui ne devraient pas être accessibles à l'extérieur. En même temps, en Python, vous ne pouvez pas y parvenir car il n'y a pas de "caractère privé" (comme le dit l'un des principes de Python - "Nous sommes tous des adultes"). Donc, le double soulignement signifie seulement "Les gars, n'utilisez pas ce champ directement". La même signification a un trait de soulignement unique, qui dans le même temps ne cause aucun mal de tête lorsque vous devez hériter d'une classe considérée (juste un exemple de problème possible causé par un double tiret bas).

Donc, je vous recommande d'utiliser un seul trait de soulignement par défaut pour les membres "privés".

Romain Bodnarchuk
la source
Utilisez un double trait de soulignement pour "privé" et un seul tiret bas pour "protégé". Habituellement, les gens n'utilisent qu'un seul trait de soulignement pour tout (le double soulignement aidera à renforcer la confidentialité, ce qui est généralement contraire au style Python).
Jonathan Sternberg
1
Mais cela ne fait-il pas deux traits de soulignement similaires à privé et un trait de soulignement similaire à protégé? Pourquoi ne pas simplement commencer par «privé»?
Paul Manta
@Paul Non, ce n'est pas le cas. Il n'y a pas de privé en Python et vous ne devriez pas essayer d'y parvenir.
Roman Bodnarchuk
@Roman Conceptuellement parlant ... Notez les citations autour de «privé».
Paul Manta
1

"En cas de doute sur la question de savoir si une variable doit être privée ou protégée, il vaut mieux choisir private." - oui, c'est la même chose en Python.

Certaines réponses ici parlent de «conventions», mais ne donnent pas les liens vers ces conventions. Le guide faisant autorité pour Python, PEP 8 déclare explicitement:

En cas de doute, choisissez non public; il est plus facile de le rendre public plus tard que de rendre un attribut public non public.

La distinction entre public et privé, et la transformation des noms en Python ont été prises en compte dans d'autres réponses. Du même lien,

Nous n'utilisons pas le terme «privé» ici, car aucun attribut n'est vraiment privé en Python (sans une quantité de travail généralement inutile).

Yaroslav Nikitenko
la source
-1

#EXEMPLE DE PROGRAMME POUR LA MANGULATION DE NOM PYTHON

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


[n for n in dir(Demo) if 'any' in n]   # GIVES OUTPUT AS ['_Demo__any_name', 
                                       #    '_Demo__any_other_name_']
jai ganesh
la source
1
Cela ne répond pas du tout à la question - cela montre un exemple mais cela ne va pas au cœur de la question réelle. Cela et cette question a presque 9 ans avec une réponse acceptée. Cela ajoute-t-il quelque chose aux réponses déjà fournies ici?
rayryeng il y a