0Pricing
Python Academy · Lekcja

Pisanie własnych metaklas

Proszę używać __new__ i __init__ w metaklasach do dostosowywania tworzenia klas.

Pisanie własnych metaklas to bezpłatna lekcja Python Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Python Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Python Academy zawiera 4 lekcji w sumie.

Deklarowanie metaklasy

Proszę przekazać metaclass=MyMeta w definicji klasy, aby użyć niestandardowej metaklasy. Metaklasa musi dziedziczyć po type.

class MyMeta(type):
    pass

class Foo(metaclass=MyMeta):
    pass

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

Nadpisywanie __new__

__new__(mcs, name, bases, namespace) tworzy obiekt klasy. Proszę użyć tej metody do zmodyfikowania przestrzeni nazw przed zbudowaniem klasy.

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

Nadpisywanie __init__

__init__(cls, name, bases, namespace) jest wykonywana po utworzeniu klasy. Proszę używać jej do konfiguracji po utworzeniu klasy, na przykład do rejestracji.

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

Wymuszanie kontraktów interfejsu

Proszę zgłosić TypeError w metodzie __new__, jeśli w treści klasy brakuje wymaganych metod.

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 za pomocą metaklasy

Proszę nadpisać __call__ w metaklasie, aby przechwycić tworzenie instancji i za każdym razem zwracać tę samą instancję.

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

Dynamiczne dodawanie metod

Proszę wstrzyknąć metody narzędziowe do każdej klasy używającej metaklasy, bez modyfikowania poszczególnych klas.

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__ dla uporządkowanej przestrzeni nazw

Proszę nadpisać __prepare__, aby dostarczyć niestandardowe mapowanie jako przestrzeń nazw klasy, na przykład w celu zachowania kolejności definicji lub wykrywania duplikatów.

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

Rozwiązywanie konfliktów metaklas

Gdy wiele klas bazowych ma różne metaklasy, Python wymaga jawnego użycia najbardziej pochodnej metaklasy; w przeciwnym razie zgłaszany jest błąd konfliktu.

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

Metaklasa a __init_subclass__

Do rejestrowania klas pochodnych i implementowania prostych punktów rozszerzeń __init_subclass__ jest prostsza i nie wymaga metaklasy.

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]

Śledzenie dostępu do atrybutów

Proszę nadpisać __getattribute__ w metaklasie, aby przechwytywać dostęp do atrybutów klasy, a nie instancji.

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

Wzorzec zbierania pól ORM

ORM-y w stylu Django używają metaklas do zbierania deskryptorów Field z treści klasy i umieszczania ich w rejestrze _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'])

Szybki test

W niestandardowej metaklasie która metoda jest wywoływana w celu utworzenia samego obiektu klasy, przed __init__?

Podsumowanie

Niestandardowe metaklasy nadpisują __new__/__init__, aby kontrolować tworzenie klas: wymuszać kontrakty, automatycznie rejestrować klasy pochodne, wstrzykiwać metody lub zbierać deskryptory pól. W przypadku prostych potrzeb związanych z rejestracją należy preferować __init_subclass__.

Często zadawane pytania

Czy lekcja „Pisanie własnych metaklas” jest bezpłatna?

Tak — pełny tekst „Pisanie własnych metaklas” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Python Academy, przejdź na CoddyKit PRO. Kurs Python Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Pisanie własnych metaklas”?

Proszę używać __new__ i __init__ w metaklasach do dostosowywania tworzenia klas. Ćwiczysz Python Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Python Academy?

Nie wymagamy żadnego doświadczenia. Python Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Pisanie własnych metaklas”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Python Academy?

Tak. Każda lekcja Python Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Jak tworzone są klasy Pythona
  2. Pisanie własnych metaklas
  3. Deskryptory: __get__, __set__, __delete__
  4. __slots__ i optymalizacja pamięci
← Powrót do Python Academy