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.
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:
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:
>>> 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
typetypeis the “class of classes”
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!
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.
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.
# 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.
# 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.
# 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
# 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:
type.__init__(MyClass, 'MyClass', (), namespace)
After this, MyClass is ready to use! It’s a fully-formed class object that can create instances.
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)
# 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.
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:
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
One of the most popular uses of metaclasses is implementing the Singleton pattern - ensuring only one instance of a class can exist:
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!
Let’s visualize the complete picture:
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:
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!
>>> isinstance(type, object)
True
>>> isinstance(object, type)
True
>>> issubclass(type, object)
True
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
Everything in Python is an object - including classes themselves
typeis the default metaclass that creates classesClass creation is a multi-step process involving
__prepare__,__new__, and__init__You can create classes dynamically using
type()directlyCustom metaclasses let you control class creation for advanced use cases
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
1Leave a comment