J'ai finalement mis à jour ma version python et je découvrais les nouvelles fonctionnalités ajoutées. Entre autres, je me grattais la tête autour de la nouvelle __init_subclass__
méthode. De la documentation:
Cette méthode est appelée chaque fois que la classe conteneur 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 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 est appelée après la définition de la sous-classe, mais mes questions portent particulièrement sur l'utilisation de cette fonctionnalité. J'ai également lu l'article PEP 487 , mais ne m'a pas beaucoup aidé. Où cette méthode serait-elle utile? Est-ce pour:
De plus, dois-je comprendre le __set_name__
pour bien comprendre son utilisation?
__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 nécessitaient auparavant une métaclasse complète. Le PEP 487 traite 2 des utilisations les plus courantes des métaclasses:
__init_subclass__
)__set_name__
)Comme le PEP le dit:
Bien qu'il existe de nombreuses façons d'utiliser une métaclasse, la grande majorité des cas d'utilisation se divise en trois catégories: du code d'initialisation exécuté 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 être facilement obtenues en ayant de simples crochets dans la création de classe:
- Un
__init_subclass__
hook qui initialise toutes les sous-classes d'une classe donnée.- lors de la création de la classe, un
__set_name__
hook 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 52 .
Notez également que si __init_subclass__
remplace l'utilisation d'une métaclasse dans ceci l'arbre d'héritage de la classe, __set_name__
dans une classe de descripteurs remplace l'utilisation d'une métaclasse pour la classe qui a une instance du descripteur comme attribut.
Le PEP 487 se propose de prendre deux cas d'utilisation communs de métaclasses et de les rendre plus accessibles sans avoir à comprendre tous les tenants et aboutissants des métaclasses. Les deux nouvelles fonctionnalités, __init_subclass__
et __set_name__
sont autrement indépendants, ils ne dépendent pas les uns des autres.
__init_subclass__
n'est qu'une méthode de hook. Vous pouvez l'utiliser pour tout ce que vous voulez. Il est utile pour enregistrer les sous-classes d'une certaine manière, et pour définir les valeurs d'attribut par défaut sur ces sous-classes.
Nous l'avons récemment utilisé pour fournir des "adaptateurs" pour 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 de définir de manière triviale des classes de gestionnaires pour des référentiels 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 de personnalisation plus simple 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 inquiéter des conflits de métaclasses sur toute la ligne. Comme n message par Nick Coghlan sur la première phase de ce PEP déclare:
Le principal avantage prévu en termes de lisibilité/maintenabilité est dans la perspective de distinguer plus clairement le cas "personnalise l'initialisation de la sous-classe" du cas "personnalise le comportement d'exécution des sous-classes".
Une métaclasse personnalisée complète ne fournit aucune indication de l'étendue de l'impact, tandis que
__init_subclass__
indique plus clairement qu'il n'y a pas d'effets persistants sur la création de comportement après 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 puis c'est fait. (voir sa documentation pour la fonctionnalité exacte.)
L'intérêt du PEP 487 est de simplifier (c'est-à-dire de supprimer la nécessité d'utiliser) des métaclasses pour certaines utilisations courantes.
__init_subclass__
s'occupe de 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 (en gardant l'ordre de définition) qui est mentionné, a également été simplifié . Cela a été résolu sans crochet, en utilisant un mappage ordonné pour l'espace de noms (qui dans 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 peut être utile.
Contexte
__init_subclass__
a été introduit comme alternative à la création de métaclasses. Voici un résumé de 2 minutes de PEP 487 dans un talk par l'un des développeurs principaux, Brett Cannon.
Références recommandées