0Pricing
Python Academy · Lección

Escritura de metaclases personalizadas

Utilice __new__ y __init__ en metaclases para personalizar la creación de clases.

Escritura de metaclases personalizadas es una lección gratuita de Python Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Python Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Python Academy incluye 4 lecciones en total.

Declaración de una metaclase

Incluya metaclass=MyMeta en la definición de la clase para usar una metaclase personalizada. La metaclase debe heredar de type.

class MyMeta(type):
    pass

class Foo(metaclass=MyMeta):
    pass

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

Sobrescritura de __new__

__new__(mcs, name, bases, namespace) crea el objeto de clase. Úselo para modificar el espacio de nombres antes de construir la clase.

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

Sobrescritura de __init__

__init__(cls, name, bases, namespace) se ejecuta después de crear la clase. Úselo para tareas posteriores a la creación, como el registro.

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

Imposición de contratos de interfaz

Genere TypeError en __new__ si faltan métodos necesarios en el cuerpo de la clase.

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 mediante una metaclase

Sobrescriba __call__ en la metaclase para interceptar la creación de instancias y devolver siempre la misma instancia.

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

Adición dinámica de métodos

Inyecte métodos de utilidad en cada clase que use la metaclase, sin modificar cada clase individualmente.

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__ para un espacio de nombres ordenado

Sobrescriba __prepare__ para proporcionar un mapeo personalizado como espacio de nombres de la clase, por ejemplo, para conservar el orden de definición o detectar duplicados.

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

Resolución de conflictos entre metaclases

Cuando varias clases base tienen metaclases diferentes, Python exige usar explícitamente la metaclase más derivada o genera un error de conflicto.

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

Metaclase frente a __init_subclass__

Para registrar subclases y crear hooks sencillos, __init_subclass__ es más simple y no requiere una metaclase.

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]

Seguimiento del acceso a atributos

Sobrescriba __getattribute__ en la metaclase para interceptar el acceso a atributos de la clase, no de las instancias.

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

Patrón de recopilación de campos de ORM

Los ORM al estilo de Django usan metaclases para recopilar los descriptores Field del cuerpo de la clase en 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'])

Comprobación rápida

En una metaclase personalizada, ¿qué método se llama para crear el propio objeto de clase antes de __init__?

Recapitulación

Las metaclases personalizadas sobrescriben __new__/__init__ para controlar la creación de clases: imponen contratos, registran subclases automáticamente, inyectan métodos o recopilan descriptores de campos. Prefiera __init_subclass__ para necesidades sencillas de registro.

Preguntas frecuentes

¿La lección «Escritura de metaclases personalizadas» es gratis?

Sí — el texto completo de «Escritura de metaclases personalizadas» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Python Academy, actualiza a CoddyKit PRO. El curso de Python Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Escritura de metaclases personalizadas»?

Utilice __new__ y __init__ en metaclases para personalizar la creación de clases. Practicas Python Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Python Academy?

No se requiere experiencia previa. Python Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Escritura de metaclases personalizadas»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Python Academy?

Sí. Cada lección de Python Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Cómo se crean las clases de Python
  2. Escritura de metaclases personalizadas
  3. Descriptores: __get__, __set__, __delete__
  4. __slots__ y optimización de memoria
← Volver a Python Academy