web-dev-qa-db-fra.com

Comprendre __init_subclass__

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:

  • la superclasse pour enregistrer les sous-classes lors de la création?
  • forcer la sous-classe à définir un champ au moment de la définition?

De plus, dois-je comprendre le __set_name__ pour bien comprendre son utilisation?

34
EsotericVoid

__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:

  • comment faire savoir au parent quand il est sous-classé (__init_subclass__)
  • comment faire savoir à une classe de descripteurs le nom de la propriété pour laquelle elle est utilisée (__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, et

La 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.

21
Antti Haapala

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.

24
Martijn Pieters

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 :-)

5

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

  • Guido van Rossum's blog post on the early history of metaclasses in Python
  • Jake Vanderplas article de blog approfondir la mise en œuvre des métaclasses
1
pylang