2012-06-18 12 views
5

Nach this answer mit so scheint es, dass eine Klasse Einstellung metaclass nach der Klasse mithilfe des folgenden * definiert wurde geändert werden können:eine Klasse metaclass einen Dekorateur

class MyMetaClass(type): 
    # Metaclass magic... 

class A(object): 
    pass 

A = MyMetaClass(A.__name__, A.__bases__, dict(A.__dict__)) 

Definieren einer Funktion

def metaclass_wrapper(cls): 
    return MyMetaClass(cls.__name__, cls.__bases__, dict(cls.__dict__)) 

ermöglicht es mir, wie so, es

@metaclass_wrapper 
class B(object): 
    pass 

einen Dekorateur zu einer Klassendefinition anzuwenden sehen ms dass die Metaklassen-Magie auf B angewendet wird, jedoch hat B kein __metaclass__ Attribut. Ist das obige Verfahren eine vernünftige Art und Weise Metaklassen Klassendefinitionen anwenden, auch wenn ich definiting und wieder definiting eine Klasse, oder würde ich einfach besser dran

Schreiben
class B(object): 
    __metaclass__ = MyMetaClass 
    pass 

Ich nehme an, es gibt einige Unterschiede zwischen den beiden Methoden.


* Anmerkung, die ursprüngliche Antwort in der Frage verbunden, MyMetaClass(A.__name__, A.__bases__, A.__dict__), kehrt ein TypeError:

TypeError: type() argument 3 must be a dict, not dict_proxy

Es scheint, dass das __dict__ Attribut A (die Klassendefinition) einen Typ dict_proxy, während Der Typ des __dict__-Attributs einer Instanz von A hat einen Typ dict. Warum ist das? Ist das ein Unterschied zwischen Python 2.x und 3.x?

Antwort

1

Die Klasse hat kein __metaclass__ Attribut gesetzt ... weil Sie es nie gesetzt haben!

Welche Metaklasse wird normalerweise verwendet von ein Name __metaclass__ in einem Klassenblock festgelegt. Das __metaclass__ Attribut wird nicht von der Metaklasse festgelegt. Wenn Sie also direkt eine Metaklasse aufrufen und nicht __metaclass__ setzen und es Python überlassen, es herauszufinden, wird kein Attribut __metaclass__ gesetzt.

In der Tat sind normale Klassen alle Instanzen der Metaklasse type, also wenn die Metaklasse immer das __metaclass__ Attribut auf seinen Instanzen gesetzt dann jede Klasse würde ein __metaclass__ Attribut (die meisten von ihnen auf type).


Ich würde nicht Ihre Dekorateur Ansatz verwenden. Es verdunkelt die Tatsache, dass eine Metaklasse involviert ist (und welche), ist immer noch eine Zeile von Boilerplate, und es ist einfach chaotisch eine Klasse aus den 3 definierenden Features von (name, bases, attributes) zu erstellen, nur um diese 3 Bits aus der resultierenden Klasse herauszuziehen, Wirf die Klasse weg und mach aus diesen 3 Bits eine neue Klasse!

Wenn Sie dies tun in Python 2.x:

class A(object): 
    __metaclass__ = MyMeta 
    def __init__(self): 
     pass 

Sie in etwa das gleiche Ergebnis erhalten würden, wenn Sie dies geschrieben hatte:

attrs = {} 
attrs['__metaclass__'] = MyMeta 
def __init__(self): 
    pass 
attrs['__init__'] = __init__ 
A = attrs.get('__metaclass__', type)('A', (object,), attrs) 

In Wirklichkeit die Metaklasse Berechnung ist mehr kompliziert, da es tatsächlich eine Suche durch alle Basen geben muss, um zu bestimmen, ob es einen Metaklassenkonflikt gibt, und wenn eine der Basen type nicht als Metaklasse hat und attrs keine __metaclass__ enthält, dann ist die Standardmetaklasse die Metaklasse des Vorfahren statt type. Dies ist eine Situation, in der ich davon ausgehe, dass sich die "Lösung" Ihres Dekorateurs von der direkten Verwendung von __metaclass__ unterscheidet. Ich bin nicht sicher, was genau passieren würde, wenn Sie Ihren Dekorateur in einer Situation verwenden würden, in der die Verwendung von __metaclass__ Ihnen einen Metaklassenkonfliktfehler geben würde, aber ich würde nicht erwarten, dass es angenehm ist.

Wenn andere Metaklassen beteiligt sind, würde Ihre Methode dazu führen, dass sie zuerst ausgeführt werden (möglicherweise ändern, was der Name, die Basen und Attribute sind!) Und dann aus der Klasse herausziehen und daraus eine erstellen neue Klasse. Dies könnte möglicherweise ganz anders sein als das, was Sie mit __metaclass__ bekommen würden.

Wie für die __dict__ nicht geben Sie ein echtes Wörterbuch, das ist nur ein Implementierungsdetail; Ich würde aus Leistungsgründen raten. Ich bezweifle, gibt es eine Spezifikation, die besagt, dass die einer (Nicht-Klasse) Instanz muss der gleiche Typ sein wie die __dict__ einer Klasse (die auch eine Instanz BTW ist, nur eine Instanz einer Metaklasse). Das Attribut einer Klasse ist ein "dictproxy", mit dem Sie Attributschlüssel nachschlagen können, als wäre es ein dict, aber immer noch kein dict. type ist wählerisch über den Typ seines dritten Arguments; es will ein echtes Diktat, nicht nur ein "dict-like" -Objekt (Schande über das verdorbene Tippen). Es ist keine 2.x vs 3.x Sache; Python 3 verhält sich auf die gleiche Art und Weise, obwohl es Ihnen eine schönere Zeichenfolgendarstellung des dictproxy gibt. Python 2.4 (welches das älteste 2.x ist, das ich leicht verfügbar habe) hat auch dictproxy Objekte für Klasse __dict__ Objekte.

1

Meine Zusammenfassung Ihrer Frage: "Ich habe einen neuen kniffligen Weg versucht, etwas zu tun, und es hat nicht ganz funktioniert. Soll ich stattdessen den einfachen Weg verwenden?"

Ja, Sie sollten es auf die einfache Weise tun. Sie haben nicht gesagt, warum Sie daran interessiert sind, einen neuen Weg dafür zu finden.

+0

Ich frage mich nur, ob es einen Dekorator gab, der dem Attribut '__metaclass__' in einer Klassendefinition entspricht. Ich glaube, ein anderer Teil meiner Frage ist, dass mein 'Dekorateur' * schien * zu arbeiten, aber ich bemerkte, dass die dekorierte Klasse kein '__metaclass__' Attribut hat, also gibt es einen klaren Unterschied zwischen den beiden Methoden; warum ist das? – Chris

3

Zugegeben, ich bin ein bisschen zu spät zur Party. Wie dem auch sei, das war es wert hinzugefügt zu werden.

Dies ist vollständig machbar. Davon abgesehen gibt es viele andere Möglichkeiten, dasselbe Ziel zu erreichen. Insbesondere erlaubt die Dekorationslösung jedoch eine verzögerte Auswertung (obj = dec(obj)), was bei __metaclass__ innerhalb der Klasse nicht der Fall ist. Im typischen Decorator-Stil ist meine Lösung unten.

Es gibt eine knifflige Sache, auf die Sie stoßen können, wenn Sie nur die Klasse konstruieren, ohne das Wörterbuch zu ändern oder seine Attribute zu kopieren. Alle Attribute, die die Klasse zuvor (vor dem Verzieren) hatte, scheinen zu fehlen. Es ist also unbedingt notwendig, diese zu kopieren und dann wie in meiner Lösung zu optimieren.

Persönlich mag ich in der Lage zu verfolgen, wie ein Objekt gewickelt wurde. Also habe ich das Attribut __wrapped__ hinzugefügt, was nicht unbedingt notwendig ist. Es macht es auch mehr wie functools.wraps in Python 3 für Klassen. Es kann jedoch hilfreich sein bei Introspektion. Außerdem wird __metaclass__ hinzugefügt, um eher wie der normale Metaklassen-Anwendungsfall zu fungieren.

def metaclass(meta): 
    def metaclass_wrapper(cls): 
     __name = str(cls.__name__) 
     __bases = tuple(cls.__bases__) 
     __dict = dict(cls.__dict__) 

     for each_slot in __dict.get("__slots__", tuple()): 
      __dict.pop(each_slot, None) 

     __dict["__metaclass__"] = meta 

     __dict["__wrapped__"] = cls 

     return(meta(__name, __bases, __dict)) 
    return(metaclass_wrapper) 

Nehmen Sie für ein triviales Beispiel Folgendes.

class MetaStaticVariablePassed(type): 
    def __new__(meta, name, bases, dct): 
     dct["passed"] = True 

     return(super(MetaStaticVariablePassed, meta).__new__(meta, name, bases, dct)) 

@metaclass(MetaStaticVariablePassed) 
class Test(object): 
    pass 

Dies ergibt das schöne Ergebnis ...

|1> Test.passed 
|.> True 

den Dekorateur in dem weniger üblichen Verwendung, aber identische Art und Weise ...

class Test(object): 
    pass 

Test = metaclass_wrapper(Test) 

... Ausbeuten, wie erwartet , das gleiche schöne Ergebnis.

|1> Test.passed 
|.> True 
Verwandte Themen