¿Cuál es el propósito de las clases internas de python?

Las clases internas / anidadas de Python me confunden. ¿Hay algo que no se pueda lograr sin ellos? Si es así, ¿qué es esa cosa?

Citado en http://www.geekinterview.com/question_details/64739 :

Ventajas de la clase interior:

  • Agrupación lógica de clases : si una clase es útil solo para otra clase, entonces es lógico incluirla en esa clase y mantenerlas juntas. Anidar tales “clases de ayuda” hace que su paquete sea más ágil.
  • Aumento de la encapsulación : considere dos clases de nivel superior A y B, donde B necesita acceso a miembros de A que de otro modo se declararían privados. Al ocultar la clase B dentro de la clase, los miembros de AA pueden ser declarados privados y B puede acceder a ellos. Además, la propia B puede ocultarse del mundo exterior.
  • Código más legible y mantenible : la agrupación de clases pequeñas dentro de las clases de nivel superior coloca el código más cerca de donde se usa.

La principal ventaja es la organización. Cualquier cosa que pueda lograrse con clases internas puede lograrse sin ellas.

¿Hay algo que no se pueda lograr sin ellos?

No. Son absolutamente equivalentes a definir la clase normalmente en el nivel superior, y luego copiar una referencia a ella en la clase externa.

No creo que exista una razón especial para que las clases anidadas sean ‘permitidas’, aparte de que no tiene ningún sentido en particular ‘no permitirlas’ explícitamente.

Si está buscando una clase que existe dentro del ciclo de vida del objeto externo / propietario, y siempre tiene una referencia a una instancia de la clase externa – las clases internas como Java lo hace – entonces las clases anidadas de Python no son esa cosa. Pero puedes hackear algo como eso:

import weakref, new class innerclass(object): """Descriptor for making inner classes. Adds a property 'owner' to the inner class, pointing to the outer owner instance. """ # Use a weakref dict to memoise previous results so that # instance.Inner() always returns the same inner classobj. # def __init__(self, inner): self.inner= inner self.instances= weakref.WeakKeyDictionary() # Not thread-safe - consider adding a lock. # def __get__(self, instance, _): if instance is None: return self.inner if instance not in self.instances: self.instances[instance]= new.classobj( self.inner.__name__, (self.inner,), {'owner': instance} ) return self.instances[instance] # Using an inner class # class Outer(object): @innerclass class Inner(object): def __repr__(self): return '<%s.%s inner object of %r>' % ( self.owner.__class__.__name__, self.__class__.__name__, self.owner ) >>> o1= Outer() >>> o2= Outer() >>> i1= o1.Inner() >>> i1 > >>> isinstance(i1, Outer.Inner) True >>> isinstance(i1, o1.Inner) True >>> isinstance(i1, o2.Inner) False 

(Esto utiliza decoradores de clase, que son nuevos en Python 2.6 y 3.0. De lo contrario, tendría que decir “Inner = innerclass (Inner)” después de la definición de la clase.)

Hay algo que necesitas envolver alrededor de tu cabeza para poder entender esto. En la mayoría de los idiomas, las definiciones de clase son directivas para el comstackdor. Es decir, la clase se crea antes de ejecutar el progtwig. En Python, todas las declaraciones son ejecutables. Eso significa que esta statement:

 class foo(object): pass 

es una statement que se ejecuta en tiempo de ejecución como esta:

 x = y + z 

Esto significa que no solo puede crear clases dentro de otras clases, sino que también puede crear clases en cualquier lugar que desee. Considere este código:

 def foo(): class bar(object): ... z = bar() 

Por lo tanto, la idea de una “clase interna” no es realmente una construcción de lenguaje; Es una construcción de progtwigdor. Guido tiene un muy buen resumen de cómo ocurrió esto aquí . Pero esencialmente, la idea básica es que esto simplifica la gramática del lenguaje.

Clases de anidación dentro de las clases:

  • Las clases anidadas inflan la definición de la clase, lo que dificulta ver qué sucede.

  • Las clases anidadas pueden crear un acoplamiento que dificultaría las pruebas.

  • En Python, puedes poner más de una clase en un archivo / módulo, a diferencia de Java, por lo que la clase aún permanece cerca de la clase de nivel superior e incluso podría tener el nombre de la clase prefijado con una “_” para ayudar a indicar que otros no deberían estar usandolo

El lugar donde las clases anidadas pueden resultar útiles es dentro de las funciones.

 def some_func(a, b, c): class SomeClass(a): def some_method(self): return b SomeClass.__doc__ = c return SomeClass 

La clase captura los valores de la función, lo que le permite crear dinámicamente una clase como la metaprogtwigción de plantillas en C ++

Entiendo los argumentos en contra de las clases anidadas, pero hay un caso para usarlos en algunas ocasiones. Imagine que estoy creando una clase de lista con doble enlace y necesito crear una clase de nodo para mantener los nodos. Tengo dos opciones, crear la clase Node dentro de la clase DoublyLinkedList o crear la clase Node fuera de la clase DoublyLinkedList. Prefiero la primera opción en este caso, porque la clase Nodo solo es significativa dentro de la clase DoublyLinkedList. Si bien no hay beneficio de ocultación / encapsulación, existe un beneficio de agrupación al poder decir que la clase Nodo es parte de la clase DoublyLinkedList.

He utilizado las clases internas de Python para crear subclases deliberadamente con errores dentro de las funciones de unittest (es decir, dentro de def test_something(): con el fin de acercarme al 100% de la cobertura de prueba (por ejemplo, probar las declaraciones de registro muy raramente activadas al anular algunos métodos).

En retrospectiva, es similar a la respuesta de Ed https://stackoverflow.com/a/722036/1101109

Dichas clases internas deberían quedar fuera del scope y estar listas para la recolección de basura una vez que se hayan eliminado todas las referencias a ellas. Por ejemplo, tome el siguiente archivo inner.py :

 class A(object): pass def scope(): class Buggy(A): """Do tests or something""" assert isinstance(Buggy(), A) 

Obtengo los siguientes resultados curiosos bajo OSX Python 2.7.6:

 >>> from inner import A, scope >>> A.__subclasses__() [] >>> scope() >>> A.__subclasses__() [] >>> del A, scope >>> from inner import A >>> A.__subclasses__() [] >>> del A >>> import gc >>> gc.collect() 0 >>> gc.collect() # Yes I needed to call the gc twice, seems reproducible 3 >>> from inner import A >>> A.__subclasses__() [] 

Sugerencia: no continúe e intente hacer esto con los modelos de Django, que parecían mantener otras referencias (¿guardadas en caché?) A mis clases de buggy.

Por lo tanto, en general, no recomendaría el uso de clases internas para este tipo de propósitos a menos que realmente valore el 100% de la cobertura de prueba y no pueda usar otros métodos. Aunque creo que es bueno tener en cuenta que si usas __subclasses__() , a veces puede ser contaminado por clases internas. De cualquier manera, si seguiste hasta aquí, creo que estamos bastante metidos en Python en este punto, en las puntuaciones privadas y todo.

El principal caso de uso que utilizo para esto es prevenir la proliferación de pequeños módulos y prevenir la contaminación del espacio de nombres cuando no se necesitan módulos separados. Si estoy extendiendo una clase existente, pero esa clase existente debe hacer referencia a otra subclase que siempre debería estar acoplada a ella. Por ejemplo, puedo tener un módulo utils.py que tiene muchas clases de ayuda, que no están necesariamente acopladas, pero quiero reforzar el acoplamiento para algunas de esas clases de ayuda. Por ejemplo, cuando implemento https://stackoverflow.com/a/8274307/2718295

: utils.py :

 import json, decimal class Helper1(object): pass class Helper2(object): pass # Here is the notorious JSONEncoder extension to serialize Decimals to JSON floats class DecimalJSONEncoder(json.JSONEncoder): class _repr_decimal(float): # Because float.__repr__ cannot be monkey patched def __init__(self, obj): self._obj = obj def __repr__(self): return '{:f}'.format(self._obj) def default(self, obj): # override JSONEncoder.default if isinstance(obj, decimal.Decimal): return self._repr_decimal(obj) # else super(self.__class__, self).default(obj) # could also have inherited from object and used return json.JSONEncoder.default(self, obj) 

Entonces podemos:

 >>> from utils import DecimalJSONEncoder >>> import json, decimal >>> json.dumps({'key1': decimal.Decimal('1.12345678901234'), ... 'key2':'strKey2Value'}, cls=DecimalJSONEncoder) {"key2": "key2_value", "key_1": 1.12345678901234} 

Por supuesto, podríamos haber evitado heredar json.JSONEnocder completo y simplemente anular el valor predeterminado ():

:

 import decimal, json class Helper1(object): pass def json_encoder_decimal(obj): class _repr_decimal(float): ... if isinstance(obj, decimal.Decimal): return _repr_decimal(obj) return json.JSONEncoder(obj) >>> json.dumps({'key1': decimal.Decimal('1.12345678901234')}, default=json_decimal_encoder) '{"key1": 1.12345678901234}' 

Pero a veces solo por convención, quieres que los utils estén compuestos de clases para la extensibilidad.

Aquí hay otro caso de uso: quiero una fábrica de mutables en mi clase externa sin tener que invocar copy :

 class OuterClass(object): class DTemplate(dict): def __init__(self): self.update({'key1': [1,2,3], 'key2': {'subkey': [4,5,6]}) def __init__(self): self.outerclass_dict = { 'outerkey1': self.DTemplate(), 'outerkey2': self.DTemplate()} obj = OuterClass() obj.outerclass_dict['outerkey1']['key2']['subkey'].append(4) assert obj.outerclass_dict['outerkey2']['key2']['subkey'] == [4,5,6] 

Prefiero este patrón sobre el decorador de @staticmethod que usaría para una función de fábrica.