Dashboard
Python OOPs Low Level

How Python Creates Classes: A Journey into Metaclasses and the Object Model

Tashif Ahmad Khan August 19, 2025 7 min read 35 views 3 likes

You’ve probably heard the phrase “everything in Python is an object.” It’s not just a catchy slogan - it’s a fundamental truth about how Python works. Numbers are objects. Strings are objects. Functions are objects. But here’s where it gets really interesting: classes themselves are objects too.

If classes are objects, then something must create them, right? Just like a class creates its instances, something creates the class itself. That “something” is called a metaclass, and understanding this concept opens up a whole new level of Python mastery.

The type Enigma: Function or Class?#

Let’s start with type - one of the most fundamental yet confusing elements in Python. You’ve probably used it to check an object’s type:

python
x = 5
print(type(x))  # <class 'int'>

But here’s the twist: type is both a function AND a class. Mind-bending? Let’s unpack it.

type as a Function#

When you call type() with one argument, it acts like a function that returns the type of an object:

python
>>> type(42)
<class 'int'>
>>> type("hello")
<class 'str'>
>>> type([1, 2, 3])
<class 'list'>

type as a Metaclass#

But type has a secret identity. It’s actually the default metaclass - the class that creates other classes. Every class you define in Python is, by default, an instance of type.

Think about this hierarchy:

  • A specific car (my Honda) is an instance of the Car class

  • The Car class is an instance of type

  • type is the “class of classes”

python
class MyClass:
    pass

# MyClass is an object...
print(type(MyClass))  # <class 'type'>

# ...created by type!
my_instance = MyClass()
print(type(my_instance))  # <class '__main__.MyClass'>

So type is to MyClass what MyClass is to my_instance. It’s turtles all the way down!

The Class Creation Process: Behind the Scenes#

When Python encounters a class definition, it doesn’t just magically create a class object. There’s an elegant choreography happening behind the scenes. Let’s break it down step by step.

python
class MyClass:
    class_variable = 10

    def __init__(self, value):
        self.value = value

    def display(self):
        print(f"Value: {self.value}")

When Python reads this, here’s what actually happens:

Step 1: Determine the Metaclass#

Python first figures out which metaclass will be responsible for creating this class. Unless you explicitly specify otherwise, it defaults to type.

python
# Explicitly specifying a metaclass
class MyClass(metaclass=type):  # This is the default
    pass

# Or with a custom metaclass
class MyClass(metaclass=CustomMetaclass):
    pass

Step 2: Prepare the Class Namespace#

The metaclass’s __prepare__ method is called to create a dictionary that will hold the class’s attributes. For type, this just returns an empty dictionary, but custom metaclasses can return specialized containers.

python
# What happens internally (simplified)
namespace = type.__prepare__('MyClass', (), {})
# Returns: {}

Step 3: Execute the Class Body#

Python executes all the code inside the class block. Each method definition, class variable, and nested class gets added to the namespace dictionary.

python
# Conceptually, this is what happens:
namespace['class_variable'] = 10
namespace['__init__'] = <function __init__ at 0x...>
namespace['display'] = <function display at 0x...>

At this point, the class doesn’t exist yet - we just have a dictionary full of its would-be attributes.

Step 4: Create the Class Object#

Now comes the magic! The metaclass’s __new__ method is called with:

  • The class name (“MyClass”)

  • A tuple of base classes (empty if there are none, or (object,))

  • The namespace dictionary we just built

python
# Internally, something like this happens:
MyClass = type.__new__(
    type,                    # The metaclass
    'MyClass',               # The name
    (),                      # Base classes (empty tuple)
    namespace                # The attributes dictionary
)

This creates the actual class object and allocates memory for it.

Step 5: Initialize the Class Object#

Finally, the metaclass’s __init__ method is called to perform any additional initialization:

python
type.__init__(MyClass, 'MyClass', (), namespace)

After this, MyClass is ready to use! It’s a fully-formed class object that can create instances.

Creating Classes Dynamically with type()#

Here’s where things get really cool. Since type is what creates classes, you can call it directly to create classes programmatically, without using the class statement at all!

The syntax is: type(name, bases, dict)

python
# Creating a class the traditional way
class Dog:
    species = "Canis familiaris"

    def __init__(self, name):
        self.name = name

    def bark(self):
        return f"{self.name} says woof!"

# Creating the exact same class with type()
def dog_init(self, name):
    self.name = name

def dog_bark(self):
    return f"{self.name} says woof!"

Dog = type('Dog', (), {
    'species': 'Canis familiaris',
    '__init__': dog_init,
    'bark': dog_bark
})

# Both work identically!
fido = Dog("Fido")
print(fido.bark())  # Fido says woof!
print(fido.species)  # Canis familiaris

This is incredibly powerful for metaprogramming - creating classes on the fly based on configuration, data, or other runtime conditions.

Custom Metaclasses: Taking Control#

Sometimes you want to customize how classes are created. This is where custom metaclasses shine. They allow you to:

  • Automatically add methods or attributes to classes

  • Validate class definitions

  • Register classes automatically

  • Implement design patterns like Singleton

  • Add debugging or logging capabilities

Here’s a practical example - a metaclass that automatically adds a describe() method to every class:

python
class DescriptiveMeta(type):
    def __new__(mcs, name, bases, namespace):
        # Add a describe method to every class
        def describe(self):
            return f"I am an instance of {name}"

        namespace['describe'] = describe

        # Call the parent metaclass to actually create the class
        return super().__new__(mcs, name, bases, namespace)

class Person(metaclass=DescriptiveMeta):
    def __init__(self, name):
        self.name = name

class Dog(metaclass=DescriptiveMeta):
    def __init__(self, name):
        self.name = name

# Both classes automatically have the describe method!
person = Person("Alice")
dog = Dog("Rex")

print(person.describe())  # I am an instance of Person
print(dog.describe())      # I am an instance of Dog

Real-World Example: A Singleton Metaclass#

One of the most popular uses of metaclasses is implementing the Singleton pattern - ensuring only one instance of a class can exist:

python
class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        # __call__ is invoked when you do MyClass()
        if cls not in cls._instances:
            # First time creating this class - create the instance
            instance = super().__call__(*args, **kwargs)
            cls._instances[cls] = instance
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    def __init__(self):
        print("Initializing database connection...")
        self.connection = "Connected!"

# Try to create multiple instances
db1 = Database()  # Initializing database connection...
db2 = Database()  # (no output - returns existing instance)

print(db1 is db2)  # True - same object!

The Object Model Hierarchy#

Let’s visualize the complete picture:

text
         type (the root metaclass)
           ↑
           | is an instance of
           |
      MyClass (a class)
           ↑
           | is an instance of
           |
    my_instance (an object)

And here’s the inheritance hierarchy:

text
       object (the root base class)
           ↑
           | inherits from
           |
      MyClass
           ↑
           | inherits from
           |
    (instances don't inherit, they instantiate)

Fun fact: type itself inherits from object, and object is an instance of type. It’s a beautiful circular relationship that forms the foundation of Python’s object model!

python
>>> isinstance(type, object)
True
>>> isinstance(object, type)
True
>>> issubclass(type, object)
True

When Should You Use Metaclasses?#

Here’s the honest truth: most of the time, you shouldn’t. As Python core developer Tim Peters famously said:

“Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t.”

Metaclasses are powerful but complex. They’re most useful for:

  • Framework development (like Django’s ORM, which uses metaclasses to create database models)

  • Enforcing coding standards across large codebases

  • Creating domain-specific languages within Python

  • Advanced design patterns that need class-level behavior modification

For everyday programming, simpler alternatives usually suffice:

  • Decorators for modifying classes

  • Class methods and properties for class-level behavior

  • Regular inheritance for sharing functionality

Key Takeaways#

  1. Everything in Python is an object - including classes themselves

  2. type is the default metaclass that creates classes

  3. Class creation is a multi-step process involving __prepare__, __new__, and __init__

  4. You can create classes dynamically using type() directly

  5. Custom metaclasses let you control class creation for advanced use cases

  6. Use metaclasses sparingly - they’re powerful but add complexity

Understanding how Python creates classes deepens your knowledge of the language’s internals and opens up powerful metaprogramming capabilities. Even if you never write a custom metaclass, knowing how they work helps you understand Python’s elegant object model.

Now when someone asks you “how are classes created in Python?”, you can confidently say: “By metaclasses! And by default, that metaclass is type.” You might just blow their mind a little. 😊

Discussion

1

Leave a comment