← Back to list

Python Metaclasses— (Python Classes Internals — Post 6/7)

You have learned that type creates every class in Python. You have learned that object is the root every class inherits from. You have…

Zeba Tasneem · 2026-03-08 09:56 · 1 claps · 8.2 min read
#metaclass #python-metaclasses #oops-concepts
Open on Medium ↗

Python Metaclasses— (Python Classes Internals — Post 6/7)

You have learned that type creates every class in Python. You have learned that object is the root every class inherits from. You have learned how type.__call__ orchestrates the two-step __new____init__ process.

Now the natural question is: can you customize how a class is built? Can you intercept that process and add your own logic — before the class even exists?

Yes. That is exactly what a metaclass does.

The Problem Metaclasses Solve

Imagine you are building a framework. You have 50 classes. You want every single one of them to automatically have:

  • A created_at timestamp
  • A greet() method
  • A rule that class names must start with uppercase

Without metaclasses you would copy-paste this into all 50 classes — and forget it in at least 10 of them.

# ❌ Without metaclass — repetitive and error-prone
class Person:
    created_at = "2026"
    def greet(self):
        return "Hello from Person!"

class Robot:
    created_at = "2026"
    def greet(self):
        return "Hello from Robot!"
class vehicle:           # ← lowercase - nobody caught this!
    created_at = "2026"
    def greet(self):
        return "Hello from vehicle!"

A metaclass solves all three problems in one place — automatically, for every class that uses it.

What is a Metaclass — The Concept

You already know:

class  →  creates  →  instances
type   →  creates  →  classes

A metaclass is simply a custom version of type — one where you have added your own rules to the class-building process.

your metaclass  →  creates  →  classes (with your custom rules)

Every time you write class Dog:, Python uses type to build Dog. A metaclass lets you say: "use MY builder instead of the default type."

Class of a Class

Here is the precise definition:

A metaclass is the class of a class.

Just like d is an instance of Dog, Dog is an instance of type. When you create a custom metaclass, your classes become instances of that metaclass instead.

print(type(int))    # <class 'type'>      ← int is instance of type
print(type(str))    # <class 'type'>      ← str is instance of type
class MyMeta(type):
    pass

class Dog(metaclass=MyMeta):
    pass
print(type(Dog))    # <class '__main__.MyMeta'>  ← Dog is now instance of MyMeta

Before Metaclass — What Was Happening

When you write any class, Python internally does this:

class Person:
    pass

# Python compiles this to:
Person = type('Person', (object,), {})

type.__new__ runs and builds the Person class. You had zero control over this process. The class was built, handed to you, done.

A metaclass intercepts exactly this moment — before the class is finalized — and lets you add, modify, or enforce things.

Your First Metaclass — Step by Step

Let us build AutoGreet from scratch, one line at a time.

The Goal

Every class using our metaclass should automatically get a greet() method — without writing it manually.

Step 1:- Inherit from type

class AutoGreet(type):
    pass

AutoGreet is now a metaclass. It does nothing custom yet — it just inherits everything type already does.

Step 2:- Override __new__

class AutoGreet(type):
    def __new__(mcs, name, bases, dct):
        return super().__new__(mcs, name, bases, dct)

__new__ in a metaclass runs at the moment a class is being created — not when an instance is created. We intercept here.

Step 3:- Add the method before the class is finalized

class AutoGreet(type):
    def __new__(mcs, name, bases, dct):
        dct['greet'] = lambda self: f"Hello from {name}!"
        return super().__new__(mcs, name, bases, dct)

Before calling super().__new__() to finalize the class, we add greet to dct — the class's toolbox.

Step 4:- Use the metaclass

class Person(metaclass=AutoGreet):
    pass

class Robot(metaclass=AutoGreet):
    pass
p = Person()
r = Robot()
print(p.greet())    # Hello from Person!
print(r.greet())    # Hello from Robot!

Person and Robot both have greet() — even though we wrote pass inside them. The metaclass added it automatically.

The 4 Parameters of Metaclass __new__

def __new__(mcs, name, bases, dct):

This is the most important line to understand. Each parameter carries specific information about the class being built:

mcs — The metaclass itself

# mcs is to metaclass what cls is to a normal class
# It refers to AutoGreet itself

class AutoGreet(type):
    def __new__(mcs, name, bases, dct):
        print(f"mcs = {mcs}")   # <class '__main__.AutoGreet'>
        return super().__new__(mcs, name, bases, dct)
class Person(metaclass=AutoGreet):
    pass
# mcs = <class '__main__.AutoGreet'>

name — The name of the class being built

class AutoGreet(type):
    def __new__(mcs, name, bases, dct):
        print(f"name = {name}")   # Person
        return super().__new__(mcs, name, bases, dct)

class Person(metaclass=AutoGreet):
    pass
# name = Person

bases — The parent classes as a tuple

class AutoGreet(type):
    def __new__(mcs, name, bases, dct):
        print(f"bases = {bases}")   # (<class 'object'>,)
        return super().__new__(mcs, name, bases, dct)

class Person(metaclass=AutoGreet):
    pass
# bases = (<class 'object'>,)

If Person had a custom parent:

class Human:
    pass

class Person(Human, metaclass=AutoGreet):
    pass
# bases = (<class '__main__.Human'>,)

dct — The class toolbox (all methods and attributes so far)

class AutoGreet(type):
    def __new__(mcs, name, bases, dct):
        print(f"dct = {dct}")
        return super().__new__(mcs, name, bases, dct)

class Person(metaclass=AutoGreet):
    species = "human"
    def talk(self):
        return "talking"
# dct = {'species': 'human', 'talk': <function>, ...}

dct is a dictionary of everything written inside the class body. Adding to dct before calling super().__new__() injects methods into the class before it is finalized.

What Happens Step by Step

Let us trace exactly what Python does when it reads class Person(metaclass=AutoGreet): pass:

Step 1: Python reads class keyword
         ↓
Step 2: Python sees metaclass=AutoGreet
         → uses AutoGreet as builder instead of default type
         ↓
Step 3: Python collects everything inside the class body
         → name  = "Person"
         → bases = (object,)   ← added silently
         → dct   = {}          ← empty because Person has only pass
         ↓
Step 4: AutoGreet.__new__(AutoGreet, "Person", (object,), {}) is called
         ↓
Step 5: Inside __new__:
         dct was {}
         we add: dct['greet'] = lambda self: "Hello from Person!"
         dct is now: {'greet': <function>}
         ↓
Step 6: super().__new__(mcs, name, bases, dct) is called
         → type builds Person using our UPDATED dct
         ↓
Step 7: Person class is ready
         Person has greet() even though we wrote pass!
         ↓
Step 8: p = Person()   → creates a Person instance
         p.greet()     → "Hello from Person!"

__new__ in Metaclass vs __new__ in Normal Class

This is the most common confusion point. They share the same name but run at completely different levels:

Normal class __new__Metaclass __new__Runs when?d = Dog() — instance createdclass Dog: — CLASS createdcls/mcs refers to?The class (Dog)The metaclass (AutoGreet)Parameterscls onlymcs, name, bases, dctCreates?An instanceA classReturns?An instanceA classsuper().__new__() calls?object.__new__type.__new__

# Normal __new__ — runs when d = Dog() is called
class Dog:
    def __new__(cls):
        print(f"Building Dog instance, cls={cls}")
        return super().__new__(cls)

# Metaclass __new__ - runs when class Dog: is written
class MyMeta(type):
    def __new__(mcs, name, bases, dct):
        print(f"Building {name} class, mcs={mcs}")
        return super().__new__(mcs, name, bases, dct)

Use Cases

Real Use Case 1 — Enforcing Naming Rules

class EnforceUpperCase(type):
    def __new__(mcs, name, bases, dct):
        if not name[0].isupper():
            raise TypeError(
                f"Class name '{name}' must start with uppercase letter!"
            )
        return super().__new__(mcs, name, bases, dct)

class Student(metaclass=EnforceUpperCase):   # ✅ OK
    pass

class teacher(metaclass=EnforceUpperCase):   # ❌ TypeError!
    pass
# TypeError: Class name 'teacher' must start with uppercase letter!

This rule is enforced at class definition time — not at instantiation time. The moment someone writes class teacher, Python raises an error immediately.

Real Use Case 2 — Auto-registering Classes

class PluginRegistry(type):
    registry = {}
    def __new__(mcs, name, bases, dct):
        cls = super().__new__(mcs, name, bases, dct)
        if bases:   # skip the base Plugin class itself
            mcs.registry[name] = cls
        return cls

class Plugin(metaclass=PluginRegistry):
    pass
class AudioPlugin(Plugin):
    pass
class VideoPlugin(Plugin):
    pass
print(PluginRegistry.registry)
# {'AudioPlugin': <class 'AudioPlugin'>, 'VideoPlugin': <class 'VideoPlugin'>}

Every class that inherits from Plugin is automatically registered. No manual registration needed. This pattern is used in Django for model registration, pytest for test discovery, and many plugin systems.

Real Use Case 3 — Adding Timestamps Automatically

import datetime

class AutoTimestamp(type):
    def __new__(mcs, name, bases, dct):
        dct['created_at'] = datetime.datetime.now().strftime("%Y-%m-%d")
        return super().__new__(mcs, name, bases, dct)
class UserProfile(metaclass=AutoTimestamp):
    pass
class OrderModel(metaclass=AutoTimestamp):
    pass
print(UserProfile.created_at)   # 2026-03-03
print(OrderModel.created_at)    # 2026-03-03

Where You See Metaclasses in Real Projects

1: Django ORM

from django.db import models

class UserProfile(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField()

When you write this, Django’s metaclass (ModelBase) intercepts the class creation and:

  • Registers the class as a database table
  • Converts CharField and EmailField into actual database column definitions
  • Sets up query managers (UserProfile.objects.all())
  • Validates field definitions

You never call any of this manually — the metaclass handles it all at class definition time.

2: Django REST Framework

from rest_framework import serializers

class UserSerializer(serializers.Serializer):
    name = serializers.CharField()
    email = serializers.EmailField()

DRF’s metaclass reads the field definitions and sets up serialization/deserialization automatically.

3: Python’s enum module

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

EnumMeta — the metaclass behind Enum — intercepts class creation and converts class attributes into proper enum members with iteration, comparison, and lookup support.

Metaclass vs Inheritance — Key Difference

This is a question every developer has when first learning metaclasses:

“Can I just use inheritance instead?”

Sometimes yes. But they solve different problems:

# Inheritance — controls instance behaviour
class Animal:
    def breathe(self):
        return "breathing"

class Dog(Animal):      # Dog instances can breathe
    pass

# Metaclass - controls class structure
class EnforceUpperCase(type):
    def __new__(mcs, name, bases, dct):
        if not name[0].isupper():
            raise TypeError(f"'{name}' must start uppercase")
        return super().__new__(mcs, name, bases, dct)
class Dog(metaclass=EnforceUpperCase):  # Dog class must be uppercase
    pass

When to Use and When NOT to Use Metaclasses

Use metaclasses when:

  • Building a framework that others will use
  • You need rules enforced at class definition time
  • You need automatic registration of subclasses
  • You are working with Django/SQLAlchemy/DRF internals

Do NOT use metaclasses when:

  • A simple class decorator would do the job
  • __init_subclass__ (Python 3.6+) would be simpler
  • You just need shared behaviour → use inheritance
# ✅ Simpler alternative — class decorator
def enforce_uppercase(cls):
    if not cls.__name__[0].isupper():
        raise TypeError(f"'{cls.__name__}' must start uppercase")
    return cls

@enforce_uppercase
class Student:
    pass
# ✅ Even simpler - __init_subclass__ (Python 3.6+)
class Base:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if not cls.__name__[0].isupper():
            raise TypeError(f"'{cls.__name__}' must start uppercase")
class Student(Base):    # ✅ OK
    pass
class teacher(Base):    # ❌ TypeError
    pass

Common Mistakes and Edge Cases

Mistake 1 — Metaclass conflict

If two parent classes have different metaclasses, Python raises a TypeError:

class Meta1(type):
    pass

class Meta2(type):
    pass

class A(metaclass=Meta1):
    pass

class B(metaclass=Meta2):
    pass
class C(A, B):
    pass   # ❌ TypeError: metaclass conflict!

Fix by creating a combined metaclass:

class CombinedMeta(Meta1, Meta2):
    pass

class C(A, B, metaclass=CombinedMeta):
    pass   # ✅

Mistake 2 — Forgetting return in __new__

class MyMeta(type):
    def __new__(mcs, name, bases, dct):
        dct['x'] = 10
        #❌ forgot return , class is None!

class Dog(metaclass=MyMeta):
    pass

# Dog is None - broken

Always return super().__new__(mcs, name, bases, dct).

Mistake 3 — Modifying dct after super().__new__() call

class MyMeta(type):
    def __new__(mcs, name, bases, dct):
        cls = super().__new__(mcs, name, bases, dct)
        dct['x'] = 10   # ❌ too late! class is already built
        return cls

Always modify dct before calling super().__new__().

Mistake 4 — Using metaclass when __init_subclass__ is enough

# ❌ Overcomplicated for a simple task
class Meta(type):
    def __new__(mcs, name, bases, dct):
        if bases:
            dct['category'] = 'registered'
        return super().__new__(mcs, name, bases, dct)

# ✅ Simpler with __init_subclass__
class Base:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.category = 'registered'

Complete Flow — Everything Together

You write:  class Person(metaclass=AutoGreet): pass
                              ↓
Python collects:  name="Person", bases=(object,), dct={}
                              ↓
Calls: AutoGreet.__new__(AutoGreet, "Person", (object,), {})
                              ↓
Inside __new__:
  dct['greet'] = lambda self: "Hello from Person!"
  dct = {'greet': <function>}
                              ↓
super().__new__(mcs, "Person", (object,), {'greet': <function>})
→ type builds Person with greet() already inside
                              ↓
Person class is ready ✅
Person has greet() even though you wrote pass
Later:
p = Person()
→ type.__call__(Person)
→ object.__new__(Person) → empty instance
→ object.__init__(p)     → does nothing (no __init__ written)
→ p is ready
p.greet()   → "Hello from Person!" ✅

Key Takeaways

1.  A metaclass is a class whose instances are other classes
2.  Every class is already using a metaclass — the default is type
3.  Custom metaclass = subclass type and override __new__
4.  __new__ in metaclass runs at CLASS DEFINITION TIME — not instance creation
5.  4 parameters: mcs (metaclass), name (class name), bases (parents), dct (toolbox)
6.  Modify dct BEFORE calling super().__new__() to inject methods
7.  Always return super().__new__(mcs, name, bases, dct)
8.  Django ORM, DRF, enum all use metaclasses internally
9.  Metaclass conflict happens when parent classes have different metaclasses
10. Consider class decorators or __init_subclass__ before reaching for metaclasses
11. Metaclass controls class structure — inheritance controls instance behaviour

Series Roadmap:

**Post 6/7** — "Python Classes → Internals" series

Next → **Post 7**: PyObject/PyTypeObject internals

Full roadmap:
Post 1: Types of Classes 
Post 2: __new__ vs __init__ 
Post 3: object - The Root of Every Class 
Post 4: type - class factory 
Post 5: type vs object 
Post 6: metaclasses  ✅
Post 7: PyObject/PyTypeObject internals

Hope you'll find this journey helpful! 🐍

메타데이터
post_id
e2ae221594bf
slug
python-metaclasses-python-classes-internals-post-6-7-e2ae221594bf
url
https://medium.com/@Zeba_/python-metaclasses-python-classes-internals-post-6-7-e2ae221594bf
canonical_url
https://medium.com/@Zeba_/python-metaclasses-python-classes-internals-post-6-7-e2ae221594bf
author_url
https://medium.com/@Zeba_
status
ok
fetched_at
2026-06-21 15:33:18