Python Academy · leksjon

Skrive egendefinerte metaklasser

Bruk __new__ og __init__ i metaklasser til å tilpasse klasseopprettingen.

Leksjon 2 av 413 trinn

Skrive egendefinerte metaklasser er en gratis leksjon i Python Academy på CoddyKit. Dette er leksjon 2 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Python Academy, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Python Academy inneholder totalt 4 leksjoner.

Deklarering av en metaklasse

Send inn metaclass=MyMeta i klassedefinisjonen for å bruke en egendefinert metaklasse. Metaklassen må arve fra type.

class MyMeta(type):
    pass

class Foo(metaclass=MyMeta):
    pass

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

Overstyring av __new__

__new__(mcs, name, bases, namespace) oppretter klasseobjektet. Bruk den til å endre navnerommet før klassen bygges.

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

Overstyring av __init__

__init__(cls, name, bases, namespace) kjører etter at klassen er opprettet. Bruk den til oppsett etter opprettelsen, for eksempel registrering.

_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': ...}

Håndheving av grensesnittkontrakter

Utløs TypeError i __new__ hvis nødvendige metoder mangler i klassekroppen.

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

Overstyr __call__ på metaklassen for å fange opp opprettelsen av instanser og returnere den samme instansen hver gang.

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

Dynamisk tillegging av metoder

Sett inn hjelpemetoder i hver klasse som bruker metaklassen, uten å endre hver enkelt klasse.

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__ for ordnet navnerom

Overstyr __prepare__ for å levere en egendefinert mapping som klassens navnerom, for eksempel for å bevare definisjonsrekkefølgen eller oppdage duplikater.

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

Løsning av metaklassekonflikter

Når flere basisklasser har forskjellige metaklasser, krever Python at den mest avledede metaklassen brukes eksplisitt. Ellers utløses en konfliktfeil.

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

Metaklasse kontra __init_subclass__

For registrering av underklasser og enkle kroker er __init_subclass__ enklere og krever ingen metaklasse.

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]

Sporing av attributttilgang

Overstyr __getattribute__ på metaklassen for å fange opp attributttilgang på klassen, ikke på instanser.

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

Mønster for innsamling av ORM-felt

ORM-er i Django-stil bruker metaklasser til å samle Field-deskriptorer fra klassekroppen i et _meta-register.

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

Hurtigsjekk

I en egendefinert metaklasse, hvilken metode kalles for å opprette selve klasseobjektet, før __init__?

Oppsummering

Egendefinerte metaklasser overstyrer __new__/__init__ for å styre opprettelsen av klasser: håndheve kontrakter, automatisk registrere underklasser, sette inn metoder eller samle feltbeskrivelser. Foretrekk __init_subclass__ når du bare trenger enkel registrering.

Gratis å komme i gang

Lær deg Python med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
76
Leksjoner
320

Ofte stilte spørsmål

Er leksjonen «Skrive egendefinerte metaklasser» gratis?

Ja – hele teksten i «Skrive egendefinerte metaklasser» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Python Academy-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Python Academy inneholder totalt 4 leksjoner.

Hva lærer jeg i «Skrive egendefinerte metaklasser»?

Bruk __new__ og __init__ i metaklasser til å tilpasse klasseopprettingen. Du øver på Python Academy med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Python Academy?

Ingen tidligere erfaring er nødvendig. Python Academy på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 2 av 4.

Hvor lang tid tar leksjonen «Skrive egendefinerte metaklasser»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Python Academy-leksjonen?

Ja. Alle Python Academy-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Hvordan Python-klasser opprettes
  2. Skrive egendefinerte metaklasser
  3. Deskriptorer: __get__, __set__, __delete__
  4. __slots__ og minneoptimalisering
← Tilbake til Python Academy