web-dev-qa-db-fra.com

Héritage de méthodes privées et protégées en Python

Je sais qu'il n'y a pas de «vraies» méthodes privées/protégées en Python. Cette approche ne vise à rien cacher; Je veux juste comprendre ce que fait Python.

class Parent(object):
    def _protected(self):
        pass

    def __private(self):
        pass

class Child(Parent):
    def foo(self):
        self._protected()   # This works

    def bar(self):
        self.__private()    # This doesn't work, I get a AttributeError:
                            # 'Child' object has no attribute '_Child__private'

Alors, ce comportement signifie-t-il que les méthodes "protégées" seront héritées mais que "privées" ne le sera pas? 
Ou ai-je manqué quelque chose?

48
uloco

Python n'a pas de modèle de confidentialité, il n'y a pas de modificateurs d'accès comme en C++, C # ou Java. Il n'y a pas d'attributs véritablement "protégés" ou "privés".

Les noms avec un double soulignement avant et sans le soulignement final sont mutilés pour les protéger des conflits lorsqu'ils sont hérités. Les sous-classes peuvent définir leur propre méthode __private() et celles-ci n'interféreront pas avec le même nom dans la classe parente. Ces noms sont considérés class private ; ils sont toujours accessibles de l’extérieur de la classe mais sont beaucoup moins susceptibles de s’affronter accidentellement.

La gravure est effectuée en ajoutant au préalable un tel nom avec un trait de soulignement supplémentaire et le nom de la classe (quel que soit le mode d'utilisation du nom ou son existence), en leur attribuant un espace de nom . Dans la classe Parent, tout identificateur __private est remplacé (au moment de la compilation) par le nom _Parent__private, tandis que dans la classe Child l'identificateur est remplacé par _Child__private, partout dans la définition de classe.

Ce qui suit fonctionnera:

class Child(Parent):
    def foo(self):
        self._protected()

    def bar(self):
        self._Parent__private()

Voir Classes d'identificateurs réservés dans la documentation de l'analyse lexicale:

__*
Noms de classe privés. Les noms de cette catégorie, lorsqu'ils sont utilisés dans le contexte d'une définition de classe, sont réécrits pour utiliser un formulaire mutilé afin d'éviter des conflits de noms entre les attributs "privés" des classes de base et dérivées.

et le référencé documentation sur les noms :

Nom particulier : lorsqu'un identifiant apparaissant textuellement dans une définition de classe commence par au moins deux caractères de soulignement et ne se termine pas par deux ou plus de soulignés, il est considéré comme un nom privé de cette classe. Les noms privés sont transformés en un formulaire plus long avant que le code ne soit généré pour eux. La transformation insère le nom de la classe, en supprimant les traits de soulignement principaux et en insérant un seul soulignement, devant le nom. Par exemple, l'identifiant __spam apparaissant dans une classe nommée Ham sera transformé en _Ham__spam. Cette transformation est indépendante du contexte syntaxique dans lequel l'identifiant est utilisé.

N'utilisez pas de noms de classe privés sauf si vous spécifiquement voulez éviter de dire aux développeurs souhaitant sous-classer votre classe qu'ils ne peuvent pas utiliser certains noms ou risquer de casser votre classe. En dehors des frameworks et des bibliothèques publiés, cette fonctionnalité est peu utilisée.

Le Guide de style PEP 8 Python a ceci à dire à propos de la gestion privée des noms:

Si votre classe est destinée à être sous-classe et que vous ne voulez pas que les sous-classes soient utilisées, pensez à les nommer avec des traits de soulignement doubles et aucun tiret de fin. Ceci appelle l'algorithme de changement de nom de Python, où le nom de la classe est mutilé dans le nom de l'attribut. Cela permet d'éviter les collisions de noms d'attributs si des sous-classes contiennent par inadvertance des attributs portant le même nom.

Remarque 1: Notez que seul le nom de classe simple est utilisé dans le nom mutilé. Par conséquent, si une sous-classe choisit le même nom de classe et le même nom d'attribut, vous pouvez toujours obtenir des collisions de noms.

Remarque 2: La gestion des noms peut rendre certaines utilisations, telles que le débogage et __getattr__(), moins pratiques. Cependant, le nom d'algorithme est bien documenté et facile à exécuter manuellement.

Note 3: Tout le monde n'aime pas les noms malins. Essayez d’équilibrer la nécessité d’éviter les conflits de noms accidentels avec une utilisation potentielle par les appelants expérimentés.

85
Martijn Pieters

Le double attribut __ est remplacé par _ClassName__method_name, ce qui le rend plus privé que la confidentialité sémantique suggérée par _method_name.

Techniquement, vous pouvez toujours y arriver si vous le souhaitez vraiment, mais personne ne le fera probablement. Par conséquent, pour des raisons d’abstraction du code, la méthode pourrait aussi bien être privée.

class Parent(object):
    def _protected(self):
        pass

    def __private(self):
        print("Is it really private?")

class Child(Parent):
    def foo(self):
        self._protected()

    def bar(self):
        self.__private()

c = Child()
c._Parent__private()

Cela a l'avantage supplémentaire (ou certains diraient que l'avantage principal) de permettre à une méthode de ne pas entrer en collision avec les noms de méthode de la classe enfant.

8
Tim Wilder

Aussi PEP8 dit

Utilisez un tiret bas uniquement pour les méthodes non-publiques et l'instance variables.

Pour éviter les conflits de noms avec les sous-classes, utilisez deux traits de soulignement principaux en invoquer le nom de Python en manipulant les règles.

Python corrige ces noms avec le nom de la classe: si class Foo a un attribut nommé __a, il n’est pas accessible par Foo.__a. (Un utilisateur Persistant pourrait toujours y accéder en appelant Foo._Foo__a.) Généralement, les traits de soulignement doubles doivent être utilisés uniquement pour éviter les conflits de noms avec des attributs dans des classes conçues pour être sous-classées.

Vous devriez également vous tenir à l'écart de _such_methods, par convention. Je veux dire que vous devriez les traiter comme private

6
Deck

En déclarant votre membre de données privé:

__private()

vous ne pouvez simplement pas y accéder de l'extérieur de la classe

Python supporte une technique appelée nom mangling .

Cette fonctionnalité transforme le membre de classe préfixé avec deux traits de soulignement en:

_className.memberName

si vous voulez y accéder depuis Child(), vous pouvez utiliser: self._Parent__private()

4
Kobi K

Bien que cette question soit ancienne, je l'ai rencontrée et j'ai trouvé une solution de contournement de Nice.

Dans le cas où votre nom est mutilé sur la classe parent parce que vous vouliez imiter une fonction protégée, mais que vous souhaitiez tout de même accéder à la fonction de manière simple sur la classe enfant.

parent_class_private_func_list = [func for func in dir(Child) if func.startswith ('_Parent__')]

for parent_private_func in parent_class_private_func_list:
        setattr(self, parent_private_func.replace("_Parent__", "_Child"), getattr(self, parent_private_func))        

L'idée est de remplacer manuellement le nom de la fonction parent par un nom correspondant à l'espace de nom actuel . Après l'avoir ajouté à la fonction init de la classe enfant, vous pouvez appeler la fonction facilement.

self.__private()
1
Rohi

Autant que je sache, dans le second cas, Python effectue "name mangling", ainsi le nom de la méthode __private de la classe parente est vraiment:

_Parent__private

Et vous ne pouvez pas l'utiliser chez l'enfant sous cette forme non plus

0
volcano