web-dev-qa-db-fra.com

Python changement de nom

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

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

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

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

Je viens de langues qui vous entraînent à penser que tout devrait ê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 - à partir du public et en allant vers le caché - est étrange pour moi.

84
Paul Manta

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 en souciez 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 façon controversée de faire les choses. Python les débutants détestent ça et même certains vieux Python les gars 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 vraiment souhaitez envoyer le message "Impossible de toucher à cela!" pour vos utilisateurs, la manière habituelle est de faire précéder la variable avec un trait de soulignement. Ce n'est qu'une convention, mais les gens la comprennent et font preuve d'une double prudence 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é et les noms d'attribut:

 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

Et le double soulignement? Eh bien, la magie du double soulignement est principalement utilisée pour éviter une surcharge accidentelle des méthodes et des 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é.

[~ # ~] modifier [~ # ~] : Pourquoi en est-il ainsi? Eh bien, le style habituel Python ne met pas l'accent sur le fait de rendre les choses privées - au contraire! Il y a beaucoup de raisons à cela - la plupart controversées ... Voyons-en quelques-unes.

Python a des propriétés

La plupart des langues OO utilisent aujourd'hui l'approche inverse: ce qui ne devrait pas être utilisé ne devrait pas être visible, donc les attributs devraient être privés. Théoriquement, cela produirait des classes plus faciles à gérer et moins couplées, car personne ne le ferait changer les valeurs à l'intérieur des objets avec insouciance.

Mais ce n'est pas si simple. Par exemple, Java ont beaucoup d'attributs et des getters qui obtiennent les valeurs et des paramètres qui ne font que définir les valeurs. Vous avez besoin, disons, de sept lignes de code pour déclarer un seul attribut - ce qu'un programmeur Python dirait inutilement complexe. De plus, en pratique, vous écrivez simplement tout ce code dans obtenir un champ public, car vous pouvez modifier sa valeur à l'aide des getters et setters.

Alors pourquoi suivre cette politique de confidentialité par défaut? Rendez simplement vos attributs publics par défaut. Bien sûr, cela pose problème en Java, car si vous décidez d'ajouter une certaine validation à votre attribut, cela vous obligerait à 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, dans Java (et autres langages), la valeur par défaut est de toute façon d'utiliser les getters et setters, car ils peuvent être ennuyeux à écrire mais peuvent vous faire gagner beaucoup de temps si vous vous trouvez dans la situation Je l'ai décrit.

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

 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 modifier les éléments person.age = age 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 tout en utilisant person.age = age, Pourquoi ajouteriez-vous des champs privés et des getters et setters?

(Voir aussi Python n'est pas Java et cet article sur les inconvénients de l'utilisation des getters et setters .).

Tout est visible de toute façon - et essayer de cacher ne fait que compliquer 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épondre à 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 des attributs publics.

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

Le problème n'est pas possible de voir - il est requis pour 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 permet de l'utiliser 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 Python).

Maintenant, si vous avez écrit votre classe de manière à pouvoir l'utiliser sans avoir à penser aux détails de l'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 . (Recherchez "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. En outre, vous pouvez 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: car c'est la culture de ces langages, et chaque choix a ses avantages et ses 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 :)

162
brandizzi

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

Une solution bien meilleure est la route qui Python encourage: Vos classes et variables devraient être bien documentées et leur comportement clair. La source devrait être disponible. C'est une manière beaucoup plus extensible et fiable d'écrire code.

Ma stratégie en Python est la suivante:

  1. Il suffit d'écrire la fichue chose, de ne faire aucune hypothèse sur la façon dont vos données doivent ê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 de début pour les éléments qui probablement ne seront pas utilisés en externe et ne font pas partie de l'interface normale de "code client".
  3. N'utilisez le double trait de soulignement que pour les objets purement pratiques à l'intérieur de la classe ou qui causeront des dommages considérables en cas d'exposition accidentelle.

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

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

15
Matt Joiner

Premièrement - Qu'est-ce que le changement de nom?

Le changement de nom est invoqué lorsque vous êtes dans une définition de classe et utilisez __any_name Ou __any_name_, C'est-à-dire deux ( ou plus) des 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, que faire?

L'utilisation ostensible est d'empêcher les sous-classes d'utiliser un attribut utilisé par la classe.

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

L'inconvénient est qu'il 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 soulignement dans la source et un nom altéré 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 dépassent comme un pouce endolori et ne semblent pas justifiées.

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

PEP 8

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

Il y a une 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 souhaitez pas que les sous-classes utilisent, envisagez de les nommer avec des soulignements doubles de début et sans soulignement de fin.

  1. Notez que seul le nom de classe simple est utilisé dans le nom modifié, 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 pratiques. Cependant, l'algorithme de changement de nom est bien documenté et facile à exécuter manuellement.

  3. Tout le monde n'aime pas le changement de nom. 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 tirets bas) dans une définition de classe, le nom sera modifié et un trait de soulignement suivi du nom de la classe sera ajouté à 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 modifiés que lorsque la définition de classe sera 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 raison valable contre cela, mais c'est quelque chose à considérer si vous avez un public apprenant.

Un soulignement?

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

Lorsque mon intention est que les utilisateurs gardent les mains sur un attribut, j'ai tendance à n'utiliser que le seul trait de soulignement, mais c'est parce que dans mon modèle mental, les sous-classes 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 la modification de nom, et s'ils ne pouvaient pas faire aussi bien avec un seul soulignement, en gardant à l'esprit que si les sous-classes choisissent le même nom pour la classe et l'attribut class il y aura malgré tout une collision de noms.

14
Aaron Hall

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

D'autres langues rendent difficile de rendre privé ce qui était autrefois public. C'est à dire. Je casserai beaucoup de code si je rend ma variable privée ou protégée. Mais avec des propriétés dans python ce n'est pas le cas. Au contraire, je peux maintenir la même interface même en réorganisant les données internes.

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

9
Winston Ewert

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 continuent de dire que le double soulignement n'est pas privé (c'est vraiment).

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

Maintenant, chaque fois que vous appelez une fonction en Python, vous utilisez de manière inhérente 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 le dernier morceau de code. Principalement parce que l'utilisation de getattr est déjà laide avec un seul appel de fonction. La situation empire à partir de là.

Donc, avec cela, ne peut pas être une 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 elles ne savent pas d'où la fonction est appelée).

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

Avertissement: je n'ai jamais entendu personne du développement Python dire quelque chose comme ça. 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 que pour le temps de compilation.

6
Jonathan Sternberg

La réponse choisie explique bien comment les propriétés suppriment le besoin de 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 Pythonic que d'essayer de cacher des méthodes avec un changement de nom.

4
Tanner_Wauchope

Premièrement: pourquoi voulez-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 d'autres le font.

Si vous ne voulez vraiment vraiment pas que les gens utilisent quelque chose, ajoutez n souligné devant. C'est tout ... Les pythonistes savent que les choses avec un trait de soulignement ne sont pas garanties de fonctionner à chaque fois et peuvent changer à votre insu.

C'est ainsi que nous vivons et nous sommes d'accord avec ça.

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

4
JBernardo

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

  • deux soulignements principaux (__a)
  • trait de soulignement simple (_a)
  • pas 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 empêcher que cette variable soit remplacée par l'une des sous-classes. Ce concept est connu sous le nom de "Name Mangling" en python. Vous pouvez y accéder comme ceci:

testObj2 = Test()
print testObj2._Test__a

test1

De même, dans le cas de _a, la variable est juste pour 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é :)

3
Nitish Chauhan

À première vue, il 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.

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

Par conséquent, je vous recommande d'utiliser le soulignement unique par défaut pour les membres "privés".

2
Roman Bodnarchuk

"En cas de doute quant à savoir si une variable doit être privée ou protégée, il vaut mieux opter pour 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 mangling de nom dans Python ont été prises en compte dans d'autres réponses. À partir du même lien ,

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

0
Yaroslav Nikitenko