Ga naar hoofdinhoud

Introductie tot Python-metaklassen

Leer in deze tutorial wat metaklassen zijn, hoe je ze implementeert in Python en hoe je aangepaste metaklassen maakt.
· 6 min lezen

Verken met AI

Een metaklas in Python is een klasse van een klasse die bepaalt hoe een klasse zich gedraagt. Een klasse is zelf een instantie van een metaklas. Een klasse in Python bepaalt hoe de instantie van die klasse zich zal gedragen. Om metaklassen goed te begrijpen, is het handig als je al ervaring hebt met Python-klassen. Voordat we dieper in metaklassen duiken, halen we eerst een paar begrippen uit de weg.

Alles in Python is een object

class TestClass():
    pass

my_test_class = TestClass()
print(my_test_class)
<__main__.TestClass object at 0x7f6fcc6bf908>

Python-klassen kunnen dynamisch worden aangemaakt

type in Python stelt ons in staat het type van een object te achterhalen. We kunnen het type controleren van het object dat we hierboven hebben gemaakt.

type(TestClass)
type
type(type)
type

Wacht, wat gebeurde hier? Je zou verwachten dat het type van het hierboven gemaakte object class is, maar dat is het niet. Houd die gedachte vast; we komen er zo op terug. We zien ook dat het type van type zelf type is. Het is een instantie van type. Iets anders magisch dat type doet, is dat het ons in staat stelt klassen dynamisch te maken. Hieronder laten we zien hoe we dat zouden doen. De klasse DataCamp hieronder zou met type als volgt worden aangemaakt:

class DataCamp():
    pass
DataCampClass = type('DataCamp', (), {})
print(DataCampClass)
print(DataCamp())
<class '__main__.DataCamp'>
<__main__.DataCamp object at 0x7f6fcc66e358>

In het bovenstaande voorbeeld is DataCamp de klassenaam, terwijl DataCampClass de variabele is die naar de klasse verwijst. Wanneer we type gebruiken, kunnen we attributen van de klasse meegeven via een dictionary, zoals hieronder getoond:

PythonClass = type('PythonClass', (), {'start_date': 'August 2018', 'instructor': 'John Doe'} )
print(PythonClass.start_date, PythonClass.instructor)
print(PythonClass)
August 2018 John Doe
<class '__main__.PythonClass'>

Als we willen dat onze PythonClass erft van de klasse DataCamp, geven we die als tweede argument door bij het definiëren van de klasse met type

PythonClass = type('PythonClass', (DataCamp,), {'start_date': 'August 2018', 'instructor': 'John Doe'} )
print(PythonClass)
<class '__main__.PythonClass'>

Nu die twee concepten uit de weg zijn, zien we dat Python klassen aanmaakt met behulp van een metaklas. We hebben gezien dat alles in Python een object is; deze objecten worden gemaakt door metaklassen. Wanneer we class gebruiken om een klasse te maken, is er een metaklas die achter de schermen de klasse creëert. We hebben type dit hierboven al in de praktijk zien doen. Het is vergelijkbaar met str dat strings maakt en int dat gehele getallen maakt. In Python kun je met het attribuut ___class__ het type van de huidige instantie controleren. Laten we hieronder een string maken en het type controleren.

article = 'metaclasses'
article.__class__
str

We kunnen het type ook controleren met type(article).

type(article)
str

Als we het type van str controleren, blijkt ook dat het type is.

type(str)
type

Wanneer we het type controleren voor float, int, list, tuple en dict, krijgen we een vergelijkbare output. Dit komt omdat al deze objecten van het type type zijn.

print(type(list),type(float), type(dict), type(tuple))
<class 'type'> <class 'type'> <class 'type'> <class 'type'>

We hebben al gezien dat type klassen creëert. Dus wanneer we de __class__ van __class__ controleren, zou dat type moeten opleveren.

article.__class__.__class__
type

Aangepaste metaklassen maken

In Python kunnen we het aanmaakproces van een klasse aanpassen door het trefwoord metaclass in de klassedefinitie mee te geven. Dit kan ook door te erven van een klasse waarin dit trefwoord al is meegegeven.

class MyMeta(type):
    pass

class MyClass(metaclass=MyMeta):
    pass

class MySubclass(MyClass):
    pass

Hieronder zie je dat het type van de klasse MyMeta type is en dat het type van MyClass en MySubClass MyMeta is.

print(type(MyMeta))
print(type(MyClass))
print(type(MySubclass))
<class 'type'>
<class '__main__.MyMeta'>
<class '__main__.MyMeta'>

Als je een klasse definieert en er is geen metaklas gedefinieerd, wordt standaard de metaklas type gebruikt. Als een metaklas is opgegeven en deze is geen instantie van type(), dan wordt die direct als metaklas gebruikt.

__new__ en __init__

Metaklassen kunnen ook op een van de twee onderstaande manieren worden gedefinieerd. We leggen hieronder het verschil tussen beide uit.

class MetaOne(type):
    def __new__(cls, name, bases, dict):
        pass

class MetaTwo(type):
    def __init__(self, name, bases, dict):
        pass

__new__ wordt gebruikt wanneer je de dict of bases-tuple wilt definiëren voordat de klasse wordt aangemaakt. De retourwaarde van __new__ is meestal een instantie van cls. __new__ stelt subklassen van onveranderlijke typen in staat om het aanmaken van instanties aan te passen. Het kan in aangepaste metaklassen worden overschreven om de klassecreatie te beïnvloeden. __init__ wordt meestal aangeroepen nadat het object is aangemaakt om het te initialiseren.

Metaklasmethode __call__

Volgens de officiële documentatie kunnen we ook andere klassemethode overschrijven door een aangepaste __call__()-methode in de metaklas te definiëren, die aangepast gedrag mogelijk maakt wanneer de klasse wordt aangeroepen.

Metaklasmethode __prepare__

Volgens Python's data model-docs

Zodra de juiste metaklas is bepaald, wordt de klassenamespace voorbereid. Als de metaklas een attribuut __prepare__ heeft, wordt dit aangeroepen als namespace = metaclass.__prepare__(name, bases, **kwds) (waarbij de extra keyword-argumenten, indien aanwezig, uit de klassedefinitie komen). Als de metaklas geen attribuut __prepare__ heeft, dan wordt de klassenamespace geïnitialiseerd als een lege geordende mapping. - docs.python.org

Singleton-ontwerp met een metaklas

Dit is een ontwerppatroon dat het instantiëren van een klasse beperkt tot slechts één object. Dit kan bijvoorbeeld handig zijn bij het ontwerpen van een klasse die verbinding maakt met de database. Je wilt misschien slechts één instantie van de verbindingsklasse hebben.

class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonMeta,cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class SingletonClass(metaclass=SingletonMeta):
    pass

Conclusie

In dit artikel hebben we geleerd wat metaklassen zijn en hoe we ze kunnen toepassen in onze Python-programmering. Metaklassen kun je onder andere gebruiken voor logging, het registreren van klassen tijdens het aanmaken en voor profiling. Het lijken vrij abstracte concepten, en je vraagt je misschien af of je ze überhaupt nodig hebt. Doorgewinterde Pythonista Tim Peters beantwoordt die vraag het best.

"Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).” - realpython

Wil je meer leren over Python? Start dan met DataCamp's Python Programming skill track en bekijk onze Python Classes Tutorial.

Referenties

Link één

Link twee

Onderwerpen

Gerelateerd