Creare metaclassi personalizzate
In Python possiamo personalizzare il processo di creazione delle classi passando la keyword metaclass nella definizione della classe. Questo può essere fatto anche ereditando da una classe che ha già passato questa keyword.
class MyMeta(type):
pass
class MyClass(metaclass=MyMeta):
pass
class MySubclass(MyClass):
pass
Possiamo vedere sotto che il tipo della classe MyMeta è type e che il tipo di MyClass e MySubClass è MyMeta.
print(type(MyMeta))
print(type(MyClass))
print(type(MySubclass))
<class 'type'>
<class '__main__.MyMeta'>
<class '__main__.MyMeta'>
Quando si definisce una classe e non è definita alcuna metaclasse, verrà usata la metaclasse predefinita type. Se viene fornita una metaclasse e non è un'istanza di type(), allora viene usata direttamente come metaclasse.
__new__ e __init__
Le metaclassi possono essere definite anche in uno dei due modi mostrati sotto. Spiegheremo la differenza tra i due più avanti.
class MetaOne(type):
def __new__(cls, name, bases, dict):
pass
class MetaTwo(type):
def __init__(self, name, bases, dict):
pass
__new__ si usa quando si vuole definire il dict o la tupla bases prima che la classe sia creata. Il valore di ritorno di __new__ è di solito un'istanza di cls. __new__ permette alle sottoclassi di tipi immutabili di personalizzare la creazione delle istanze. Può essere sovrascritto nelle metaclassi personalizzate per personalizzare la creazione della classe. __init__ viene di solito chiamato dopo che l'oggetto è stato creato, per inizializzarlo.
Metodo __call__ della metaclasse
Secondo la documentazione ufficiale, possiamo anche sovrascrivere altri metodi di classe definendo un metodo __call__() personalizzato nella metaclasse che consente un comportamento custom quando la classe viene chiamata.
Metodo __prepare__ della metaclasse
Secondo la documentazione del data model di Python
Una volta identificata la metaclasse appropriata, viene quindi preparato lo spazio dei nomi della classe. Se la metaclasse ha un attributo __prepare__, viene chiamato come namespace = metaclass.__prepare__(name, bases, **kwds) (dove gli argomenti keyword aggiuntivi, se presenti, provengono dalla definizione della classe). Se la metaclasse non ha l'attributo __prepare__, lo spazio dei nomi della classe viene inizializzato come una mappatura ordinata vuota. - docs.python.org
Design Singleton usando una metaclasse
È un pattern di progettazione che limita l'istanza di una classe a un solo oggetto. Questo può essere utile, ad esempio, quando si progetta una classe per connettersi a un database. Si potrebbe voler avere una sola istanza della classe di connessione.
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
Conclusione
In questo articolo abbiamo visto cosa sono le metaclassi e come possiamo implementarle nella nostra programmazione in Python. Le metaclassi possono essere applicate al logging, alla registrazione delle classi al momento della creazione, al profiling e altro ancora. Sembrano concetti piuttosto astratti e potresti chiederti se ti servano davvero. Un Pythonista di lungo corso, Tim Peters, risponde al meglio a questa domanda.
"Le metaclassi sono una magia più profonda di quanto il 99% degli utenti dovrebbe mai preoccuparsi. Se ti chiedi se ti servono, non ti servono (le persone a cui servono davvero lo sanno con certezza, e non hanno bisogno di una spiegazione del perché)." - realpython
Se vuoi imparare di più su Python, inizia il percorso di competenze Python Programming di DataCamp e dai un'occhiata al nostro Tutorial sulle classi in Python.


Una metaclasse in Python è una classe di una classe che definisce come si comporta una classe. Una classe è a sua volta un'istanza di una metaclasse. Una classe in Python definisce come si comporteranno le sue istanze. Per capire bene le metaclassi, è utile avere già esperienza con le classi in Python. Prima di addentrarci nelle metaclassi, liberiamoci di alcuni concetti preliminari.
In Python tutto è un oggetto
Le classi Python possono essere create dinamicamente
typein Python ci permette di trovare il tipo di un oggetto. Possiamo verificarlo per l'oggetto creato sopra.Aspetta, cosa è successo? Ci aspetteremmo che il tipo dell'oggetto creato sopra fosse class, ma non lo è. Teniamo a mente questo punto: lo approfondiremo tra poco. Notiamo anche che il tipo di
typestesso ètype. È un'istanza ditype. Un'altra cosa “magica” che fatypeè permetterci di creare classi dinamicamente. Vediamo come farlo qui sotto. La classeDataCampmostrata sotto verrebbe creata come segue usandotype:Nell'esempio sopra
DataCampè il nome della classe mentreDataCampClassè la variabile che contiene il riferimento alla classe. Usandotypepossiamo passare attributi della classe tramite un dizionario, come mostrato sotto:Se volessimo che la nostra
PythonClassereditasse dalla classeDataCamp, la passiamo come secondo argomento quando definiamo la classe contypeChiariti questi due concetti, capiamo che Python crea le classi usando una metaclasse. Abbiamo visto che in Python tutto è un oggetto, e questi oggetti sono creati dalle metaclassi. Ogni volta che usiamo
classper creare una classe, è una metaclasse a fare la magia dietro le quinte. L'abbiamo già visto in pratica contypequi sopra. È simile astrche crea stringhe eintche crea interi. In Python, l'attributo___class__ci permette di controllare il tipo dell'istanza corrente. Creiamo una stringa qui sotto e verifichiamone il tipo.Possiamo anche verificare il tipo usando
type(article).Quando controlliamo il tipo di
str, scopriamo anche che è type.Se controlliamo il tipo di
float,int,list,tupleedict, otterremo un output simile. Questo perché tutti questi oggetti sono di tipotype.Abbiamo già visto che
typecrea classi. Quindi, quando controlliamo il__class__di__class__dovrebbe restituiretype.