Was sind Metaklassen in Python?


Answers

Klassen als Objekte

Bevor Sie die Metaklassen verstehen, müssen Sie die Klassen in Python beherrschen. Und Python hat eine sehr eigenartige Vorstellung davon, welche Klassen aus der Smalltalk-Sprache stammen.

In den meisten Sprachen sind Klassen nur Codeabschnitte, die beschreiben, wie ein Objekt erzeugt wird. Das stimmt auch in Python:

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

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

Aber Klassen sind mehr als das in Python. Klassen sind auch Objekte.

Ja, Objekte.

Sobald Sie die Schlüsselwortklasse verwenden, führt Python sie aus und erstellt ein OBJEKT. Die Anleitung

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

erstellt im Speicher ein Objekt mit dem Namen "ObjectCreator".

Dieses Objekt (die Klasse) ist selbst in der Lage, Objekte (die Instanzen) zu erzeugen, und deshalb ist es eine Klasse .

Aber trotzdem ist es ein Objekt und daher:

  • Sie können es einer Variablen zuweisen
  • Sie können es kopieren
  • Sie können Attribute hinzufügen
  • Sie können es als Funktionsparameter übergeben

z.B:

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

Klassen dynamisch erstellen

Da Klassen Objekte sind, können Sie sie wie jedes andere Objekt erstellen.

Zuerst können Sie eine Klasse in einer Funktion mit der class erstellen:

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

Aber es ist nicht so dynamisch, da du die ganze Klasse noch selbst schreiben musst.

Da Klassen Objekte sind, müssen sie von etwas erzeugt werden.

Wenn Sie das Schlüsselwort class , erstellt Python dieses Objekt automatisch. Aber wie bei den meisten Dingen in Python, gibt es Ihnen eine Möglichkeit, es manuell zu tun.

Merken Sie sich den Funktionstyp? Die gute alte Funktion, die Sie wissen lässt, um welchen Typ es sich bei einem Objekt handelt:

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

Nun, type hat eine völlig andere Fähigkeit, es kann auch Klassen im laufenden Betrieb erstellen. type kann die Beschreibung einer Klasse als Parameter annehmen und eine Klasse zurückgeben.

(Ich weiß, es ist albern, dass die gleiche Funktion zwei völlig unterschiedliche Anwendungen haben kann, je nach den Parametern, die Sie übergeben. Es ist ein Problem aufgrund der Abwärtskompatibilität in Python.)

type funktioniert auf diese Weise:

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

z.B:

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

kann manuell auf diese Weise erstellt werden:

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

Sie werden feststellen, dass wir "MyShinyClass" als Namen der Klasse und als Variable für die Klassenreferenz verwenden. Sie können unterschiedlich sein, aber es gibt keinen Grund, Dinge zu komplizieren.

type akzeptiert ein Wörterbuch, um die Attribute der Klasse zu definieren. Damit:

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

Kann übersetzt werden in:

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

Und als normale Klasse verwendet:

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

Und natürlich können Sie davon erben, also:

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

wäre:

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

Schließlich möchten Sie Ihrer Klasse Methoden hinzufügen. Definieren Sie einfach eine Funktion mit der richtigen Signatur und weisen Sie sie als Attribut zu.

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

Und Sie können sogar noch weitere Methoden hinzufügen, nachdem Sie die Klasse dynamisch erstellt haben, genau wie beim Hinzufügen von Methoden zu einem normalerweise erstellten Klassenobjekt.

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

Sie sehen, wohin wir gehen: In Python sind Klassen Objekte, und Sie können dynamisch eine Klasse dynamisch erstellen.

Das macht Python, wenn Sie die Schlüsselwortklasse verwenden, und zwar mithilfe einer Metaklasse.

Was sind Metaklassen (endlich)

Metaklassen sind das Zeug, das Klassen schafft.

Sie definieren Klassen, um Objekte zu erstellen, oder?

Aber wir haben gelernt, dass Python-Klassen Objekte sind.

Nun, Metaklassen schaffen diese Objekte. Sie sind die Klassen der Klassen, man kann sie sich so vorstellen:

MyClass = MetaClass()
my_object = MyClass()

Sie haben gesehen, dass Sie mit diesem type Folgendes tun können:

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

Das liegt daran, dass der Funktionstyp tatsächlich eine Metaklasse ist. type ist die Metaklasse, mit der Python alle Klassen hinter den Kulissen erstellt.

Jetzt wundern Sie sich, warum zum Teufel ist es in Kleinbuchstaben geschrieben, und nicht Type ?

Nun, ich denke, es ist eine Frage der Konsistenz mit str , der Klasse, die Strings-Objekte erzeugt, und int der Klasse, die Integer-Objekte erzeugt. type ist nur die Klasse, die Klassenobjekte erstellt.

Das sehen Sie, indem Sie das Attribut __class__ überprüfen.

Alles, und ich meine alles, ist ein Objekt in Python. Dazu gehören Ints, Strings, Funktionen und Klassen. Sie alle sind Objekte. Und alle wurden aus einer Klasse erstellt:

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

Was ist nun die __class__ eines __class__ ?

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

Eine Metaklasse ist also nur der Stoff, der Klassenobjekte erzeugt.

Sie können es eine "Klassenfabrik" nennen, wenn Sie es wünschen.

type ist die integrierte Metaklasse, die Python verwendet, aber Sie können natürlich auch Ihre eigene Metaklasse erstellen.

Das __metaclass__ Attribut

Sie können beim Schreiben einer Klasse ein __metaclass__ Attribut hinzufügen:

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

Wenn Sie dies tun, wird Python die Metaklasse verwenden, um die Klasse Foo zu erstellen.

Vorsicht, es ist schwierig.

Sie schreiben zuerst die class Foo(object) , aber das Klassenobjekt Foo wird noch nicht im Speicher angelegt.

Python sucht nach __metaclass__ in der Klassendefinition. Wenn es gefunden wird, wird es verwendet, um die Objektklasse Foo zu erzeugen. Wenn dies nicht der Fall ist, wird type , um die Klasse zu erstellen.

Lesen Sie das mehrmals.

Wenn Sie das tun:

class Foo(Bar):
    pass

Python macht folgendes:

Gibt es in Foo ein Attribut __metaclass__ ?

Wenn ja, erstelle im Speicher ein Klassenobjekt (ich sagte ein Klassenobjekt, bleib hier bei mir) mit dem Namen Foo indem ich benutze, was in __metaclass__ .

Wenn Python __metaclass__ nicht findet, __metaclass__ es nach einer __metaclass__ auf der MODULE-Ebene und versucht, dasselbe zu tun (aber nur für Klassen, die nichts erben, im Grunde alte Klassen).

Wenn es dann überhaupt keine __metaclass__ finden kann, wird es die eigene Metaklasse des __metaclass__ (die erste übergeordnete) verwenden (die der Standardtyp sein könnte), um das Klassenobjekt zu erzeugen.

__metaclass__ Sie darauf, dass das __metaclass__ Attribut nicht vererbt wird, sondern die Metaklasse des übergeordneten Bar.__class__ ( Bar.__class__ ). Wenn Bar ein __metaclass__ Attribut verwendet, das Bar mit type() (und nicht type.__new__() ), übernehmen die Unterklassen dieses Verhalten nicht.

Jetzt ist die große Frage, was können Sie in __metaclass__ ?

Die Antwort ist: Etwas, das eine Klasse erstellen kann.

Und was kann eine Klasse schaffen? type oder alles, was es unterklassifiziert oder benutzt.

Benutzerdefinierte Metaklassen

Der Hauptzweck einer Metaklasse besteht darin, die Klasse automatisch zu ändern, wenn sie erstellt wird.

Dies tun Sie normalerweise für APIs, in denen Sie Klassen erstellen möchten, die dem aktuellen Kontext entsprechen.

Stellen Sie sich ein dummes Beispiel vor, bei dem Sie entscheiden, dass alle Klassen in Ihrem Modul ihre Attribute in Großbuchstaben geschrieben haben sollen. Es gibt mehrere Möglichkeiten, dies zu tun, aber eine Möglichkeit besteht darin, __metaclass__ auf der Modulebene __metaclass__ .

Auf diese Weise werden alle Klassen dieses Moduls mit dieser Metaklasse erstellt, und wir müssen der Metaklasse lediglich mitteilen, dass alle Attribute in Großbuchstaben umgewandelt werden sollen.

Zum Glück kann __metaclass__ tatsächlich aufrufbar sein, es muss keine formale Klasse sein (ich weiß, etwas mit "Klasse" in seinem Namen muss keine Klasse sein, geh Figur ... aber es ist hilfreich).

Wir beginnen mit einem einfachen Beispiel, indem wir eine Funktion verwenden.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

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

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

Nun, machen wir genau dasselbe, aber verwenden Sie eine echte Klasse für eine Metaklasse:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

Aber das ist nicht wirklich OOP. Wir rufen den type direkt auf, und wir überschreiben oder rufen das Elternelement __new__ . Machen wir das:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

Möglicherweise haben Sie das zusätzliche Argument upperattr_metaclass bemerkt. Es gibt nichts besonderes daran: __new__ erhält immer die Klasse, in der es definiert ist, als ersten Parameter. Genauso wie Sie self für gewöhnliche Methoden haben, die die Instanz als ersten Parameter oder die definierende Klasse für Klassenmethoden erhalten.

Natürlich sind die Namen, die ich hier verwende, der Deutlichkeit halber lang, aber wie für mich self haben alle Argumente konventionelle Namen. Eine echte Produktionsmetaklasse würde also so aussehen:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

Wir können es noch sauberer machen, indem wir super , was die Vererbung erleichtert (weil ja, Sie können Metaklassen haben, die von Metaklassen erben und vom Typ erben):

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

Das ist es. Es gibt wirklich nichts mehr über Metaklassen.

Der Grund für die Komplexität des Codes, der Metaklassen verwendet, liegt nicht an Metaklassen, sondern daran, dass Sie normalerweise Metaklassen verwenden, um verdrehte __dict__ zu tun, die auf Introspektion, Manipulation der Vererbung, Variablen wie __dict__ usw. __dict__ .

In der Tat sind Metaklassen besonders nützlich, um schwarze Magie und damit komplizierte Dinge zu tun. Aber für sich sind sie einfach:

  • Abfangen einer Klassenerstellung
  • Modifizieren Sie die Klasse
  • Gib die modifizierte Klasse zurück

Warum würden Sie Metaklassen-Klassen anstelle von Funktionen verwenden?

Da __metaclass__ aufrufbar ist, warum sollten Sie eine Klasse verwenden, da sie offensichtlich komplizierter ist?

Dafür gibt es mehrere Gründe:

  • Die Absicht ist klar. Wenn Sie UpperAttrMetaclass(type) lesen, wissen Sie, was folgt
  • Sie können OOP verwenden. Metaklasse kann von Metaklasse erben, überschreiben übergeordnete Methoden. Metaklassen können sogar Metaklassen verwenden.
  • Unterklassen einer Klasse sind Instanzen ihrer Metaklasse, wenn Sie eine Metaklasse-Klasse, aber nicht eine Metaklassen-Funktion angegeben haben.
  • Sie können Ihren Code besser strukturieren. Sie verwenden Metaklassen nie für etwas so Triviales wie das obige Beispiel. Es ist normalerweise etwas Kompliziertes. Die Fähigkeit, mehrere Methoden zu erstellen und sie in einer Klasse zu gruppieren, ist sehr nützlich, um den Code leichter lesbar zu machen.
  • Sie können auf __new__ , __init__ und __call__ . Was dir erlauben wird, verschiedene Sachen zu machen. Selbst wenn Sie normalerweise alles in __new__ , sind einige Leute einfach bequemer mit __init__ .
  • Diese heißen Metaklassen, verdammt! Es muss etwas bedeuten!

Warum würden Sie Metaklassen verwenden?

Jetzt die große Frage. Warum würden Sie ein obskures fehleranfälliges Feature verwenden?

Naja, normalerweise tust du nicht:

Metaklassen sind eine tiefere Magie, über die sich 99% der Nutzer keine Sorgen machen sollten. Wenn du dich fragst, ob du sie brauchst, tust du es nicht (die Leute, die sie wirklich brauchen, wissen mit Sicherheit, dass sie sie brauchen und brauchen keine Erklärung, warum).

Python-Guru Tim Peters

Der Hauptanwendungsfall für eine Metaklasse ist das Erstellen einer API. Ein typisches Beispiel hierfür ist das Django ORM.

Es erlaubt Ihnen, etwas wie folgt zu definieren:

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

Aber wenn du das tust:

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

Es wird kein IntegerField Objekt zurückgegeben. Es gibt ein int und kann es sogar direkt aus der Datenbank übernehmen.

Dies ist möglich, weil models.Model definiert und eine gewisse Magie verwendet, die die Person Sie gerade mit einfachen Anweisungen definiert haben, in einen komplexen Hook in ein Datenbankfeld verwandelt.

Django macht etwas Komplexes einfach, indem es eine einfache API bereitstellt und Metaklassen verwendet, um Code aus dieser API neu zu erstellen, um die eigentliche Arbeit hinter den Kulissen zu leisten.

Das letzte Wort

Zuerst wissen Sie, dass Klassen Objekte sind, die Instanzen erstellen können.

Tatsächlich sind Klassen selbst Instanzen. Von Metaklassen.

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

Alles ist ein Objekt in Python und alle sind entweder Instanzen von Klassen oder Instanzen von Metaklassen.

Außer für den type .

type ist eigentlich eine eigene Metaklasse. Dies ist nichts, was Sie in reinem Python reproduzieren könnten, und es wird getan, indem Sie ein wenig auf der Implementierungsebene betrügen.

Zweitens sind Metaklassen kompliziert. Sie können sie nicht für sehr einfache Klassenänderungen verwenden. Sie können Klassen ändern, indem Sie zwei verschiedene Techniken verwenden:

In 99% der Fälle, in denen Sie eine Klassenänderung benötigen, sollten Sie diese besser nutzen.

Aber in 98% der Fälle brauchen Sie keine Klassenänderung.

Question

Was sind Metaklassen und wofür verwenden wir sie?




The tl;dr version

The type(obj) function gets you the type of an object.

The type() of a class is its metaclass .

To use a metaclass:

class Foo(object):
    __metaclass__ = MyMetaClass



Python 3 update

There are (at this point) two key methods in a metaclass:

  • __prepare__ , and
  • __new__

__prepare__ lets you supply a custom mapping (such as an OrderedDict ) to be used as the namespace while the class is being created. You must return an instance of whatever namespace you choose. If you don't implement __prepare__ a normal dict is used.

__new__ is responsible for the actual creation/modification of the final class.

A bare-bones, do-nothing-extra metaclass would like:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

Ein einfaches Beispiel:

Say you want some simple validation code to run on your attributes -- like it must always be an int or a str . Without a metaclass, your class would look something like:

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

As you can see, you have to repeat the name of the attribute twice. This makes typos possible along with irritating bugs.

A simple metaclass can address that problem:

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

This is what the metaclass would look like (not using __prepare__ since it is not needed):

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)

A sample run of:

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

produziert:

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

Note : This example is simple enough it could have also been accomplished with a class decorator, but presumably an actual metaclass would be doing much more.

The 'ValidateType' class for reference:

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



The type() function can return the type of an object or create a new type,

for example, we can create a Hi class with the type() function and do not need to use this way with class Hi(object):

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

In addition to using type() to create classes dynamically, you can control creation behavior of class and use metaclass.

According to the Python object model, the class is the object, so the class must be an instance of another certain class. By default, a Python class is instance of the type class. That is, type is metaclass of most of the built-in classes and metaclass of user-defined classes.

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 will take effect when we passed keyword arguments in metaclass, it indicates the Python interpreter to create the CustomList through ListMetaclass. new (), at this point, we can modify the class definition, for example, and add a new method and then return the revised definition.




Role of a metaclass's __call__() method when creating a class instance

If you've done Python programming for more than a few months you'll eventually stumble upon code that looks like this:

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

The latter is possible when you implement the __call__() magic method on the class.

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

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

The __call__() method is invoked when an instance of a class is used as a callable. But as we've seen from previous answers a class itself is an instance of a metaclass, so when we use the class as a callable (ie when we create an instance of it) we're actually calling its metaclass's __call__() method. At this point most Python programmers are a bit confused because they've been told that when creating an instance like this instance = SomeClass() you're calling it's __init__() method. Some who've dug a bit deeper know that before __init__() there's __new__() . Well, today another layer of truth is being revealed, before __new__() there's the metaclass's __call__() .

Let's study the method call chain from specifically the perspective of creating an instance of a class.

This is a metaclass that logs exactly the moment before an instance is created and the moment it's about to return it.

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

This is a class that uses that metaclass

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

And now let's create an instance of 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.

The code above doesn't actually do anything other than logging the task and then delegating the actual work to the parent (ie keeping the default behavior). So with type being Meta_1 's parent class, we can imagine that this would be the pseudo implementation of 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

We can see that the metaclass's __call__() method is the one that's called first. It then delegates creation of the instance to the class's __new__() method and initialization to the instance's __init__() . It's also the one that ultimately returns the instance.

From the above it stems that the metaclass's __call__() is also given the opportunity to decide whether or not a call to Class_1.__new__() or Class_1.__init__() will eventually be made. Over the course of its execution it could actually return an object that hasn't been touched by either of these methods. Take for example this approach to the singleton pattern:

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

Let's observe what happens when repeatedly trying to create an object of type 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



Ich denke, die ONLamp-Einführung in die Metaklassen-Programmierung ist gut geschrieben und gibt trotz einiger Jahre eine wirklich gute Einführung in das Thema.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

Kurz gesagt: Eine Klasse ist ein Entwurf für die Erstellung einer Instanz, eine Metaklasse ist ein Entwurf für die Erstellung einer Klasse. Es ist leicht zu erkennen, dass in Python Klassen auch erstklassige Objekte sein müssen, um dieses Verhalten zu ermöglichen.

Ich habe selbst nie einen selbst geschrieben, aber ich denke, einer der schönsten Einsatzbereiche von Metaklassen ist im Django-Framework zu sehen . Die Modellklassen verwenden einen Metaklassenansatz, um einen deklarativen Stil zum Schreiben neuer Modelle oder Formularklassen zu ermöglichen. Während die Metaklasse die Klasse erstellt, haben alle Mitglieder die Möglichkeit, die Klasse selbst anzupassen.

Die Sache, die noch zu sagen ist, ist: Wenn Sie nicht wissen, was Metaklassen sind, ist die Wahrscheinlichkeit, dass Sie sie nicht brauchen, 99%.




Eine Verwendung für Metaklassen besteht darin, einer Instanz automatisch neue Eigenschaften und Methoden hinzuzufügen.

Wenn Sie sich beispielsweise Django-Modelle ansehen, sieht ihre Definition etwas verwirrend aus. Es sieht so aus, als ob Sie nur Klasseneigenschaften definieren:

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

Zur Laufzeit sind die Person-Objekte jedoch mit allen möglichen Methoden gefüllt. Siehe die source für eine erstaunliche Metaklasse.




Related