subclases - Herencia de métodos privados y protegidos en Python
python poo tutorial (6)
AFAIK, en el segundo caso Python realiza "name mangling", por lo que el nombre del método privado __private de la clase principal es realmente:
_Parent__private
Y no puedes usarlo en niños de esta forma ni
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 que "privados" no lo serán?
¿O me perdí algo?
Al declarar su miembro de datos privado:
__private()
simplemente no puedes acceder desde fuera de la clase
Python es compatible con una técnica llamada name mangling .
Esta característica convierte al miembro de la clase con el prefijo dos guiones bajos en:
_className.memberName
si quiere acceder desde Child()
puede usar: self._Parent__private()
Aunque esta es una vieja pregunta, la encontré y encontré una buena solución.
En el caso de que el nombre se destruyó en la clase principal 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 infantil.
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 de los padres en un ajuste al espacio de nombres actual. Después de agregar esto en la función init de la clase secundaria, puede llamar a la función de una manera fácil.
self.__private()
El atributo doble __
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 puede obtenerlo si realmente lo desea, pero probablemente nadie lo hará, 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 punto.
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 el lado positivo adicional (o algunos dirían que es el lado positivo) de permitir que un método no colisione con los nombres de los métodos de la clase infantil.
Python no tiene un modelo de privacidad, no hay modificadores de acceso como en C ++, C # o Java. No hay atributos realmente "protegidos" o "privados".
Los nombres con un guion bajo doble inicial y ningún guion bajo doble final son mutilados para protegerlos de los enfrentamientos cuando se heredan. Las subclases pueden definir su propio método __private()
y no interferirán con el mismo nombre en la clase principal. Tales nombres se consideran privados de clase ; todavía son accesibles desde fuera de la clase, pero es mucho menos probable que choquen accidentalmente.
Mangling se realiza anteponiendo cualquier nombre con un guión bajo adicional y el nombre de la clase (independientemente de cómo se usa el nombre o si existe), efectivamente dándoles un espacio de nombre . En la clase Parent
, cualquier identificador __private
se reemplaza (en tiempo de compilación) por el nombre _Parent__private
, mientras que en la clase Child
el identificador se reemplaza por _Child__private
, en todas partes en la definición de clase.
Lo siguiente funcionará:
class Child(Parent):
def foo(self):
self._protected()
def bar(self):
self._Parent__private()
Consulte Clases de identificadores reservados en la documentación de análisis léxico:
__*
Nombres de clase privada. Los nombres en esta categoría, cuando se usan dentro del contexto de una definición de clase, se vuelven a escribir para usar una forma truncada para ayudar a evitar los conflictos de nombres entre los atributos "privados" de las clases base y derivadas.
y la documentación referenciada en los nombres :
Eliminación de nombres privados : 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 a 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 caracteres de subrayado iniciales eliminados y un único guión bajo insertado, delante del nombre. Por ejemplo, el identificador
__spam
ocurre 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 use nombres de clase privada a menos que específicamente desee evitar tener que decirles a los desarrolladores que quieren subclasificar a su clase que no pueden usar ciertos nombres o arriesgarse a romper su clase. Fuera de los marcos y las bibliotecas publicados, hay poco uso para esta función.
La Guía de estilo de PEP 8 Python tiene esto que decir sobre la creación de nombres privados:
Si su clase está destinada a ser subclasificada, y tiene atributos que no quiere que usen las subclases, considere nombrarlos con guiones bajos dobles y sin guiones bajos posteriores. Esto invoca el algoritmo de manipulación de nombre de Python, donde el nombre de la clase se destroza en el nombre del atributo. Esto ayuda a evitar colisiones de nombres de atributos si las subclases contienen inadvertidamente atributos con el mismo nombre.
Nota 1: Tenga en cuenta que solo se utiliza el nombre de clase simple en el nombre destruido, de modo que si una subclase elige tanto el mismo nombre de clase como el nombre de atributo, aún puede obtener colisiones de nombres.
Nota 2: El cambio de nombre puede hacer que ciertos usos, como la depuración y
__getattr__()
, sean menos convenientes. Sin embargo, el algoritmo de creación de nombres está bien documentado y es fácil de realizar de forma manual.Nota 3: No a todos les gusta el cambio de nombre. Trate de equilibrar la necesidad de evitar conflictos de nombres accidentales con el uso potencial por parte de los llamadores avanzados.
También dice PEP8
Utilice un guion bajo principal solo para métodos no públicos y variables de instancia.
Para evitar conflictos de nombres con subclases, utilice dos guiones bajos principales para invocar las reglas de creación de nombres de Python.
Python transforma estos nombres con el nombre de la clase: si la
class Foo
tiene un atributo llamado__a
,Foo.__a
no puede acceder a ella. (Un usuario insistente aún podría obtener acceso al llamar aFoo._Foo__a
). En general, los guiones bajos dobles deben usarse solo para evitar conflictos de nombres con los atributos en las clases diseñadas para ser subclasificadas.
También debe mantenerse alejado de _such_methods
, por convención. Quiero decir que debes tratarlos como private