2012-07-24 20 views
7

Las clases de Python no tienen ningún concepto de público/privado, por lo que se nos dice que no toquemos algo que comience con un guión bajo a menos que lo hayamos creado. ¿Pero esto no requiere un conocimiento completo de todas las clases de las cuales heredamos, directa o indirectamente? Testigo:¿Requiere Python un conocimiento profundo de todas las clases en la cadena de herencia?

class Base(object): 
    def __init__(self): 
     super(Base, self).__init__() 
     self._foo = 0 

    def foo(self): 
     return self._foo + 1 

class Sub(Base): 
    def __init__(self): 
     super(Sub, self).__init__() 
     self._foo = None 

Sub().foo() 

era de esperar, un TypeError se eleva cuando se evalúa None + 1. Así que tengo que saber que existe _foo en la clase base. Para evitar esto, se puede usar __foo en su lugar, lo que soluciona el problema modificando el nombre. Esto parece ser, si no elegante, una solución aceptable. Sin embargo, ¿qué ocurre si Base hereda de una clase (en un paquete separado) llamado Sub? Ahora __foo en mi Sub anula __foo en el abuelo Sub.

Esto implica que tengo que conocer toda la cadena de herencia, incluidos todos los objetos "privados" que utiliza cada uno. El hecho de que Python sea de tipo dinámico lo hace aún más difícil, ya que no hay declaraciones para buscar. La peor parte, sin embargo, es probablemente el hecho de que Base podría heredar desde object en este momento, pero en una versión futura, cambiará a heredar de Sub. Claramente, si sé que Sub se hereda de, puedo cambiar el nombre de mi clase, por muy molesto que sea. Pero no puedo ver el futuro.

¿No es este un caso en el que un tipo de datos privados podría evitar un problema? ¿Cómo, en Python, puedo estar seguro de que no pisaré accidentalmente los dedos de los pies si esos dedos podrían surgir en algún momento en el futuro?

EDIT: Al parecer, no he dejado clara la pregunta principal. Estoy familiarizado con el cambio de nombre y la diferencia entre un subrayado simple y doble. La pregunta es: ¿cómo puedo lidiar con el hecho de que podría chocar con las clases cuya existencia no conozco en este momento? Si mi clase padre (que está en un paquete que no escribí) pasa a heredar de una clase con el mismo nombre que mi clase, incluso el cambio de nombre no ayudará. ¿Me equivoco al ver esto como un caso (esquina) que los verdaderos miembros privados resolverían, pero que Python tiene problemas con?

EDITAR: Conforme a lo solicitado, el siguiente es un ejemplo completo:

Archivo parent.py:

class Sub(object): 
    def __init__(self): 
     self.__foo = 12 
    def foo(self): 
     return self.__foo + 1 
class Base(Sub): 
    pass 

Archivo sub.py:

import parent 
class Sub(parent.Base): 
    def __init__(self): 
     super(Sub, self).__init__() 
     self.__foo = None 
Sub().foo() 

se llama de foo El abuelo, pero mi __foo se usa.

Obviamente, usted no escribiría código como este usted mismo, pero parent podría ser facilitado por un tercero, cuyos detalles podrían cambiar en cualquier momento.

+0

¿Puedes publicar un ejemplo para demostrar que la otra clase con el mismo nombre no sombrea completamente tu clase existente? ... o tal vez todavía no entiendo ... nunca mencioné ... hice una y tu derecha supongo que esta es una esquina caso ... solo trato de no usar los nombres privados comunes de var/class, supongo –

Respuesta

2

Esto implica que tengo que conocer toda la cadena de herencia. . .

Sí, debe saber toda la cadena de herencia, o los documentos para el objeto que está subclasificando directamente deberían decirle lo que necesita saber.

La creación de subclases es una función avanzada que debe tratarse con cuidado.

Un buen ejemplo de docs que especifican lo que debería ser anulado en una subclase es el threading class:

Esta clase representa una actividad que se ejecuta en un hilo de control separado. Hay dos formas de especificar la actividad: pasando un objeto invocable al constructor o anulando el método run() en una subclase. Ningún otro método (excepto el constructor) debe ser anulado en una subclase. En otras palabras, solo anule los métodos __init__() y run() de esta clase.

7

Uso private names (en lugar de protegidas unos), comenzando con un doble subrayado:

class Sub(Base): 
    def __init__(self): 
     super(Sub, self).__init__() 
     self.__foo = None 
     # ^^ 

no entrará en conflicto con _foo o __foo en Base.Esto se debe a que Python reemplaza el guion bajo doble con un guion bajo y el nombre de la clase; las dos líneas siguientes son equivalentes:

class Sub(Base): 
    def x(self): 
     self.__foo = None # .. is the same as .. 
     self._Sub__foo = None 

(En respuesta a la edición :) La probabilidad de que dos clases en una jerarquía de clases no sólo tienen el mismo nombre, pero que ambos están utilizando el mismo nombre de la propiedad, y ambos usan el formulario privado mutilado (__) es tan minúsculo que se puede ignorar de forma segura en la práctica (por mi parte, no he oído hablar de un solo caso).

En teoría, sin embargo, tiene razón en que para verificar formalmente la corrección de un programa, uno conoce la cadena de herencia completa. Afortunadamente, la verificación formal generalmente requiere un conjunto fijo de bibliotecas en cualquier caso.

esto es en el espíritu de la Zen of Python, que incluye

practicidad late pureza.

+0

¿Pero entraría en conflicto con un hipotético 'Base .__ foo'? – inspectorG4dget

+0

@ inspectorG4dget No. – phihag

+0

Creo que merece una mención especial en su respuesta, ya que está más en línea con lo que el OP parece preguntar – inspectorG4dget

3
  1. Nombre mangling incluye la clase para que su Base.__foo y Sub.__foo tendrá diferentes nombres. Esta fue toda la razón para agregar la función de creación de nombres a Python en primer lugar. Uno será _Base__foo, el otro _Sub__foo.

  2. Muchas personas prefieren usar la composición (has-a) en lugar de la herencia (is-a) por algunas de estas razones.

+0

¿No está esto mal? Pensé que la destrucción solo se produjo con dobles guiones bajos, no solo ... –

+0

Mencioné el cambio de nombre; el caso especial que noté fue algo así como A-> B-> A (donde el primero A está obviamente en un paquete separado). Ahora la clase de abuelos manipula las cosas de la misma manera que mi subclase, eliminando la ventaja que brinda el cambio de nombre. – Chris

+0

@JoranBeasley: Estoy usando dobles guiones bajos ... –

0

Como se indica, puede usar el nombre mangling. Sin embargo, puede seguir con un solo guión bajo (o ninguno) si documenta su código de manera adecuada; no debe tener tantas variables privadas que esto resulte ser un problema. Simplemente diga si un método se basa en una variable privada y agregue la variable o el nombre del método a la clase docstring para alertar a los usuarios.

Además, si crea pruebas unitarias, debe crear pruebas que verifiquen invariantes en los miembros, y en consecuencia, estos deberían poder mostrar tales conflictos de nombres.

Si realmente quiere tener variables "privadas", y por cualquier motivo nombre-mangling no satisface sus necesidades, puede factorizar su estado privada en otro objeto:

class Foo(object): 

    class Stateholder(object): pass 

    def __init__(self): 
     self._state = Stateholder() 
     self.state.private = 1 
+0

La implicación es que una vez que escribes una clase, nunca puedes modificar cómo funciona, porque cualquiera que herede de ella podría haber usado un nombre que tú mismo podrías usar en tu modificaciones? Las pruebas unitarias podrían ser la mejor manera de tratar el síntoma, si no hay una forma real de tratar la causa. – Chris

+0

@Chris El problema que imaginas no existe en la forma que estás imaginando. Lo mismo sucedería si introdujeses un nuevo nombre público. La solución a eso es la administración de configuración, que es una parte normal del desarrollo de software. En cualquier caso, este problema solo surge si está creando bibliotecas: si está dentro de una base de código, simplemente hable con sus colegas. – Marcin

+0

Eso es cierto en el contexto de un lenguaje que es muy flexible con la idea de público/privado. Un lenguaje con una fuerte distinción entre los dos haría más fácil tener un enfoque de "API estable, elementos internos mutables"; Puedo estar detrás de una API invariable mucho más que funcionalidades inmutables. Considero la falta de tal distinción un problema, pero acepto que otros no lo hacen. – Chris

0

Autonombrado sucede con el doble subrayados. Los guiones bajos individuales son más un "por favor no".

No es necesario que conozca todos los detalles de todas las clases principales (tenga en cuenta que es mejor evitar la herencia profunda), porque aún puede dirigir() y ayudar() y cualquier otra forma de introspección. con.

2

¿Con qué frecuencia modifica las clases base en las cadenas de herencia para introducir la herencia de una clase con el mismo nombre que una subclase más abajo en la cadena?

Con menos ligereza, sí, debe conocer el código con el que está trabajando. Usted ciertamente tiene que conocer los nombres públicos que se utilizan, después de todo. Python siendo python, descubrir los nombres públicos en uso por las clases de sus ancestros lleva más o menos el mismo esfuerzo que descubrir los privados.

En los años de programación de Python, nunca he visto que esto sea un gran problema en la práctica. Cuando nombra variables de instancia, debe tener una muy buena idea de si (a) un nombre es lo suficientemente genérico como para ser utilizado en otros contextos y (b) es probable que la clase que está escribiendo esté involucrada en un jerarquía de herencia con otras clases desconocidas. En tales casos, piensas un poco más cuidadosamente sobre los nombres que estás usando; self.value no es una gran idea para un nombre de atributo, y tampoco es algo así como Adaptor un gran nombre de clase.

En contraste, he tenido dificultades con el uso excesivo de nombres de guiones bajos varias veces. Python es Python, incluso los nombres "privados" tienden a ser accedidos por un código definido fuera de la clase. Podría pensar que siempre sería una mala práctica dejar que una función externa acceda a los atributos "privados", pero ¿qué pasa con cosas como getattr y hasattr? La invocación de ellos puede estar en el código de la clase, por lo que la clase todavía controla todo el acceso a los atributos privados, pero aún no funcionan sin que usted haga el cambio de nombre manualmente. Si Python tuviera variables privadas implementadas realmente, no podría usar funciones como esas en absoluto. Estos días tiendo a reservar nombres de dos subrayados para los casos en que estoy escribiendo algo muy genérico como decorador, metaclase o mixin que necesita agregar un "atributo secreto" a las instancias de las clases (desconocidas) a las que se aplica.

Y, por supuesto, existe el argumento del lenguaje dinámico estándar: la realidad es que tiene que probar su código a fondo para tener mucha justificación al hacer la afirmación "mi software funciona". Es poco probable que estas pruebas se pierdan los errores causados ​​por nombres que chocan accidentalmente. Si no está haciendo esa prueba, entonces muchos más errores no detectados se introducirán por otros medios que por los conflictos de nombres accidentales.

En resumen, la falta de variables privadas no es tan importante en el código idiomático de Python en la práctica, y la adición de verdaderas variables privadas causaría problemas más frecuentes en otras formas en mi humilde opinión.

Cuestiones relacionadas