Vai al contenuto principale

Introduzione alle metaclassi in Python

In questo tutorial scoprirai cosa sono le metaclassi, come implementarle in Python e come crearne di personalizzate.
· 6 min leggi

Scopri con l'IA

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

class TestClass():
    pass

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

Le classi Python possono essere create dinamicamente

type in Python ci permette di trovare il tipo di un oggetto. Possiamo verificarlo per l'oggetto creato sopra.

type(TestClass)
type
type(type)
type

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 type stesso è type. È un'istanza di type. Un'altra cosa “magica” che fa type è permetterci di creare classi dinamicamente. Vediamo come farlo qui sotto. La classe DataCamp mostrata sotto verrebbe creata come segue usando type:

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

Nell'esempio sopra DataCamp è il nome della classe mentre DataCampClass è la variabile che contiene il riferimento alla classe. Usando type possiamo passare attributi della classe tramite un dizionario, come mostrato sotto:

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

Se volessimo che la nostra PythonClass ereditasse dalla classe DataCamp, la passiamo come secondo argomento quando definiamo la classe con type

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

Chiariti 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 class per creare una classe, è una metaclasse a fare la magia dietro le quinte. L'abbiamo già visto in pratica con type qui sopra. È simile a str che crea stringhe e int che 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.

article = 'metaclasses'
article.__class__
str

Possiamo anche verificare il tipo usando type(article).

type(article)
str

Quando controlliamo il tipo di str, scopriamo anche che è type.

type(str)
type

Se controlliamo il tipo di float, int, list, tuple e dict, otterremo un output simile. Questo perché tutti questi oggetti sono di tipo type.

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

Abbiamo già visto che type crea classi. Quindi, quando controlliamo il __class__ di __class__ dovrebbe restituire type.

article.__class__.__class__
type

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.

Riferimenti

Link uno

Link due

Argomenti

Correlato