Comprendre la sous-classe init
J'ai finalement mis à jour ma version python et je découvrais les nouvelles fonctionnalités ajoutées. Entre autres choses, je me grattais la tête autour du nouveau __init_subclass__
méthode. De la docs:
Cette méthode est appelée chaque fois que la classe contenant est sous-classée. cls est alors la nouvelle sous-classe. Si elle est définie comme une méthode d'instance normale, cette méthode est implicitement convertie en une méthode de classe.
J'ai donc commencé à jouer un peu avec, en suivant l'exemple dans les documents:
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)
Produit cette sortie:
Called __init_subclass(<class '__main__.AustralianPhilosopher'>, 'Bruce')
'Set name to Hegel'
Called __init_subclass(<class '__main__.GermanPhilosopher'>, 'Nietzsche')
'Bruce'
'Nietzsche'
Je comprends que cette méthode s'appelle après La définition de la sous-classe, mais mes questions portent particulièrement sur l'utilisation de cette fonctionnalité. J'ai aussi lu l'article pep 487, mais cela ne m'a pas beaucoup aidé. Où serait cette méthode-il être utile? Est-ce pour:
- la superclasse pour enregistrer les sous-classes lors de la création?
- forcer la sous-classe à définir un champ à la définition de temps?
Aussi, ai-je besoin de comprendre le __set_name__
pour comprendre pleinement son utilisation?
4 réponses
__init_subclass__
et __set_name__
sont des mécanismes orthogonaux - ils ne sont pas liés les uns aux autres, juste décrits dans le même PEP. Les deux sont des fonctionnalités qui avaient besoin d'une métaclasse complète avant. Les adresses PEP 4872 des utilisations les plus courantes de métaclasses:
- Comment faire savoir au parent quand il est sous-classé (
__init_subclass__
) - Comment faire connaître à une classe de descripteur le nom de la propriété pour laquelle elle est utilisée (
__set_name__
)
Comme le dit le PEP:
Alors que il existe de nombreuses façons d'utiliser une métaclasse, la grande majorité des cas d'utilisation se divise en trois catégories: un code d'initialisation s'exécutant après la création de la classe, l'initialisation des descripteurs et le maintien de l'ordre dans lequel les attributs de classe ont été définis.
Les deux premières catégories peuvent facilement être réalisées en ayant des crochets simples dans la création de classe:
- Un
__init_subclass__
crochet qui initialise toutes les sous-classes d'une classe donnée.- sur création de classe, un crochet
__set_name__
est appelé sur tous les attributs (descripteurs) définis dans la classe, etLa troisième catégorie est le sujet d'un autre PEP, pep 520 .
Notez aussi que, tandis que __init_subclass__
est un remplacement pour l'utilisation d'une métaclasse dans ce classe de l'arbre d'héritage, __set_name__
dans a descripteur de classe est un remplacement pour l'utilisation d'une métaclasse pour la classe qui a une instance de la descripteur comme un attribut.
PEP 487 se propose de prendre deux cas d'utilisation de métaclasses communs et de les rendre plus accessibles sans avoir à comprendre tous les tenants et les aboutissants des métaclasses. Les deux nouvelles fonctionnalités, __init_subclass__
et __set_name__
sont sinon indépendantes, elles ne dépendent pas l'une de l'autre.
__init_subclass__
est juste une méthode de crochet. Vous pouvez l'utiliser pour tout ce que vous voulez. Il est utile à la fois pour enregistrer des sous-classes d'une manière ou d'une autre, et pour définir des valeurs d'attribut par défaut sur ces sous-classes.
Nous avons récemment pour fournir des "adaptateurs" pour les différents systèmes de contrôle de version, par exemple:
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__(**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
Cela nous permet trivialement de définir des classes de gestionnaire pour des dépôts spécifiques sans avoir à recourir à une métaclasse ou à des décorateurs.
Le point principal de __init_subclass__
était, comme le suggère le titre du PEP, d'offrir une forme plus simple de personnalisation pour les classes.
C'est un crochet qui vous permet de bricoler avec des classes sans avoir besoin de connaître les métaclasses, de garder une trace de tous les aspects de la construction de classes ou de vous soucier des conflits de métaclasses. Comme un message {[10] } de Nick Coghlan sur la première phase de ce PEP déclare:
Le principal avantage de lisibilité/maintenabilité prévu est perspective de distinguer plus clairement la sous-classe " personnalise initialisation "case from the" personnalise le comportement d'exécution de les sous-classes" de cas.
Une métaclasse personnalisée complète ne fournit aucune indication de la portée de impact, alors que
__init_subclass__
indique plus clairement qu'il n'y a pas effets persistants sur le comportement après la création de la sous-classe.
Les métaclasses sont considérées comme magiques pour une raison, vous ne savez pas quels seront leurs effets après la création de la classe. __init_subclass__
, d'autre part, est juste une autre méthode de classe, elle s'exécute une fois et c'est fait. (voir sa documentation pour la fonctionnalité exacte.)
Le but de PEP 487 est de simplifier (c'est-à-dire de supprimer le besoin d'utiliser) les métaclasses pour certaines utilisations courantes.
__init_subclass__
prend en charge l'initialisation post-classe tandis que __set_name__
(qui n'a de sens que pour les classes de descripteurs) a été ajouté pour simplifier l'initialisation des descripteurs. Au-delà de cela, ils ne sont pas liés.
Le troisième cas commun pour les métaclasses (maintien de l'ordre de définition) qui est mentionné, a également été simplifié . Ceci a été adressé sans crochet, en utilisant un mappage ordonné pour l'espace de noms (qui en Python 3.6 est un dict
, mais c'est un détail d'implémentation: -)
Je voudrais ajouter quelques références liées aux métaclasses et __init_subclass__
qui peuvent être utiles.
arrière-plan
__init_subclass__
a été introduit comme une alternative à la création de métaclasses.
Voici un résumé de 2 minutes de pep 487 dans untalk par L'un des développeurs principaux, Brett Cannon.
Références Recommandées
- billet de blog de Guido van Rossum sur les débuts de l'histoire des métaclasses en Python
- Jake Le blog de Vanderplas examine plus en profondeur la mise en œuvre des métaclasses