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) # TrueNadpisywanie __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()) # TrueDynamiczne 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): passMetaklasa 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: HOSTWzorzec 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
- Jak tworzone są klasy Pythona
- Pisanie własnych metaklas
- Deskryptory: __get__, __set__, __delete__
- __slots__ i optymalizacja pamięci