Vraag Wat zijn metaclasses in Python?


Wat zijn metaclasses en waarvoor gebruiken we deze?


4572
2017-09-19 06:10


oorsprong


antwoorden:


Een metaclass is de klasse van een klasse. Zoals een klasse definieert hoe een instantie van de klasse zich gedraagt, definieert een metaclass hoe een klasse zich gedraagt. Een klasse is een instantie van een metaclass.

metaclass diagram

In Python kun je willekeurige callables gebruiken voor metaclasses (zoals Jerub shows), is de meer nuttige benadering eigenlijk om er een echte klasse van te maken. type is de gebruikelijke metaclass in Python. In het geval je je afvraagt, ja, type is zelf een klasse, en het is zijn eigen type. Je kunt niet zoiets opnieuw creëren type puur in Python, maar Python cheats een beetje. Om je eigen metaclass in Python te maken, wil je eigenlijk gewoon een subklasse type.

Een metaclass wordt meestal gebruikt als een klassenfabriek. Zoals je een instantie van de klasse maakt door de klasse aan te roepen, maakt Python een nieuwe klasse (wanneer het de 'klasse'-instructie uitvoert) door de metaclass te bellen. Gecombineerd met de normale __init__ en __new__ methoden, metaclasses staan ​​je toe om 'extra dingen' te doen bij het maken van een klasse, zoals het registreren van de nieuwe klasse met een of ander register, of zelfs de klasse geheel te vervangen door iets anders.

Wanneer de class statement wordt uitgevoerd, Python voert eerst de body van de class verklaring als een normaal codeblok. De resulterende naamruimte (een dictaat) bevat de kenmerken van de betreffende klasse. De metacursus wordt bepaald door te kijken naar de basisklassen van de toekomstige klasse (metaclasses zijn geërfd), aan de __metaclass__ attribuut van de toekomstige klasse (indien aanwezig) of de __metaclass__ globale variabele. De metacursus wordt vervolgens aangeroepen met de naam, bases en attributen van de klasse om deze te instantiëren.

Metaclasses definiëren echter de type van een klasse, niet alleen een fabriek ervoor, dus je kunt er veel meer mee doen. U kunt bijvoorbeeld normale methoden definiëren voor de metaclass. Deze metaclass-methoden zijn vergelijkbaar met class methodes, in die zin dat ze zonder instantie op de klasse kunnen worden aangeroepen, maar ze zijn ook niet als class methodes omdat ze niet kunnen worden aangeroepen op een instantie van de klasse. type.__subclasses__() is een voorbeeld van een methode op de type metaclass. Je kunt ook de normale 'magische' methoden definiëren, zoals __add__, __iter__ en __getattr__, om te implementeren of te veranderen hoe de klasse zich gedraagt.

Hier is een samengevoegd voorbeeld van de stukjes en beetjes:

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__

2079
2017-09-19 07:01



Klassen als objecten

Voordat je metaclasses begrijpt, moet je lessen in Python onder de knie hebben. En Python heeft een heel eigenaardig idee van wat klassen zijn, geleend van de Smalltalk-taal.

In de meeste talen zijn klassen slechts stukjes code die beschrijven hoe een object moet worden gemaakt. Dat klopt ook in Python:

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

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

Maar klassen zijn meer dan dat in Python. Klassen zijn ook objecten.

Ja, objecten.

Zodra u het sleutelwoord gebruikt class, Python voert het uit en maakt een voorwerp. De instructie

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

creëert in het geheugen een object met de naam "ObjectCreator".

Dit object (de klasse) is zelf in staat om objecten te maken (de instanties), en dit is waarom het een klas is.

Maar toch, het is een object en daarom:

  • je kan het aan een variabele toewijzen
  • je kunt het kopiëren
  • je kunt attributen eraan toevoegen
  • je kunt het doorgeven als een functieparameter

bijv .:

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

Omdat klassen objecten zijn, kunt u ze meteen maken, net als elk ander object.

Eerst kunt u een klasse in een functie maken met behulp van 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>

Maar het is niet zo dynamisch, omdat je de hele klas nog steeds zelf moet schrijven.

Omdat klassen objecten zijn, moeten ze door iets worden gegenereerd.

Wanneer u de class trefwoord, Python maakt dit object automatisch. Maar zoals met de meeste dingen in Python, geeft het je een manier om het handmatig te doen.

Onthoud de functie type? De goede oude functie waarmee je weet wat type een object is:

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

Goed, type heeft een heel ander vermogen, het kan ook on-the-fly lessen creëren. type kan de beschrijving van een klasse als parameters nemen, en retourneer een klas.

(Ik weet het, het is gek dat dezelfde functie twee volledig verschillende gebruiksmogelijkheden kan hebben volgens de parameters die je eraan geeft. compatibiliteit in Python)

type werkt op deze manier:

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

bijv .:

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

kan op deze manier handmatig worden gemaakt:

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

Je zult merken dat we "MyShinyClass" gebruiken als de naam van de klas en als de variabele om de klasseverwijzing te houden. Ze kunnen anders zijn, maar er is geen reden om dingen te compliceren.

type accepteert een woordenboek om de kenmerken van de klasse te definiëren. Zo:

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

Kan worden vertaald naar:

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

En gebruikt als een normale klas:

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

En natuurlijk kun je ervan erven, dus:

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

zou zijn:

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

Uiteindelijk wil je methoden aan je klas toevoegen. Definieer gewoon een functie met de juiste handtekening en wijs het toe als attribuut.

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

En u kunt nog meer methoden toevoegen nadat u de klasse dynamisch hebt gemaakt, net als het toevoegen van methoden aan een normaal gemaakt klasseobject.

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

Je ziet waar we naar toe gaan: in Python zijn klassen objecten en kun je dynamisch een klasse maken.

Dit is wat Python doet wanneer u het sleutelwoord gebruikt class, en het doet dit door een metaclass te gebruiken.

Wat zijn metaclasses (eindelijk)

Metaclasses zijn de 'dingen' die klassen creëren.

Je definieert klassen om objecten te maken, toch?

Maar we hebben geleerd dat Python-klassen objecten zijn.

Nou, metaclasses zijn wat deze objecten maken. Ze zijn de klassenklassen, je kunt ze op deze manier voorstellen:

MyClass = MetaClass()
my_object = MyClass()

Dat heb je gezien type laat je zoiets als dit doen:

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

Het is vanwege de functie type is in feite een metaclass. type is de metaclass Python gebruikt om alle klassen achter de schermen te maken.

Nu vraag je je af waarom het in godsnaam is geschreven in kleine letters, en niet Type?

Nou, ik denk dat het een kwestie is van consistentie met str, de klasse die creëert tekenreeksen, en intde klasse die objecten met een geheel getal maakt. type is alleen de klasse die klassenobjecten maakt.

U ziet dat door het __class__ attribuut.

Alles, en ik bedoel alles, is een object in Python. Dat omvat ints, strings, functies en klassen. Allemaal zijn het objecten. En dat allemaal gemaakt van een klasse:

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

Wat is het nu? __class__ van welke dan ook __class__ ?

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

Een metaclass is dus precies het spul dat lesobjecten maakt.

Je kunt het een 'klassenfabriek' noemen als je dat wilt.

type is de ingebouwde metaclass die Python gebruikt, maar je kunt natuurlijk je eigen metaclass maken eigen metaclass.

De __metaclass__ attribuut

U kunt een toevoegen __metaclass__ attribuut bij het schrijven van een klasse:

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

Als je dat doet, zal Python de metacursus gebruiken om de klasse te maken Foo.

Pas op, het is lastig.

Jij schrijft class Foo(object) eerste, maar het klasse-object Foo is niet gemaakt in het geheugen nog niet.

Python zal zoeken naar __metaclass__ in de klassedefinitie. Als het het vindt, het zal het gebruiken om de objectklasse te maken Foo. Als dit niet het geval is, wordt het gebruikt type om de klasse te maken.

Lees dat meerdere keren.

Wanneer u:

class Foo(Bar):
    pass

Python doet het volgende:

is er een __metaclass__ attribuut in Foo?

Zo ja, creëer in het geheugen een klasseobject (ik zei een klassenobject, blijf hier bij mij), met de naam Foo door te gebruiken wat er in zit __metaclass__.

Als Python het niet kan vinden __metaclass__, het zal zoeken naar een __metaclass__ op het MODULE-niveau en probeer hetzelfde te doen (maar alleen voor klassen die niets overnemen, in feite oude stijlklassen).

Als het dan geen kan vinden __metaclass__ helemaal, het zal de Bar's (de eerste ouder) heeft een metaclass (mogelijk de standaard type) om het klasseobject te maken.

Wees hier voorzichtig dat de __metaclass__ attribuut zal niet worden geërfd, de meta-klasse van de ouder (Bar.__class__) zal zijn. Als Bar gebruikte a __metaclass__ kenmerk dat is gemaakt Bar met type() (en niet type.__new__()), zullen de subklassen dat gedrag niet erven.

De grote vraag is nu: wat kun je erin stoppen? __metaclass__ ?

Het antwoord is: iets dat een klasse kan maken.

En wat kan een klas maken? type, of iets dat deze subklassen gebruikt of gebruikt.

Aangepaste metaclasses

Het hoofddoel van een metaclass is om de klasse automatisch te wijzigen, wanneer het is gemaakt.

U doet dit meestal voor API's, waar u klassen wilt maken die overeenkomen met de huidige context.

Stel je een stom voorbeeld voor, waarbij je beslist dat alle klassen in je module moeten hun attributen in hoofdletters hebben geschreven. Er zijn verschillende manieren om doe dit, maar een manier is om in te stellen __metaclass__ op moduleniveau.

Op deze manier worden alle klassen van deze module gemaakt met behulp van deze metacursus, en we moeten alleen het metacijfer vertellen om alle attributen in hoofdletters te zetten.

Gelukkig, __metaclass__ kan eigenlijk elke callable zijn, het hoeft geen a te zijn formele klasse (ik weet het, iets met 'klasse' in zijn naam hoeft dat niet te zijn een klasse, ga figuur ... maar het is nuttig).

Dus we zullen beginnen met een eenvoudig voorbeeld, door een functie te gebruiken.

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

Laten we nu precies hetzelfde doen, maar een echte klasse gebruiken voor een metacursus:

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

Maar dit is niet echt OOP. Wij bellen type rechtstreeks en we hebben geen voorrang of bel de ouder __new__. Laten we het doen:

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)

Je hebt misschien het extra argument opgemerkt upperattr_metaclass. Er bestaat niets bijzonders aan: __new__ ontvangt altijd de klasse waarin deze is gedefinieerd, als eerste parameter. Net zoals jij self voor gewone methoden die de instantie als eerste parameter ontvangen, of de definiërende klasse voor klassemethoden.

Natuurlijk zijn de namen die ik hier gebruik, lang voor de duidelijkheid, maar zoals voor self, alle argumenten hebben conventionele namen. Dus een echte productie metaclass ziet er als volgt uit:

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)

We kunnen het nog schoner maken door te gebruiken super, wat overerving zal verlichten (want ja, je kunt metaclasses hebben, erven van metaclasses, van type erven):

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)

Dat is het. Er is echt niets meer over metaclasses.

De reden achter de complexiteit van de code met behulp van metaclasses is niet omdat van metaclasses, dit komt omdat je meestal metaclasses gebruikt om gedraaide dingen te doen vertrouwen op introspectie, manipulatie van erfenis, vars zoals __dict__, enz.

Metaclasses zijn inderdaad vooral handig om zwarte magie te doen en daarom ingewikkelde dingen. Maar op zichzelf zijn ze eenvoudig:

  • een klasse creatie onderscheppen
  • wijzig de klasse
  • stuur de gewijzigde klasse terug

Waarom zou je klassen met metaclasses gebruiken in plaats van functies?

Sinds __metaclass__ kan elke opvraag accepteren, waarom zou je een klasse gebruiken? omdat het duidelijk ingewikkelder is?

Er zijn verschillende redenen om dit te doen:

  • De bedoeling is duidelijk. Wanneer je leest UpperAttrMetaclass(type), je weet wel wat zal er volgen
  • U kunt OOP gebruiken. Metaclass kan erven van metaclass, overschrijf bovenliggende methoden. Metaclasses kunnen zelfs metaclasses gebruiken.
  • Subklassen van een klasse zijn instanties van de metaclass als u een metaclass-klasse hebt opgegeven, maar niet met een metaclass-functie.
  • U kunt uw code beter structureren. Je gebruikt nooit metaclasses voor zoiets als triviaal als het bovenstaande voorbeeld. Het is meestal voor iets ingewikkelds. Het hebben van het vermogen om verschillende methoden te maken en ze in één klas te groeperen is erg handig om de code leesbaarder te maken.
  • Je kunt aansluiten __new__, __init__ en __call__. Dat zal toestaan jij om verschillende dingen te doen. Zelfs als je het meestal allemaal kunt doen __new__, sommige mensen zijn gewoon comfortabeler in het gebruik __init__.
  • Dit worden metaclasses genoemd, verdomme! Het moet iets betekenen!

Waarom zou je metaclasses gebruiken?

Nu de grote vraag. Waarom zou je een obscure foutgevoelige functie gebruiken?

Nou, meestal doe je dat niet:

Metaclasses zijn diepere magie dan dat   99% van de gebruikers zou zich nooit zorgen moeten maken.   Als je je afvraagt ​​of je ze nodig hebt,   jij niet (de mensen die dat eigenlijk doen   moet ze dat met zekerheid weten   ze hebben ze nodig en hebben geen behoefte aan een   uitleg over waarom).

Python Guru Tim Peters

De belangrijkste use case voor een metaclass is het maken van een API. Een typisch voorbeeld hiervan is de Django ORM.

Hiermee kunt u iets als volgt definiëren:

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

Maar als je dit doet:

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

Het zal niet terugkeren IntegerField voorwerp. Het zal terugkeren int, en kan het zelfs rechtstreeks uit de database halen.

Dit is mogelijk omdat models.Model definieert __metaclass__ en het gebruikt wat magie die de Person je hebt het zojuist gedefinieerd met eenvoudige uitspraken in een complexe haak naar een databaseveld.

Django maakt iets complex eenvoudig door een eenvoudige API te tonen en het gebruik van metaclasses, het herscheppen van code uit deze API om het echte werk te doen Achter de schermen.

Het laatste woord

Ten eerste weet u dat klassen objecten zijn die instanties kunnen maken.

In feite zijn klassen zelf voorbeelden. Van metaclasses.

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

Alles is een object in Python en het zijn allemaal voorbeelden van klassen of instanties van metaclasses.

Behalve voor type.

type is eigenlijk zijn eigen metacursus. Dit is niet iets wat je zou kunnen reproduceren in zuivere Python, en wordt gedaan door een klein beetje vals te spelen bij de implementatie niveau.

Ten tweede zijn metaclasses gecompliceerd. U wilt ze misschien niet gebruiken hele simpele klasseveranderingen. Je kunt klassen veranderen door twee verschillende technieken te gebruiken:

99% van de tijd dat je les moet wisselen, kun je deze beter gebruiken.

Maar 98% van de tijd heb je helemaal geen klassewijziging nodig.


5755
2017-09-19 06:26



Let op, dit antwoord is voor Python 2.x zoals het in 2008 werd geschreven, metaclasses zijn iets anders in 3.x, zie de opmerkingen.

Metaclasses zijn de geheime saus die 'klasse' werkt. De standaard metacursus voor een nieuw stijlobject wordt 'type' genoemd.

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

Metaclasses nemen 3 args. 'naam','bases'en'dict'

Hier begint het geheim. Zoek naar waar de naam, basissen en het dictaat vandaan komen in deze voorbeeldklasse-definitie.

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

Laten we een metaclass definiëren die laat zien hoe 'klasse:'noemt het.

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'

En nu, een voorbeeld dat feitelijk iets betekent, hierdoor worden de variabelen in de lijst "attributen" automatisch ingesteld op de klasse en ingesteld op Geen.

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

Merk op dat het magische gedrag dat 'geïnitaliseerd' oplevert door het metaclass te hebben init_attributes wordt niet doorgegeven aan een subklasse van geïnitialiseerd.

Hier is een nog concreter voorbeeld dat laat zien hoe u 'type' kunt subclasseren om een ​​metaclass te maken die een actie uitvoert wanneer de klasse wordt gemaakt. Dit is best lastig:

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

311
2017-09-19 06:45



Eén gebruik voor metaclasses is het automatisch toevoegen van nieuwe eigenschappen en methoden aan een instantie.

Bijvoorbeeld, als je kijkt naar Django-modellen, hun definitie ziet er een beetje verwarrend uit. Het lijkt erop dat u alleen klasseseigenschappen definieert:

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

Tijdens runtime zijn de Person-objecten echter gevuld met allerlei handige methoden. Zie de bron voor verbazingwekkende meta-categorieën.


124
2018-06-21 16:30



Anderen hebben uitgelegd hoe metaclasses werken en hoe ze in het Python-type systeem passen. Hier is een voorbeeld van waar ze voor kunnen worden gebruikt. In een testraamwerk dat ik schreef, wilde ik de volgorde bijhouden waarin de klassen werden gedefinieerd, zodat ik ze later in deze volgorde kon maken. Ik vond het het gemakkelijkst om dit te doen met een metaclass.

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

Alles wat een subklasse is van MyType krijgt dan een klassenattribuut _order die de volgorde registreert waarin de klassen zijn gedefinieerd.


117
2017-09-19 06:32



Ik denk dat de ONLAMP introductie tot metaclass programmeren goed geschreven is en een goede introductie tot het onderwerp geeft, ondanks dat het al een aantal jaar oud is.

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

Kort gezegd: een klasse is een blauwdruk voor het maken van een instantie, een metaclass is een blauwdruk voor het maken van een klasse. Het is gemakkelijk te zien dat klassen in Python ook eersteklas objecten moeten zijn om dit gedrag mogelijk te maken.

Ik heb er zelf nooit een geschreven, maar ik denk dat een van de mooiste toepassingen van metaclasses te zien is in de Django-raamwerk. De modelklassen gebruiken een metaclassbenadering om een ​​declaratieve stijl van schrijven van nieuwe modellen of klassen van formulieren mogelijk te maken. Terwijl de metacursus de klasse maakt, krijgen alle leden de mogelijkheid om de klasse zelf aan te passen.

Het enige dat overblijft om te zeggen is: als je niet weet metaclasses, is de kans dat jij zal ze niet nodig hebben is 99%.


86
2017-08-10 23:28



Wat zijn metaclasses? Waar gebruik je ze voor?

TLDR: een metaclass maakt een instantine en definieert het gedrag van een klasse, net zoals een klasse het gedrag van een instantie definieert en definieert.

pseudocode:

>>> Class(...)
instance

Het bovenstaande moet bekend voorkomen. Wel, waar doet Class Komt van? Het is een instantie van een metaclass (ook pseudocode):

>>> Metaclass(...)
Class

In echte code kunnen we de standaard metacursus passeren, type, alles wat we nodig hebben om een ​​klas te instantiëren en we krijgen een klas:

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

Anders gezegd

  • Een klasse is een instantie als een metaclass voor een klasse.

    Wanneer we een object instantiëren, krijgen we een exemplaar:

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

    Evenzo, wanneer we een klasse expliciet definiëren met de standaard metaclass, type, we tonen het:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • Anders gezegd, een klasse is een voorbeeld van een metacursus:

    >>> isinstance(object, type)
    True
    
  • Zet een derde weg, een meta-klasse is de klasse van een klasse.

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

Wanneer u een klassedefinitie schrijft en Python deze uitvoert, wordt een metacursus gebruikt om het klassenobject te instantiëren (dat op zijn beurt wordt gebruikt om Instanties van die klasse te instantiëren).

Net zoals we klassedefinities kunnen gebruiken om te wijzigen hoe aangepaste objectexemplaren zich gedragen, kunnen we een metaclassklasse-definitie gebruiken om de manier waarop een klasseobject zich gedraagt ​​te wijzigen.

Waar kunnen ze voor worden gebruikt? Van de docs:

De potentiële toepassingen voor metaclasses zijn grenzeloos. Enkele ideeën die zijn onderzocht, zijn onder meer logboekregistratie, interfacecontrole, automatische overdracht, automatische eigenschappen, proxies, frameworks en automatische vergrendeling / synchronisatie van bronnen.

Niettemin wordt het meestal aangemoedigd voor gebruikers om metaclasses te vermijden, tenzij dit absoluut noodzakelijk is.

U gebruikt een metaclass elke keer dat u een klasse maakt:

Wanneer u bijvoorbeeld een klassendefinitie schrijft, zoals deze,

class Foo(object): 
    'demo'

U instantieert een klasseobject.

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

Het is hetzelfde als functioneel bellen type met de juiste argumenten en toewijzen van het resultaat aan een variabele van die naam:

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

Let op, sommige dingen worden automatisch toegevoegd aan de __dict__, d.w.z. de naamruimte:

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

De metaclass van het object dat we hebben gemaakt, in beide gevallen type.

(Een kanttekening bij de inhoud van de klas __dict__: __module__ is er omdat klassen moeten weten waar ze zijn gedefinieerd, en __dict__ en __weakref__ zijn er omdat we niet definiëren __slots__ - als wij bepalen __slots__ we zullen wat ruimte besparen in de gevallen, omdat we dit niet kunnen toestaan __dict__ en __weakref__ door ze uit te sluiten. Bijvoorbeeld:

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

... maar ik dwaal af.)

We kunnen uitbreiden type net als elke andere klassedefinitie:

Dit is de standaard __repr__ van klassen:

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

Een van de meest waardevolle dingen die we standaard kunnen doen bij het schrijven van een Python-object is om het een goed doel te bieden __repr__. Wanneer we bellen help(repr) we leren dat er een goede test is voor a __repr__ dat vereist ook een test voor gelijkheid - obj == eval(repr(obj)). De volgende eenvoudige implementatie van __repr__ en __eq__ voor klasseninstanties van onze typeklasse wordt een demonstratie gegeven die de standaard kan verbeteren __repr__ van klassen:

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

Dus nu, wanneer we een object maken met deze metaclass, de __repr__ echode op de opdrachtregel geeft een veel minder lelijk gezicht dan de standaard:

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

Met een leuke __repr__ gedefinieerd voor de klasse instantie, hebben we een sterkere mogelijkheid om onze code te debuggen. Echter, veel verder controleren met eval(repr(Class)) is onwaarschijnlijk (aangezien functies vrij onmogelijk zijn om van hun standaardwaarde te evalue- ren __repr__'S).

Een verwacht gebruik: __prepare__ een naamruimte

Als we bijvoorbeeld willen weten in welke volgorde de methoden van een klasse worden gemaakt, kunnen we een geordend dictaat leveren als de naamruimte van de klas. We zouden dit doen met __prepare__ welke retourneert de naamruimtedict voor de klas als deze is geïmplementeerd 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

En gebruik:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

En nu hebben we een record van de volgorde waarin deze methoden (en andere klassekenmerken) zijn gemaakt:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Merk op dat dit voorbeeld is aangepast vanuit de documentatie - de nieuwe enum in de standaardbibliotheek doet dit.

Dus wat we deden was een metaclass instantiëren door een klasse te maken. We kunnen de metaclass ook behandelen als elke andere klas. Het heeft een volgorde voor de methodeomzetting:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

En het heeft ongeveer het juiste repr (die we niet langer kunnen evalueren tenzij we een manier kunnen vinden om onze functies te vertegenwoordigen.):

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

74
2018-03-01 19:48



Python 3 update

Er zijn (op dit moment) twee belangrijke methoden in een metacursus:

  • __prepare__, en
  • __new__

__prepare__ laat u een aangepaste toewijzing leveren (zoals een OrderedDict) die moet worden gebruikt als de naamruimte terwijl de klasse wordt gemaakt. U moet een exemplaar van elke naamruimte die u kiest, retourneren. Als u niet implementeert __prepare__een normaal dict is gebruikt.

__new__ is verantwoordelijk voor de daadwerkelijke creatie / wijziging van de laatste klasse.

Een bare-bones, do-nothing-extra metaclass zou willen:

class Meta(type):

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

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

Een eenvoudig voorbeeld:

Stel dat u een eenvoudige validatiecode wilt gebruiken voor uw kenmerken, zoals het altijd moet zijn int of a str. Zonder een metaclass zou je klas er ongeveer zo uitzien:

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

Zoals je kunt zien, moet je de naam van het attribuut twee keer herhalen. Dit maakt typefouten mogelijk samen met irritante bugs.

Een eenvoudige metaclass kan dat probleem aanpakken:

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

Dit is hoe de metaclass eruit zou zien (niet gebruiken __prepare__ omdat het niet nodig is):

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)

Een voorbeeldrun van:

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

produceert:

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

Notitie: Dit voorbeeld is eenvoudig genoeg, maar het zou ook met een klasse-decorateur kunnen zijn bereikt, maar vermoedelijk zou een echte metaclass veel meer doen.

De klasse 'ValidateType' ter referentie:

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

55
2017-10-13 09:21



Een metaclass is een klasse die aangeeft hoe (een) andere klasse moet worden gemaakt.

Dit is een geval waarbij ik metaclass zag als een oplossing voor mijn probleem: Ik had een heel ingewikkeld probleem, dat waarschijnlijk anders had kunnen worden opgelost, maar ik koos ervoor om het op te lossen met behulp van een metaclass. Vanwege de complexiteit is het een van de weinige modules die ik heb geschreven waarbij de opmerkingen in de module het aantal geschreven code overschrijden. Hier is het...

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

41
2017-08-09 18:49