oop metaclass __new__ - Que sont les métaclasses en Python?



7 Answers

Les classes en tant qu'objets

Avant de comprendre les métaclasses, vous devez maîtriser les classes en Python. Et Python a une idée très particulière de ce que sont les classes, empruntées au langage Smalltalk.

Dans la plupart des langages, les classes ne sont que des éléments de code décrivant comment produire un objet. C'est un peu vrai en Python aussi:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

Mais les classes sont plus que cela en Python. Les classes sont aussi des objets.

Oui, les objets.

Dès que vous utilisez la class mots-clés, Python l'exécute et crée un OBJECT. L'instruction

>>> class ObjectCreator(object):
...       pass
...

crée en mémoire un objet nommé "ObjectCreator".

Cet objet (la classe) est lui-même capable de créer des objets (les instances), c'est pourquoi il s'agit d'une classe .

Mais quand même, c'est un objet, et donc:

  • vous pouvez l'assigner à une variable
  • vous pouvez le copier
  • vous pouvez y ajouter des attributs
  • vous pouvez le passer comme paramètre de fonction

par exemple:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Créer des classes dynamiquement

Comme les classes sont des objets, vous pouvez les créer à la volée, comme n'importe quel objet.

Tout d'abord, vous pouvez créer une classe dans une fonction à l'aide de la class :

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

Mais ce n'est pas si dynamique, car vous devez toujours écrire vous-même toute la classe.

Comme les classes sont des objets, elles doivent être générées par quelque chose.

Lorsque vous utilisez le mot class clé class , Python crée cet objet automatiquement. Mais comme avec la plupart des choses en Python, cela vous donne un moyen de le faire manuellement.

Rappelez-vous le type fonction? La bonne vieille fonction qui vous permet de savoir quel type d'objet est:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

Eh bien, le type a une capacité complètement différente, il peut également créer des classes à la volée. type peut prendre la description d'une classe en tant que paramètres et renvoyer une classe.

(Je sais, c’est idiot que la même fonction puisse avoir deux utilisations complètement différentes selon les paramètres que vous lui transmettez. C’est un problème dû à la compatibilité ascendante en Python)

type fonctionne de cette façon:

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

par exemple:

>>> class MyShinyClass(object):
...       pass

peut être créé manuellement de cette façon:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

Vous remarquerez que nous utilisons "MyShinyClass" comme nom de la classe et comme variable pour contenir la référence de la classe. Ils peuvent être différents, mais il n'y a aucune raison de compliquer les choses.

type accepte un dictionnaire pour définir les attributs de la classe. Alors:

>>> class Foo(object):
...       bar = True

Peut être traduit en:

>>> Foo = type('Foo', (), {'bar':True})

Et utilisé comme classe normale:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

Et bien sûr, vous pouvez en hériter, alors:

>>>   class FooChild(Foo):
...         pass

serait:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

Finalement, vous voudrez ajouter des méthodes à votre classe. Il suffit de définir une fonction avec la signature appropriée et de l’affecter à un attribut.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

Et vous pouvez ajouter encore plus de méthodes après avoir créé la classe de manière dynamique, comme si vous ajoutiez des méthodes à un objet de classe créé normalement.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

Vous voyez où nous allons: en Python, les classes sont des objets, et vous pouvez créer une classe à la volée, de manière dynamique.

C'est ce que fait Python lorsque vous utilisez la class mot-clé, et ce, à l'aide d'une métaclasse.

Que sont les métaclasses (enfin)

Les métaclasses sont le «matériel» qui crée des classes.

Vous définissez des classes afin de créer des objets, non?

Mais nous avons appris que les classes Python sont des objets.

Eh bien, ce sont les métaclasses qui créent ces objets. Ce sont les classes des classes, vous pouvez les imaginer de cette façon:

MyClass = MetaClass()
my_object = MyClass()

Vous avez vu que ce type vous permet de faire quelque chose comme ceci:

MyClass = type('MyClass', (), {})

C'est parce que le type fonction est en fait une métaclasse. type est la métaclasse utilisée par Python pour créer toutes les classes en arrière-plan.

Maintenant vous vous demandez pourquoi diable est-il écrit en minuscule et non en Type ?

Eh bien, je suppose que c'est une question de cohérence avec str , la classe qui crée les objets chaînes, et la classe qui crée les objets entiers. type est juste la classe qui crée des objets de classe.

Vous voyez cela en vérifiant l'attribut __class__ .

Tout, et je veux dire tout, est un objet en Python. Cela inclut les ints, les chaînes, les fonctions et les classes. Tous sont des objets. Et tous ont été créés à partir d'une classe:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

Maintenant, quelle est la __class__ de toute __class__ ?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

Une métaclasse est donc ce qui crée des objets de classe.

Vous pouvez appeler cela une "usine de classe" si vous le souhaitez.

type est la métaclasse intégrée utilisée par Python, mais vous pouvez bien sûr créer votre propre métaclasse.

L'attribut __metaclass__

En Python 2, vous pouvez ajouter un attribut __metaclass__ lorsque vous écrivez une classe (voir la section suivante pour la syntaxe Python 3):

class Foo(object):
    __metaclass__ = something...
    [...]

Si vous le faites, Python utilisera la métaclasse pour créer la classe Foo .

Attention, c'est délicat.

Vous écrivez d'abord la class Foo(object) , mais l'objet de classe Foo n'est pas encore créé en mémoire.

Python recherchera __metaclass__ dans la définition de la classe. S'il le trouve, il l'utilisera pour créer la classe d'objets Foo . Si ce n'est pas le cas, il utilisera le type pour créer la classe.

Lisez cela plusieurs fois.

Quand tu fais:

class Foo(Bar):
    pass

Python fait ce qui suit:

Y a-t-il un attribut __metaclass__ dans Foo ?

Si oui, créez en mémoire un objet de classe (j'ai dit un objet de classe, restez avec moi ici), avec le nom Foo en utilisant le contenu de __metaclass__ .

Si Python ne peut pas trouver __metaclass__ , il recherchera un __metaclass__ au niveau MODULE et tentera de faire de même (mais uniquement pour les classes qui n'héritent de rien, essentiellement des classes de style ancien).

Ensuite, s'il ne trouve pas du tout __metaclass__ , il utilisera la propre métaclasse de Bar (le premier parent) (qui peut être le type par défaut) pour créer l'objet de classe.

Veillez ici à ce que l'attribut __metaclass__ ne soit pas hérité, mais la métaclasse du parent ( Bar.__class__ ). Si Bar utilisé un attribut __metaclass__ ayant créé Bar avec type() (et non pas type.__new__() ), les sous-classes n'hériteront pas de ce comportement.

Maintenant, la grande question est: que pouvez-vous mettre dans __metaclass__ ?

La réponse est: quelque chose qui peut créer une classe.

Et que peut créer une classe? type , ou tout ce qui sous-classe ou l'utilise.

Métaclasses dans Python 3

La syntaxe pour définir la métaclasse a été modifiée dans Python 3:

class Foo(object, metaclass=something):
    [...]

c'est-à-dire que l'attribut __metaclass__ n'est plus utilisé, en faveur d'un argument de mot clé dans la liste des classes de base.

Le comportement des métaclasses reste toutefois sensiblement le même .

Métaclasses personnalisées

L'objectif principal d'une métaclasse est de changer automatiquement la classe lorsqu'elle est créée.

Vous faites généralement cela pour les API, où vous voulez créer des classes correspondant au contexte actuel.

Imaginez un exemple stupide, dans lequel vous décidez que toutes les classes de votre module doivent avoir leurs attributs écrits en majuscules. Il existe plusieurs façons de procéder, mais l'une d'elles consiste à définir __metaclass__ au niveau du module.

De cette façon, toutes les classes de ce module seront créées en utilisant cette métaclasse, et nous devons simplement dire à la métaclasse de mettre tous les attributs en majuscule.

Heureusement, __metaclass__ peut en réalité être __metaclass__ , il n'a pas besoin d'être une classe formelle (je sais, quelque chose avec "classe" dans son nom n'a pas besoin d'être une classe, allez comprendre ... mais c'est utile).

Nous allons donc commencer par un exemple simple, en utilisant une fonction.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

Faisons exactement la même chose, mais en utilisant une vraie classe pour une métaclasse:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

Mais ce n'est pas vraiment la POO. Nous appelons directement le type et nous ne __new__ le parent __new__ . Faisons le:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

Vous avez peut-être remarqué l'argument supplémentaire upperattr_metaclass . Cela n'a rien de spécial: __new__ reçoit toujours la classe dans laquelle elle est définie, en tant que premier paramètre. Tout comme vous avez self pour les méthodes ordinaires qui reçoivent l'instance comme premier paramètre, ou la classe de définition pour les méthodes de classe.

Bien sûr, les noms que j'ai utilisés ici sont longs pour des raisons de clarté, mais comme pour self - self , tous les arguments ont des noms conventionnels. Donc, une vraie métaclasse de production ressemblerait à ceci:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

Nous pouvons le rendre encore plus propre en utilisant super , ce qui facilitera l'héritage (car oui, vous pouvez avoir des métaclasses, héritant de métaclasses, héritant de type):

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

C'est tout. Il n'y a vraiment rien de plus sur les métaclasses.

La complexité du code à l'aide de métaclasses ne s'explique pas par les métaclasses, mais par le fait que vous utilisez généralement des métaclasses pour créer des éléments tordus reposant sur l'introspection, la manipulation de l'héritage, des vars tels que __dict__ , etc.

En effet, les métaclasses sont particulièrement utiles pour faire de la magie noire, donc compliquée. Mais par eux-mêmes, ils sont simples:

  • intercepter une création de classe
  • modifier la classe
  • retourne la classe modifiée

Pourquoi voudriez-vous utiliser des classes métaclasses au lieu de fonctions?

Puisque __metaclass__ peut accepter n'importe quel appelable, pourquoi utiliseriez-vous une classe, car elle est évidemment plus compliquée?

Il y a plusieurs raisons de le faire:

  • L'intention est claire. Lorsque vous lisez UpperAttrMetaclass(type) , vous savez ce qui va suivre
  • Vous pouvez utiliser la POO. La métaclasse peut hériter de la métaclasse et remplacer les méthodes parent. Les métaclasses peuvent même utiliser des métaclasses.
  • Les sous-classes d'une classe seront des instances de sa métaclasse si vous avez spécifié une classe de métaclasse, mais pas avec une fonction de métaclasse.
  • Vous pouvez mieux structurer votre code. Vous n'utilisez jamais de métaclasse pour quelque chose d'aussi trivial que l'exemple ci-dessus. C'est généralement pour quelque chose de compliqué. Avoir la possibilité de créer plusieurs méthodes et de les regrouper dans une même classe est très utile pour faciliter la lecture du code.
  • Vous pouvez accrocher __new__ , __init__ et __call__ . Ce qui vous permettra de faire des choses différentes. Même si généralement vous pouvez tout faire dans __new__ , certaines personnes sont simplement plus à l'aise avec __init__ .
  • Ce sont des métaclasses, bon Dieu! Cela doit vouloir dire quelque chose!

Pourquoi utiliseriez-vous des métaclasses?

Maintenant la grande question. Pourquoi voudriez-vous utiliser une fonctionnalité obscure sujette aux erreurs?

En général, vous ne le faites pas:

Les métaclasses sont une magie plus profonde que 99% des utilisateurs ne devraient jamais s'inquiéter. Si vous vous demandez si vous en avez besoin, vous n'en avez pas (les personnes qui en ont réellement besoin savent avec certitude qu'elles en ont besoin et n'ont pas besoin d'explication quant à pourquoi).

Tim Peters, gourou du python

Le principal cas d'utilisation d'une métaclasse est la création d'une API. Un exemple typique de ceci est l'ORM de Django.

Cela vous permet de définir quelque chose comme ceci:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

Mais si vous faites ceci:

guy = Person(name='bob', age='35')
print(guy.age)

Il ne retournera pas d'objet IntegerField . Il retournera un int , et peut même le prendre directement à partir de la base de données.

Ceci est possible car models.Model définit __metaclass__ et utilise une magie qui transformera la Person vous venez de définir avec de simples instructions en un __metaclass__ complexe avec un champ de base de données.

Django donne un aspect simple à quelque chose de complexe en exposant une API simple et en utilisant des métaclasses, en recréant le code à partir de cette API pour faire le vrai travail en arrière-plan.

Le dernier mot

Tout d'abord, vous savez que les classes sont des objets pouvant créer des instances.

En fait, les classes sont elles-mêmes des instances. De métaclasses.

>>> class Foo(object): pass
>>> id(Foo)
142630324

Tout est un objet en Python, et il s’agit d’instances de classes ou de métaclasses.

Sauf pour le type .

type est en fait sa propre métaclasse. Ce n'est pas quelque chose que vous pouvez reproduire en Python pur, et cela se fait en trichant un peu au niveau de la mise en œuvre.

Deuxièmement, les métaclasses sont compliquées. Vous ne voudrez peut-être pas les utiliser pour des modifications de classe très simples. Vous pouvez changer de classe en utilisant deux techniques différentes:

99% du temps que vous avez besoin d'une modification de classe, vous feriez mieux de les utiliser.

Mais 98% du temps, vous n'avez pas du tout besoin de changer de classe.

heritage abstraite python3

Que sont les métaclasses et à quoi les utilisons-nous?




Une utilisation des métaclasses consiste à ajouter automatiquement de nouvelles propriétés et méthodes à une instance.

Par exemple, si vous regardez les modèles Django , leur définition est un peu déroutante. Il semble que vous ne définissiez que les propriétés de classe:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

Cependant, au moment de l'exécution, les objets Personne sont remplis de toutes sortes de méthodes utiles. Voir la source pour une métaclasserie incroyable.




Je pense que l’introduction d’ONLamp à la programmation de métaclasse est bien écrite et donne une très bonne introduction au sujet bien qu’elle ait déjà plusieurs années.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (archivé à l' https://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html )

En bref: une classe est un modèle pour la création d'une instance, une métaclasse est un modèle pour la création d'une classe. On peut facilement voir que, dans Python, les classes doivent également être des objets de première classe pour activer ce comportement.

Je n'en ai jamais écrit moi-même, mais je pense qu'une des plus belles utilisations des métaclasses est visible dans le framework Django . Les classes de modèle utilisent une approche de métaclasse pour permettre un style déclaratif d'écriture de nouveaux modèles ou de classes de formulaire. Pendant que la métaclasse crée la classe, tous les membres ont la possibilité de personnaliser la classe elle-même.

La chose qui reste à dire est: Si vous ne savez pas ce que sont les métaclasses, la probabilité que vous n'en ayez pas besoin est de 99%.




Mise à jour Python 3

Il existe (à ce stade) deux méthodes clés dans une métaclasse:

  • __prepare__ , et
  • __new__

__prepare__vous permet de fournir un mappage personnalisé (tel qu'un OrderedDict) à utiliser comme espace de noms pendant la création de la classe. Vous devez renvoyer une instance de l'espace de nom choisi. Si vous n'implémentez pas, __prepare__une normale dictest utilisée.

__new__ est responsable de la création / modification effective de la classe finale.

Une métaclasse dépouillée, sans rien-extra, aimerait:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

Un exemple simple:

Supposons que vous souhaitiez qu'un code de validation simple soit exécuté sur vos attributs - comme si cela devait toujours être un intou str. Sans métaclasse, votre classe ressemblerait à quelque chose comme:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

Comme vous pouvez le constater, vous devez répéter le nom de l'attribut deux fois. Cela rend les fautes de frappe possibles avec les insectes irritants.

Une simple métaclasse peut résoudre ce problème:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

Voici à quoi ressemblerait la métaclasse (ne pas utiliser __prepare__car ce n’est pas nécessaire):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

Un exemple de:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

produit:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

Remarque : Cet exemple est assez simple et aurait pu également être réalisé avec un décorateur de classe, mais une métaclasse réelle ferait probablement beaucoup plus.

La classe 'ValidateType' pour référence:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value



Une métaclasse est une classe qui indique comment (une autre) classe doit être créée.

C'est un cas où j'ai vu la métaclasse comme une solution à mon problème: j'avais un problème vraiment compliqué, qui aurait probablement pu être résolu différemment, mais j'ai choisi de le résoudre en utilisant une métaclasse. En raison de sa complexité, c’est l’un des rares modules que j’ai écrit où les commentaires dans ce module dépassent la quantité de code écrite. C'est ici...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()



La version tl; dr

La type(obj)fonction vous obtient le type d'un objet.

Le type()d'une classe est sa métaclasse .

Pour utiliser une métaclasse:

class Foo(object):
    __metaclass__ = MyMetaClass



La fonction type () peut renvoyer le type d’un objet ou en créer un nouveau,

Par exemple, nous pouvons créer une classe Hi avec la fonction type () et n'avons pas besoin de l'utiliser de la sorte avec la classe Hi (objet):

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

En plus d'utiliser type () pour créer des classes de manière dynamique, vous pouvez contrôler le comportement de création de la classe et utiliser une métaclasse.

Selon le modèle d'objet Python, la classe est l'objet. La classe doit donc être une instance d'une autre classe. Par défaut, une classe Python est une instance de la classe type. Autrement dit, type est une métaclasse de la plupart des classes intégrées et une métaclasse de classes définies par l'utilisateur.

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

La magie prend effet lorsque nous passons des arguments de mots clés dans une métaclasse. Elle indique à l'interpréteur Python de créer la liste de personnalisation via ListMetaclass. new (), à ce stade, nous pouvons modifier la définition de classe, par exemple, ajouter une nouvelle méthode, puis renvoyer la définition révisée.




Related