Quali sono alcuni casi d'uso (concreti) per le metaclassi?


118

Ho un amico a cui piace usare le metaclassi e le offre regolarmente come soluzione.

Sono dell'idea che non hai quasi mai bisogno di usare le metaclassi. Perché? perché immagino che se stai facendo qualcosa del genere a una classe, probabilmente dovresti farlo a un oggetto. Ed è necessaria una piccola riprogettazione / refactoring.

Essere in grado di utilizzare le metaclassi ha indotto molte persone in molti posti a utilizzare le classi come una sorta di oggetto di second'ordine, il che mi sembra disastroso. La programmazione deve essere sostituita dalla meta-programmazione? L'aggiunta di decoratori di classe lo ha purtroppo reso ancora più accettabile.

Quindi, per favore, sono disperato nel conoscere i tuoi casi d'uso validi (concreti) per le metaclassi in Python. O per essere illuminato sul motivo per cui mutare le classi è meglio che mutare gli oggetti, a volte.

Inizierò:

A volte, quando si usa una libreria di terze parti, è utile poter modificare la classe in un certo modo.

(Questo è l'unico caso a cui riesco a pensare e non è concreto)


3
Questa è un'ottima domanda. A giudicare dalle risposte seguenti, è abbastanza chiaro che non esiste un uso concreto delle metaclassi.
Marcus Ottosson

Risposte:


25

Ho una classe che gestisce la stampa non interattiva, come frontend per Matplotlib. Tuttavia, a volte si desidera eseguire la stampa interattiva. Con solo un paio di funzioni ho scoperto di essere in grado di aumentare il conteggio delle cifre, chiamare manualmente il disegno, ecc., Ma avevo bisogno di farlo prima e dopo ogni chiamata di plottaggio. Quindi, per creare sia un wrapper di plottaggio interattivo che un wrapper di plottaggio fuori schermo, ho scoperto che era più efficiente farlo tramite metaclassi, avvolgendo i metodi appropriati, piuttosto che fare qualcosa come:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

Questo metodo non tiene il passo con le modifiche all'API e così via, ma quello che itera sugli attributi della classe __init__prima di reimpostare gli attributi della classe è più efficiente e mantiene le cose aggiornate:

class _Interactify(type):
    def __init__(cls, name, bases, d):
        super(_Interactify, cls).__init__(name, bases, d)
        for base in bases:
            for attrname in dir(base):
                if attrname in d: continue # If overridden, don't reset
                attr = getattr(cls, attrname)
                if type(attr) == types.MethodType:
                    if attrname.startswith("add_"):
                        setattr(cls, attrname, wrap_pylab_newplot(attr))
                    elif attrname.startswith("set_"):
                        setattr(cls, attrname, wrap_pylab_show(attr))

Naturalmente, potrebbero esserci modi migliori per farlo, ma ho trovato che sia efficace. Naturalmente, questo potrebbe anche essere fatto in __new__o __init__, ma questa è stata la soluzione che ho trovato più semplice.


103

Recentemente mi è stata posta la stessa domanda e ho trovato diverse risposte. Spero che sia OK per far rivivere questo thread, poiché volevo elaborare alcuni dei casi d'uso menzionati e aggiungerne alcuni nuovi.

La maggior parte delle metaclassi che ho visto fare una delle due cose:

  1. Registrazione (aggiunta di una classe a una struttura dati):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Ogni volta che fai una sottoclasse Model, la tua classe viene registrata nel modelsdizionario:

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}

    Questo può essere fatto anche con i decoratori di classe:

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass

    Oppure con una funzione di registrazione esplicita:

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)

    In realtà, è più o meno lo stesso: menzioni sfavorevolmente i decoratori di classe, ma in realtà non è altro che zucchero sintattico per un'invocazione di funzione su una classe, quindi non c'è magia al riguardo.

    Ad ogni modo, il vantaggio delle metaclassi in questo caso è l'ereditarietà, poiché funzionano per eventuali sottoclassi, mentre le altre soluzioni funzionano solo per sottoclassi esplicitamente decorate o registrate.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
  2. Refactoring (modifica degli attributi della classe o aggiunta di nuovi):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Ogni volta che si sottoclasse Modele si definiscono alcuni Fieldattributi, vengono iniettati i loro nomi (per messaggi di errore più informativi, ad esempio) e raggruppati in un _fieldsdizionario (per una facile iterazione, senza dover guardare attraverso tutti gli attributi della classe e tutte le sue classi base ' attributi ogni volta):

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}

    Di nuovo, questo può essere fatto (senza ereditarietà) con un decoratore di classi:

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(

    O esplicitamente:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!

    Sebbene, al contrario della tua difesa di una meta programmazione leggibile e manutenibile, questa è molto più ingombrante, ridondante e soggetta a errori:

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}

Dopo aver considerato i casi d'uso più comuni e concreti, gli unici casi in cui DEVI assolutamente usare le metaclassi sono quando vuoi modificare il nome della classe o l'elenco delle classi base, perché una volta definiti, questi parametri sono inseriti nella classe e nessun decoratore o la funzione può sbloccarli.

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

Ciò può essere utile nei framework per l'emissione di avvisi ogni volta che vengono definite classi con nomi simili o alberi di ereditarietà incompleti, ma non riesco a pensare a un motivo oltre al troll per modificare effettivamente questi valori. Forse David Beazley può.

Ad ogni modo, in Python 3, le metaclassi hanno anche il __prepare__metodo, che ti consente di valutare il corpo della classe in una mappatura diversa da a dict, supportando così attributi ordinati, attributi sovraccaricati e altre cose fantastiche:

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

 

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

Si potrebbe sostenere che gli attributi ordinati possono essere ottenuti con i contatori di creazione e il sovraccarico può essere simulato con argomenti predefiniti:

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

 

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

Oltre ad essere molto più brutto, è anche meno flessibile: e se volessi attributi letterali ordinati, come numeri interi e stringhe? E se Noneè un valore valido per x?

Ecco un modo creativo per risolvere il primo problema:

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

Ed ecco un modo creativo per risolvere il secondo:

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

Ma questo è molto, MOLTO vudù di una semplice metaclasse (specialmente la prima, che ti scioglie davvero il cervello). Il punto è che tu consideri le metaclassi come poco familiari e controintuitive, ma puoi anche considerarle come il prossimo passo dell'evoluzione nei linguaggi di programmazione: devi solo aggiustare la tua mentalità. Dopotutto, potresti probabilmente fare tutto in C, inclusa la definizione di una struttura con puntatori a funzione e il suo passaggio come primo argomento alle sue funzioni. Una persona che vede C ++ per la prima volta potrebbe dire: "cos'è questa magia? Perché il compilatore passa implicitamentethisai metodi, ma non alle funzioni regolari e statiche? È meglio essere espliciti e verbosi sui propri argomenti ". Ma poi, la programmazione orientata agli oggetti è molto più potente una volta ottenuta; e anche questa, uh ... programmazione orientata agli aspetti, immagino. E una volta che tu capire le metaclassi, in realtà sono molto semplici, quindi perché non usarle quando è conveniente?

Infine, le metaclassi sono fantastiche e la programmazione dovrebbe essere divertente. L'utilizzo costante di costrutti di programmazione e modelli di progettazione standard è noioso e poco interessante e ostacola la tua immaginazione. Vivi un po! Ecco una metametaclass, solo per te.

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan

modificare

Questa è una domanda piuttosto vecchia, ma sta ancora ricevendo voti positivi, quindi ho pensato di aggiungere un collegamento a una risposta più completa. Se desideri saperne di più sulle metaclassi e sul loro utilizzo, ho appena pubblicato un articolo a riguardo qui .


5
È un'ottima risposta, grazie per il tempo che ci dedichi a scriverla e a fornire diversi esempi
Chen A.

"... il vantaggio delle metaclassi in questo caso è l'ereditarietà, poiché funzionano per tutte le sottoclassi" - non in Python 3, suppongo? Penso che funzioni in Python 2 solo perché ogni classe figlia eredita l' __metaclass__attributo, ma questo attributo non è più speciale in Python 3. Esiste un modo per far funzionare questa cosa "anche le classi figli sono costruite dalla metaclasse del genitore" in Python 3 ?
ForceBru

2
Questo vale anche per Python 3, perché una classe B, che eredita da A, la cui metaclasse è M, è anche un tipo di M. Quindi, quando B viene valutato, M viene invocato per crearlo, e questo effettivamente ti consente a "lavorare su qualsiasi sottoclasse" (di A). Detto questo, Python 3.6 ha introdotto il molto più semplice init_subclass, quindi ora puoi manipolare le sottoclassi in una classe base e non hai più bisogno di una metaclasse per quello scopo.
Dan Gittik

Questo è geniale, ho letto tanti post sul blog sulle metaclassi, solo questo fa conoscere i pro e i contro e le alternative alla metaclasse.
Ospider

36

Lo scopo delle metaclassi non è quello di sostituire la distinzione classe / oggetto con metaclasse / classe - è quello di cambiare il comportamento delle definizioni di classe (e quindi delle loro istanze) in qualche modo. Effettivamente è quello di alterare il comportamento dell'istruzione class in modi che potrebbero essere più utili per il tuo particolare dominio rispetto a quello predefinito. Le cose per cui li ho usati sono:

  • Monitoraggio delle sottoclassi, solitamente per registrare i gestori. Ciò è utile quando si utilizza un'impostazione in stile plug-in, in cui si desidera registrare un gestore per una cosa particolare semplicemente creando sottoclassi e impostando alcuni attributi di classe. per esempio. supponiamo di scrivere un gestore per vari formati musicali, dove ogni classe implementa metodi appropriati (play / get tag, ecc.) per il suo tipo. L'aggiunta di un gestore per un nuovo tipo diventa:

    class Mp3File(MusicFile):
        extensions = ['.mp3']  # Register this type as a handler for mp3 files
        ...
        # Implementation of mp3 methods go here

    La metaclasse quindi mantiene un dizionario di {'.mp3' : MP3File, ... }ecc e costruisce un oggetto del tipo appropriato quando si richiede un gestore tramite una funzione di fabbrica.

  • Cambiare comportamento. Potresti voler attribuire un significato speciale a determinati attributi, con conseguente comportamento alterato quando sono presenti. Ad esempio, potresti voler cercare metodi con il nome _get_fooe _set_fooconvertirli in modo trasparente in proprietà. Come esempio nel mondo reale, ecco una ricetta che ho scritto per dare più definizioni di strutture tipo C. La metaclasse viene utilizzata per convertire gli elementi dichiarati in una stringa di formato struct, gestire l'ereditarietà, ecc. E produrre una classe in grado di gestirla.

    Per altri esempi reali, dare un'occhiata a vari ORM, come del SQLAlchemy ORM o SQLObject . Ancora una volta, lo scopo è interpretare le definizioni (qui definizioni di colonne SQL) con un significato particolare.


3
Ebbene sì, monitoraggio delle sottoclassi. Ma perché mai lo vorresti? Il tuo esempio è solo implicito per register_music_file (Mp3File, ['.mp3']), e il modo esplicito è più leggibile e gestibile. Questo è un esempio dei casi gravi di cui parlo.
Ali Afshar

Riguardo al caso ORM, stai parlando del modo basato sulla classe di definire le tabelle o delle metaclassi sugli oggetti mappati. Perché SQLAlchemy può (giustamente) mappare a qualsiasi classe (e presumo che non utilizzi una metaclasse per quell'attività).
Ali Afshar

10
Preferisco lo stile più dichiarativo, piuttosto che richiedere metodi di registrazione aggiuntivi per ogni sottoclasse, meglio se tutto è racchiuso in una singola posizione.
Brian

Per sqlalchemy, sto pensando principalmente al livello dichiarativo, quindi forse sqlobject è un esempio migliore. Tuttavia, le metaclassi utilizzate internamente sono anche esempi di reinterpretazione simile di attributi particolari per dichiarare il significato.
Brian

2
Spiacente, uno dei miei confermi si è perso nello scenario di timeout SO. Trovo che le classi per dichiarativo siano quasi un abominio. So che le persone lo adorano ed è un comportamento accettato. Ma (per esperienza) so che è inutilizzabile in una situazione in cui si desidera dichiarare le cose all'ONU. Annullare la registrazione di un corso è difficile .
Ali Afshar

17

Cominciamo con la classica citazione di Tim Peter:

Le metaclassi sono una magia più profonda di quanto il 99% degli utenti dovrebbe mai preoccuparsi. Se ti chiedi se hai bisogno di loro, non lo fai (le persone che effettivamente ne hanno bisogno sanno con certezza che ne hanno bisogno e non hanno bisogno di una spiegazione sul perché). Tim Peters (clp post 2002-12-22)

Detto questo, mi sono (periodicamente) imbattuto in veri usi delle metaclassi. Quello che mi viene in mente è in Django, dove tutti i tuoi modelli ereditano da models.Model. models.Model, a sua volta, fa una seria magia per avvolgere i tuoi modelli di DB con la bontà ORM di Django. Quella magia avviene tramite metaclassi. Crea tutti i tipi di classi di eccezione, classi manager, ecc. Ecc.

Vedi django / db / models / base.py, class ModelBase () per l'inizio della storia.


Ebbene sì, capisco il punto. Non mi chiedo "come" o "perché" usare le metaclassi, mi chiedo il "chi" e il "cosa". Gli ORM sono un caso comune qui che vedo. Sfortunatamente, l'ORM di Django è piuttosto scadente rispetto a SQLAlchemy che ha meno magia. La magia è cattiva e le metaclassi non sono davvero necessarie per questo.
Ali Afshar

9
Avendo letto la citazione di Tim Peters in passato, il tempo ha dimostrato che la sua affermazione è piuttosto inutile. Solo dopo aver ricercato le metaclassi Python qui su StackOverflow è diventato evidente come implementarle. Dopo essermi sforzato di imparare a scrivere e usare le metaclassi, le loro abilità mi hanno stupito e mi hanno dato una migliore comprensione di come funziona davvero Python. Le classi possono fornire codice riutilizzabile e le metaclassi possono fornire miglioramenti riutilizzabili per quelle classi.
Noctis Skytower

6

Le metaclassi possono essere utili per la costruzione di linguaggi specifici del dominio in Python. Esempi concreti sono Django, la sintassi dichiarativa di SQLObject degli schemi di database.

Un esempio di base da A Conservative Metaclass di Ian Bicking:

Le metaclassi che ho usato sono state principalmente per supportare una sorta di stile di programmazione dichiarativo. Ad esempio, considera uno schema di convalida:

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()

Alcune altre tecniche: Ingredienti per costruire un DSL in Python (pdf).

Modifica (di Ali): un esempio di utilizzo di raccolte e istanze è ciò che preferirei. Il fatto importante sono le istanze, che ti danno più potere ed eliminano le ragioni per usare le metaclassi. Inoltre, vale la pena notare che il tuo esempio utilizza una combinazione di classi e istanze, il che è sicuramente un'indicazione che non puoi fare tutto con le metaclassi. E crea un modo davvero non uniforme di farlo.

number_validator = [
    v.OneOf('type', ['home', 'work']),
    v.PhoneNumber('phone_number'),
]

validators = [
    v.String('first_name', notEmpty=True),
    v.String('last_name', notEmpty=True),
    v.MaxLength('mi', 1),
    v.ForEach([number_validator,])
]

Non è perfetto, ma c'è già quasi zero magia, nessuna necessità di metaclassi e una migliore uniformità.


Grazie per questo. Questo è un ottimo esempio di un caso d'uso che ritengo non necessario, brutto e non gestibile, il che sarebbe più semplice basato su una semplice istanza di raccolta (con raccolte nidificate come richiesto).
Ali Afshar

1
@Ali A: sei libero di fornire un esempio concreto di confronto fianco a fianco tra la sintassi dichiarativa tramite metaclassi e un approccio basato su una semplice istanza di raccolta.
jfs

@Ali A: puoi modificare la mia risposta inplace per aggiungere un esempio di stile di raccolta.
jfs

Ok fatto. Scusa, ho un po 'di fretta oggi, ma cercherò di rispondere a qualsiasi domanda più tardi / domani. Buone vacanze!
Ali Afshar

2
Il secondo esempio è brutto perché dovevi legare l'istanza del validatore al loro nome. Un modo leggermente migliore per farlo è usare un dizionario invece di un elenco, ma poi, nelle classi Python c'è solo zucchero di sintassi per dizionario, quindi perché non usare le classi? Ottieni anche la convalida del nome gratuita perché le ragazze python non possono contenere spazi o caratteri speciali che una stringa potrebbe.
Lie Ryan

6

Un modello ragionevole di utilizzo della metaclasse consiste nel fare qualcosa una volta quando una classe viene definita piuttosto che ripetutamente ogni volta che viene istanziata la stessa classe.

Quando più classi condividono lo stesso comportamento speciale, ripetere __metaclass__=Xè ovviamente meglio che ripetere il codice per scopi speciali e / o introdurre superclassi condivise ad-hoc.

Ma anche con una sola classe speciale e nessuna estensione prevedibile, __new__e __init__di una metaclasse sono un modo più pulito per inizializzare le variabili di classe o altri dati globali piuttosto che mescolare codice per scopi speciali e normali defe classistruzioni nel corpo della definizione della classe.


5

L'unica volta che ho utilizzato le metaclassi in Python è stato durante la scrittura di un wrapper per l'API di Flickr.

Il mio obiettivo era quello di raschiare il sito API di flickr e generare dinamicamente una gerarchia di classi completa per consentire l'accesso API utilizzando oggetti Python:

# Both the photo type and the flickr.photos.search API method 
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
    print photo.description

Quindi in quell'esempio, poiché ho generato l'intera API Python Flickr dal sito Web, non conosco davvero le definizioni delle classi in fase di esecuzione. Essere in grado di generare dinamicamente tipi è stato molto utile.


2
È possibile generare tipi dinamicamente senza utilizzare metaclassi. >>> help (tipo)
Ali Afshar

8
Anche se non siete a conoscenza di esso, si sono utilizzando metaclassi poi. type è una metaclasse, infatti la più comune. :-)
Veky

5

Stavo pensando la stessa cosa solo ieri e sono completamente d'accordo. Le complicazioni nel codice causate dai tentativi di renderlo più dichiarativo generalmente rendono la base di codice più difficile da mantenere, più difficile da leggere e meno pitonica secondo me. Normalmente richiede anche molto copy.copy () ing (per mantenere l'ereditarietà e copiare da classe a istanza) e significa che devi guardare in molti posti per vedere cosa sta succedendo (guardando sempre dalla metaclasse in su) che va contro il anche il grano di pitone. Ho selezionato formencode e codice sqlalchemy per vedere se valesse la pena uno stile così dichiarativo e chiaramente no. Tale stile dovrebbe essere lasciato ai descrittori (come proprietà e metodi) e ai dati immutabili. Ruby ha un supporto migliore per tali stili dichiarativi e sono contento che il linguaggio principale di Python non stia seguendo questa strada.

Posso vedere il loro uso per il debug, aggiungere una metaclasse a tutte le classi di base per ottenere informazioni più ricche. Vedo anche il loro uso solo in progetti (molto) grandi per sbarazzarsi di un po 'di codice standard (ma con perdita di chiarezza). sqlalchemy ad esempio li usa altrove, per aggiungere un particolare metodo personalizzato a tutte le sottoclassi in base a un valore di attributo nella loro definizione di classe, ad esempio un esempio di giocattolo

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

potrebbe avere una metaclasse che ha generato un metodo in quella classe con proprietà speciali basate su "hello" (ad esempio un metodo che ha aggiunto "hello" alla fine di una stringa). Potrebbe essere utile per la manutenibilità assicurarsi di non dover scrivere un metodo in ogni sottoclasse che crei, invece tutto ciò che devi definire è method_maker_value.

La necessità di questo è così raro però e riduce solo un po 'di digitazione che non vale davvero la pena considerare a meno che tu non abbia una base di codice abbastanza grande.


5

Non hai mai assolutamente bisogno di usare una metaclasse, poiché puoi sempre costruire una classe che fa quello che vuoi usando l'ereditarietà o l'aggregazione della classe che vuoi modificare.

Detto questo, può essere molto utile in Smalltalk e Ruby poter modificare una classe esistente, ma a Python non piace farlo direttamente.

C'è un eccellente articolo DeveloperWorks sulla metaclassificazione in Python che potrebbe aiutare. Anche l' articolo di Wikipedia è abbastanza buono.


1
Inoltre non hai bisogno di oggetti per fare la programmazione orientata agli oggetti: potresti farlo con funzioni di prima classe. Quindi non è necessario utilizzare oggetti. Ma sono lì per comodità. Quindi non sono sicuro di quale punto stai cercando di sottolineare nel primo paragrafo.
Tyler Crompton

1
Guarda indietro alla domanda.
Charlie Martin

4

Alcune librerie GUI hanno problemi quando più thread tentano di interagire con esse. tkinterè uno di questi esempi; e sebbene si possa gestire esplicitamente il problema con eventi e code, può essere molto più semplice usare la libreria in un modo che ignori del tutto il problema. Ecco: la magia delle metaclassi.

Essere in grado di riscrivere dinamicamente un'intera libreria senza interruzioni in modo che funzioni correttamente come previsto in un'applicazione multithread può essere estremamente utile in alcune circostanze. Il modulo safetkinter lo fa con l'aiuto di una metaclasse fornita dal modulo threadbox - eventi e code non necessari.

Un aspetto interessante threadboxè che non si preoccupa di quale classe clona. Fornisce un esempio di come tutte le classi base possono essere toccate da una metaclasse, se necessario. Un ulteriore vantaggio offerto dalle metaclassi è che vengono eseguite anche su classi che ereditano. Programmi che si scrivono da soli - perché no?


4

L'unico caso d'uso legittimo di una metaclasse è impedire ad altri sviluppatori ficcanaso di toccare il tuo codice. Una volta che uno sviluppatore ficcanaso padroneggia le metaclassi e inizia a frugare con le tue, aggiungi un altro livello o due per tenerlo fuori. Se ciò non funziona, inizia a usare type.__new__o forse qualche schema usando una metaclasse ricorsiva.

(scritto ironicamente, ma ho visto fare questo tipo di offuscamento. Django è un esempio perfetto)


7
Non sono sicuro che la motivazione fosse la stessa in Django.
Ali Afshar

3

I metaclassi non sostituiscono la programmazione! Sono solo un trucco che può automatizzare o rendere più eleganti alcune attività. Un buon esempio di ciò è la libreria di evidenziazione della sintassi di Pygments . Ha una classe chiamata RegexLexerche consente all'utente di definire un insieme di regole di lexing come espressioni regolari su una classe. Una metaclasse viene utilizzata per trasformare le definizioni in un utile parser.

Sono come il sale; è facile da usare troppo.


Ebbene, secondo me, quel caso Pygments è semplicemente inutile. Perché non avere solo una semplice raccolta come un dict, perché costringere una classe a farlo?
Ali Afshar

4
Perché una classe simpatica racchiude l'idea di Lexer e ha altri metodi utili come guess_filename (), ecc.
Benjamin Peterson,

3

Il modo in cui ho usato le metaclassi era fornire alcuni attributi alle classi. Prendiamo ad esempio:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

metterà l' attributo name su ogni classe che avrà la metaclasse impostata in modo che punti a NameClass.


5
Sì, funziona. Potresti anche usare una superclasse, che è almeno esplicita e seguibile nel codice. Per interesse, per cosa l'hai usato?
Ali Afshar

2

Questo è un uso minore, ma ... una cosa per cui ho trovato utili le metaclassi è invocare una funzione ogni volta che viene creata una sottoclasse. L'ho codificato in una metaclasse che cerca un __initsubclass__attributo: ogni volta che viene creata una sottoclasse, tutte le classi genitore che definiscono quel metodo vengono invocate con __initsubclass__(cls, subcls). Ciò consente la creazione di una classe genitore che quindi registra tutte le sottoclassi con un registro globale, esegue controlli invarianti sulle sottoclassi ogni volta che vengono definite, esegue operazioni di associazione tardiva, ecc ... il tutto senza dover chiamare manualmente funzioni o creare metaclassi personalizzate che svolgere ciascuno di questi compiti separati.

Intendiamoci, mi sono reso conto lentamente che la magia implicita di questo comportamento è in qualche modo indesiderabile, dal momento che è inaspettato se si guarda una definizione di classe fuori contesto ... e quindi mi sono allontanato dall'usare quella soluzione per qualcosa di serio oltre inizializzazione di un __superattributo per ogni classe e istanza.


1

Recentemente ho dovuto utilizzare una metaclasse per aiutare a definire in modo dichiarativo un modello SQLAlchemy attorno a una tabella di database popolata con i dati del censimento degli Stati Uniti da http://census.ire.org/data/bulkdata.html

IRE fornisce shell di database per le tabelle dei dati del censimento, che creano colonne di numeri interi seguendo una convenzione di denominazione del Census Bureau di p012015, p012016, p012017, ecc.

Volevo a) essere in grado di accedere a queste colonne usando una model_instance.p012017sintassi, b) essere abbastanza esplicito su ciò che stavo facendo ec) non dover definire esplicitamente dozzine di campi sul modello, quindi ho sottoclasse SQLAlchemy DeclarativeMetaper iterare attraverso una gamma di le colonne e creano automaticamente i campi del modello corrispondenti alle colonne:

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

Potrei quindi utilizzare questa metaclasse per la definizione del mio modello e accedere ai campi enumerati automaticamente sul modello:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...

1

Sembra esserci un uso legittimo descritto qui : riscrivere le stringhe di Python con una metaclasse.


0

Ho dovuto usarli una volta per un parser binario per renderlo più facile da usare. Si definisce una classe di messaggio con attributi dei campi presenti sul filo. Dovevano essere ordinati nel modo in cui erano stati dichiarati per costruire il formato finale del filo da esso. Puoi farlo con le metaclassi, se usi un dict dello spazio dei nomi ordinato. In effetti, è negli esempi per Metaclassi:

https://docs.python.org/3/reference/datamodel.html#metaclass-example

Ma in generale: valuta molto attentamente se hai davvero bisogno della complessità aggiuntiva delle metaclassi.


0

la risposta di @Dan Gittik è fantastica

gli esempi alla fine potrebbero chiarire molte cose, l'ho cambiato in python 3 e do qualche spiegazione:

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls

        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


print(A._label)  # Made in China
print(B._label)  # Made in Taiwan
  • tutto è oggetto, quindi la classe è oggetto
  • oggetto di classe viene creato da metaclass
  • tutta la classe ereditata dal tipo è metaclasse
  • metaclass potrebbe controllare la creazione della classe
  • metaclass potrebbe controllare anche la creazione di metaclass (quindi potrebbe andare in loop per sempre)
  • questa è la metaprogrammazione ... potresti controllare il sistema dei tipi in fase di esecuzione
  • di nuovo, tutto è oggetto, questo è un sistema uniforme, digita create type e digitate create instance

0

Un altro caso d'uso è quando si desidera essere in grado di modificare gli attributi a livello di classe e assicurarsi che influisca solo sull'oggetto in questione. In pratica, ciò implica "fondere" le fasi delle metaclassi e delle istanze di classi, portando così a trattare solo con istanze di classe del loro tipo (unico).

Ho dovuto farlo anche quando (per questioni di leggibilità e polimorfismo ) volevamo definire dinamicamente property i valori restituiti (possono) derivare da calcoli basati su attributi a livello di istanza (spesso cambianti), che possono essere eseguiti solo a livello di classe , cioè dopo l'istanziazione della metaclasse e prima dell'istanziazione della classe.

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.