Python Dynamic Class Creation Practice Problems & Exercises
Practice: Dynamic Class Creation
← Back to lessonEasy
Create a Rectangle class entirely at runtime using the three-argument form of type(). It must have __init__, __repr__, and an area() method.
def rect_init(self, width, height):
self.width = width
self.height = height
def rect_repr(self):
return f"Rectangle(width={self.width}, height={self.height})"
def rect_area(self):
return self.width * self.height
Rectangle = type(
"Rectangle",
(object,),
{
"__init__": rect_init,
"__repr__": rect_repr,
"area": rect_area,
},
)
r = Rectangle(4, 5)
print(r)
print(f"area = {r.area()}")Expected Output
Rectangle(width=4, height=5)
area = 20Hints
Hint 1: type(name, bases, dict) creates a class named name, inheriting from bases, with the given dict as its namespace.
Hint 2: Pass __init__, __repr__, and any methods as regular functions in the dict.
Use type() to create an AdminUser class that dynamically inherits from both User and Auditable.
class User:
def __init__(self, username):
self.username = username
def greet(self):
return f"Hello, {self.username}"
class Auditable:
def audit_log(self):
return f"Audit: {self.__class__.__name__} accessed"
def admin_init(self, username):
User.__init__(self, username)
self.permissions = ["read", "write", "delete"]
AdminUser = type(
"AdminUser",
(User, Auditable), # Dynamic multi-inheritance
{
"__init__": admin_init,
},
)
admin = AdminUser("alice")
print(f"AdminUser is subclass of User: {issubclass(AdminUser, User)}")
print(f"AdminUser is subclass of Auditable: {issubclass(AdminUser, Auditable)}")
print(f"AdminUser permissions: {admin.permissions}")Expected Output
AdminUser is subclass of User: True
AdminUser is subclass of Auditable: True
AdminUser permissions: ['read', 'write', 'delete']Hints
Hint 1: Pass a tuple of base classes as the second argument to type(). The new class inherits from all of them.
Hint 2: The namespace dict overrides or extends anything defined on the parents.
Start with a minimal Robot class, then dynamically attach two new methods (greet and describe) to it at runtime using setattr.
class Robot:
def __init__(self, name, version):
self.name = name
self.version = version
# Dynamically add methods
def greet(self):
return f"Hello, I am a {self.name}"
def describe(self):
return f"{self.name} (v{self.version})"
setattr(Robot, "greet", greet)
setattr(Robot, "describe", describe)
r = Robot("Robot", "2.0")
print(f"greet() added: {r.greet()}")
print(f"describe() added: {r.describe()}")Expected Output
greet() added: Hello, I am a Robot
describe() added: Robot (v2.0)Hints
Hint 1: setattr(MyClass, method_name, function) adds or replaces a method at runtime.
Hint 2: The first parameter of the function will receive the instance (self) when called as a method.
Medium
Write a make_struct factory that generates a named struct class from a list of field names, similar to a simplified namedtuple.
def make_struct(name, fields):
fields = tuple(fields)
def struct_init(self, *args, **kwargs):
if args:
if len(args) != len(fields):
raise TypeError(
f"{name} takes {len(fields)} arguments, got {len(args)}"
)
for f, v in zip(fields, args):
setattr(self, f, v)
else:
for f in fields:
setattr(self, f, kwargs[f])
def struct_repr(self):
parts = [f"{f}={getattr(self, f)!r}" for f in fields]
return f"{name}(" + ", ".join(parts) + ")"
def struct_eq(self, other):
if type(self) is not type(other):
return NotImplemented
return all(getattr(self, f) == getattr(other, f) for f in fields)
return type(
name,
(object,),
{
"__init__": struct_init,
"__repr__": struct_repr,
"__eq__": struct_eq,
"_fields": fields,
},
)
Point = make_struct("Point", ["x", "y"])
Color = make_struct("Color", ["r", "g", "b"])
p = Point(1, 2)
c = Color(255, 128, 0)
print(p)
print(c)
print(f"Point fields: {Point._fields}")
print(f"Color fields: {Color._fields}")Expected Output
Point(x=1, y=2)
Color(r=255, g=128, b=0)
Point fields: ('x', 'y')
Color fields: ('r', 'g', 'b')Hints
Hint 1: Write a function make_struct(name, fields) that builds a class with __init__, __repr__, and a _fields class attribute.
Hint 2: Build __init__ by dynamically assigning each field from *args or **kwargs.
Build a make_enum factory that creates an enum-like class from a list of member names, with integer values starting from 0. Support lookup by value and by name.
def make_enum(name, members):
namespace = {}
value_to_name = {}
for idx, member in enumerate(members):
namespace[member] = idx
value_to_name[idx] = member
namespace["_value_to_name"] = value_to_name
namespace["_members"] = {m: i for i, m in enumerate(members)}
@classmethod
def by_value(cls, val):
return cls._value_to_name[val]
@classmethod
def from_name(cls, n):
return cls._members[n]
namespace["__getitem__"] = by_value.__func__ # class[val]
namespace["by_value"] = by_value
namespace["from_name"] = from_name
cls = type(name, (object,), namespace)
# Make Status[1] work via class-level __getitem__
# We need to attach it on the metaclass level — use a simple workaround:
cls.__class_getitem__ = classmethod(lambda c, val: c._value_to_name[val])
return cls
Status = make_enum("Status", ["PENDING", "ACTIVE", "CLOSED"])
print(f"Status.PENDING = {Status.PENDING}")
print(f"Status.ACTIVE = {Status.ACTIVE}")
print(f"Status.CLOSED = {Status.CLOSED}")
print(f"Status[1] = {Status.by_value(1)}")
print(f"Status('ACTIVE') = {Status.from_name('ACTIVE')}")Expected Output
Status.PENDING = 0
Status.ACTIVE = 1
Status.CLOSED = 2
Status[1] = ACTIVE
Status('ACTIVE') = 1Hints
Hint 1: Create the class namespace with each member name as a key and its integer index as the value.
Hint 2: Add a __getitem__ classmethod (or a class-level dict _by_value) to support lookup by integer. Add __call__ support by adding __call__ or __new__.
Write an inject_mixins function that takes a base class and any number of mixin classes and returns a new dynamically-created class that inherits from all of them.
def inject_mixins(cls, *mixins):
"""Return a new class that inherits from cls and all mixins."""
name = cls.__name__ + "Extended"
return type(name, (cls, *mixins), {})
class Service:
def __init__(self, name):
self.name = name
def serialize(self):
return f"Service({self.name!r})"
class JsonMixin:
def serialize(self):
import json
return json.dumps({"name": self.name, "age": 30})
class LogMixin:
def log_call(self, method_name):
print(f"[LOG] {self.__class__.__name__}.{method_name} called")
class CacheMixin:
_cache = {}
def cached_serialize(self):
key = self.name
if key not in self._cache:
self._cache[key] = self.serialize()
return self._cache[key]
return "cache hit"
LoggedService = inject_mixins(Service, LogMixin)
JsonService = inject_mixins(Service, JsonMixin)
CachedJsonService = inject_mixins(Service, JsonMixin, CacheMixin)
ls = LoggedService("svc")
print(f"LoggedService methods: {[m for m in dir(ls) if not m.startswith('_') and callable(getattr(ls, m))][:2]}")
js = JsonService("Alice")
print(f"JsonMixin method works: {js.serialize()}")
cjs = CachedJsonService("Alice")
_ = cjs.cached_serialize() # fills cache
print(f"CachedMixin method works: {cjs.cached_serialize()}")Expected Output
LoggedService methods: serialize, log_call
JsonMixin method works: {"name": "Alice", "age": 30}
CachedMixin method works: cache hitHints
Hint 1: Create a function inject_mixins(cls, *mixins) that builds a new class via type() with (cls, *mixins) as the bases.
Hint 2: The new class name can be something like cls.__name__ + "Extended".
Write a ClassBuilder that provides a fluent interface for declaring fields with validators, then generates a class dynamically.
class ClassBuilder:
def __init__(self, name):
self.name = name
self._fields = [] # list of (field_name, field_type, validator_or_None)
def field(self, name, field_type, validator=None):
self._fields.append((name, field_type, validator))
return self # fluent
def build(self):
fields = list(self._fields)
class_name = self.name
def generated_init(self_inner, *args, **kwargs):
if args:
pairs = zip(fields, args)
else:
pairs = ((f, kwargs[f[0]]) for f in fields)
for (fname, ftype, fvalidator), value in pairs:
if not isinstance(value, ftype):
try:
value = ftype(value)
except Exception:
raise TypeError(f"{fname} must be {ftype.__name__}")
if fvalidator:
fvalidator(fname, value)
setattr(self_inner, fname, value)
def generated_repr(self_inner):
parts = [f"{f[0]}={getattr(self_inner, f[0])!r}" for f in fields]
return f"{class_name}(" + ", ".join(parts) + ")"
return type(
class_name,
(object,),
{
"__init__": generated_init,
"__repr__": generated_repr,
"_fields_meta": fields,
},
)
def positive(name, value):
if value <= 0:
raise ValueError(f"{name} must be > 0, got {value}")
Order = (
ClassBuilder("Order")
.field("item", str)
.field("qty", int, positive)
.field("price", float, positive)
.build()
)
o = Order("Widget", 5, 9.99)
print(f"{o} total={o.qty * o.price:.2f}")
try:
Order("Widget", 0, 9.99)
except ValueError as e:
print(f"ValueError: {e}")
try:
Order("Widget", 3, -1.0)
except ValueError as e:
print(f"ValueError: {e}")Expected Output
Order(item='Widget', qty=5, price=9.99) total=49.95
ValueError: qty must be > 0, got 0
ValueError: price must be > 0, got -1.0Hints
Hint 1: Build a ClassBuilder that accumulates field definitions (name, type, validator) and emits the class when .build() is called.
Hint 2: Generate __init__ that runs each validator against the corresponding argument.
Hard
Build a dataclass_like function that inspects a class body's __annotations__ and injects __init__, __repr__, __eq__, and __hash__. Add a frozen flag that prevents attribute mutation.
class FrozenInstanceError(AttributeError):
pass
def dataclass_like(cls=None, frozen=False):
def decorator(klass):
annotations = klass.__dict__.get("__annotations__", {})
fields = list(annotations.keys())
defaults = {
f: klass.__dict__[f]
for f in fields
if f in klass.__dict__
}
# Build __init__
def generated_init(self, *args, **kwargs):
bound = {}
for i, fname in enumerate(fields):
if i < len(args):
bound[fname] = args[i]
elif fname in kwargs:
bound[fname] = kwargs[fname]
elif fname in defaults:
bound[fname] = defaults[fname]
else:
raise TypeError(f"Missing field: {fname}")
for fname, value in bound.items():
object.__setattr__(self, fname, value)
def generated_repr(self):
parts = [f"{f}={getattr(self, f)!r}" for f in fields]
return f"{klass.__name__}(" + ", ".join(parts) + ")"
def generated_eq(self, other):
if type(self) is not type(other):
return NotImplemented
return all(getattr(self, f) == getattr(other, f) for f in fields)
def generated_hash(self):
return hash(tuple(getattr(self, f) for f in fields))
new_ns = dict(klass.__dict__)
new_ns["__init__"] = generated_init
new_ns["__repr__"] = generated_repr
new_ns["__eq__"] = generated_eq
if frozen:
new_ns["__hash__"] = generated_hash
def frozen_setattr(self, name, value):
if name in fields:
raise FrozenInstanceError(
f"cannot assign to field '{name}'"
)
object.__setattr__(self, name, value)
new_ns["__setattr__"] = frozen_setattr
return type(klass.__name__, klass.__bases__, new_ns)
if cls is not None:
return decorator(cls)
return decorator
@dataclass_like(frozen=True)
class Point3D:
x: float
y: float
z: float
p1 = Point3D(1.0, 2.0, 3.0)
p2 = Point3D(1.0, 2.0, 3.0)
print(p1)
print(f"Equal points: {p1 == p2}")
print(f"Hash works: {hash(p1) == hash(p2)}")
try:
p1.x = 99.0
except FrozenInstanceError as e:
print(f"Frozen violation: FrozenInstanceError: {e}")Expected Output
Point3D(x=1.0, y=2.0, z=3.0)
Equal points: True
Hash works: True
Frozen violation: FrozenInstanceError: cannot assign to field 'x'Hints
Hint 1: Inspect the class __annotations__ dict to extract field names and optional default values.
Hint 2: Generate __init__ from annotations, __repr__ from field names, __eq__ by comparing all field values, and optionally __hash__ when frozen=True.
Hint 3: For frozen, generate __setattr__ and __delattr__ that raise FrozenInstanceError.
Build a runtime class patcher that supports versioned method replacement and rollback. Each patch() saves the prior state; rollback() restores it.
class ClassPatcher:
_history = {} # cls -> list of (method_name, old_method)
@classmethod
def patch(cls, target_cls, method_name, new_func):
if target_cls not in cls._history:
cls._history[target_cls] = []
old = getattr(target_cls, method_name, None)
cls._history[target_cls].append((method_name, old))
setattr(target_cls, method_name, new_func)
@classmethod
def rollback(cls, target_cls):
if not cls._history.get(target_cls):
raise RuntimeError("Nothing to rollback")
method_name, old = cls._history[target_cls].pop()
if old is None:
delattr(target_cls, method_name)
else:
setattr(target_cls, method_name, old)
class Greeter:
def say_hello(self):
return "hello from original"
g = Greeter()
print(f"v1: {g.say_hello()}")
def say_hello_v2(self):
return "hello from patched (double)"
ClassPatcher.patch(Greeter, "say_hello", say_hello_v2)
print(f"v2: {g.say_hello()}")
def say_hello_v3(self):
result = "hello from patched (double) with logging"
print(f"[LOG] say_hello called")
return result
ClassPatcher.patch(Greeter, "say_hello", say_hello_v3)
print(f"v3: {g.say_hello()}")
# Roll back twice
ClassPatcher.rollback(Greeter)
ClassPatcher.rollback(Greeter)
print(f"Rolled back to v1: {g.say_hello()}")Expected Output
v1: hello from original
v2: hello from patched (double)
v3: hello from patched (double) with logging
Rolled back to v1: hello from originalHints
Hint 1: Maintain a stack of (method_name, old_method) tuples on the class to support rollback.
Hint 2: patch(cls, method_name, new_func) saves the old method, applies the new one, and records the change. rollback(cls) pops and restores the previous method.
Build a make_interface factory that creates an interface class. Any subclass is checked at definition time to ensure it implements all required methods.
class InterfaceError(TypeError):
pass
def make_interface(name, required_methods):
required = frozenset(required_methods)
def interface_init_subclass(cls, **kwargs):
super_cls = interface_base
type.__init_subclass__.__func__(super_cls)
missing = required - set(cls.__dict__)
if missing:
raise InterfaceError(
f"{cls.__name__} missing required methods: {missing}"
)
interface_base = type(
name,
(object,),
{
"__init_subclass__": classmethod(
lambda cls, **kw: (
setattr(cls, "_implements_" + name, True),
(_ for _ in ()).throw(
InterfaceError(
f"{cls.__name__} missing required methods: "
f"{required - set(cls.__dict__)}"
)
) if (required - set(cls.__dict__)) else None,
)
),
},
)
# Cleaner approach: override __init_subclass__ directly
def clean_init_subclass(cls, **kwargs):
missing = required - set(cls.__dict__)
if missing:
raise InterfaceError(
f"{cls.__name__} missing required methods: {missing}"
)
interface_base.__init_subclass__ = classmethod(clean_init_subclass)
return interface_base
Serializable = make_interface("Serializable", ["serialize", "deserialize"])
class JSONSerializer(Serializable):
def serialize(self, data):
import json
return json.dumps(data)
def deserialize(self, text):
import json
return json.loads(text)
print(f"JSONSerializer implements interface: {hasattr(JSONSerializer, 'serialize')}")
try:
class XMLSerializer(Serializable):
def deserialize(self, text):
return text
except InterfaceError as e:
print(f"InterfaceError: {e}")Expected Output
JSONSerializer implements interface: True
InterfaceError: XMLSerializer missing required methods: {'serialize'}Hints
Hint 1: Write make_interface(name, required_methods) that returns an interface class with a custom __init_subclass__ verifying required methods.
Hint 2: Alternatively, build a validates_interface(cls, interface) checker that walks cls.__dict__ and checks for each required method name.
Build a make_typed_node factory that generates typed node classes in a registry. Each node validates its value type. The base class tracks all generated node types.
class NodeRegistry:
_nodes = {}
def __init_subclass__(cls, value_type=None, **kwargs):
super().__init_subclass__(**kwargs)
if value_type is not None:
cls.value_type = value_type
NodeRegistry._nodes[value_type.__name__] = cls
class Node(NodeRegistry):
value_type = object
def __init__(self, value):
if not isinstance(value, self.value_type):
# Try coercion
try:
value = self.value_type(value)
except (TypeError, ValueError) as exc:
raise TypeError(
f"{type(self).__name__}: cannot coerce {value!r} to "
f"{self.value_type.__name__}: {exc}"
)
self.value = value
def __repr__(self):
return f"{type(self).__name__}({self.value!r})"
def make_typed_node(value_type):
class_name = value_type.__name__.capitalize() + "Node"
return type(
class_name,
(Node,),
{"value_type": value_type, "__init_subclass_kw__": {"value_type": value_type}},
)
# Register via __init_subclass__ keyword
class IntNode(Node, value_type=int):
pass
class StrNode(Node, value_type=str):
pass
class FloatNode(Node, value_type=float):
pass
print(f"Node classes: {list(NodeRegistry._nodes.keys())}")
n1 = IntNode(42)
n2 = StrNode("hello")
n3 = FloatNode(3) # coercion from int
print(f"IntNode(42) valid: {isinstance(n1.value, int)}")
print(f"StrNode('hello') valid: {isinstance(n2.value, str)}")
print(f"FloatNode(3.14) coerced from int: {FloatNode(3.0).value}")Expected Output
Node classes: ['IntNode', 'StrNode', 'FloatNode']
IntNode(42) valid: True
StrNode('hello') valid: True
FloatNode(3.14) coerced from int: 3.0Hints
Hint 1: Write make_typed_node(value_type) that returns a class subclassing a base Node, with __init__ validating and storing the value.
Hint 2: Use a shared metaclass or __init_subclass__ on Node to register every generated class by value_type name.
