🎉 75% of content is free forever — Unlock Premium from $10/mo →
CW
Search courses…
💼 Servicesℹ️ About✉️ ContactView Pricing Plansfrom $10

Metaclasses: type, __new__, __init_subclass__, Descriptors

PythonMetaclasses & Metaprogramming⭐ Premium

Advertisement

Google & Meta Interview

Metaclasses: type, new, init_subclass, Descriptors

Understanding Python's object model and metaprogramming

Interview Question

"Explain Python metaclasses. What is type and how does it relate to classes? How do __new__ and __init__ differ? When would you use metaclasses vs class decorators vs __init_subclass__?"

Difficulty: Hard | Frequently asked at Google, Meta, Microsoft


Theoretical Foundation

Everything is an Object

In Python, everything is an object, including classes themselves.

# Classes are objects
class MyClass:
    pass

print(f"MyClass type: {type(MyClass)}")
print(f"MyClass id: {id(MyClass)}")
print(f"MyClass dict: {MyClass.__dict__}")

# type is the metaclass of all classes
print(f"\ntype(MyClass): {type(MyClass)}")
print(f"type(type): {type(type)}")
print(f"type is its own type: {type is type(MyClass)}")

Output:

Architecture Diagram
MyClass type: <class 'type'>
MyClass id: 140234866577152
MyClass dict: {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <...>, '__doc__': None}

type(MyClass): <class 'type'>
type(type): <class 'type'>
type is its own type: True

ℹ️

Key Concept: type is the metaclass for all classes. When you define a class, type is used to create it.

Creating Classes Dynamically

# Using type() to create classes
def __init__(self, name, age):
    self.name = name
    self.age = age

def greet(self):
    return f"Hello, {self.name}!"

# type(name, bases, dict)
Person = type('Person', (), {'__init__': __init__, 'greet': greet})

# Usage
person = Person("Alice", 30)
print(person.greet())
print(f"Person type: {type(Person)}")

# With inheritance
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        raise NotImplementedError

def dog_speak(self):
    return f"{self.name} says Woof!"

# Create Dog class dynamically
Dog = type('Dog', (Animal,), {'speak': dog_speak})

dog = Dog("Buddy")
print(dog.speak())
print(f"Dog bases: {Dog.__bases__}")

Output:

Architecture Diagram
Hello, Alice!
Person type: <class 'type'>
Buddy says Woof!
Dog bases: (<class '__main__.Animal'>,)

Metaclasses

Basic Metaclass

class MyMeta(type):
    """Custom metaclass."""
    
    def __new__(cls, name, bases, dict):
        # Called when class is created
        print(f"Creating class: {name}")
        print(f"Bases: {bases}")
        
        # Add custom attributes
        dict['class_id'] = name.lower()
        dict['created_by'] = 'MyMeta'
        
        return super().__new__(cls, name, bases, dict)
    
    def __init__(cls, name, bases, dict):
        # Called after class is created
        print(f"Initializing class: {name}")
        super().__init__(name, bases, dict)
    
    def __call__(cls, *args, **kwargs):
        # Called when class is instantiated
        print(f"Instantiating class: {cls.__name__}")
        instance = super().__call__(*args, **kwargs)
        return instance

# Using metaclass
class MyClass(metaclass=MyMeta):
    def __init__(self, value):
        self.value = value

# Creating instance
obj = MyClass(42)
print(f"obj.value: {obj.value}")
print(f"MyClass.class_id: {MyClass.class_id}")
print(f"MyClass.created_by: {MyClass.created_by}")

Output:

Architecture Diagram
Creating class: MyClass
Bases: ()
Initializing class: MyClass
Instantiating class: MyClass
obj.value: 42
MyClass.class_id: myclass
MyClass.created_by: MyMeta

new vs init

class MyClass:
    def __new__(cls, *args, **kwargs):
        print("__new__ called")
        # Create the instance
        instance = super().__new__(cls)
        return instance
    
    def __init__(self, value):
        print("__init__ called")
        self.value = value

# Usage
print("Creating instance:")
obj = MyClass(42)
print(f"obj.value: {obj.value}")

print("\nCreating another instance:")
obj2 = MyClass(100)
print(f"obj2.value: {obj2.value}")

Output:

Architecture Diagram
Creating instance:
__new__ called
__init__ called
obj.value: 42

Creating another instance:
__new__ called
__init__ called
obj2.value: 100

⚠️

Key Difference: __new__ creates the instance, __init__ initializes it. __new__ is called first.

Singleton Pattern with Metaclass

class SingletonMeta(type):
    """Metaclass for Singleton pattern."""
    
    _instances = {}
    
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Database(metaclass=SingletonMeta):
    def __init__(self):
        self.connection = "Connected"
    
    def query(self, sql):
        return f"Executing: {sql}"

# Usage
db1 = Database()
db2 = Database()

print(f"db1 is db2: {db1 is db2}")
print(f"db1.connection: {db1.connection}")
print(f"db2.query('SELECT *'): {db2.query('SELECT *')}")

Output:

Architecture Diagram
db1 is db2: True
db1.connection: Connected
db2.query('SELECT *'): Executing: SELECT *

init_subclass

Modern Alternative to Metaclasses

class BasePlugin:
    """Base class for plugins using __init_subclass__."""
    
    _plugins = {}
    
    def __init_subclass__(cls, plugin_name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        # Register plugin
        name = plugin_name or cls.__name__
        BasePlugin._plugins[name] = cls
    
    @classmethod
    def get_plugin(cls, name):
        return cls._plugins.get(name)

class JSONPlugin(BasePlugin, plugin_name="json"):
    def process(self, data):
        return f"JSON: {data}"

class XMLPlugin(BasePlugin, plugin_name="xml"):
    def process(self, data):
        return f"XML: {data}"

# Usage
print(f"Available plugins: {list(BasePlugin._plugins.keys())}")

json_plugin = JSONPlugin()
xml_plugin = XMLPlugin()

print(json_plugin.process("data"))
print(xml_plugin.process("data"))

# Get plugin by name
PluginClass = BasePlugin.get_plugin("json")
plugin = PluginClass()
print(plugin.process("test"))

Output:

Architecture Diagram
Available plugins: ['json', 'xml']
JSON: data
XML: data
JSON: test

Validation with init_subclass

class Validated:
    """Base class that validates subclass attributes."""
    
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        
        # Check for required attributes
        if not hasattr(cls, 'name'):
            raise TypeError(f"Class {cls.__name__} must define 'name'")
        
        if not isinstance(cls.name, str):
            raise TypeError(f"{cls.__name__}.name must be a string")

class User(Validated):
    name = "user"  # Required
    email = "user@example.com"

class Admin(Validated):
    name = "admin"  # Required
    permissions = ["read", "write"]

# This would raise TypeError
# class BadClass(Validated):
#     pass  # Missing 'name'

print(f"User.name: {User.name}")
print(f"Admin.name: {Admin.name}")

💡

Interview Tip: __init_subclass__ is often simpler and more Pythonic than metaclasses for many use cases.


Descriptor Protocol

Understanding Descriptors

Descriptors are objects that define __get__, __set__, or __delete__.

class Property:
    """Simple property descriptor."""
    
    def __init__(self, fget=None, fset=None, fdel=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)
    
    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)
    
    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)
    
    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel)
    
    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel)
    
    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel)

# Usage
class Temperature:
    def __init__(self):
        self._celsius = 0
    
    @Property
    def celsius(self):
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero")
        self._celsius = value
    
    @Property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        self._celsius = (value - 32) * 5/9

# Usage
temp = Temperature()
temp.celsius = 100
print(f"Celsius: {temp.celsius}")
print(f"Fahrenheit: {temp.fahrenheit}")

temp.fahrenheit = 32
print(f"New Celsius: {temp.celsius}")

Output:

Architecture Diagram
Celsius: 100
Fahrenheit: 212.0
New Celsius: 0.0

Class and Static Methods as Descriptors

class ClassMethod:
    """Class method descriptor."""
    
    def __init__(self, func):
        self.func = func
    
    def __get__(self, obj, objtype=None):
        if objtype is None:
            objtype = type(obj)
        
        def method(*args, **kwargs):
            return self.func(objtype, *args, **kwargs)
        return method

class StaticMethod:
    """Static method descriptor."""
    
    def __init__(self, func):
        self.func = func
    
    def __get__(self, obj, objtype=None):
        return self.func

class MyClass:
    @ClassMethod
    def create(cls, value):
        return cls(value)
    
    @StaticMethod
    def utility(x, y):
        return x + y

# Usage
obj = MyClass.create(42)
print(f"Created: {obj}")
print(f"Utility: {MyClass.utility(1, 2)}")

Metaclass Applications

Validation Metaclass

class ValidatedMeta(type):
    """Metaclass that validates class definitions."""
    
    def __new__(cls, name, bases, dict):
        # Skip validation for base class
        if not bases:
            return super().__new__(cls, name, bases, dict)
        
        # Validate required methods
        required_methods = ['process', 'validate']
        for method in required_methods:
            if method not in dict:
                raise TypeError(f"Class {name} must implement '{method}'")
        
        # Validate type hints
        annotations = dict.get('__annotations__', {})
        for attr, expected_type in annotations.items():
            if attr in dict:
                value = dict[attr]
                if not isinstance(value, expected_type):
                    raise TypeError(
                        f"{name}.{attr} must be {expected_type}, got {type(value)}"
                    )
        
        return super().__new__(cls, name, bases, dict)

class BaseHandler(metaclass=ValidatedMeta):
    """Base handler with validation."""
    pass

# This works
class ValidHandler(BaseHandler):
    name: str = "valid"
    
    def process(self, data):
        return data
    
    def validate(self, data):
        return True

# This would raise TypeError
# class InvalidHandler(BaseHandler):
#     name: str = "valid"
#     # Missing process and validate methods

print(f"ValidHandler.name: {ValidHandler.name}")

Registry Metaclass

class RegistryMeta(type):
    """Metaclass that maintains a registry of classes."""
    
    _registry = {}
    
    def __new__(cls, name, bases, dict):
        new_class = super().__new__(cls, name, bases, dict)
        
        # Don't register the base class
        if bases:
            cls._registry[name] = new_class
        
        return new_class
    
    @classmethod
    def get_registry(cls):
        return cls._registry.copy()
    
    @classmethod
    def get_class(cls, name):
        return cls._registry.get(name)

class Serializer(metaclass=RegistryMeta):
    """Base serializer class."""
    pass

class JSONSerializer(Serializer):
    def serialize(self, data):
        return f"JSON: {data}"

class XMLSerializer(Serializer):
    def serialize(self, data):
        return f"XML: {data}"

# Usage
print(f"Registry: {list(RegistryMeta.get_registry().keys())}")

json_serializer = JSONSerializer()
print(json_serializer.serialize("data"))

# Get class from registry
SerializerClass = RegistryMeta.get_class("XMLSerializer")
serializer = SerializerClass()
print(serializer.serialize("data"))

ℹ️

Pattern: Metaclass registries are used in frameworks like Django and SQLAlchemy for automatic class registration.


Metaclasses vs Alternatives

Comparison

# 1. Metaclass approach
class MetaApproach(type):
    def __new__(cls, name, bases, dict):
        dict['created_by'] = 'Meta'
        return super().__new__(cls, name, bases, dict)

class Class1(metaclass=MetaApproach):
    pass

# 2. Class decorator approach
def class_decorator(cls):
    cls.created_by = 'Decorator'
    return cls

@class_decorator
class Class2:
    pass

# 3. __init_subclass__ approach
class Base:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.created_by = 'InitSubclass'

class Class3(Base):
    pass

# All achieve similar results
print(f"Class1.created_by: {Class1.created_by}")
print(f"Class2.created_by: {Class2.created_by}")
print(f"Class3.created_by: {Class3.created_by}")

When to Use What?

TechniqueUse CaseComplexity
MetaclassComplex class modification, framework internalsHigh
Class DecoratorSimple class modificationLow
init_subclassSubclass registration, validationMedium
DescriptorAttribute access controlMedium

💡

Interview Tip: Start with simpler alternatives (decorators, __init_subclass__). Only use metaclasses when necessary.


Practical Examples

ORM-Style Field Definition

class Field:
    """Base field descriptor."""
    
    def __init__(self, field_type, required=True, default=None):
        self.field_type = field_type
        self.required = required
        self.default = default
    
    def __set_name__(self, owner, name):
        self.name = name
        self.private_name = f'_{name}'
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self.private_name, self.default)
    
    def __set__(self, obj, value):
        if value is None and self.required:
            raise ValueError(f"{self.name} is required")
        if value is not None and not isinstance(value, self.field_type):
            raise TypeError(f"{self.name} must be {self.field_type}")
        setattr(obj, self.private_name, value)

class ModelMeta(type):
    """Metaclass for model classes."""
    
    def __new__(cls, name, bases, dict):
        fields = {}
        
        # Collect fields from base classes
        for base in bases:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        
        # Collect fields from current class
        for key, value in dict.items():
            if isinstance(value, Field):
                fields[key] = value
        
        dict['_fields'] = fields
        
        return super().__new__(cls, name, bases, dict)

class Model(metaclass=ModelMeta):
    """Base model class."""
    
    def __init__(self, **kwargs):
        for field_name, field in self._fields.items():
            value = kwargs.get(field_name, field.default)
            setattr(self, field_name, value)
    
    def to_dict(self):
        return {name: getattr(self, name) for name in self._fields}

class User(Model):
    name = Field(str, required=True)
    email = Field(str, required=True)
    age = Field(int, required=False, default=0)

# Usage
user = User(name="Alice", email="alice@example.com", age=30)
print(f"User: {user.to_dict()}")

# Validation works
try:
    bad_user = User(name=123)  # Wrong type
except TypeError as e:
    print(f"Error: {e}")

Output:

Architecture Diagram
User: {'name': 'Alice', 'email': 'alice@example.com', 'age': 30}
Error: name must be <class 'str'>

API Endpoint Registration

class APIMeta(type):
    """Metaclass for API endpoint registration."""
    
    _endpoints = {}
    
    def __new__(cls, name, bases, dict):
        new_class = super().__new__(cls, name, bases, dict)
        
        if bases:  # Not base class
            # Register methods decorated with @endpoint
            for method_name, method in dict.items():
                if hasattr(method, '_endpoint'):
                    path = method._endpoint
                    cls._endpoints[path] = {
                        'class': new_class,
                        'method': method_name,
                        'methods': method._methods
                    }
        
        return new_class
    
    @classmethod
    def get_endpoints(cls):
        return cls._endpoints.copy()

def endpoint(path, methods=None):
    """Decorator to register API endpoint."""
    if methods is None:
        methods = ['GET']
    
    def decorator(func):
        func._endpoint = path
        func._methods = methods
        return func
    return decorator

class UserAPI(metaclass=APIMeta):
    @endpoint('/users', methods=['GET'])
    def list_users(self):
        return "List users"
    
    @endpoint('/users', methods=['POST'])
    def create_user(self):
        return "Create user"
    
    @endpoint('/users/{id}', methods=['GET'])
    def get_user(self, user_id):
        return f"Get user {user_id}"

# Usage
print("Registered endpoints:")
for path, info in APIMeta.get_endpoints().items():
    print(f"  {path}: {info['method']} ({info['methods']})")

Output:

Architecture Diagram
Registered endpoints:
  /users: list_users (['GET'])
  /users: create_user (['POST'])
  /users/{id}: get_user (['GET'])

ℹ️

Framework Pattern: This pattern is used in FastAPI, Django, and other web frameworks for automatic route registration.


Complexity Analysis

Performance Impact

OperationWithout MetaclassWith MetaclassOverhead
Class creationO(1)O(n)One-time
Instance creationO(1)O(1)Negligible
Attribute accessO(1)O(1)Negligible

Memory Usage

import sys

class RegularClass:
    def __init__(self):
        self.value = 42

class MetaClass(type):
    def __new__(cls, name, bases, dict):
        dict['meta_attr'] = 'meta'
        return super().__new__(cls, name, bases, dict)

class MetaclassedClass(metaclass=MetaClass):
    def __init__(self):
        self.value = 42

# Compare memory
regular = RegularClass()
metaclassed = MetaclassedClass()

print(f"RegularClass size: {sys.getsizeof(regular)} bytes")
print(f"MetaclassedClass size: {sys.getsizeof(metaclassed)} bytes")

Interview Tips

Common Follow-up Questions

  1. "What's the difference between __new__ and __init__?"

    • __new__: Creates the instance (static method)
    • __init__: Initializes the instance
    • __new__ is called first
  2. "When would you use a metaclass?"

    • Class registration
    • Validation
    • Modification of class behavior
    • Framework internals
  3. "What are the alternatives to metaclasses?"

    • Class decorators
    • __init_subclass__
    • Descriptors
    • Simple inheritance

Code Review Tips

# BAD: Unnecessary metaclass
class OverkillMeta(type):
    pass

class MyClass(metaclass=OverkillMeta):
    pass

# GOOD: Simple class decorator
def simple_decorator(cls):
    cls.created_by = 'Decorator'
    return cls

@simple_decorator
class MyClass:
    pass

# BAD: Metaclass for simple registration
class RegistryMeta(type):
    _registry = {}
    def __new__(cls, name, bases, dict):
        cls._registry[name] = super().__new__(cls, name, bases, dict)
        return cls._registry[name]

# GOOD: __init_subclass__ for registration
class Registry:
    _registry = {}
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        Registry._registry[cls.__name__] = cls

⚠️

Common Mistake: Using metaclasses when simpler alternatives exist. Always prefer decorators or __init_subclass__ first.


Summary

ConceptPurposeWhen to Use
typeCreate/inspect classesDynamic class creation
MetaclassModify class creationComplex class modification
newCreate instancesSingleton, immutable types
init_subclassSubclass hookRegistration, validation
DescriptorAttribute accessProperties, computed attrs

Best Practices

  1. Prefer simpler alternatives (decorators, __init_subclass__)
  2. Use metaclasses sparingly for complex cases
  3. Document metaclass usage clearly
  4. Test metaclass behavior thoroughly
  5. Consider readability over cleverness

ℹ️

Key Takeaway: Metaclasses are powerful but complex. Use them only when simpler alternatives won't work.


Practice Problems

  1. Singleton Metaclass: Implement a thread-safe singleton metaclass
  2. Validation Metaclass: Create a metaclass that validates class attributes
  3. Registry Pattern: Build a plugin registry using metaclasses
  4. ORM Fields: Implement database field descriptors
  5. API Router: Create an automatic API route registration system

Further Reading

  • PEP 3119: Abstract Base Classes
  • PEP 3135: New super()
  • Python Docs: type, object.__init_subclass__
  • Books: "Python Metaprogramming" by David Mertz

Remember: Metaclasses are the "class of a class." They control how classes are created and behave. Use them wisely.

Advertisement