Question Quelles sont les métaclasses en Python?


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


4572
2017-09-19 06:10


origine


Réponses:


Une métaclasse est la classe d'une classe. Comme une classe définit comment une instance de la classe se comporte, une métaclasse définit le comportement d'une classe. Une classe est une instance d'une métaclasse.

metaclass diagram

En Python, vous pouvez utiliser des callables arbitraires pour les métaclasses (comme Jerub montre), l'approche la plus utile consiste à en faire une classe réelle. type est la métaclasse habituelle en Python. Au cas où vous vous demandez, oui, type est lui-même une classe, et c'est son propre type. Vous ne serez pas capable de recréer quelque chose comme type purement en Python, mais Python triche un peu. Pour créer votre propre métaclasse en Python, vous voulez juste faire une sous-classe type.

Une métaclasse est le plus souvent utilisée en tant qu'usine de classe. Comme vous créez une instance de la classe en appelant la classe, Python crée une nouvelle classe (lorsqu'elle exécute l'instruction 'class') en appelant la métaclasse. Combiné avec la normale __init__ et __new__ méthodes, les métaclasses vous permettent donc de faire des «choses supplémentaires» lors de la création d'une classe, comme enregistrer la nouvelle classe avec un registre, ou même remplacer la classe par quelque chose d'autre.

Quand le class instruction est exécutée, Python exécute d'abord le corps du class déclaration comme un bloc de code normal. L'espace de noms résultant (un dict) contient les attributs de la future classe. La métaclasse est déterminée en regardant les baseclasses de la future classe (les métaclasses sont héritées), au __metaclass__ attribut de la classe-à-être (le cas échéant) ou le __metaclass__ variable globale. La métaclasse est ensuite appelée avec le nom, les bases et les attributs de la classe pour l'instancier.

Cependant, les métaclasses définissent réellement type d'une classe, pas seulement une usine pour cela, donc vous pouvez faire beaucoup plus avec eux. Vous pouvez, par exemple, définir des méthodes normales sur la métaclasse. Ces méthodes metaclass sont comme classmethods, en ce sens qu'elles peuvent être appelées sur la classe sans instance, mais elles ne sont pas non plus comme classmethods en ce qu'elles ne peuvent pas être appelées sur une instance de la classe. type.__subclasses__() est un exemple d'une méthode sur le type métaclasse. Vous pouvez également définir les méthodes "magiques" normales, comme __add__, __iter__ et __getattr__, pour implémenter ou changer le comportement de la classe.

Voici un exemple agrégé des bits et des pièces:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__

2079
2017-09-19 07:01



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 langues, les classes sont juste des morceaux de code qui décrivent comment produire un objet. C'est aussi vrai en Python:

>>> 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 le mot-clé class, Python l'exécute et crée un objet. L'instruction

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

crée en mémoire un objet avec le nom "ObjectCreator".

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

Mais encore, 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 en 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

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

D'abord, vous pouvez créer une classe dans une fonction en utilisant 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, puisque vous devez toujours écrire toute la classe vous-même.

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

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

Rappelez-vous la fonction type? La bonne vieille fonction qui vous permet de savoir ce que taper un objet est:

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

Bien, 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 retourne un cours.

(Je sais, c'est bête que la même fonction puisse avoir deux utilisations complètement différentes selon les paramètres que vous lui passez. compatibilité 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 en tant que variable pour contenir la référence de classe. Ils peuvent être différents, mais il n'y a pas de 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 une 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, donc:

>>>   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. Définissez simplement une fonction avec la signature appropriée et attribuez-le en tant qu'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éé dynamiquement la classe, tout comme ajouter 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, dynamiquement.

C'est ce que fait Python lorsque vous utilisez le mot-clé class, et il le fait en utilisant une métaclasse.

Quelles sont les métaclasses (enfin)

Les métaclasses sont les «trucs» qui créent des classes.

Vous définissez des classes pour créer des objets, n'est-ce pas?

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

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

MyClass = MetaClass()
my_object = MyClass()

Vous avez vu ça type vous permet de faire quelque chose comme ceci:

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

C'est parce que la fonction type est en fait une métaclasse. type est le metaclass Python utilise pour créer toutes les classes dans les coulisses.

Maintenant, vous vous demandez pourquoi le diable est-il écrit en minuscules, et non Type?

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

Vous voyez cela en vérifiant le __class__ attribut.

Tout, et je veux dire tout, est un objet en Python. Cela inclut ints, chaînes, fonctions et classes. Tous sont des objets. Et tous ont été créé à 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'>

Ainsi, une métaclasse est juste la substance qui crée des objets de classe.

Vous pouvez l'appeler une «usine de classe» si vous le souhaitez.

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

le __metaclass__ attribut

Vous pouvez ajouter un __metaclass__ Attribut lorsque vous écrivez une classe:

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

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

Attention, c'est difficile.

vous écrivez class Foo(object) d'abord, mais l'objet de classe Foo n'est pas créé en mémoire pour le moment.

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

Lisez cela plusieurs fois.

Quand vous faites:

class Foo(Bar):
    pass

Python fait ce qui suit:

y a t-il __metaclass__ attribuer dans Foo?

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

Si Python ne peut pas trouver __metaclass__, il va chercher un __metaclass__ au niveau MODULE, et essayez de faire la même chose (mais seulement pour les classes qui n'héritent d'rien, essentiellement des classes de style ancien).

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

Faites attention ici que le __metaclass__ attribut ne sera pas hérité, la métaclasse du parent (Bar.__class__) sera. Si Bar utilisé un __metaclass__ attribut qui a créé Bar avec type() (et pas type.__new__()), les sous-classes n'hériteront pas de ce comportement.

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

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

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

Métaclasses personnalisées

Le but principal d'une métaclasse est de changer la classe automatiquement, quand c'est créé.

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

Imaginez un exemple stupide, où vous décidez que toutes les classes de votre module devrait avoir leurs attributs écrits en majuscules. Il y a plusieurs façons de faites ceci, mais une façon est de __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 juste dire à la métaclasse de transformer tous les attributs en majuscules.

Heureusement, __metaclass__ peut en fait être appelable, il n'a pas besoin d'être un 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'

Maintenant, 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 OOP. Nous appelons type directement et nous ne remplaçons pas ou appelez 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. Il y a rien de spécial à ce sujet: __new__ reçoit toujours la classe dans laquelle il est défini, en tant que premier paramètre. Juste comme tu as self pour les méthodes ordinaires qui reçoivent l'instance en tant que 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 la clarté, mais comme pour self, tous les arguments ont des noms conventionnels. Donc une vraie production La métaclasse 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ériter des métaclasses, hériter du 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 raison derrière la complexité du code utilisant des métaclasses n'est pas parce que des métaclasses, c'est parce que vous utilisez généralement des métaclasses pour faire des choses tordues s'appuyant sur l'introspection, la manipulation de l'héritage, les vars tels que __dict__, etc.

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

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

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

Depuis __metaclass__ peut accepter tout appelable, pourquoi utiliseriez-vous une classe puisque c'est évidemment plus compliqué?

Il y a plusieurs raisons de le faire:

  • L'intention est claire. Quand tu lis UpperAttrMetaclass(type), tu sais ce qui va suivre
  • Vous pouvez utiliser OOP. La métaclasse peut hériter de la métaclasse, remplacer les méthodes parentes. 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 métaclasse, mais pas une métaclasse.
  • Vous pouvez mieux structurer votre code. Vous n'utilisez jamais de métaclasses pour quelque chose comme trivial comme l'exemple ci-dessus. C'est habituellement pour quelque chose de compliqué. Avoir le la possibilité de faire plusieurs méthodes et de les regrouper en une classe est très utile pour rendre le code plus facile à lire.
  • Vous pouvez accrocher sur __new__, __init__ et __call__. Ce qui permettra vous faire des choses différentes. Même si d'habitude vous pouvez tout faire dans __new__, certaines personnes sont juste plus à l'aise en utilisant __init__.
  • Ce sont des métaclasses, bon sang! Cela doit signifier quelque chose!

Pourquoi utiliseriez-vous des métaclasses?

Maintenant, la grande question. Pourquoi utiliseriez-vous une fonction sujettes aux erreurs obscures?

Eh bien, d'habitude vous ne le faites pas:

Les métaclasses sont une magie plus profonde   99% des utilisateurs ne devraient jamais s'inquiéter.   Si vous vous demandez si vous en avez besoin,   vous ne le faites pas (les gens qui   besoin d'eux savoir avec certitude que   ils ont besoin d'eux, et n'ont pas besoin d'un   explication sur pourquoi).

Python Guru Tim Peters

Le cas d'utilisation principal 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 un IntegerField objet. Il retournera un int, et peut même le prendre directement à partir de la base de données.

C'est possible parce que models.Model définit __metaclass__ et il utilise une magie qui va tourner la Person vous venez de définir avec des déclarations simples dans un crochet complexe à un champ de base de données.

Django simplifie l'apparence de quelque chose de complexe en exposant une API simple et en utilisant des métaclasses, en recréant du code à partir de cette API pour faire le vrai travail Dans les coulisses.

Le dernier mot

D'abord, vous savez que les classes sont des objets qui peuvent 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 ils sont tous des instances de classes ou des instances de métaclasses.

À l'exception de type.

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

Deuxièmement, les métaclasses sont compliquées. Vous pouvez ne pas vouloir les utiliser pour altérations de classe très simples. Vous pouvez changer de classe en utilisant deux techniques différentes:

99% du temps, vous avez besoin d'altération de classe, vous feriez mieux d'utiliser ceux-ci.

Mais 98% du temps, vous n'avez pas besoin d'altération de classe du tout.


5755
2017-09-19 06:26



Notez, cette réponse est pour Python 2.x comme il a été écrit en 2008, les métaclasses sont légèrement différentes dans 3.x, voir les commentaires.

Les métaclasses sont la sauce secrète qui fait le travail de «classe». La métaclasse par défaut pour un nouvel objet de style s'appelle 'type'.

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

Les métaclasses prennent 3 args. 'prénom','bases' et 'dict'

Voici où commence le secret. Cherchez d'où viennent le nom, les bases et la dict dans cet exemple de définition de classe.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Permet de définir une métaclasse qui montrera commentclasse:l'appelle.

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

Et maintenant, un exemple qui signifie réellement quelque chose, cela va automatiquement rendre les variables de la liste "attributs" définies sur la classe, et mettre à None.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

Notez que le comportement magique que 'Initalisé' gagne en ayant la métaclasse init_attributes n'est pas transmis à une sous-classe de Initalised.

Voici un exemple encore plus concret, montrant comment vous pouvez sous-classer 'type' pour faire une métaclasse qui effectue une action lors de la création de la classe. C'est assez difficile:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

 class Foo(object):
     __metaclass__ = MetaSingleton

 a = Foo()
 b = Foo()
 assert a is b

311
2017-09-19 06:45



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

Par exemple, si vous regardez Modèles Django, leur définition semble un peu confuse. 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, à l'exécution, les objets Person sont remplis de toutes sortes de méthodes utiles. Voir le la source pour une métaclasse étonnante.


124
2018-06-21 16:30



D'autres ont expliqué comment fonctionnent les métaclasses et comment elles s'intègrent dans le système de type Python. Voici un exemple de ce qu'ils peuvent être utilisés. Dans un cadre de test que j'ai écrit, je voulais garder une trace de l'ordre dans lequel les classes étaient définies, afin que je puisse les instancier plus tard dans cet ordre. J'ai trouvé le plus facile de le faire en utilisant une métaclasse.

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

Tout ce qui est une sous-classe de MyType puis obtient un attribut de classe _order qui enregistre l'ordre dans lequel les classes ont été définies.


117
2017-09-19 06:32



Je pense que l'introduction de ONLamp à la programmation de la métaclasse est bien écrite et donne une très bonne introduction au sujet en dépit d'être déjà plusieurs années.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

En bref: Une classe est un plan pour la création d'une instance, une métaclasse est un plan pour la création d'une classe. On peut facilement voir que dans les classes Python, il faut aussi des objets de première classe pour activer ce comportement.

Je n'en ai jamais écrit moi-même, mais je pense que l'une des plus belles utilisations des métaclasses Le framework Django. Les classes de modèle utilisent une approche de métaclasse pour activer 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'aura pas besoin d'eux est de 99%.


86
2017-08-10 23:28



Quelles sont les métaclasses? A quoi les utilisez-vous?

TLDR: une métaclasse instancie et définit le comportement d'une classe comme une instance instanciée et définit le comportement d'une instance.

Pseudocode:

>>> Class(...)
instance

Ce qui précède devrait sembler familier. Eh bien, où est-ce que Class viens de? C'est une instance d'une métaclasse (aussi pseudocode):

>>> Metaclass(...)
Class

En code réel, nous pouvons passer la métaclasse par défaut, type, tout ce dont nous avons besoin pour instancier une classe et nous obtenons une classe:

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

Le mettre différemment

  • Une classe est une instance comme une métaclasse est à une classe.

    Lorsque nous instancions un objet, nous obtenons une instance:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance
    

    De même, lorsque nous définissons explicitement une classe avec la métaclasse par défaut, type, nous l'instancions:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • En d'autres termes, une classe est une instance d'une métaclasse:

    >>> isinstance(object, type)
    True
    
  • Mettez une troisième façon, une métaclasse est la classe d'une classe.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>
    

Lorsque vous écrivez une définition de classe et que Python l'exécute, il utilise une métaclasse pour instancier l'objet de classe (qui, à son tour, sera utilisé pour instancier des instances de cette classe).

Tout comme nous pouvons utiliser les définitions de classes pour modifier le comportement des instances d'objets personnalisées, nous pouvons utiliser une définition de classe de métaclasse pour modifier le comportement d'un objet de classe.

À quoi peuvent-ils servir? Du docs:

Les utilisations potentielles pour les métaclasses sont illimitées. Certaines idées explorées incluent la journalisation, la vérification d'interface, la délégation automatique, la création automatique de propriété, les proxies, les frameworks et le verrouillage / synchronisation automatique des ressources.

Néanmoins, il est généralement recommandé aux utilisateurs d'éviter d'utiliser des métaclasses, sauf en cas de nécessité absolue.

Vous utilisez une métaclasse à chaque fois que vous créez une classe:

Lorsque vous écrivez une définition de classe, par exemple, comme ceci,

class Foo(object): 
    'demo'

Vous instanciez un objet de classe.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

C'est la même chose que d'appeler fonctionnellement type avec les arguments appropriés et en affectant le résultat à une variable de ce nom:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Notez que certaines choses sont automatiquement ajoutées à __dict__, c'est-à-dire, l'espace de noms:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

le métaclasse de l'objet que nous avons créé, dans les deux cas, est type.

(Une note sur le contenu de la classe __dict__: __module__ est là parce que les classes doivent savoir où elles sont définies, et __dict__ et __weakref__ sont là parce que nous ne définissons pas __slots__ - si nous définir __slots__ nous allons économiser un peu d'espace dans les instances, comme nous pouvons l'interdire __dict__ et __weakref__ en les excluant. Par exemple:

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

... mais je m'égare.)

Nous pouvons étendre type comme toute autre définition de classe:

Voici la valeur par défaut __repr__ des classes:

>>> Foo
<class '__main__.Foo'>

L'une des choses les plus précieuses que nous pouvons faire par défaut en écrivant un objet Python est de lui fournir un bon __repr__. Quand nous appelons help(repr) nous apprenons qu'il y a un bon test pour un __repr__ cela nécessite également un test d'égalité - obj == eval(repr(obj)). La mise en œuvre simple suivante de __repr__ et __eq__ pour les instances de classe de notre classe de type nous fournit une démonstration qui peut améliorer la valeur par défaut __repr__ des classes:

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

Alors maintenant, quand nous créons un objet avec cette métaclasse, le __repr__ répercuté sur la ligne de commande fournit un spectacle beaucoup moins laid que le défaut:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

Avec une belle __repr__ défini pour l'instance de classe, nous avons une plus grande capacité à déboguer notre code. Cependant, beaucoup plus de vérification avec eval(repr(Class)) est peu probable (car les fonctions seraient plutôt impossibles à évaluer à partir de leur défaut __repr__de).

Une utilisation prévue: __prepare__ un espace de noms

Si, par exemple, nous voulons savoir dans quel ordre les méthodes d'une classe sont créées, nous pourrions fournir une dict ordonnée en tant qu'espace de noms de la classe. Nous ferions cela avec __prepare__ lequel renvoie la dict de l'espace de noms pour la classe si elle est implémentée en Python 3:

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

Et utilisation:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

Et maintenant nous avons un enregistrement de l'ordre dans lequel ces méthodes (et d'autres attributs de classe) ont été créés:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Notez que cet exemple a été adapté de Documentation - le nouveau enum dans la bibliothèque standard est ce que ca.

Nous avons donc instancié une métaclasse en créant une classe. Nous pouvons également traiter la métaclasse comme nous le ferions pour n'importe quelle autre classe. Il a un ordre de résolution de méthode:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

Et il a à peu près le bon repr (que nous ne pouvons plus évaluer à moins que nous puissions trouver un moyen de représenter nos fonctions.):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})

74
2018-03-01 19:48



Python 3 mise à jour

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 noms de votre choix. Si vous ne mettez pas en œuvre __prepare__un normal dict est utilisé.

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

Une métaclasse de bare-bones, do-nothing-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 utiliser un code de validation simple pour vos attributs, comme s'il devait toujours être un int ou un str. Sans une métaclasse, votre classe ressemblerait à ceci:

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

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

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

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

C'est à quoi ressemblerait la métaclasse (n'utilisant pas __prepare__ puisque 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, il aurait aussi pu être accompli avec un décorateur de classe, mais vraisemblablement une métaclasse réelle ferait 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

55
2017-10-13 09:21



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'ai eu un problème très 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 la complexité, c'est l'un des rares modules que j'ai écrits où les commentaires dans le module dépassent la quantité de code qui a été é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()

41
2017-08-09 18:49



Rôle d'une métaclasse __call__() méthode lors de la création d'une instance de classe

Si vous avez fait de la programmation Python pendant plus de quelques mois, vous finirez par tomber sur du code qui ressemble à ceci:

# define a class
class SomeClass(object):
    # ...
    # some definition here ...
    # ...

# create an instance of it
instance = SomeClass()

# then call the object as if it's a function
result = instance('foo', 'bar')

Ce dernier est possible lorsque vous implémentez le __call__() méthode magique sur la classe.

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

le __call__() La méthode est invoquée lorsqu'une instance d'une classe est utilisée comme appelable. Mais comme nous l'avons vu dans les réponses précédentes, une classe elle-même est une instance d'une métaclasse, donc quand nous utilisons la classe comme callable (c'est-à-dire quand nous en créons une instance), nous appelons sa métaclasse __call__() méthode. À ce stade, la plupart des programmeurs Python sont un peu confus, car on leur a dit que lors de la création d'une telle instance instance = SomeClass() vous appelez c'est __init__() méthode. Certains qui ont creusé un peu plus profond savent qu'avant __init__() il y a __new__(). Eh bien, aujourd'hui une autre couche de vérité est révélée, avant __new__() il y a la métaclasse __call__().

Étudions la chaîne d'appel de la méthode à partir de la perspective de créer une instance d'une classe.

Ceci est une métaclasse qui se connecte exactement au moment où une instance est créée et au moment où elle est sur le point de la renvoyer.

class Meta_1(type):
    def __call__(cls):
        print "Meta_1.__call__() before creating an instance of ", cls
        instance = super(Meta_1, cls).__call__()
        print "Meta_1.__call__() about to return instance."
        return instance

C'est une classe qui utilise cette métaclasse

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

Et maintenant, créons une instance de Class_1

instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.

Le code ci-dessus ne fait rien d'autre que de consigner la tâche et de déléguer ensuite le travail réel au parent (c'est-à-dire en conservant le comportement par défaut). Donc avec type étant Meta_1la classe parent, nous pouvons imaginer que ce serait la pseudo implémentation de type.__call__():

class type:
    def __call__(cls, *args, **kwarg):

        # ... maybe a few things done to cls here

        # then we call __new__() on the class to create an instance
        instance = cls.__new__(cls, *args, **kwargs)

        # ... maybe a few things done to the instance here

        # then we initialize the instance with its __init__() method
        instance.__init__(*args, **kwargs)

        # ... maybe a few more things done to instance here

        # then we return it
        return instance

Nous pouvons voir que la métaclasse __call__() méthode est celle qui est appelée en premier. Il délègue ensuite la création de l'instance à la classe __new__()méthode et initialisation à l'instance de l'instance __init__(). C'est aussi celui qui retourne finalement l'instance.

De ce qui précède, il se trouve que la métaclasse __call__() a également la possibilité de décider si un appel à Class_1.__new__() ou Class_1.__init__() sera finalement fait. Au cours de son exécution, il pourrait effectivement retourner un objet qui n'a pas été touché par l'une ou l'autre de ces méthodes. Prenons par exemple cette approche du modèle singleton:

class Meta_2(type):
    singletons = {}

    def __call__(cls, *args, **kwargs):
        if cls in Meta_2.singletons:
            # we return the only instance and skip a call to __new__() 
            # and __init__()
            print ("{} singleton returning from Meta_2.__call__(), "
                   "skipping creation of new instance.".format(cls))
            return Meta_2.singletons[cls]

        # else if the singleton isn't present we proceed as usual
        print "Meta_2.__call__() before creating an instance."
        instance = super(Meta_2, cls).__call__(*args, **kwargs)
        Meta_2.singletons[cls] = instance
        print "Meta_2.__call__() returning new instance."
        return instance

class Class_2(object):

    __metaclass__ = Meta_2

    def __new__(cls, *args, **kwargs):
        print "Class_2.__new__() before creating instance."
        instance = super(Class_2, cls).__new__(cls)
        print "Class_2.__new__() returning instance."
        return instance

    def __init__(self, *args, **kwargs):
        print "entering Class_2.__init__() for initialization."
        super(Class_2, self).__init__()
        print "exiting Class_2.__init__()."

Observons ce qui se passe quand on essaie à plusieurs reprises de créer un objet de type Class_2

a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.

b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

a is b is c # True

37
2017-12-27 02:21