Parler des attributs de classe Python et des métaclasses

problème

class Base:
    __BASE_PARAMS__ = ["base"]


class A(Base):
    __PARAMS__ = ["a"]

    
class B(Base):
    __PARAMS__ = ["b"]

Comme mentionné ci-dessus, il existe une classe «Base» et les classes «A» et «B» héritent de la classe «Base». Je veux ajouter un attribut de classe appelé __ALL_PARAMS__ qui intègre la classe parente __BASE_PARAMS__ et mon propre __PARAMS__ aux classes ʻA et B`. Autrement dit, nous voulons obtenir les résultats suivants.

A.__ALL_PARAMS__
# ['base', 'a']

B.__ALL_PARAMS__
# ['base', 'b']

S'il est implémenté normalement, cela ressemblerait à ceci:

class Base:
    __BASE_PARAMS__ = ["base"]


class A(Base):
    __PARAMS__ = ["a"]
    __ALL_PARAMS__ = Base.__BASE_PARAMS__ + __PARAMS__

    
class B(Base):
    __PARAMS__ = ["b"]
    __ALL_PARAMS__ = Base.__BASE_PARAMS__ + __PARAMS__


A.__ALL_PARAMS__
# ['base', 'a']

B.__ALL_PARAMS__
# ['base', 'b']

Cependant, il existe de nombreuses classes qui héritent de la classe Base, et il est difficile d'écrire l'attribut de classe __ALL_PARAMS__ = Base .__ BASE_PARAMS__ + __PARAMS__, qui n'est pas lié au traitement, dans toutes les classes héritées.

Existe-t-il un moyen simple de résoudre ce problème sans que la classe héritée n'écrive sans signification? Je veux que vous y réfléchissiez.

Métaprogrammation

La métaprogrammation est une technique permettant de définir un programme par programmation.

Je ne parlerai pas des bases de la métaprogrammation ici. Si vous souhaitez en savoir plus sur la métaprogrammation, veuillez vous référer aux articles suivants.

Métaprogrammation avec Python \ _ \ _ Nouveau \ _ \ _ et \ _ \ _ init \ _ \ _ et métaclasse Dans la métaclasse python, passez l'opérateur de comparaison sous forme de chaîne à la méthode ORM pour construire la clause where.

Maintenant, en utilisant des métaclasses, nous pouvons résoudre le problème ci-dessus comme suit.

Métaclasse pour Python3


class MetaClass(type):    
    def __init__(cls, name, bases, attribute):
        super(MetaClass, cls).__init__(name, bases, attribute)
        cls.__ALL_PARAMS__ = cls.__BASE_PARAMS__ + getattr(cls, "__PARAMS__", [])


class Base(metaclass=MetaClass):
    __BASE_PARAMS__ = ["base"]


class A(Base):
    __PARAMS__ = ["a"]

    
class B(Base):
    __PARAMS__ = ["b"]


A.__ALL_PARAMS__
# ['base', 'a']

B.__ALL_PARAMS__
# ['base', 'b']

Pour Python2, assignez une métaclasse à l'attribut spécial «metaclass».

Pour Python 2


class Base(object):
    __metaclass__ = MetaClass

Diverses méta-programmation

Vous pouvez même étendre la langue avec la métaprogrammation. Les classes finales en Java n'existent pas en Python, mais les métaclasses vous permettent de définir des fonctionnalités similaires.

final_metaclass.py


class FinalMetaClass(type):
    def __init__(cls, name, bases, namespace):
        super(FinalMetaClass, cls).__init__(name, bases, namespace)
        for _class in bases:
            if isinstance(_class, FinalMetaClass):
                raise TypeError()


class A:
    pass


class B(A, metaclass=FinalMetaClass):
    pass


# Error!!
class C(B):
    pass

\ _ \ _ Init \ _ \ _ ou \ _ \ _ nouveau \ _ \ _?

Dois-je utiliser «init» ou «new» dans une métaclasse qui hérite du type? C'est essentiellement une question de goût, et peu importe lequel. Cependant, «new» a un degré de liberté plus élevé, comme la possibilité de réécrire «slots».

Nouvelle méthode spéciale \ _ \ _ prepare \ _ \ _

Une méthode spéciale «prepare» a été ajoutée à partir de Python3. Normalement, __dict__ est un type de dict dont l'ordre n'est pas garanti (ce qui s'est passé dans Python3.6), et vous pouvez contrôler cela avec __prepare__.

Ce qui suit est un extrait de la documentation Python.

import collections

class OrderedClass(type):

    @classmethod
    def __prepare__(metacls, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(metacls, name, bases, namespace, **kwds):
        cls = type.__new__(metacls, name, bases, dict(namespace))
        cls.members = tuple(namespace)
        return cls

class A(metaclass=OrderedClass):
    def one(self): pass
    def two(self): pass
    def three(self): pass
    def four(self): pass
    
A.members
# ('__module__', '__qualname__', 'one', 'two', 'three', 'four')

Il peut être confirmé que l'énumération des membres de la classe est dans l'ordre de définition.

Héritage de la métaclasse

class MetaA(type):
    def __new__(mcls, *args, **kwargs):
        cls = type.__new__(mcls, *args, **kwargs)
        cls.MetaA = "Yes"
        return cls


class MetaB(type):
    def __new__(mcls, *args, **kwargs):
        cls = type.__new__(mcls, *args, **kwargs)
        cls.MetaB = "Yes"
        return cls        


class A(metaclass=MetaA):
    pass


class B(A, metaclass=MetaB):
    pass

Lorsque j'essaye de définir la classe B, j'obtiens l'erreur suivante:

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Non seulement les sous-classes de «type» peuvent être passées à la «métaclasse». Tout ce qui est appelable qui correspond à un argument spécifique peut être utilisé. Dans ce cas, une fonction peut être transmise pour éliminer l'héritage du diamant.

class MetaA(type):
    def __new__(mcls, *args, **kwargs):
        cls = type.__new__(mcls, *args, **kwargs)
        cls.MetaA = "Yes"
        return cls


class A(metaclass=MetaA):
    pass


def MetaB(mcls, *args, **kwargs):
    cls = type(mcls, *args, **kwargs)
    cls.MetaB = "Yes"
    return cls
    

class B(A, metaclass=MetaAB):
    pass

B.MetaA, B.MetaB
# ('Yes', 'Yes')

Utilisation de la métaprogrammation

La métaprogrammation est suffisamment puissante pour changer les spécifications linguistiques. Par conséquent, une utilisation intensive de la métaprogrammation peut être une source de confusion dans la programmation d'équipe.

Fondamentalement, si le design est solide, la métaprogrammation devrait rarement entrer en jeu. Exceptionnellement, lors de la génération de l'attribut de classe hérité, il est souvent possible d'écrire clairement en utilisant une méta-classe.

référence

http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Metaprogramming.html

Recommended Posts

Parler des attributs de classe Python et des métaclasses
À propos de python, classe
Parler d'anciennes et de nouvelles classes en Python
À propos de la classe et de l'instance
Python: variables de classe et d'instance
À propos des objets et des classes Python
À propos des variables et des objets Python
À propos de Python, len () et randint ()
À propos de la date et du fuseau horaire Python
Variables de classe et d'instance Python
À propos de Python et des expressions régulières
À propos des opérations Python et OS
Python # À propos de la référence et de la copie
À propos de Python sort () et reverse ()
À propos de l'installation des séries Pwntools et Python2
objet perl et classe python partie 2.
Définitions de classe Python et gestion des instances
À propos de Python dict et des fonctions triées
[Python] À propos des classes Executor et Future
À propos de Python, à partir et à l'importation, comme
(Notes personnelles) Métaclasses et métaprogrammation Python
À propos de _ et __
[Python] Différence entre la méthode de classe et la méthode statique
Une histoire sur Python pop and append
À propos des tranches Python
À propos de la notation d'inclusion de python
[Python] classe, instance
"Kanrika" la classe python
À propos de Python tqdm.
À propos du rendement Python
[python] Différence entre variable et self. Variable dans la classe
J'ai écrit une classe en Python3 et Java
À propos de l'héritage Python
À propos de python, range ()
Problème avec les variables pseudo-privées Python et l'héritage de classe
À propos de Python Decorator
classe python, instance
Une histoire sur la modification de Python et l'ajout de fonctions
[Python] En savoir plus sur la programmation asynchrone et les boucles d'événements
[Python] À propos du multi-processus
À propos des copies superficielles et profondes de Python / Ruby
[Python] Type de classe et utilisation du module datetime
Les bases de #Python (classe)
Premiers pas avec python3 # 2 En savoir plus sur les types et les variables
[Python] Convertit un conteneur à usage général et une classe entre eux
À propos de la création et de la modification de thèmes personnalisés pour Python IDLE
Exemple d'obtention du nom du module et du nom de la classe en Python
[Python] Chapitre 01-02 À propos de Python (Exécution et installation de l'environnement de développement)
[python] Compresser et décompresser
À propos de Python for loop
classe wrapper python syslog
Classe Python (mémo d'apprentissage Python ⑦)
Astuces Python et Numpy
[Python] pip et roue
classe de cas en python
Itérateur et générateur Python
[Python] Héritage de classe (super)