0Pricing
Python Academy · Lezione

Scrittura di metaclassi personalizzate

Utilizzi __new__ e __init__ nelle metaclassi per personalizzare la creazione delle classi.

Scrittura di metaclassi personalizzate è una lezione Python Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Python Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Python Academy include 4 lezioni in totale.

Dichiarazione di una metaclasse

Passi metaclass=MyMeta nella definizione della classe per utilizzare una metaclasse personalizzata. La metaclasse deve ereditare da type.

class MyMeta(type):
    pass

class Foo(metaclass=MyMeta):
    pass

print(type(Foo))   # <class '__main__.MyMeta'>

Sovrascrittura di __new__

__new__(mcs, name, bases, namespace) crea l'oggetto classe. Lo utilizzi per modificare lo spazio dei nomi prima della costruzione della classe.

class UpperAttrMeta(type):
    def __new__(mcs, name, bases, namespace):
        upper = {k.upper(): v for k, v in namespace.items()
                 if not k.startswith("_")}
        return super().__new__(mcs, name, bases, {**namespace, **upper})

class Config(metaclass=UpperAttrMeta):
    debug = True

print(Config.DEBUG)  # True

Sovrascrittura di __init__

__init__(cls, name, bases, namespace) viene eseguito dopo la creazione della classe. Lo utilizzi per la configurazione successiva alla creazione, ad esempio la registrazione.

_registry = {}

class AutoRegisterMeta(type):
    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)
        if bases:   # skip the base class itself
            _registry[name] = cls

class Handler(metaclass=AutoRegisterMeta): pass
class JSONHandler(Handler): pass
class XMLHandler(Handler):  pass

print(_registry)  # {'JSONHandler': ..., 'XMLHandler': ...}

Imposizione dei contratti dell'interfaccia

Sollevi TypeError in __new__ se nel corpo della classe mancano i metodi richiesti.

class InterfaceMeta(type):
    REQUIRED = frozenset({"process", "validate"})

    def __new__(mcs, name, bases, ns):
        if bases and not mcs.REQUIRED.issubset(ns):
            missing = mcs.REQUIRED - ns.keys()
            raise TypeError(f"{name} must define: {missing}")
        return super().__new__(mcs, name, bases, ns)

class Worker(metaclass=InterfaceMeta):
    def process(self): ...
    def validate(self): ...

Singleton tramite metaclasse

Sovrascriva __call__ nella metaclasse per intercettare la creazione delle istanze e restituire ogni volta la stessa istanza.

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

class DB(metaclass=SingletonMeta):
    pass

print(DB() is DB())  # True

Aggiunta dinamica di metodi

Inserisca metodi di utilità in ogni classe che utilizza la metaclasse, senza modificare singolarmente ciascuna classe.

class MethodInjectorMeta(type):
    def __new__(mcs, name, bases, ns):
        ns["describe"] = lambda self: f"I am {type(self).__name__}"
        return super().__new__(mcs, name, bases, ns)

class Widget(metaclass=MethodInjectorMeta):
    pass

print(Widget().describe())   # I am Widget

__prepare__ per uno spazio dei nomi ordinato

Sovrascriva __prepare__ per fornire una mappatura personalizzata come spazio dei nomi della classe, ad esempio per conservare l'ordine delle definizioni o rilevare i duplicati.

class NoDuplicatesMeta(type):
    class _NS(dict):
        def __setitem__(self, k, v):
            if k in self and not k.startswith("_"):
                raise ValueError(f"Duplicate attribute: {k}")
            super().__setitem__(k, v)

    @classmethod
    def __prepare__(mcs, name, bases):
        return mcs._NS()

Risoluzione dei conflitti tra metaclassi

Quando più classi base hanno metaclassi diverse, Python richiede l'utilizzo esplicito della metaclasse più derivata, altrimenti solleva un errore di conflitto.

class MetaA(type): pass
class MetaB(type): pass

# class Bad(A, B): pass  # TypeError: metaclass conflict

class MetaAB(MetaA, MetaB): pass   # resolve manually

class A(metaclass=MetaA): pass
class B(metaclass=MetaB): pass
class Good(A, B, metaclass=MetaAB): pass

Metaclasse vs __init_subclass__

Per la registrazione delle sottoclassi e gli hook semplici, __init_subclass__ è più semplice e non richiede una metaclasse.

class Base:
    _registry = []
    def __init_subclass__(cls, **kw):
        super().__init_subclass__(**kw)
        Base._registry.append(cls)

class ChildA(Base): pass
class ChildB(Base): pass

print(Base._registry)  # [ChildA, ChildB]

Tracciamento dell'accesso agli attributi

Sovrascriva __getattribute__ nella metaclasse per intercettare l'accesso agli attributi della classe, non delle istanze.

class TraceMeta(type):
    def __getattribute__(cls, name):
        val = super().__getattribute__(name)
        if not name.startswith("_"):
            print(f"Accessing class attr: {name}")
        return val

class Config(metaclass=TraceMeta):
    HOST = "localhost"

_ = Config.HOST   # logs: Accessing class attr: HOST

Pattern di raccolta dei campi ORM

Gli ORM in stile Django utilizzano le metaclassi per raccogliere i descriptor Field dal corpo della classe in un registro _meta.

class Field:
    def __init__(self, col): self.col = col

class ModelMeta(type):
    def __new__(mcs, name, bases, ns):
        fields = {k: v for k, v in ns.items() if isinstance(v, Field)}
        ns["_fields"] = fields
        return super().__new__(mcs, name, bases, ns)

class User(metaclass=ModelMeta):
    name = Field("name")
    email = Field("email")

print(User._fields.keys())  # dict_keys(['name', 'email'])

Verifica rapida

In una metaclasse personalizzata, quale metodo viene chiamato per creare l'oggetto classe stesso, prima di __init__?

Riepilogo

Le metaclassi personalizzate sovrascrivono __new__/__init__ per controllare la creazione delle classi: impongono contratti, registrano automaticamente le sottoclassi, inseriscono metodi o raccolgono descriptor di campo. Preferisca __init_subclass__ per le semplici esigenze di registrazione.

Domande Frequenti

La lezione «Scrittura di metaclassi personalizzate» è gratuita?

Sì — il testo completo di «Scrittura di metaclassi personalizzate» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Python Academy, passa a CoddyKit PRO. Il corso Python Academy include 4 lezioni in totale.

Cosa imparerò in «Scrittura di metaclassi personalizzate»?

Utilizzi __new__ e __init__ nelle metaclassi per personalizzare la creazione delle classi. Eserciti Python Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Python Academy?

Non è richiesta alcuna esperienza precedente. Python Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Scrittura di metaclassi personalizzate»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Python Academy?

Sì. Ogni lezione Python Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Come vengono create le classi Python
  2. Scrittura di metaclassi personalizzate
  3. Descriptor: __get__, __set__, __delete__
  4. __slots__ e ottimizzazione della memoria
← Torna a Python Academy