Domanda

Cosa sono le metaclassi e per cosa le usiamo?

È stato utile?

Soluzione

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

Mentre in Python puoi usare chiamabili arbitrari per le metaclassi (come Jerub mostra), l'approccio migliore è renderlo esso stesso una classe vera e propria. type è la solita metaclasse in Python. type è esso stesso una classe ed è un tipo a sé stante.Non sarai in grado di ricreare qualcosa di simile type puramente in Python, ma Python imbroglia un po'.Per creare la tua metaclasse in Python devi solo creare una sottoclasse type.

Una metaclasse viene comunemente utilizzata come class factory.Quando crei un oggetto chiamando la classe, Python crea una nuova classe (quando esegue l'istruzione 'class') chiamando la metaclasse.Combinato con il normale __init__ E __new__ metodi, le metaclassi quindi ti permettono di fare 'cose extra' quando crei una classe, come registrare la nuova classe con qualche registro o sostituire la classe con qualcos'altro completamente.

Quando il class viene eseguita l'istruzione, Python esegue prima il corpo dell'istruzione class istruzione come un normale blocco di codice.Lo spazio dei nomi risultante (un dict) contiene gli attributi della futura classe.La metaclasse viene determinata osservando le classi base della futura classe (le metaclassi vengono ereditate), __metaclass__ attributo della futura classe (se presente) o del file __metaclass__ variabile globale.La metaclasse viene quindi richiamata con il nome, le basi e gli attributi della classe per istanziarla.

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

Ecco un esempio aggregato dei frammenti:

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__

Altri suggerimenti

Classi come oggetti

Prima di comprendere le metaclassi, è necessario padroneggiare le lezioni in Python.E Python ha un'idea molto particolare di cosa siano le classi, presa in prestito dal linguaggio Smalltalk.

Nella maggior parte dei linguaggi, le classi sono semplicemente pezzi di codice che descrivono come produrre un oggetto.Questo è 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 questo in Python.Anche le classi sono oggetti.

Sì, oggetti.

Non appena usi la parola chiave class, Python lo esegue e crea un oggetto.Le istruzioni

>>> 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 è comunque 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 di classi

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

Innanzitutto, puoi creare una classe in una funzione utilizzando 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, dato che devi comunque scrivere tu stesso l'intera lezione.

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

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

Ricorda la funzione type?La buona vecchia funzione che ti consente di sapere che tipo è 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 classi 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 a seconda dei parametri che le passi.È un problema a causa della compatibilità arretrata 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 della classe.Possono essere diversi, ma non c'è motivo di complicare le cose.

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

>>> 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 vorrai aggiungere metodi alla tua classe.Basta definire una funzione con la firma corretta e assegnarla 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 aggiungi 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 fa Python quando usi la parola chiave class, e lo fa utilizzando una metaclasse.

Cosa sono le metaclassi (finalmente)

Le metaclassi sono il "materiale" che crea le classi.

Definisci le classi per creare oggetti, giusto?

Ma abbiamo imparato che le classi Python sono oggetti.

Bene, le metaclassi sono ciò che crea questi oggetti.Sono le lezioni delle classi, puoi immaginarle in questo modo:

MyClass = MetaClass()
my_object = MyClass()

L'hai visto type ti permette di fare qualcosa del genere:

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

È perché la funzione type è infatti una metaclasse. type è il metaclass che Python utilizza per creare tutte le classi dietro le quinte.

Ora ti chiedi perché cavolo è scritto in minuscolo, e non Type?

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

Lo vedi controllando il __class__ attributo.

Tutto, e intendo proprio tutto, è un oggetto in Python.Ciò include INT, stringhe, funzioni e classi.Sono tutti oggetti.E tutti sono stati creati 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, una metaclasse è solo ciò che crea oggetti di classe.

Puoi chiamarla una "fabbrica di classi", se lo desideri.

type è il metaclass integrato che Python usa, ma ovviamente puoi creare il tuo metaclass.

IL __metaclass__ attributo

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

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

Se lo fai, Python utilizzerà la metaclasse per creare la classe Foo.

Attento, è complicato.

Scrivi class Foo(object) primo, ma l'oggetto della classe Foo non è ancora creato in memoria.

Python cercherà __metaclass__ nella definizione della classe.Se lo trova, lo utilizzerà per creare la classe di oggetti Foo.In caso contrario, utilizzeràtype per creare la classe.

Leggilo più volte.

Quando lo fai:

class Foo(Bar):
    pass

Python fa quanto segue:

C'è un __metaclass__ attribuire in Foo?

Se sì, crea in memoria un oggetto classe (ho detto oggetto classe, resta con me qui), con il nome Foo utilizzando ciò che c'è dentro __metaclass__.

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

Quindi se non riesce a trovarne nessuno __metaclass__ affatto, utilizzerà il Bar(il primo genitore) propria metaclasse (che potrebbe essere l'impostazione predefinita type) per creare l'oggetto della classe.

Fai attenzione qui che il __metaclass__ l'attributo non verrà ereditato, la metaclasse del genitore (Bar.__class__) sarà.Se Bar usato a __metaclass__ attributo che ha creato Bar con type() (e non type.__new__()), le sottoclassi non erediteranno questo comportamento.

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

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

E cosa può creare una classe? type, o qualsiasi cosa che lo sottoclassi o lo utilizzi.

Metaclassi in Python 3

La sintassi per impostare la metaclasse è stata modificata in Python 3:

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

cioè.IL __metaclass__ L'attributo non viene più utilizzato, a favore di un argomento con parola chiave nell'elenco delle classi base.

Il comportamento delle metaclassi rimane comunque in gran parte lo stesso.

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

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

Leggi la sezione seguente per sapere come Python gestisce questa cosa.

Metaclassi personalizzate

Lo scopo principale di una metaclass è cambiare automaticamente la classe, quando viene creata.

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

Immagina uno stupido esempio, in cui decidi che tutte le classi nel tuo modulo dovrebbero avere i loro attributi scritti in maiuscolo.Esistono diversi modi per farlo, ma un modo è impostare __metaclass__ a livello di modulo.

In questo modo, tutte le classi di questo modulo verranno create usando questa metaclasse e dobbiamo solo dire alla metaclasse di trasformare tutti gli attributi in maiuscolo.

Fortunatamente, __metaclass__ Può effettivamente essere richiamabile, non deve essere una 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 la stessa cosa, ma usando una vera classe per una 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 è veramente OOP.Noi chiamiamo type direttamente e non sovrascriviamo o chiamiamo 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 in più upperattr_metaclass.Non c'è niente di speciale al riguardo: __new__ riceve sempre la classe in cui è definito, come primo parametro.Proprio come hai fatto tu self per i metodi ordinari che ricevono l'istanza come primo parametro, oppure la classe che definisce i metodi di classe.

Certo, i nomi che ho usato qui sono molto tempo per la chiarezza, ma come per self, tutti gli argomenti hanno nomi convenzionali.Quindi una vera metaclasse di produzione sembrerebbe così:

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 utilizzando super, che faciliterà l'ereditarietà (perché sì, puoi avere metaclassi, ereditare da metaclassi, ereditare da 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 esegui questa chiamata con argomenti di parole chiave, in questo modo:

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.

Il motivo alla base della complessità del codice usando i metaclassi non è dovuto alle metaclassi, è perché di solito si usano metaclassi per fare cose contorte che si affidano all'introspezione, manipolando l'eredità, vari come __dict__, eccetera.

In effetti, i metaclassi sono particolarmente utili per fare la magia nera e quindi cose complicate.Ma di per sé sono semplici:

  • intercettare la creazione di una classe
  • modificare la classe
  • restituire la classe modificata

Perché dovresti usare le classi di metaclassi invece delle funzioni?

Da __metaclass__ Può accettare qualsiasi callabile, perché dovresti usare una lezione poiché è ovviamente più complicato?

Ci sono diversi motivi per farlo:

  • L'intenzione è chiara.Quando leggi UpperAttrMetaclass(type), sai cosa seguirà
  • Puoi usare l'OOP.La metaclasse può ereditare dalla metaclasse e sovrascrivere i metodi principali.Le metaclassi possono anche utilizzare metaclassi.
  • Le sottoclassi di una classe saranno istanze della sua metaclasse se hai specificato una classe metaclasse, ma non con una funzione metaclasse.
  • Puoi strutturare meglio il tuo codice.Non usi mai metaclassi per qualcosa di così banale come l'esempio sopra.Di solito è per qualcosa di complicato.Avere la capacità di fare diversi metodi e raggrupparli in una classe è molto utile per rendere il codice più facile da leggere.
  • Puoi agganciarti __new__, __init__ E __call__.Che ti permetterà di fare cose diverse.Anche se di solito puoi fare tutto dentro __new__, alcune persone sono solo più a loro agio nell'uso __init__.
  • Queste si chiamano metaclassi, dannazione!Deve significare qualcosa!

Perché dovresti usare le metaclassi?

Ora la grande domanda.Perché dovresti utilizzare alcune funzionalità oscure soggette a errori?

Beh, di solito non lo fai:

I metaclassi sono più profondi che il 99% degli utenti non dovrebbe mai preoccuparsi.Se ti chiedi se ne hai bisogno, non lo fai (le persone che hanno effettivamente bisogno di loro sanno con certezza che ne hanno bisogno e non hanno bisogno di una spiegazione del perché).

Guru Python Tim Peters

Il caso d'uso principale per una metaclasse è la creazione di un'API.Un tipico esempio di ciò è il Django ORM.

Ti permette di definire qualcosa del genere:

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 file IntegerField oggetto.Restituirà un file int, e può anche prelevarlo direttamente dal database.

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

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

L'ultima parola

Innanzitutto, sai che le classi sono oggetti che possono creare istanze.

Ebbene, in effetti, le classi stesse sono istanze.Delle metaclassi.

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

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

Eccetto per type.

type è in realtà la propria metaclasse.Questo non è qualcosa che potresti riprodurre in puro Python e viene fatto tradimenti un po 'a livello di implementazione.

In secondo luogo, le metaclassi sono complicate.Potresti non voler usarli per modifiche di classe molto semplici.Puoi cambiare classe utilizzando due tecniche diverse:

Il 99% delle volte in cui hai bisogno di un cambiamento di classe, è meglio usarli.

Ma il 98% delle volte non è necessario alcun cambiamento di classe.

Nota, questa risposta è per Python 2.x poiché è stata scritta nel 2008, le metaclassi sono leggermente diverse in 3.x.

Le metaclassi sono la salsa segreta che fa funzionare la "classe".La metaclasse predefinita per un nuovo oggetto di stile è chiamata "tipo".

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

Le metaclassi richiedono 3 argomenti.'nome', 'basi' E 'dict'

È qui che inizia il segreto.Cerca da dove provengono nome, basi e dict in questa definizione di classe di esempio.

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

Definiamo una metaclasse 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 nell'elenco "attributi" impostate sulla classe e impostate su 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

Nota che il comportamento magico "Inizializzato" guadagna avendo la metaclasse init_attributes non viene passato a una sottoclasse di Inizializzato.

Ecco un esempio ancora più concreto, che mostra come è possibile creare una sottoclasse 'tipo' per creare una metaclasse che esegua un'azione quando la classe viene creata.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

Un utilizzo delle metaclassi è l'aggiunta automatica di nuove proprietà e metodi a un'istanza.

Ad esempio, se guardi Modelli Django, la loro 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, in fase di esecuzione gli oggetti Person sono pieni di ogni sorta di metodi utili.Vedi il fonte per alcuni fantastici metaclassi.

Altri hanno spiegato come funzionano le metaclassi e come si inseriscono nel sistema di tipi Python.Ecco un esempio di cosa possono essere utilizzati.In un framework di test che ho scritto, volevo tenere traccia dell'ordine in cui le classi venivano definite, in modo da poterle successivamente istanziare in questo ordine.Ho trovato più semplice farlo utilizzando 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

Tutto ciò che è una sottoclasse di MyType quindi ottiene un attributo di classe _order che registra l'ordine in cui sono state definite le classi.

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

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (archiviato in 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, una metaclasse è un progetto per la creazione di una classe.Si può facilmente vedere che in Python anche le classi devono essere oggetti di prima classe per abilitare questo comportamento.

Non ne ho mai scritto uno personalmente, ma penso che uno degli usi più belli delle metaclassi possa essere visto nel file Quadro Django.Le classi del modello utilizzano un approccio di metaclasse per abilitare uno stile dichiarativo nella scrittura di nuovi modelli o classi di moduli.Mentre la metaclasse crea la classe, tutti i membri hanno la possibilità di personalizzare la classe stessa.

Ciò che resta da dire è:Se non sai cosa sono le metaclassi, la probabilità che tu non ne avranno bisogno è del 99%.

Cosa sono le metaclassi?Per cosa li usi?

TLDR:Una metaclasse istanzia e definisce il comportamento di una classe proprio come una classe istanzia e definisce il comportamento di un'istanza.

Pseudocodice:

>>> Class(...)
instance

Quanto sopra dovrebbe sembrarti familiare.Ebbene, da dove viene Class vieni?È un'istanza di una metaclasse (anche pseudocodice):

>>> Metaclass(...)
Class

Nel codice reale, possiamo passare la metaclasse predefinita, type, tutto ciò di cui abbiamo bisogno per istanziare una classe e otteniamo una classe:

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

Detto diversamente

  • Una classe sta a un'istanza come una metaclasse sta a una classe.

    Quando istanziamo un oggetto, otteniamo un'istanza:

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

    Allo stesso modo, quando definiamo esplicitamente una classe con la metaclasse predefinita, type, lo istanziamo:

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

    >>> isinstance(object, type)
    True
    
  • In altre parole, una metaclasse è la classe di una 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 della classe (che, a sua volta, verrà usato per istanziare le istanze di quella classe).

Proprio come possiamo utilizzare le definizioni di classe per modificare il comportamento delle istanze di oggetti personalizzati, possiamo utilizzare una definizione di classe metaclasse per modificare il modo in cui si comporta un oggetto di classe.

Per cosa possono essere utilizzati?Dal documenti:

I potenziali usi delle 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 si consiglia agli utenti di evitare l'uso delle metaclassi a meno che non sia assolutamente necessario.

Utilizzi una metaclasse ogni volta che crei una classe:

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

class Foo(object): 
    'demo'

Si crea un'istanza di un oggetto di classe.

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

È lo stesso della chiamata funzionalmente type con gli argomenti appropriati e assegnando il risultato a una variabile con quel nome:

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

Nota: alcune cose vengono aggiunte automaticamente al file __dict__, ovvero 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 metaclasse dell'oggetto che abbiamo creato, in entrambi i casi, lo è type.

(Una nota a margine sui contenuti della lezione __dict__: __module__ è lì perché le classi devono sapere dove sono definite e __dict__ E __weakref__ sono lì perché non li definiamo __slots__ - Se noi definire __slots__ risparmieremo un po' di spazio nelle istanze, poiché possiamo non consentirle __dict__ E __weakref__ escludendoli.Per 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 l'impostazione predefinita __repr__ di classi:

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

Una delle cose più preziose che possiamo fare per impostazione predefinita quando scriviamo un oggetto Python è fornirgli un buono __repr__.Quando chiamiamo help(repr) apprendiamo che esiste un buon test per a __repr__ che richiede anche un test di uguaglianza - obj == eval(repr(obj)).La seguente semplice implementazione di __repr__ E __eq__ per le istanze di classe del nostro tipo class ci fornisce una dimostrazione che potrebbe migliorare l'impostazione predefinita __repr__ di 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__)

Quindi ora quando creiamo un oggetto con questa metaclasse, il file __repr__ ripetuto sulla riga di comando fornisce uno spettacolo molto meno brutto rispetto all'impostazione predefinita:

>>> 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, è necessario effettuare ulteriori controlli eval(repr(Class)) è improbabile (poiché sarebbe piuttosto impossibile valutare le funzioni dal loro valore predefinito __repr__'S).

Un utilizzo previsto: __prepare__ uno spazio dei nomi

Se, ad esempio, volessimo sapere in quale ordine vengono creati i metodi di una classe, potremmo fornire un dict ordinato come spazio dei nomi della classe.Lo faremmo con __prepare__ Quale restituisce il dict dello spazio dei nomi per la classe se è implementato 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 da documentazione - il nuovo enum nella libreria standard fa questo.

Quindi quello che abbiamo fatto è stato istanziare una metaclasse creando una classe.Possiamo anche trattare la metaclasse 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 troviamo 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>})

Aggiornamento Python 3

Ci sono (a questo punto) due metodi chiave in una metaclasse:

  • __prepare__, E
  • __new__

__prepare__ ti 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 qualunque spazio dei nomi tu scelga.Se non implementi __prepare__ una normale dict si usa.

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

Una metaclasse semplice, che non fa nulla in più, 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 che un semplice codice di convalida venga eseguito sui tuoi attributi, come se dovesse sempre essere un int o a str.Senza una 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.

Una semplice metaclasse può risolvere questo problema:

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

Questo è come apparirebbe la metaclasse (non using __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 esempio di esecuzione 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:Questo esempio è abbastanza semplice e avrebbe potuto essere realizzato anche con un decoratore di classi, ma presumibilmente una vera metaclasse farebbe molto di più.

La classe "ValidateType" come 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

Ruolo di una metaclasse' __call__() metodo durante la creazione di un'istanza di classe

Se hai programmato Python per più di qualche mese, alla fine ti imbatterai in un 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 il file __call__() metodo magico sulla classe.

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

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

IL __call__() Il metodo viene richiamato quando un'istanza di una classe viene utilizzata come richiamabile.Ma come abbiamo visto dalle risposte precedenti, una classe stessa è un'istanza di una metaclasse, quindi quando usiamo la classe come richiamabile (cioèquando ne creiamo un'istanza) in realtà ne chiamiamo la metaclasse' __call__() metodo.A questo punto la maggior parte dei programmatori Python sono un po' confusi perché gli è stato detto questo durante la creazione di un'istanza come questa instance = SomeClass() lo stai chiamando __init__() metodo.Alcuni che hanno scavato un po' più a fondo lo sanno già __init__() c'è __new__().Ebbene, oggi viene svelato un altro strato di verità, prima __new__() c'è la metaclasse' __call__().

Studiamo la catena di chiamate del metodo specificamente dalla prospettiva della creazione di un'istanza di una classe.

Questa è una metaclasse che registra esattamente il momento prima che un'istanza venga creata e il 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 utilizza quella metaclasse

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 in realtà non fa altro che registrare le attività.Ogni metodo delega il lavoro effettivo all'implementazione del genitore, mantenendo così il comportamento predefinito.Da type È Meta_1la classe genitore di (type essendo la metaclasse genitore predefinita) 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 la metaclasse' __call__() Il metodo è quello che viene chiamato per primo.Quindi delega la creazione dell'istanza alla classe __new__() metodo e inizializzazione dell'istanza __init__().È anche quello che alla fine restituisce l'istanza.

Da quanto sopra risulta che la metaclasse' __call__() viene inoltre data la possibilità di decidere se effettuare o meno una chiamata Class_1.__new__() O Class_1.__init__() alla fine verrà realizzato.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 si tenta ripetutamente di 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

Una metaclasse è una classe che indica come (alcune) altre classi dovrebbero essere create.

Questo è un caso in cui ho visto metaclass come una soluzione al mio problema:Ho avuto un problema davvero complicato, che probabilmente avrebbe potuto essere risolto diversamente, ma ho scelto di risolverlo utilizzando una metaclasse.A causa della complessità, è uno dei pochi moduli che ho scritto in cui i commenti nel modulo superano la quantità di codice scritto.Ecco qui...

#!/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()

type è in realtà un metaclass -- una classe che crea un'altra classe.Maggior parte metaclass sono le sottoclassi di type.IL metaclass riceve il new class come primo argomento e fornisce l'accesso all'oggetto class 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 nessun momento;il semplice atto di creare la classe ha innescato l'esecuzione del file metaclass.

La versione tl;dr

IL type(obj) la funzione ti dà il tipo di un oggetto.

IL type() di una classe è il suo metaclasse.

Per utilizzare una metaclasse:

class Foo(object):
    __metaclass__ = MyMetaClass

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

La metaclasse predefinita, che viene applicata quando si determinano le classi come:

class foo:
    ...

le meta classi vengono utilizzate per applicare alcune regole a un intero insieme di classi.Ad esempio, supponiamo che tu stia creando un ORM per accedere a un database e desideri che i record di ciascuna tabella appartengano a una classe mappata su quella tabella (in base a campi, regole aziendali, ecc.), un possibile utilizzo di metaclasse è ad esempio la logica del pool di connessioni, che è condivisa da tutte le classi di record di tutte le tabelle.Un altro utilizzo è logico per supportare chiavi esterne, che coinvolgono più classi di record.

quando definisci la metaclasse, digita la 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 gli hook più comunemente usati.la metaclassificazione è potente e sopra non c'è neanche lontanamente un elenco esaustivo di usi per la metaclassificazione.

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

ad esempio, possiamo creare una classe Hi con la funzione type() e non è necessario utilizzarla in questo modo con class 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 utilizzare type() per creare classi in modo dinamico, 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 del tipo class.Cioè, type è la metaclasse della maggior parte delle classi integrate e la metaclasse delle 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 nella metaclasse, indica all'interprete Python di creare CustomList 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.

Oltre alle risposte pubblicate posso dire che a metaclass definisce il comportamento di una classe.Quindi, puoi impostare esplicitamente la tua metaclasse.Ogni volta che Python ottiene una parola chiave class quindi inizia a cercare il file metaclass.Se non viene trovato, il tipo di metaclasse predefinito viene utilizzato per creare l'oggetto della classe.Usando il __metaclass__ attributo, è possibile 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 crearne uno tuo metaclass per definire il comportamento di qualsiasi classe creata utilizzando la tua classe.

Per farlo, il tuo default metaclass type class deve essere ereditata poiché questa è la main metaclass:

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'
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top