Cosa sono i metaclassi in Python?


Risposte:


2869

Una metaclasse è la classe di una classe. Una classe definisce il comportamento di un'istanza della classe (ovvero un oggetto) mentre una metaclasse definisce il comportamento di una classe. Una classe è un'istanza di una metaclasse.

Mentre in Python è possibile utilizzare callable arbitrari per le metaclasse (come mostra Jerub ), l'approccio migliore è renderlo una classe reale stessa. typeè la solita metaclasse in Python. typeè di per sé una classe ed è un suo tipo. Non sarai in grado di ricreare qualcosa come typepuramente in Python, ma Python imbroglia un po '. Per creare la tua metaclasse in Python, vuoi davvero solo subclassare type.

Una metaclasse è comunemente usata come una fabbrica di classe. Quando si crea un oggetto chiamando la classe, Python crea una nuova classe (quando esegue l'istruzione 'class') chiamando la metaclasse. Combinati con il normale __init__e i __new__metodi, i metaclassi consentono quindi di fare "cose ​​extra" durante la creazione di una classe, come registrare la nuova classe con un registro o sostituire la classe con qualcos'altro.

Quando classviene eseguita l' istruzione, Python esegue innanzitutto il corpo classdell'istruzione come un normale blocco di codice. Lo spazio dei nomi risultante (un dict) contiene gli attributi della classe futura. La metaclasse viene determinata osservando le basi della classe futura (le metaclasse sono ereditate), l' __metaclass__attributo della classe futura (se presente) o la __metaclass__variabile globale. La metaclasse viene quindi chiamata con il nome, le basi e gli attributi della classe per istanziarla.

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

Ecco un esempio aggregato di 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__

13
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
pppery

20
ppperry, ovviamente, intendeva dire che non è possibile ricreare il tipo senza usare il tipo stesso come metaclasse. Il che è abbastanza giusto da dire.
Holle van,

3
Unregister () non dovrebbe essere chiamato dall'istanza della classe Example?
Ciasto piekarz,

5
Nota che __metaclass__non è supportato in Python 3. In Python 3 class MyObject(metaclass=MyType), consulta python.org/dev/peps/pep-3115 e la risposta di seguito.
BlackShift

2
La documentazione descrive come viene scelta la metaclasse . La metaclasse non è ereditata tanto quanto è derivata. Se si specifica una metaclasse, deve essere un sottotipo di ciascuna metaclasse della classe base; in caso contrario, utilizzerai una metaclasse di classe base che è un sottotipo di ogni altra metaclasse di classe base. Si noti che è possibile che non metaclasse valida può essere trovato, e la definizione non riuscirà.
Chepner

6820

Classi come oggetti

Prima di comprendere i metaclassi, devi padroneggiare le classi in Python. E Python ha un'idea molto particolare di cosa siano 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. Questo è un po '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 si utilizza la parola chiave class, Python la 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>

Creare classi in modo dinamico

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

Innanzitutto, 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 da solo l'intera classe.

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

Quando si utilizza il class parola chiave, Python crea automaticamente questo oggetto. 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 di oggetto è:

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

Bene, typeha un'abilità completamente diversa, può anche creare classi al volo.typepuò 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 passi ad essa. È un problema dovuto alla retrocompatibilità in Python)

type funziona in questo modo:

type(name, bases, attrs)

Dove:

  • name: nome della classe
  • bases: tupla della classe genitore (per ereditarietà, può essere vuota)
  • attrs: dizionario contenente attributi nomi e valori

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 utilizziamo "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.

typeaccetta 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 aggiungere metodi a un oggetto classe normalmente creato.

>>> 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 si usa la parola chiave class, e lo fa usando una metaclasse.

Cosa sono i metaclassi (finalmente)

I metaclassi sono le "cose" che creano classi.

Definisci le classi per creare oggetti, giusto?

Ma abbiamo imparato che le classi Python sono oggetti.

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

MyClass = MetaClass()
my_object = MyClass()

Hai visto che typeti permette di fare qualcosa del genere:

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

È perché la funzione typeè in effetti una metaclasse. typeè la metaclasse utilizzata da Python per creare tutte le classi dietro le quinte.

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

Bene, suppongo sia una questione di coerenza con strla classe che crea oggetti stringhe e intla classe che crea oggetti interi. typeè solo la classe che crea oggetti di classe.

Lo vedi controllando l' __class__attributo.

Tutto, e intendo tutto, è un oggetto in Python. Ciò include ints, stringhe, funzioni e classi. Tutti loro sono 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 nessuno __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 la roba che crea oggetti di classe.

Puoi chiamarlo una "fabbrica di classe" se lo desideri.

type è la metaclasse incorporata utilizzata da Python, ma ovviamente puoi creare la tua metaclasse.

L' __metaclass__attributo

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

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

In tal caso, Python utilizzerà la metaclasse per creare la classe Foo.

Attento, è difficile.

class Foo(object)Prima scrivi , ma l'oggetto di classe Foonon è ancora stato 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, verrà utilizzato typeper creare la classe.

Leggi più volte.

Quando lo fai:

class Foo(Bar):
    pass

Python effettua le seguenti operazioni:

C'è un __metaclass__attributo inFoo ?

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

Se Python non riesce a trovarlo __metaclass__, cercherà un __metaclass__livello MODULE e proverà a fare lo stesso (ma solo per le classi che non ereditano nulla, sostanzialmente classi vecchio stile).

Quindi, se non riesce a trovarne __metaclass__affatto, utilizzerà la Barpropria metaclasse (il primo genitore) (che potrebbe essere quella predefinita type) per creare l'oggetto classe.

Fai attenzione che l' __metaclass__attributo non verrà ereditato, la metaclasse del parent ( Bar.__class__) sarà. Se Barutilizzato un __metaclass__attributo creato Barcon type()(e non type.__new__()), le sottoclassi non erediteranno quel comportamento.

Ora la grande domanda è: cosa puoi inserire __metaclass__?

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

E cosa può creare una classe? typeo qualsiasi cosa che lo abbia in sottoclasse o lo usi.

Metaclass in Python 3

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

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

cioè l' __metaclass__attributo non viene più utilizzato, a favore di un argomento di parole chiave nell'elenco delle classi base.

Il comportamento dei metaclassi rimane comunque sostanzialmente lo stesso .

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

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

Leggi la sezione qui sotto per come Python gestisce questo.

Metaclass personalizzati

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

Di solito lo fai per le API, dove desideri creare classi corrispondenti al contesto corrente.

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

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

Fortunatamente, in __metaclass__realtà può essere qualsiasi richiamo, non deve essere una classe formale (lo so, qualcosa con "classe" nel suo nome non deve essere una classe, vai a capire ... ma è utile).

Quindi inizieremo con un semplice esempio, usando 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_attrs):
    """
      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_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

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

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

Controlliamo:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'

Ora facciamo esattamente lo stesso, 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_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

Riscriviamo quanto sopra, ma con nomi di variabili più brevi e più realistici ora che sappiamo cosa significano:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

Potresti aver notato l'argomento in più cls. Non c'è nulla di speciale al riguardo: __new__riceve sempre la classe in cui è definita, come primo parametro. Proprio come selfper i metodi ordinari che ricevono l'istanza come primo parametro o la classe che definisce i metodi di classe.

Ma questo non è corretto OOP. Stiamo chiamando typedirettamente e non stiamo sostituendo o chiamando i genitori __new__. Facciamolo invece:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

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

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

Oh, e in Python 3 se si esegue questa chiamata con argomenti di parole chiave, in questo modo:

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

Si traduce in questo nella metaclasse per usarlo:

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

Questo è tutto. Non c'è davvero niente di più sui metaclassi.

Il motivo alla base della complessità del codice che utilizza metaclassi non è dovuto ai metaclassi, ma perché di solito si usano i metaclassi per fare cose contorte basandosi sull'introspezione, manipolando l'eredità, variando come __dict__, ecc.

In effetti, i metaclassi sono particolarmente utili per fare magie nere e quindi cose complicate. Ma da soli, sono semplici:

  • intercettare una creazione di classe
  • modifica la classe
  • restituisce la classe modificata

Perché dovresti usare le classi metaclass invece delle funzioni?

Dal momento che __metaclass__può accettare qualsiasi richiamo, perché dovresti usare una classe poiché è ovviamente più complicata?

Esistono diversi motivi per farlo:

  • L'intenzione è chiara. Quando leggi UpperAttrMetaclass(type), sai cosa seguirà
  • Puoi usare OOP. La metaclasse può ereditare dalla metaclasse, sovrascrivere i metodi parent. I metaclassi possono persino usare i metaclassi.
  • Le sottoclassi di una classe saranno istanze della sua metaclasse se è stata specificata una classe di metaclasse, ma non con una funzione di metaclasse.
  • Puoi strutturare meglio il tuo codice. Non usi mai i metaclassi per qualcosa di così banale come nell'esempio sopra. Di solito è per qualcosa di complicato. Avere la possibilità di creare diversi metodi e raggrupparli in una classe è molto utile per facilitare la lettura del codice.
  • Puoi agganciare __new__, __init__e __call__. Che ti permetterà di fare cose diverse. Anche se di solito puoi fare tutto __new__, alcune persone sono più a suo agio nell'utilizzare __init__.
  • Questi sono chiamati metaclassi, accidenti! Deve significare qualcosa!

Perché dovresti usare i metaclassi?

Ora la grande domanda. Perché dovresti usare qualche oscura funzione soggetta a errori?

Bene, di solito non:

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

Python Guru Tim Peters

Il caso d'uso principale per una metaclasse è la creazione di un'API. Un tipico esempio di ciò è 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:

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

Non restituirà un IntegerFieldoggetto. Restituirà un inte può anche prenderlo direttamente dal database.

Questo è possibile perché models.Modeldefinisce __metaclass__e usa un po 'di magia che trasformerà il Personappena definito con semplici istruzioni in un hook complesso in un campo di database.

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

L'ultima parola

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

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 istanze di classi o istanze di metaclassi.

Tranne per type.

typeè in realtà la sua metaclasse. Questo non è qualcosa che potresti riprodurre in Python puro, ed è fatto ingannando un po 'a livello di implementazione.

In secondo luogo, i metaclassi sono complicati. Potresti non volerli usare per modifiche di classe molto semplici. Puoi cambiare classe usando due diverse tecniche:

Il 99% delle volte che hai bisogno di un cambio di classe, è meglio usarli.

Ma il 98% delle volte, non hai assolutamente bisogno dell'alterazione di classe.


30
Sembra che in Django models.Modelnon utilizzi, __metaclass__ma piuttosto class Model(metaclass=ModelBase):per fare riferimento a una ModelBaseclasse che quindi fa la suddetta magia metaclasse. Ottimo post! Ecco la fonte di Django: github.com/django/django/blob/master/django/db/models/…
Max Goodridge

15
<< Fai attenzione qui che l' __metaclass__attributo non sarà ereditato, la metaclasse del parent ( Bar.__class__) sarà. Se viene Barutilizzato un __metaclass__attributo creato Barcon type()(e non type.__new__()), le sottoclassi non erediteranno quel comportamento. >> - Potresti / potresti spiegare un po 'più a fondo questo passaggio?
petrux,

15
@MaxGoodridge Questa è la sintassi di Python 3 per i metaclassi. Vedi il modello dati Python 3.6 VS Modello dati Python 2.7
TBBle

2
Now you wonder why the heck is it written in lowercase, and not Type?- beh perché è implementato in C - è lo stesso motivo per cui defaultdict è in minuscolo mentre OrderedDict (in python 2) è normale CamelCase
Mr_and_Mrs_D

15
È una risposta wiki della comunità (quindi, coloro che hanno commentato correzioni / miglioramenti potrebbero considerare di modificare i loro commenti nella risposta, se sono sicuri che siano corretti).
Brōtsyorfuzthrāx

403

Nota, questa risposta è per Python 2.x come è stata scritta nel 2008, i metaclassi sono leggermente diversi in 3.x.

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

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

I metaclassi prendono 3 argomenti. ' name ', ' bases ' e ' dict '

Qui è dove 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

Consente di definire una metaclasse che dimostrerà come " class: " la 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 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

Nota che il comportamento magico che si Initialisedottiene avendo la metaclasse init_attributesnon viene passato in una sottoclasse di Initialised.

Ecco un esempio ancora più concreto, che mostra come è possibile sottoclassare 'type' per creare una 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

169

Altri hanno spiegato come funzionano i 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 venivano definite le classi, in modo da poterle istanziare in seguito 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

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


Grazie per l'esempio Perché l'hai trovato più facile che ereditare da MyBase, di chi __init__(self)dice type(self)._order = MyBase.counter; MyBase.counter += 1?
Michael Gundlach,

1
Volevo che le classi stesse, non le loro istanze, fossero numerate.
kindall

Bene, duh. Grazie. Il mio codice ripristinerebbe l'attributo di MyType ad ogni istanza e non imposterebbe mai l'attributo se non viene mai creata un'istanza di MyType. Ops. (E una proprietà di classe potrebbe anche funzionare, ma a differenza della metaclasse non offre un posto ovvio dove riporre il bancone.)
Michael Gundlach,

1
Questo è un esempio jolly interessante, anche perché si può davvero vedere perché una metaclasse potrebbe essere necessaria con ciò, per fornire una soluzione a una difficoltà specifica. OTOH Faccio fatica a convincermi che qualcuno avrebbe davvero bisogno di creare un'istanza degli oggetti nell'ordine in cui sono state definite le loro classi: immagino che dobbiamo solo prendere la tua parola per quello :).
mike rodent,

159

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

Ad esempio, se guardi i modelli Django , la loro definizione sembra un po 'confusa. Sembra che tu stia solo definendo 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 vengono riempiti con ogni sorta di metodi utili. Vedi la fonte per alcune straordinarie metaclasserie.


6
L'uso di meta-classi non aggiunge nuove proprietà e metodi a una classe e non a un'istanza? Per quanto ho capito, la meta-classe 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 sulle istanze può essere ottenuto per inerenza normale. Tuttavia, il riferimento al codice Django come esempio è buono.
Trixn,

119

Penso che l'introduzione di ONLamp alla programmazione delle metaclasse 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 in https://web.archive.org/web/20080206005253/http://www.onlamp. com / pub / a / python / 2003/04/17 / metaclasses.html )

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

Non ne ho mai scritto uno da solo, ma penso che uno degli usi più belli delle metaclasse possa essere visto nel framework Django . Le classi del modello utilizzano un approccio metaclasse per consentire uno stile dichiarativo di scrittura di nuovi modelli o classi di moduli. Mentre la metaclasse sta creando la classe, tutti i membri hanno la possibilità di personalizzare la classe stessa.

La cosa che resta da dire è: se non sai cosa sono i metaclassi, la probabilità che non ti servano è del 99%.


109

Cosa sono i metaclassi? Per cosa li usi?

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

pseudocodice:

>>> Class(...)
instance

Quanto sopra dovrebbe sembrare familiare. Bene, da dove Classviene? È 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 creare un'istanza di una classe e otteniamo una classe:

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

Mettendolo diversamente

  • Una classe è in un'istanza come una metaclasse è in 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, la 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
  • Detto in terzo luogo, una metaclasse è una classe di classe.

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

Quando si scrive una definizione di classe e Python la esegue, utilizza una metaclasse per creare un'istanza dell'oggetto classe (che, a sua volta, verrà utilizzata per istanziare istanze di quella classe).

Proprio come possiamo usare le definizioni di classe per cambiare il comportamento delle istanze di oggetti personalizzati, possiamo usare una definizione di classe di metaclasse per cambiare il comportamento di un oggetto di classe.

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 di proprietà, i proxy, i framework e il blocco / sincronizzazione automatici delle risorse.

Tuttavia, di solito è incoraggiato gli utenti a evitare l'uso di metaclassi se non assolutamente necessario.

Usi una metaclasse ogni volta che crei una classe:

Quando scrivi una definizione di classe, ad esempio, come questa,

class Foo(object): 
    'demo'

Istanzia un oggetto di classe.

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

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

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

Nota, alcune cose vengono automaticamente aggiunte allo __dict__spazio dei nomi, ovvero:

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

La metaclasse dell'oggetto che abbiamo creato, in entrambi i casi, è type.

(Una nota a margine sul contenuto della classe __dict__: __module__c'è perché le classi devono sapere dove sono definite __dict__e __weakref__ci sono perché non definiamo __slots__- se definiamo__slots__ risparmieremo un po 'di spazio nelle istanze, come possiamo non consentirli __dict__ed __weakref__escluderli. Ad esempio:

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

... ma sto divagando.)

Possiamo estendere typecome qualsiasi altra definizione di classe:

Ecco il valore predefinito __repr__delle classi:

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

Una delle cose più preziose che possiamo fare per impostazione predefinita nella scrittura di un oggetto Python è di fornirgli un buono __repr__. Quando chiamiamo help(repr)apprendiamo che esiste 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 di tipo ci fornisce una dimostrazione che può migliorare il default __repr__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__)

Quindi ora quando creiamo un oggetto con questa metaclasse, l' __repr__eco sulla riga di comando offre uno spettacolo molto meno brutto del valore predefinito:

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

Con una buona __repr__definizione per l'istanza della classe, abbiamo una maggiore capacità di debug del nostro codice. Tuttavia, eval(repr(Class))è improbabile che si verifichino ulteriori controlli (poiché le funzioni sarebbero piuttosto impossibili da evitare dal loro default__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 comando ordinato come spazio dei nomi della classe. Faremmo questo con il __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 registro 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 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 se 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>})

78

Aggiornamento di Python 3

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

  • __prepare__, e
  • __new__

__prepare__consente di fornire un mapping personalizzato (come un OrderedDict) da utilizzare come spazio dei nomi durante la creazione della classe. È necessario restituire un'istanza di qualunque spazio dei nomi si scelga. Se non si implementa __prepare__un normaledict viene utilizzato.

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

Una metaclasse nuda e non fare nulla 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:

Supponi di voler eseguire un semplice codice di convalida sui tuoi attributi, come se fosse sempre un into un 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. Questo 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)

Ecco come apparirebbe la metaclasse (non utilizzata in __prepare__quanto non necessaria):

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:

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 che avrebbe potuto essere realizzato anche con un decoratore di classe, ma presumibilmente una metaclasse reale 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

Wow, questa è una nuova fantastica funzionalità che non sapevo esistesse in Python 3. Grazie per l'esempio !!
Rich Lysakowski PhD,

Nota che da Python 3.6 puoi usare __set_name__(cls, name) nel descrittore ( ValidateType) per impostare il nome nel descrittore ( self.namee anche in questo caso self.attr). Questo è stato aggiunto per non dover immergersi nelle metaclasse per questo specifico caso d'uso comune (vedi PEP 487).
Lars

68

Ruolo del __call__()metodo di una metaclasse 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 al seguente:

# 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 __call__()metodo magico sulla classe.

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

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

Il __call__()metodo viene invocato 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 callable (cioè quando ne creiamo un'istanza), in realtà chiamiamo il __call__()metodo della sua metaclasse . A questo punto la maggior parte dei programmatori Python sono un po 'confusi perché è stato detto loro che quando si crea un'istanza come questa instance = SomeClass()si chiama il suo __init__()metodo. Alcuni che hanno scavato un po 'più in profondità lo sanno prima che __init__()ci sia __new__(). Bene, oggi viene rivelato un altro strato di verità, prima che __new__()ci sia la metaclasse__call__() .

Studiamo la catena di chiamate del metodo in particolare la prospettiva di creare un'istanza di una classe.

Questa è una metaclasse che registra esattamente il momento prima della creazione di un'istanza 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.

Si noti che il codice sopra riportato non fa altro che registrare le attività. Ogni metodo delega il lavoro effettivo all'implementazione del suo genitore, mantenendo così il comportamento predefinito. Datype è Meta_1la classe genitore ( typeessendo la metaclasse genitore predefinita) e considerando la sequenza di ordinamento dell'output sopra, ora abbiamo un indizio su quale sarebbe l'implementazione pseudo 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 __call__()metodo della metaclasse è quello chiamato per primo. Delega quindi la creazione dell'istanza al __new__()metodo della classe e l'inizializzazione all'istanza __init__(). È anche quello che alla fine restituisce l'istanza.

Da quanto precede deriva che anche la metaclasse __call__()ha la possibilità di decidere se chiamare o menoClass_1.__new__() o Class_1.__init__()alla fine sarà fatta. 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

Questa è una buona aggiunta alla "risposta accettata" precedentemente votata. Fornisce esempi per i programmatori intermedi da masticare.
Rich Lysakowski PhD,

56

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

Questo è un caso in cui ho visto la metaclasse come una soluzione al mio problema: avevo 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 in cui i commenti nel modulo superano la quantità di codice che è stato 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()

43

La versione tl; dr

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

La type()classe di una è la sua metaclasse .

Per utilizzare una metaclasse:

class Foo(object):
    __metaclass__ = MyMetaClass

typeè la sua metaclasse. La classe di una classe è una metaclasse: il corpo di una classe sono gli argomenti passati alla metaclasse utilizzata per costruire la classe.

Qui puoi leggere come utilizzare i metaclassi per personalizzare la costruzione della classe.


42

typeè in realtà una metaclass- una classe che crea altre classi. La maggior parte metaclasssono le sottoclassi di type. La metaclassriceve la newclasse come primo parametro e fornire accesso all'oggetto classe con 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 di metaclass.


27

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

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

class foo:
    ...

la meta classe viene utilizzata per applicare alcune regole a un intero insieme di classi. Ad esempio, supponiamo che tu stia costruendo un ORM per accedere a un database e desideri che i record di ogni tabella appartengano a una classe mappata a quella tabella (basata su campi, regole aziendali, ecc.), Un possibile uso della 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 si definisce la metaclasse, si digita la sottoclasse e si possono ignorare i seguenti metodi magici per inserire la propria 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, quei due sono i ganci più comunemente usati. il metaclassing è potente e soprattutto non esiste un elenco completo ed esaustivo di usi per il metaclassing.


21

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 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 utilizzare type () per creare classi in modo dinamico, è possibile 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. 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']

Magic entrerà in vigore quando passiamo gli argomenti delle parole chiave in metaclasse, indica che l'interprete Python crea la CustomList tramite ListMetaclass. new (), a questo punto, possiamo modificare la definizione della classe, ad esempio, e aggiungere un nuovo metodo e quindi restituire la definizione rivista.


11

Oltre alle risposte pubblicate, posso dire che a metaclassdefinisce il comportamento di una classe. Quindi, puoi impostare esplicitamente la tua metaclasse. Ogni volta che Python ottiene una parola chiave class, inizia a cercare metaclass. Se non viene trovato, il tipo di metaclasse predefinito viene utilizzato per creare l'oggetto della classe. Utilizzando l' __metaclass__attributo, puoi impostare la metaclasstua 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 metaclassper definire il comportamento di qualsiasi classe creata utilizzando la tua classe.

Per fare ciò, la metaclassclasse di tipo predefinita deve essere ereditata poiché questa è la principale 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'

4

Nella programmazione orientata agli oggetti, una metaclasse è una classe le cui istanze sono classi. Proprio come una classe ordinaria definisce il comportamento di determinati oggetti, una metaclasse definisce il comportamento di determinate classi e le loro istanze Il termine metaclasse significa semplicemente qualcosa usato per creare classi. In altre parole, è la classe di una classe. La metaclasse viene utilizzata per creare la classe in modo che l'oggetto sia un'istanza di una classe, una classe è un'istanza di una metaclasse. Nelle classi python sono anche considerati oggetti.


Piuttosto che dare definizioni libresche, sarebbe stato meglio se tu avessi aggiunto alcuni esempi. La prima riga della tua risposta sembra essere stata copiata dalla voce Wikipedia di Metaclasses.
verosimiglianza

@verisimilitude Sto anche imparando puoi aiutarmi a migliorare questa risposta fornendo alcuni esempi pratici della tua esperienza ??
Venu Gopal Tewari,

2

Ecco un altro esempio di cosa può essere utilizzato per:

  • Puoi usare il metaclassper cambiare la funzione della sua istanza (la classe).
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

La metaclassè potente, ci sono molte cose (come la magia scimmia) si può fare con esso, ma attenzione questo può essere conosciuta solo a te.


2

Una classe, in Python, è un oggetto e, come qualsiasi altro oggetto, è un'istanza di "qualcosa". Questo "qualcosa" è ciò che viene definito come una metaclasse. Questa metaclasse è un tipo speciale di classe che crea oggetti di altre classi. Quindi, la metaclasse è responsabile della creazione di nuove classi. Ciò consente al programmatore di personalizzare il modo in cui le classi vengono generate.

Per creare una metaclasse, di solito viene eseguita la sostituzione dei metodi new () e init (). new () può essere ignorato per cambiare il modo in cui gli oggetti vengono creati, mentre init () può essere ignorato per cambiare il modo di inizializzare l'oggetto. La metaclasse può essere creata in vari modi. Uno dei modi è usare la funzione type (). La funzione type (), quando chiamata con 3 parametri, crea una metaclasse. I parametri sono: -

  1. Nome della classe
  2. Tupla con classi di base ereditate dalla classe
  3. Un dizionario con tutti i metodi e le variabili di classe

Un altro modo di creare una metaclasse comprende la parola chiave "metaclass". Definire la metaclasse come una classe semplice. Nei parametri della classe ereditata, passare metaclass = metaclass_name

La metaclasse può essere specificamente utilizzata nelle seguenti situazioni: -

  1. quando un particolare effetto deve essere applicato a tutte le sottoclassi
  2. È richiesto il cambio automatico di classe (alla creazione)
  3. Dagli sviluppatori API

2

Si noti che in Python 3.6 è __init_subclass__(cls, **kwargs)stato introdotto un nuovo metodo Dunder per sostituire molti casi d'uso comuni per i metaclassi. Viene chiamato quando viene creata una sottoclasse della classe di definizione. Vedi i documenti di Python .


-3

La metaclasse è un tipo di classe che definisce come la classe si comporterà o possiamo dire che una classe è essa stessa un'istanza di una metaclasse.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.