type define argument python class subclass python-3.6 metaclass

define - list api python



Comprender__init_subclass__ (4)

El punto principal de __init_subclass__ era, como sugiere el título del PEP, ofrecer una forma más simple de personalización para las clases.

Es un gancho que le permite jugar con las clases sin la necesidad de conocer las metaclases, realizar un seguimiento de todos los aspectos de la construcción de clases o preocuparse por los conflictos de metaclases en el futuro. Como un mensaje de Nick Coghlan sobre la fase inicial de este PEP dice:

El principal beneficio previsto de legibilidad / mantenibilidad es desde la perspectiva de distinguir más claramente el caso de "inicialización de subclases de personalización" del caso "comportamiento de tiempo de ejecución de personalización de subclases".

Una metaclass personalizada completa no proporciona ninguna indicación del alcance del impacto, mientras que __init_subclass__ indica más claramente que no hay efectos persistentes en la creación posterior a la creación de la __init_subclass__ de comportamiento.

Las metaclases se consideran mágicas por una razón, no sabes cuáles serán sus efectos una vez que se haya creado la clase. __init_subclass__ , por otro lado, es solo otro método de clase, se ejecuta una vez y luego está hecho. (Consulte su documentación para conocer la funcionalidad exacta).

El objetivo de PEP 487 es simplificar (es decir, eliminar la necesidad de usar) las metaclases para algunos usos comunes.

__init_subclass__ se ocupa de la inicialización posterior a la clase, mientras que __set_name__ (que solo tiene sentido para las clases de descriptores) se agregó para simplificar la inicialización de los descriptores. Más allá de eso, no están relacionados.

El tercer caso común para las metaclases (mantener orden de definición) que se menciona, también se simplificó . Esto se solucionó con un hook, utilizando un mapeo ordenado para el espacio de nombres (que en Python 3.6 es un dict , pero eso es un detalle de implementación :-)

Finalmente actualicé mi versión de Python y descubrí las nuevas características añadidas. Entre otras cosas, me estaba rascando la cabeza con el nuevo método __init_subclass__ . De los documentos:

Este método se llama siempre que la clase que lo contiene esté subclasificada. cls es entonces la nueva subclase. Si se define como un método de instancia normal, este método se convierte implícitamente en un método de clase.

Así que empecé a jugar un poco con eso, siguiendo el ejemplo en los documentos:

class Philosopher: def __init_subclass__(cls, default_name, **kwargs): super().__init_subclass__(**kwargs) print(f"Called __init_subclass({cls}, {default_name})") cls.default_name = default_name class AustralianPhilosopher(Philosopher, default_name="Bruce"): pass class GermanPhilosopher(Philosopher, default_name="Nietzsche"): default_name = "Hegel" print("Set name to Hegel") Bruce = AustralianPhilosopher() Mistery = GermanPhilosopher() print(Bruce.default_name) print(Mistery.default_name)

Produce este resultado:

Called __init_subclass(<class ''__main__.AustralianPhilosopher''>, ''Bruce'') ''Set name to Hegel'' Called __init_subclass(<class ''__main__.GermanPhilosopher''>, ''Nietzsche'') ''Bruce'' ''Nietzsche''

Entiendo que este método se llama después de la definición de la subclase, pero mis preguntas son particularmente sobre el uso de esta característica. Leí el artículo PEP 487 también, pero no me ayudó mucho. ¿Dónde sería útil este método? Es para:

  • la superclase para registrar las subclases al momento de la creación?
  • ¿obligar a la subclase a establecer un campo en el momento de la definición?

Además, ¿necesito entender el __set_name__ para comprender completamente su uso?


__init_subclass__ y __set_name__ son mecanismos ortogonales; no están vinculados entre sí, simplemente se describen en el mismo PEP. Ambas son características que necesitaban una metaclase con todas las funciones antes. El PEP 487 aborda 2 de los usos más comunes de las metaclases:

  • cómo hacer saber al padre cuándo se está subclasificando ( __init_subclass__ )
  • cómo hacer que una clase de descriptor conozca el nombre de su propiedad ( __set_name__ )

Como dice el PEP:

Si bien hay muchas maneras posibles de usar una metaclase, la gran mayoría de los casos de uso se divide en tres categorías: algún código de inicialización que se ejecuta después de la creación de clase, la inicialización de descriptores y el orden en que se definieron los atributos de la clase.

Las dos primeras categorías se pueden lograr fácilmente teniendo ganchos simples en la creación de la clase:

  • Un gancho __init_subclass__ que inicializa todas las subclases de una clase determinada.
  • al crear la clase, se llama a un gancho __set_name__ en todos los atributos (descriptores) definidos en la clase, y

La tercera categoría es el tema de otra PEP, PEP 520 .

Observe también que mientras __init_subclass__ es un reemplazo para usar una metaclase en el árbol de herencia de esta clase, __set_name__ en una clase de descriptor es un reemplazo para usar una metaclase para la clase que tiene una instancia del descriptor como atributo .


PEP 487 se propone tomar dos usos de metaclass comunes y hacerlos más accesibles sin tener que entender todos los detalles de las metaclases. Las dos nuevas funciones, __init_subclass__ y __set_name__ son independientes , no dependen el uno del otro.

__init_subclass__ es solo un método de __init_subclass__ . Puedes usarlo para lo que quieras. Es útil tanto para registrar subclases de alguna manera, como para establecer valores de atributos predeterminados en esas subclases.

Recientemente lo usamos para proporcionar ''adaptadores'' para diferentes sistemas de control de versiones, por ejemplo:

class RepositoryType(Enum): HG = auto() GIT = auto() SVN = auto() PERFORCE = auto() class Repository(): _registry = {t: {} for t in RepositoryType} def __init_subclass__(cls, scm_type=None, name=None, **kwargs): super().__init_subclass__(cls, **kwargs) if scm_type is not None: cls._registry[scm_type][name] = cls class MainHgRepository(Repository, scm_type=RepositoryType.HG, name=''main''): pass class GenericGitRepository(Repository, scm_type=RepositoryType.GIT): pass

Esto trivialmente nos permite definir clases de controlador para repositorios específicos sin tener que recurrir al uso de una metaclass o decoradores.


Me gustaría agregar algunas referencias relacionadas con las metaclases y __init_subclass__ que pueden ser útiles.

Fondo

__init_subclass__ se introdujo como una alternativa a la creación de metaclases. Aquí hay un resumen de 2 minutos de PEP 487 en una charla de uno de los desarrolladores principales, Brett Cannon.

Referencias recomendadas

  • Publicación del blog de Guido van Rossum sobre la historia temprana de las metaclases en Python
  • El blog de Jake Vanderplas analiza con más detalle la implementación de metaclases