15 Domanda: Cosa sono le metaclassi in Python?

domanda creata a Thu, Jan 11, 2018 12:00 AM

Che cosa sono i metaclassi e per cosa li usiamo?

    
5184
15 risposte                              15                         

Un metaclasse è la classe di una classe. Una classe definisce come si comporta un'istanza della classe (cioè un oggetto) mentre una metaclasse definisce come si comporta una classe. Una classe è un'istanza di un metaclass.

Mentre in Python puoi usare le callable arbitrarie per metaclassi (come Jerub mostra), l'approccio migliore è quello di renderlo una vera e propria classe. type è il solito metaclasse in Python. type è di per sé una classe ed è un suo tipo. Non sarai in grado di ricreare qualcosa come type esclusivamente in Python, ma Python imbroglia un po '. Per creare il tuo metaclasse in Python, devi solo creare una sottoclasse di type.

Un metaclasse è più comunemente usato come una fabbrica di classi. Quando crei un oggetto chiamando la classe, Python crea una nuova classe (quando esegue l'istruzione 'classe') chiamando il metaclasse. Combinati con i normali metodi __init__ e __new__, i metaclassi ti permettono quindi di fare 'cose extra' quando crei una classe, come registrare la nuova classe con qualche registro o sostituire la classe con qualcos'altro.

Quando viene eseguita l'istruzione class, Python esegue prima il corpo dell'istruzione class come un normale blocco di codice. Lo spazio dei nomi risultante (un dict) contiene gli attributi della classe-to-be. La metaclasse viene determinata osservando i baseclass della classe-to-be (i metaclassi sono ereditati), l'attributo __metaclass__ della classe-to-be (se presente) o la variabile globale __metaclass__. Il metaclasse viene quindi chiamato con il nome, le basi e gli attributi della classe per istanziarlo.

Tuttavia, le metaclassi in realtà definiscono il tipo di una classe, non solo una fabbrica per essa, quindi puoi fare molto di più con loro. Ad esempio, puoi definire i metodi normali sul metaclass. Questi metodi metaclass sono come metodi di classe in quanto possono essere chiamati sulla classe senza un'istanza, ma non sono come i metodi di classe in quanto non possono essere chiamati su un'istanza della classe. type.__subclasses__() è un esempio di un metodo sul metaclass type. Puoi anche definire i normali metodi "magici", come __add__, __iter__ e __getattr__, per implementare o modificare il comportamento della classe.

Ecco un esempio aggregato di bit e pezzi:

 
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__
    
2512
2019-03-04 21: 34: 19Z
  1. class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
    2017-08-03 14: 34: 29Z
  2. ppperry Ovviamente intendeva che non è possibile ricreare il tipo senza usare il tipo stesso come metaclass. Il che è abbastanza giusto da dire.
    2018-09-18 23: 24: 10Z
  3. Non devi annullare la registrazione () per l'istanza della classe Example?
    2018-11-29 00: 59: 15Z
  4. Nota che __metaclass__ non è supportato in Python 3. In Python 3 usa class MyObject(metaclass=MyType), vedi python.org/dev/peps/pep-3115 e la risposta sotto.
    2019-05-01 08: 36: 00Z

Classi come oggetti

Prima di capire le metaclassi, è necessario padroneggiare le classi in Python. E Python ha un'idea molto particolare di quali sono le classi, prese in prestito dal linguaggio Smalltalk.

Nella maggior parte delle lingue, le classi sono solo parti di codice che descrivono come produrre un oggetto. È vero anche in Python:

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

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

Ma le classi sono più di quelle in Python. Anche le classi sono oggetti.

Sì, oggetti.

In questo modoquando si utilizza la parola chiave class, Python la esegue e crea un oggetto. L'istruzione

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

crea in memoria un oggetto con il nome "ObjectCreator".

Questo oggetto (la classe) è esso stesso in grado di creare oggetti (le istanze), ed è per questo che è una classe .

Ma ancora, è un oggetto, e quindi:

  • puoi assegnarlo a una variabile
  • puoi copiarlo
  • puoi aggiungere attributi ad esso
  • puoi passarlo come parametro di funzione

per esempio:.

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

Creazione dinamica delle classi

Poiché le classi sono oggetti, puoi crearle al volo, come qualsiasi oggetto.

Per prima cosa, puoi creare una classe in una funzione usando 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>

Ma non è così dinamico, dal momento che devi ancora scrivere l'intera classe da solo.

Poiché le classi sono oggetti, devono essere generate da qualcosa.

Quando usi la parola chiave class, Python crea questo oggetto automaticamente. Ma come con la maggior parte delle cose in Python, ti dà un modo per farlo manualmente.

Ricordare la funzione type? La buona vecchia funzione che ti consente di sapere cosa digita un oggetto è:

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

Bene, type ha un'abilità completamente diversa, può anche creare lezioni al volo. type può prendere la descrizione di una classe come parametri, e restituire una classe.

(Lo so, è sciocco che la stessa funzione possa avere due usi completamente diversi in base ai parametri che gli si passano. È un problema dovuto all'indietro compatibilità in Python)

type funziona in questo modo:

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

per esempio:.

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

può essere creato manualmente in questo modo:

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

Noterai che usiamo "MyShinyClass" come nome della classe e come variabile per contenere il riferimento di classe. Possono essere diversi, ma non c'è motivo di complicare le cose.

type accetta un dizionario per definire gli attributi della classe. Quindi:

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

Può essere tradotto in:

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

E usato come una classe normale:

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

E, naturalmente, puoi ereditare da esso, quindi:

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

sarebbe:

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

Alla fine ti consigliamo di aggiungere metodi alla tua classe. Basta definire una funzione con la firma appropriata e assegnarlo come attributo.

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

E puoi aggiungere ancora più metodi dopo aver creato dinamicamente la classe, proprio come aggiungere metodi a un oggetto di classe creato normalmente.

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

Vedi dove stiamo andando: in Python, le classi sono oggetti e puoi creare una classe al volo, in modo dinamico.

Questo è ciò che Python fa quando usi la parola chiave class, e lo fa usando una metaclasse.

Che cosa sono le metaclassi (finalmente)

I metaclassi sono le "cose" che creano classi.

Definisci le classi per creare oggetti, giusto?

Ma abbiamo appreso che le classi Python sono oggetti.

Bene, le metaclassi sono ciò che creano questi oggetti. Sono le classi delle classi, puoi immaginarli in questo modo:

 
MyClass = MetaClass()
my_object = MyClass()

Hai visto che type ti consente di fare qualcosa di simile:

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

È perché la funzione type è in realtà un metaclasse. type è il metaclea Python usa per creare tutte le classi dietro le quinte.

Ora ti chiedi perché diavolo è scritto in lettere minuscole e non in Type?

Bene, immagino sia una questione di coerenza con str, la classe che crea oggetti di stringhe e int la classe che crea oggetti interi. type è solo la classe che crea oggetti di classe.

Lo vedi controllando l'attributo __class__.

Tutto, e intendo tutto, è un oggetto in Python. Ciò include gli ints, archi, funzioni e classi. Tutti loro sono oggetti. E tutti loro hanno stato creato da una 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'>

Ora, qual è il __class__ di qualsiasi __class__?

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

Quindi, un metaclasse è solo il materiale che crea oggetti di classe.

Se lo desideri, puoi chiamarlo "factory di classe".

type è la metaclora incorporata utilizzata da Python, ma ovviamente puoi creare il tuo metaclasse proprio.

__metaclass__ attributo

In Python 2, puoi aggiungere un attributo __metaclass__ quando scrivi una classe (vedi la prossima sezione per la sintassi di Python 3):

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

Se lo fai, Python userà il metaclass per creare la classe Foo.

Attento, è difficile.

Scrivi prima class Foo(object), ma l'oggetto di classe Foo non viene creato ancora in memoria.

Python cercherà __metaclass__ nella definizione della classe. Se lo trova, lo userà per creare la classe dell'oggetto Foo. Se non lo fa, userà  type per creare la classe.

Leggi più volte.

Quando lo fai:

 
class Foo(Bar):
    pass

Python effettua le seguenti operazioni:

Esiste un attributo __metaclass__ in Foo?

Se sì, crea in memoria un oggetto di classe (ho detto un oggetto di classe, rimani con me qui), con il nome Foo usando ciò che è in __metaclass__.

Se Python non riesce a trovare il __metaclass__, cercherà un __metaclass__ a livello di MODULO e cercherà di fare lo stesso (ma solo per le classi che non ereditano nulla, in pratica le classi vecchio stile).

Quindi se non riesce a trovare alcun __metaclass__, userà il metaclasse del Bar (il primo genitore) (che potrebbe essere il type predefinito) per creare l'oggetto classe.

Fai attenzione che l'attributo __metaclass__ non sarà ereditato, sarà il metaclasse del genitore (Bar.__class__). Se Bar ha utilizzato un attributo __metaclass__ che ha creato Bar con type() (e non type.__new__()), le sottoclassi non erediteranno tale comportamento.

Ora la grande domanda è: cosa puoi inserire in __metaclass__?

La risposta è: qualcosa che può creare una classe.

E cosa può creare una classe? type, o qualsiasi cosa che lo sottoclasse o lo usi.

Metaclasses in Python 3

La sintassi per impostare il metaclass è stata modificata in Python 3:

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

vale a dire. l'attributo __metaclass__ non è più utilizzato, a favore di un argomento di parole chiave nella lista delle classi di base.

Il comportamento dei metaclassi tuttavia rimane in gran parte lo stesso .

Una cosa aggiunta alle metaclassi in python 3 è che puoi anche passare attributi come argomenti-chiave in una metaclasse, in questo modo:

 
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

Leggi la sezione qui sotto per come Python lo gestisce.

Metaclasses personalizzati

Lo scopo principale di un metaclass è di cambiare automaticamente la classe, quando è stato creato.

Di solito lo fai per le API, dove vuoi creare classi corrispondenti a contesto attuale.

Immagina uno stupido esempio, in cui decidi tu tutte le classi nel tuo modulo dovrebbero avere i loro attributi scritti in maiuscolo. Ci sono diversi modi per fai questo, ma un modo è quello di impostare __metaclass__ a livello di modulo.

In questo modo, tutte le classi di questo modulo saranno create usando questo metaclass, e dobbiamo solo dire al metaclass di trasformare tutti gli attributi in maiuscolo.

Fortunatamente, __metaclass__ può effettivamente essere qualsiasi chiamabile, non è necessario che sia a classe formale (lo so, qualcosa con "classe" nel suo nome non ha bisogno di essere una classe, vai a capire ... ma è utile).

Quindi inizieremo con un semplice esempio, utilizzando una funzione.

 
# 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'

Ora, facciamo esattamente lo stesso, ma usando una vera classe per un metaclasse:

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

Ma questo non è davvero OOP. Chiamiamo direttamente il type e non escludiamo o chiama il genitore __new__. Facciamolo:

 
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)

Potresti aver notato l'argomento extra upperattr_metaclass. C'è niente di speciale a riguardo: __new__ riceve sempre la classe in cui è definita, come primo parametro. Proprio come hai self per i metodi ordinari che ricevono l'istanza come primo parametro, o la classe che definisce per i metodi di classe.

Naturalmente, i nomi che ho usato qui sono lunghi per motivi di chiarezza, ma come per self, tutti gli argomenti hanno nomi convenzionali. Quindi una vera produzione la metaclasse sarebbe la seguente:

 
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)

Possiamo renderlo ancora più pulito usando super, che faciliterà l'ereditarietà (perché sì, puoi avere metaclassi, che ereditano da metaclassi, che ereditano dal tipo):

 
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)

Oh, e in python 3 se fai questa chiamata con argomenti di parole chiave, come questa:

 
class Foo(object, metaclass=Thing, kwarg1=value1):
    ...

Si traduce in questo nella metaclasse per usarlo:

 
class Thing(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

Questo è tutto. Non c'è davvero nulla di più sulle metaclassi.

La ragione dietro la complessità del codice che utilizza metaclassi non è perché dei metaclassi, è perché di solito usi le metaclassi per fare cose contorte affidandosi all'introspezione, manipolando l'ereditarietà, vars come __dict__, ecc.

In effetti, le metaclassi sono particolarmente utili per fare la magia nera, e quindi roba complicata. Ma da soli, sono semplici:

  • intercetta una creazione di classe
  • modifica la classe
  • restituisce la classe modificata ​​li>

Perché dovresti usare classi di metaclassi invece di funzioni?

Poiché __metaclass__ può accettare qualsiasi chiamabile, perché dovresti usare una classe dal momento che è ovviamente più complicato?

Ci sono diversi motivi per farlo:

  • L'intenzione è chiara. Quando leggi UpperAttrMetaclass(type), lo sai cosa seguirà
  • Puoi usare OOP. Metaclass può ereditare dal metaclasse, sovrascrivere i metodi padre. Le metaclassi possono anche usare metaclassi.
  • Le sottoclassi di una classe saranno istanze del suo metaclasse se hai specificato una classe metaclasse, ma non con una funzione metaclasse.
  • Puoi strutturare meglio il tuo codice. Non usi mai le metaclassi per qualcosa come trivale come nell'esempio precedente. Di solito è per qualcosa di complicato. Avere il la capacità di creare diversi metodi e raggrupparli in una classe è molto utile per rendere il codice più facile da leggere.
  • Puoi collegare su __new__, __init__ e __call__. Che permetterà tu a fare cose diverse. Anche se di solito puoi fare tutto in __new__, alcune persone sono più a loro agio con __init__.
  • Questi sono chiamati metaclassi, dannazione! Deve significare qualcosa!

Perché dovresti usare le metaclassi?

Ora la grande domanda. Perché dovresti usare qualche oscura funzione incline agli errori?

Beh, di solito non lo fai:

  

I metaclassi sono una magia più profonda   Il 99% degli utenti non dovrebbe mai preoccuparsi.   Se ti chiedi se ne hai bisogno,   tu no (le persone che in realtà   bisogno di loro sapere con certezza che   ne hanno bisogno e non ne hanno bisogno   spiegazione sul perché).

Python Guru Tim Peters

Il caso d'uso principale per un metaclass sta creando un'API. Un tipico esempio di questo è il Django ORM.

Ti permette di definire qualcosa di simile a questo:

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

Ma se lo fai:

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

Non restituirà un oggetto IntegerField. Restituirà un int e può anche prenderlo direttamente dal database.

Questo è possibile perché models.Model definisce __metaclass__ e usa un po 'di magia che trasformerà il Person che hai appena definito con semplici istruzioni in un gancio complesso a un campo del database.

Django rende semplice l'aspetto di qualcosa di complesso esponendo una semplice API e usando metaclassi, ricreando il codice da questa API per fare il vero lavoro dietro le quinte.

L'ultima parola

In primo luogo, sai che le classi sono oggetti che possono creare istanze.

Beh, in effetti, le classi sono esse stesse istanze. Di metaclassi.

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

Tutto è un oggetto in Python e sono tutte o istanze di classi o istanze di metaclassi.

Tranne type.

type è in realtà il suo metaclasse. Questo non è qualcosa che potresti riproducibile in puro Python, e viene eseguito imbrogliando un po 'l'implementazione livello.

In secondo luogo, le metaclassi sono complicate. Potresti non volerne usare per alterazioni di classe molto semplici. Puoi cambiare classe usando due tecniche differenti:

Il 99% delle volte che hai bisogno di modifiche di classe, è meglio usare questi.

Ma il 98% delle volte non hai bisogno di alterazioni di classe.

    
6304
2019-04-16 17: 55: 24Z
  1. Sembra che in Django models.Model non usi il __metaclass__ ma piuttosto il class Model(metaclass=ModelBase): per fare riferimento a una classe ModelBase che poi fa la suddetta magia del metaclass. Ottimo post! Ecco la fonte Django: github.com/django /Django /blob /master /django /db /modelli /...
    2017-04-12 13: 18: 59Z
  2. cosa succede se indichi metaclass diversi sia in classe base che in quella derivata?
    2017-04-20 03: 24: 08Z
  3. < < Attenzione che l'attributo __metaclass__ non sarà ereditato, sarà il metaclasse del genitore (Bar.__class__). Se Bar utilizzava un attributo __metaclass__ che creava Bar con type() (e non type.__new__()), le sottoclassi non ereditano quel comportamento. &Gt; > - Potresti /qualcuno per favore spiega un po 'più a fondo questo passaggio?
    2017-04-25 21: 32: 44Z
  4. @ MaxGoodridge Questa è la sintassi di Python 3 per metaclassi. Vedi Modello dati Python 3.6 VS Python 2.7 Data model
    2017-06-13 13: 22: 27Z
  5. È una risposta wiki della comunità (quindi, chi ha commentato correzioni /miglioramenti potrebbe prendere in considerazione la possibilità di modificare i propri commenti nella risposta, se sono sicuri che siano corretti).
    2017-11-08 08: 59: 31Z

    Nota, questa risposta è per Python 2.x come è stato scritto nel 2008, le metaclassi sono leggermente diverse in 3.x.

    I metaclassi sono la salsa segreta che rende il lavoro 'di classe'. Il metaclasse predefinito per un nuovo oggetto stile è chiamato 'tipo'.

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

    I metaclassi richiedono 3 arg. ' nome ', ' basi ' e ' dict '

    Ecco dove inizia il segreto. Cerca da dove vengono il nome, le basi e il dict in questa definizione di classe di esempio.

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

    Consente di definire un metaclass che dimostrerà come " classe: " lo chiama.

     
    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'
    

    E ora, un esempio che in realtà significa qualcosa, questo renderà automaticamente le variabili nella lista "attributi" impostate sulla classe e impostate su Nessuno.

     
    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
    

    Si noti che il comportamento magico che "Initalizzato" ha quando la metaclea init_attributes non viene passata su una sottoclasse di Initalised.

    Ecco un esempio ancora più concreto, che mostra come è possibile creare una sottoclasse di "tipo" per creare un metaclasse che esegue un'azione quando viene creata la classe. Questo è abbastanza complicato:

     
    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
    
        
    359
    2019-05-13 07: 55: 44Z

    Un uso per i metaclassi è l'aggiunta automatica di nuove proprietà e metodi a un'istanza.

    Ad esempio, se guardi modelli Django , i loro la definizione sembra un po 'confusa. Sembra che tu stia definendo solo le proprietà della classe:

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

    Tuttavia, durante il runtime gli oggetti Person sono riempiti con tutti i tipi di metodi utili. Consulta la fonte per alcune stupefacenti metaclassiche.     

    146
    2008-09-19 06: 45: 40Z
    1. Non è l'uso delle meta-class che aggiungono nuove proprietà e metodi a una classe e non a un'istanza? Per quanto ho capito, la meta class altera la classe stessa e, di conseguenza, le istanze possono essere costruite diversamente dalla classe alterata. Potrebbe essere un po 'fuorviante per le persone che cercano di ottenere la natura di una meta classe. Avere metodi utili su istanze può essere ottenuto con una normale ereditarietà. Il riferimento al codice Django come esempio è buono, però.
      2017-01-27 23: 24: 42Z

    Altri hanno spiegato come funzionano le metaclassi e come si adattano al sistema di tipo Python. Ecco un esempio di ciò per cui possono essere utilizzati. In un framework di test che ho scritto, volevo tenere traccia dell'ordine in cui sono state definite le classi, in modo da poterle successivamente istanziare in questo ordine. Ho trovato più semplice farlo usando una metaclasse.

     
    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
    

    Qualsiasi cosa che sia una sottoclasse di MyType ottiene quindi un attributo di classe _order che registra l'ordine in cui sono state definite le classi.

        
    146
    2016-11-28 18: 04: 41Z
    1. Grazie per l'esempio. Perché l'hai trovato più semplice di quello ereditato da MyBase, il cui __init__(self) dice type(self)._order = MyBase.counter; MyBase.counter += 1?
      2019-04-16 17: 59: 34Z
    2. Volevo che le classi stesse, non le loro istanze, fossero numerate.
      2019-04-16 18: 09: 39Z
    3. Giusto, duh. Grazie. Il mio codice ripristinerebbe l'attributo di MyType su ogni istanza e non imposterebbe mai l'attributo se non è mai stata creata un'istanza di MyType. Ops. (E una proprietà di classe potrebbe anche funzionare, ma a differenza del metaclass offre noposto ovvio per conservare il contatore.)
      2019-04-17 21: 58: 00Z

    Penso che l'introduzione di ONLamp alla programmazione di metaclassi sia ben scritta e dia un'ottima introduzione all'argomento nonostante abbia già diversi anni.

    http://www.onlamp.com/pub /a /python /2003/04/17 /metaclasses.html (archiviato su https://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a /python/2003/04/17/metaclasses.html )

    In breve: una classe è un progetto per la creazione di un'istanza, un metaclasse è un progetto per la creazione di una classe. Si può facilmente vedere che anche in Python le classi devono essere oggetti di prima classe per abilitare questo comportamento.

    Non ne ho mai scritto uno anch'io, ma penso che uno degli usi più belli delle metaclassi possa essere visto nella struttura di Django . Le classi modello usano un approccio metaclass per abilitare uno stile dichiarativo di scrittura di nuovi modelli o classi di form. Mentre il metaclasse sta creando la classe, tutti i membri hanno la possibilità di personalizzare la classe stessa.

    La cosa che rimane da dire è: se non sai quali sono le metaclassi, la probabilità che non le serva è del 99%.

        
    106
    2018-08-13 04: 53: 15Z
      

    Che cosa sono le metaclassi? Per cosa li usi?

    TLDR: un metaclasse crea un'istanza e definisce il comportamento di una classe esattamente come una classe crea un'istanza e definisce il comportamento di un'istanza.

    Pseudocodice:

     
    >>> Class(...)
    instance
    

    Quanto sopra dovrebbe sembrare familiare. Bene, da dove viene Class? È un'istanza di un metaclasse (anche pseudocodice):

     
    >>> Metaclass(...)
    Class
    

    Nel codice reale, possiamo passare il metaclass di default, type, tutto ciò di cui abbiamo bisogno per istanziare una classe e ottenere una classe:

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

    Mettendolo diversamente

    • Una classe è un'istanza come una metaclasse appartiene a una classe.

      Quando istanziamo un oggetto, otteniamo un'istanza:

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

      Allo stesso modo, quando definiamo una classe esplicitamente con il metaclass predefinito, type, la istanziamo:

       
      >>> type('Object', (object,), {})     # instantiation of metaclass
      <class '__main__.Object'>             # instance
      
    • In altre parole, una classe è un'istanza di un metaclasse:

       
      >>> isinstance(object, type)
      True
      
    • Metti una terza via, una metaclasse è una classe di classe.

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

    Quando scrivi una definizione di classe e Python la esegue, usa una metaclasse per istanziare l'oggetto classe (che, a sua volta, sarà usato per istanziare le istanze di quella classe).

    Proprio come possiamo usare le definizioni di classe per cambiare il modo in cui si comportano le istanze di oggetti personalizzati, possiamo usare una definizione di classe metaclasse per cambiare il modo in cui un oggetto di classe si comporta.

    Per cosa possono essere usati? Dai documenti :

      

    I potenziali usi dei metaclassi sono illimitati. Alcune idee che sono state esplorate includono la registrazione, il controllo dell'interfaccia, la delega automatica, la creazione automatica delle proprietà, i proxy, i framework e il blocco /sincronizzazione automatica delle risorse.

    Tuttavia, di solito è consigliato agli utenti di evitare l'uso di metaclassi a meno che non sia assolutamente necessario.

    Usi un metaclasse ogni volta che crei una classe:

    Quando scrivi una definizione di classe, ad esempio, in questo modo

     
    class Foo(object): 
        'demo'
    

    Istanzia un oggetto di classe.

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

    È lo stesso di chiamare funzionalmente il type con gli argomenti appropriati e assegnare il risultato a una variabile di quel nome:

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

    Nota, alcune cose vengono automaticamente aggiunte al __dict__, ad esempio lo spazio dei nomi:

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

    Il metaclass dell'oggetto che abbiamo creato, in entrambi i casi, è type.

    (Un lato-nota sul contenuto della classe __dict__: __module__ è lì perché le classi devono kndove sono definiti, e __dict__ e __weakref__ ci sono perché non definiamo __slots__ - se definiamo __slots__ noi ll salvataggio di un po 'di spazio nelle istanze, poiché possiamo disabilitare il __dict__ e il __weakref__ escludendoli. Ad esempio:

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

    ... ma sto divagando.)

    Possiamo estendere type proprio come qualsiasi altra definizione di classe:

    Ecco il __repr__ predefinito delle classi:

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

    Una delle cose più preziose che possiamo fare di default nella scrittura di un oggetto Python è di fornirgli un buon __repr__. Quando chiamiamo help(repr), apprendiamo che c'è un buon test per un __repr__ che richiede anche un test per l'uguaglianza - obj == eval(repr(obj)). La seguente semplice implementazione di __repr__ e __eq__ per le istanze di classe della nostra classe tipo ci fornisce una dimostrazione che potrebbe migliorare sul __repr__ predefinito delle classi:

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

    Così ora quando creiamo un oggetto con questo metaclasse, il __repr__ echo sulla riga di comando offre una vista molto meno brutta del default:

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

    Con un bel __repr__ definito per l'istanza della classe, abbiamo una maggiore capacità di eseguire il debug del nostro codice. Tuttavia, è molto improbabile eseguire ulteriori verifiche con eval(repr(Class)) (poiché le funzioni sarebbero piuttosto impossibili da valutare rispetto al valore predefinito __repr__).

    Un utilizzo previsto: __prepare__ uno spazio dei nomi

    Se, ad esempio, vogliamo sapere in quale ordine vengono creati i metodi di una classe, potremmo fornire un dict ordinato come lo spazio dei nomi della classe. Lo faremo con __prepare__ che restituisce lo spazio dei nomi dettato per classe se è implementata in 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
    

    E utilizzo:

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

    E ora abbiamo un record dell'ordine in cui sono stati creati questi metodi (e altri attributi di classe):

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

    Nota, questo esempio è stato adattato dalla documentazione - il nuovo enum nella libreria standard lo fa.

    Quindi ciò che abbiamo fatto è stato creare un'istanza di un metaclass creando una classe. Possiamo anche trattare il metaclass come faremmo con qualsiasi altra classe. Ha un ordine di risoluzione del metodo:

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

    E ha approssimativamente il corretto repr (che non possiamo più valutare a meno che non riusciamo a trovare un modo per rappresentare le nostre funzioni.):

     
    >>> 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>})
    
        
    93
    2017-08-30 03: 19: 14Z

    Aggiornamento di Python 3

    Ci sono (a questo punto) due metodi chiave in un metaclass:

    •  __prepare__ e
    • __new__

    __prepare__ consente di fornire una mappatura personalizzata (come un OrderedDict) da utilizzare come spazio dei nomi durante la creazione della classe. Devi restituire un'istanza di qualsiasi spazio dei nomi che scegli. Se non si implementa il __prepare__, viene utilizzato un normale dict.

    __new__ è responsabile della creazione /modifica effettiva della classe finale.

    Un metaclass essenziale, niente-fare-extra vorrebbe:

     
    class Meta(type):
    
        def __prepare__(metaclass, cls, bases):
            return dict()
    
        def __new__(metacls, cls, bases, clsdict):
            return super().__new__(metacls, cls, bases, clsdict)
    

    Un semplice esempio:

    Supponiamo che tu voglia un semplice codice di convalida da eseguire sui tuoi attributi, come se fosse sempre un int o un str. Senza un metaclasse, la tua classe sarebbe simile a:

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

    Come puoi vedere, devi ripetere due volte il nome dell'attributo. Ciò rende possibili errori di battitura insieme a bug irritanti.

    Un semplice metaclasse può risolvere questo problema:

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

    Questo è l'aspetto del metaclass (non utilizzando __prepare__ poiché non è necessario):

     
    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'esecuzione di esempio di:

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

    produce:

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

    Nota ​​strong>: questo esempio è abbastanza semplice da poter essere realizzato anche con un decoratore di classe, ma presumibilmente un vero metaclass farebbe molto di più.

    La classe "ValidateType" per riferimento:

     
    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
    
        
    67
    2016-03-01 19: 48: 34Z

    Il ruolo di un metodo metaclasse __call__() durante la creazione di un'istanza di classe

    Se hai eseguito la programmazione Python per più di qualche mese, alla fine ti imbatterai in codice simile a questo:

     
    # 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')
    

    Quest'ultimo è possibile quando si implementa la magia magica __call__()od sulla classe.

     
    class SomeClass(object):
        # ...
        # some definition here ...
        # ...
    
        def __call__(self, foo, bar):
            return bar + foo
    

    Il metodo __call__() viene invocato quando un'istanza di una classe viene utilizzata come callable. Ma come abbiamo visto dalle risposte precedenti una classe stessa è un'istanza di un metaclasse, quindi quando usiamo la classe come callable (cioè quando ne creiamo un'istanza), stiamo effettivamente chiamando il metodo del metaclasse __call__(). A questo punto la maggior parte dei programmatori Python sono un po 'confusi perché gli è stato detto che quando si crea un'istanza come questa instance = SomeClass() si chiama il suo metodo __init__(). Alcuni che hanno scavato un po 'più a fondo sanno che prima del __init__() c'è il __new__(). Bene, oggi viene rivelato un altro strato di verità, prima del __new__() c'è il metaclass '__call__().

    Studiamo la catena di chiamate del metodo specificatamente dalla prospettiva di creare un'istanza di una classe.

    Questa è una metaclasse che registra esattamente il momento prima della creazione di un'istanza e nel momento in cui sta per restituirla.

     
    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
    

    Questa è una classe che usa quel metaclass

     
    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__()."
    

    E ora creiamo un'istanza di 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.
    

    Osserva che il codice sopra non fa in realtà nulla di più che registrare le attività. Ogni metodo delega il lavoro effettivo all'implementazione del genitore, mantenendo così il comportamento predefinito. Poiché type è la classe genitore di Meta_1 (type è il metaclass principale predefinito) e considerando la sequenza di ordinamento dell'output sopra, ora abbiamo un indizio su quale sarebbe la pseudo implementazione di 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
    

    Possiamo vedere che il metodo del metaclasse __call__() è quello che viene chiamato per primo. Quindi delega la creazione dell'istanza al metodo __new__() della classe e l'inizializzazione al __init__() dell'istanza. È anche quello che alla fine restituisce l'istanza.

    Da quanto precede deriva che al metaclass '__call__() è anche data l'opportunità di decidere se una chiamata al Class_1.__new__() o al Class_1.__init__() verrà effettuata o meno. Nel corso della sua esecuzione potrebbe effettivamente restituire un oggetto che non è stato toccato da nessuno di questi metodi. Prendiamo ad esempio questo approccio al modello 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__()."
    

    Osserviamo cosa succede quando proviamo ripetutamente a creare un oggetto di tipo 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
    
        
    58
    2018-08-27 17: 21: 01Z

    Un metaclasse è una classe che indica come (qualche) altra classe dovrebbe essere creata.

    Questo è un caso in cui ho visto il metaclasse come una soluzione al mio problema: Ho avuto un problema davvero complicato, che probabilmente avrebbe potuto essere risolto in modo diverso, ma ho scelto di risolverlo usando una metaclasse. A causa della complessità, è uno dei pochi moduli che ho scritto dove i commenti nel modulo superano la quantità di codice che è stata scritta. Eccolo ...

     
    #!/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()
    
        
    50
    2016-01-25 20: 08: 37Z

    type è in realtà un metaclass, una classe che crea un'altra classe. La maggior parte delle metaclass sono le sottoclassi di type. Il metaclass riceve la classe new come primo argomento e fornisce accesso all'oggetto classe con i dettagli come indicato di seguito:

     
    >>> class MetaClass(type):
    ...     def __init__(cls, name, bases, attrs):
    ...         print ('class name: %s' %name )
    ...         print ('Defining class %s' %cls)
    ...         print('Bases %s: ' %bases)
    ...         print('Attributes')
    ...         for (name, value) in attrs.items():
    ...             print ('%s :%r' %(name, value))
    ... 
    
    >>> class NewClass(object, metaclass=MetaClass):
    ...    get_choch='dairy'
    ... 
    class name: NewClass
    Bases <class 'object'>: 
    Defining class <class 'NewClass'>
    get_choch :'dairy'
    __module__ :'builtins'
    __qualname__ :'NewClass'
    

    Note:

    Si noti che la classe non è stata istanziata in qualsiasi momento; il semplice atto di creare l'esecuzione innescata dalla classe del metaclass.

        
    36
    2017-08-29 05: 23: 15Z

    La versione tl; dr

    La funzione type(obj) ti offre il tipo di oggetto.

    Il type() di una classe è il suo metaclass .

    Per utilizzare un metaclista:

     
    class Foo(object):
        __metaclass__ = MyMetaClass
    
        
    32
    2016-12-30 17: 28: 40Z

    Le classi Python sono esse stesse oggetti - come in esempio - della loro meta-classe.

    Il metaclasse predefinito, che viene applicato quando si determinano le classi come:

     
    class foo:
        ...
    

    la metaclea è utilizzata per applicare alcune regole a un intero insieme di classiS. Ad esempio, supponiamo che stai creando un ORM per accedere a un database e desideri che i record di ogni tabella siano di una classe mappata a quella tabella (basata su campi, regole aziendali, ecc.), Un possibile uso di metaclasse è ad esempio la logica del pool di connessioni, che è condivisa da tutte le classi di record da tutte le tabelle. Un altro uso è la logica per supportare le chiavi esterne, che coinvolge più classi di record.

    quando definisci il metaclasse, inserisci un tipo di sottoclasse e puoi sovrascrivere i seguenti metodi magici per inserire la tua logica.

     
    class somemeta(type):
        __new__(mcs, name, bases, clsdict):
          """
      mcs: is the base metaclass, in this case type.
      name: name of the new class, as provided by the user.
      bases: tuple of base classes 
      clsdict: a dictionary containing all methods and attributes defined on class
    
      you must return a class object by invoking the __new__ constructor on the base metaclass. 
     ie: 
        return type.__call__(mcs, name, bases, clsdict).
    
      in the following case:
    
      class foo(baseclass):
            __metaclass__ = somemeta
    
      an_attr = 12
    
      def bar(self):
          ...
    
      @classmethod
      def foo(cls):
          ...
    
          arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}
    
          you can modify any of these values before passing on to type
          """
          return type.__call__(mcs, name, bases, clsdict)
    
    
        def __init__(self, name, bases, clsdict):
          """ 
          called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
          """
          pass
    
    
        def __prepare__():
            """
            returns a dict or something that can be used as a namespace.
            the type will then attach methods and attributes from class definition to it.
    
            call order :
    
            somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
            """
            return dict()
    
        def mymethod(cls):
            """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
            """
            pass
    

    comunque, questi due sono i ganci più comunemente usati. il metaclassing è potente e al di sopra non esiste un elenco esaustivo e completo di usi per il metaclassing.

        
    22
    2017-07-13 08: 18: 22Z

    La funzione type () può restituire il tipo di un oggetto o creare un nuovo tipo,

    per esempio, possiamo creare una classe Hi con la funzione type () e non usare in questo modo con la classe Hi (oggetto):

     
    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
    

    Oltre a usare type () per creare classi dinamicamente, puoi controllare il comportamento di creazione della classe e utilizzare la metaclasse.

    Secondo il modello a oggetti Python, la classe è l'oggetto, quindi la classe deve essere un'istanza di un'altra determinata classe. Per impostazione predefinita, una classe Python è un'istanza della classe type. Ovvero, type è il metaclesso della maggior parte delle classi e metaclassi incorporate di classi definite dall'utente.

     
    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 magia avrà effetto quando passeremo gli argomenti delle parole chiave in metaclass, indica all'interprete Python di creare la lista personalizzata tramite ListMetaclass. nuovo (), a questo punto, possiamo modificare la definizione della classe, ad esempio, e aggiungere un nuovo metodo e quindi restituire la definizione rivista.

        
    17
    2018-01-12 09: 30: 20Z

    Oltre alle risposte pubblicate posso dire che un metaclass definisce il comportamento per una classe. Quindi, puoi impostare in modo esplicito il tuo metaclasse. Ogni volta che Python ottiene una parola chiave class, inizia a cercare il metaclass. Se non è stato trovato - il tipo di metaclasse predefinito viene utilizzato per creare l'oggetto della classe. Utilizzando l'attributo __metaclass__, puoi impostare metaclass della tua classe:

     
    class MyClass:
       __metaclass__ = type
       # write here other method
       # write here one more method
    
    print(MyClass.__metaclass__)
    

    Produrrà l'output in questo modo:

     
    class 'type'
    

    E, naturalmente, puoi creare il tuo metaclass per definire il comportamento di qualsiasi classe creata usando la tua classe.

    Per fare ciò, la classe di tipo metaclass predefinita deve essere ereditata poiché questo è il metaclass principale:

     
    class MyMetaClass(type):
       __metaclass__ = type
       # you can write here any behaviour you want
    
    class MyTestClass:
       __metaclass__ = MyMetaClass
    
    Obj = MyTestClass()
    print(Obj.__metaclass__)
    print(MyMetaClass.__metaclass__)
    

    L'output sarà:

     
    class '__main__.MyMetaClass'
    class 'type'
    
        
    7
    2018-09-15 13: 17: 29Z
fonte posta Qui