Herencia de métodos privados y protegidos en Python


Lo sé, no hay métodos privados/protegidos 'reales' en Python. Este enfoque no pretende ocultar nada; solo quiero entender lo que hace 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'

Entonces, ¿significa este comportamiento que los métodos 'protegidos' serán heredados pero los 'privados' no lo serán en absoluto?
¿O me perdí algo?

Author: ndmeiri, 2013-11-28

6 answers

Python no tiene modelo de privacidad, no hay modificadores de acceso como en C++, C# o Java. No hay atributos verdaderamente 'protegidos' o 'privados'.

Nombres con doble subrayado y no tirados doble subrayado son alterados para protegerlos de los enfrentamientos cuando se hereda. Las subclases pueden definir su propio método __private() y no interferirán con el mismo nombre en la clase padre. Tales nombres se consideran clase privada ; todavía son accesibles desde fuera de la clase, pero son mucho menos propensos a chocar accidentalmente.

La modificación se realiza anteponiendo cualquier nombre con un guion bajo adicional y el nombre de la clase (independientemente de cómo se use el nombre o si existe), dándoles efectivamente un espacio de nombres . En la clase Parent, cualquier identificador __private se sustituye (en el momento de la compilación) por el nombre _Parent__private, mientras que en la clase Child el identificador se sustituye por _Child__private, en todas partes de la definición de la clase.

La siguiente voluntad trabajo:

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

    def bar(self):
        self._Parent__private()

Véase Clases reservadas de identificadores en la documentación del análisis léxico:

__*
Clase-nombres privados. Los nombres en esta categoría, cuando se usan dentro del contexto de una definición de clase, se reescriben para usar una forma mutilada para ayudar a evitar choques de nombres entre atributos "privados" de las clases base y derivadas.

And the referenced documentation on names :

Nombre Privado mangling : Cuando un identificador que aparece textualmente en una definición de clase comienza con dos o más caracteres de subrayado y no termina en dos o más guiones bajos, se considera un nombre privado de esa clase. Los nombres privados se transforman en una forma más larga antes de que se genere el código para ellos. La transformación inserta el nombre de la clase, con los guiones bajos iniciales eliminados y un guion bajo único insertado, delante del nombre. Por ejemplo, el identificador __spam que aparece en una clase llamada Ham se transformará en _Ham__spam. Esta transformación es independiente del contexto sintáctico en el que se utiliza el identificador.

No uses nombres privados de clase a menos que específicamente quieras evitar tener que decirle a los desarrolladores que quieren subclase tu clase que no pueden usar ciertos nombres o arriesgarse a romper tu clase. Fuera de los frameworks y bibliotecas publicados, hay poco uso para esta característica.

La Guía de Estilo de Python PEP 8 tiene esto para decir sobre el nombre privado mangling:

Si su clase está destinada a ser subclase, y tiene atributos que no desea utilizar subclases, considere nombrarlas con doble guiones bajos iniciales y sin guiones bajos finales. Esto invoca Algoritmo de trituración de nombres de Python, donde el nombre de la clase es destrozado en el nombre del atributo. Esto ayuda a evitar el nombre del atributo las colisiones deben contener subclases inadvertidamente atributos con el el mismo nombre.

Nota 1: Tenga en cuenta que solo el nombre de clase simple se utiliza en el mangled nombre, por lo que si una subclase elige el mismo nombre de clase y atributo nombre, todavía se puede obtener nombre colisiones.

Nota 2: El mangling del nombre puede hacer ciertos usos, tales como depuración y __getattr__(), menos conveniente. Sin embargo, el algoritmo de mangling nombre está bien documentado y es fácil de realizar manualmente.

Nota 3: No a todos les gusta el destrozo de nombres. Tratar de equilibrar la necesidad de evitar nombre accidental choques con el uso potencial por parte de los llamantes avanzados.

 60
Author: Martijn Pieters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-18 18:22:26

También PEP8 dice:

Use un subrayado inicial solo para métodos e instancias no públicos variable.

Para evitar conflictos de nombres con subclases, use dos guiones bajos iniciales para invoca las reglas de trituración de nombres de Python.

Python mangles estos nombres con el nombre de la clase: si class Foo tiene un atributo llamado __a, no se puede acceder por Foo.__a. (Un insistente el usuario todavía podría obtener acceso llamando a Foo._Foo__a.) Generalmente, los guiones bajos de doble interlineado deben usarse solo para evitar conflictos de nombres con atributos en clases diseñadas para ser subclases.

Usted debe mantenerse alejado de _such_methods también, por convención. Quiero decir que deberías tratarlos como private

 6
Author: Deck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-28 09:04:06

El atributo double __ se cambia a _ClassName__method_name lo que lo hace más privado que la privacidad semántica implícita en _method_name.

Técnicamente todavía se puede llegar a él si realmente lo desea, pero presumiblemente nadie va a hacer eso, por lo que para el mantenimiento de las razones de abstracción de código, el método también podría ser privado en ese momento.

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()

Esto tiene la ventaja adicional (o algunos dirían la ventaja principal) de permitir que un método no colisione con los nombres de métodos de clase secundaria.

 6
Author: Tim Wilder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-28 09:04:35

Declarando sus datos miembro privado:

__private()

Simplemente no se puede acceder desde fuera de la clase

Python soporta una técnica llamada name mangling .

Esta característica convierte a un miembro de la clase prefijado con dos guiones bajos en:

_className.MemberName

Si quieres acceder desde Child() puedes usar: self._Parent__private()

 3
Author: Kobi K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-28 09:03:57

Aunque esta es una vieja pregunta, la encontré y encontré una buena solución.

En el caso de que nombre mangled en la clase padre porque quería imitar una función protegida, pero todavía quería acceder a la función de una manera fácil en la clase hija.

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

La idea es reemplazar manualmente el nombre de la función primaria en uno que se ajuste al espacio de nombres actual. Después de agregar esto en la función init de la clase hija, puede llamar a la función de una manera fácil manera.

self.__private()
 1
Author: Rohi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-20 15:47:34

AFAIK, en el segundo caso Python realiza "name mangling", por lo que el nombre del método __private de la clase padre es realmente:

_Parent__private

Y no se puede utilizar en el niño en esta forma tampoco

 0
Author: volcano,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-28 09:00:54