Métaclasse personnalisée

Maintenant, nous savons déjà ce que sont les métaclasses. Ainsi, du début à la fin, nous ne savons toujours pas à quoi servent les métaclasses. Je viens de découvrir les métaclasses. Avant de comprendre son utilisation, comprenons d'abord comment personnaliser les métaclasses. Car ce n’est qu’en comprenant comment le personnaliser que vous pourrez mieux comprendre sa fonction.

Tout d'abord, comprenons l'attribut __metaclass__

métaclass, littéralement traduit par métaclasse, l'explication simple est :

Après avoir défini une classe, nous pouvons créer une instance basée sur cette classe, donc : définissez d'abord la classe, Créez ensuite l'instance.

Mais et si on veut créer une classe ? Ensuite, vous devez créer une classe basée sur la métaclasse, donc : définissez d'abord la métaclasse, puis créez la classe.

La connexion est la suivante : définissez d'abord la métaclasse, puis vous pouvez créer la classe, et enfin créer l'instance.

Ainsi, la métaclasse vous permet de créer des classes ou de modifier des classes. En d’autres termes, vous pouvez considérer les classes comme des « instances » créées par une métaclasse.

class MyObject(object):
    __metaclass__ = something…
[…]

S'il est écrit comme ceci, Python utilisera des métaclasses pour créer la classe MyObject. Lorsque vous écrivez la classe MyObject(object), l'objet de classe MyObject n'a pas encore été créé en mémoire. Python recherchera l'attribut __metaclass__ dans la définition de classe. S'il est trouvé, Python l'utilisera pour créer la classe MyObject. S'il n'est pas trouvé, il utilisera la fonction de type intégrée pour créer la classe. Si vous ne le comprenez toujours pas, jetez un œil à l'organigramme ci-dessous :

c7dd72cd3c802993425a3b6516a97e2.png

Autre exemple :

class Foo(Bar):
    pass

Quel est son processus de jugement ?

Déterminez d’abord si Foo a l’attribut __metaclass__ ? Si tel est le cas, Python créera un objet de classe nommé Foo en mémoire via __metaclass__ (notez qu'il s'agit d'un objet de classe). Si Python ne trouve pas __metaclass__, il continuera à rechercher l'attribut __metaclass__ dans Bar (la classe parent) et essaiera de faire la même opération que précédemment. Si Python ne trouve pas __metaclass__ dans aucune classe parent, il recherche __metaclass__ dans la hiérarchie des modules et essaie de faire de même. Si __metaclass__ n'est toujours pas trouvé, Python utilisera le type intégré pour créer l'objet de classe.

En fait, __metaclass__ définit le comportement de la classe. De la même manière que la classe définit le comportement d'une instance, la métaclasse définit le comportement d'une classe. On peut dire que la classe est une instance de métaclasse.

Maintenant, nous comprenons essentiellement l'attribut __metaclass__, mais nous n'avons pas parlé de la façon d'utiliser cet attribut, ni de ce qui peut être mis dans cet attribut ?

La réponse est : vous pouvez créer une classe. Alors, que peut-on utiliser pour créer une classe ? type, ou tout ce qui utilise le type ou le type de sous-classes.

L'objectif principal des métaclasses est de changer automatiquement de classe lors de leur création. En règle générale, vous feriez quelque chose comme ceci pour une API, dans laquelle vous souhaitez créer des classes adaptées au contexte actuel. Imaginez un exemple idiot dans lequel vous décidez que tous les attributs de classe de votre module doivent être en majuscules. Il existe plusieurs façons de procéder, mais l'une d'entre elles consiste à définir __metaclass__ au niveau du module. En utilisant cette méthode, toutes les classes de ce module seront créées via cette métaclasse. Il suffit de dire à la métaclasse de changer tous les attributs en majuscules et tout ira bien.

Heureusement, __metaclass__ peut en fait être appelée arbitrairement, il n'est pas nécessaire qu'il s'agisse d'une classe formelle. Commençons donc par une fonction simple comme exemple.

# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    '''返回一个类对象,将属性都转为大写形式'''
    #  选择所有不以'__'开头的属性
    attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)
 
__metaclass__ = upper_attr  
#  这会作用到这个模块中的所有类
 
class Foo(object):
    # 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
    bar = 'bip'
print hasattr(Foo, 'bar')
# 输出: False
print hasattr(Foo, 'BAR')
# 输出:True
 
f = Foo()
print f.BAR
# 输出:'bip'
用 class 当做元类的做法:
# 请记住,'type'实际上是一个类,就像'str'和'int'一样
# 所以,你可以从type继承
class UpperAttrMetaClass(type):
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return type(future_class_name, future_class_parents, uppercase_attr)

Cependant, cette approche n'est en fait pas de la POO. Nous avons appelé type directement et nous n'avons pas remplacé la méthode __new__ de la classe parent. Faisons maintenant comme ceci :

class UpperAttrMetaclass(type):
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
        # 复用type.__new__方法
        # 这就是基本的OOP编程,没什么魔法
        return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)

Vous avez peut-être remarqué qu'il y a un paramètre supplémentaire upperattr_metaclass, il n'a rien de spécial. Le premier argument d’une méthode de classe représente toujours l’instance actuelle, tout comme l’argument self dans une méthode de classe ordinaire. Bien sûr, par souci de clarté, j’ai allongé les noms ici. Mais tout comme soi, tous les paramètres portent leurs noms traditionnels. Donc, dans le code de production réel, une métaclasse ressemblerait à ceci :

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')
        uppercase_attr  = dict((name.upper(), value) for name, value in attrs)
        return type.__new__(cls, name, bases, uppercase_attr)

Nous pouvons également rendre les choses un peu plus claires si nous utilisons la super méthode, qui facilite l'héritage (oui, vous pouvez avoir des métaclasses, hériter de la métaclasse, hériter du type)

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

Habituellement, nous utilisons des métaclasses pour faire des choses obscures, en nous appuyant sur l'introspection, le contrôle de l'héritage, etc. En effet, il est particulièrement utile d'utiliser des métaclasses pour faire de la « magie noire » et ainsi créer des choses complexes. Mais en ce qui concerne les métaclasses elles-mêmes, elles sont en réalité très simples :

Intercepter la création de la classe

Modifier la classe

Renvoyer la classe modifiée

Formation continue
  • Recommandations de cours
  • Téléchargement du didacticiel