Metaprogramming: Metaclasses & Descriptors

Python-এর গভীরতম OOP — metaclass ও descriptor

Read: ~40 min Hard · Advanced 5 practice problems Live code runner

1. Everything Is an Object — Including Classes

In Python, a class itself is an object, and its type is its metaclass. The default metaclass is type. Just as an instance of Dog is built by calling Dog(), the class Dog itself is built by calling type("Dog", ...).

Python-এ class নিজেই একটি object, এবং এর type হলো তার metaclass। ডিফল্ট metaclass হলো type। যেমন Dog() একটি instance তৈরি করে, তেমনি type("Dog", ...) Dog class-টিই তৈরি করে।
type_is_class.py
class Dog:
    pass

print(type(Dog()))       # <class '__main__.Dog'>
print(type(Dog))         # <class 'type'>

# Create a class dynamically with type(name, bases, namespace)
Cat = type("Cat", (), {"speak": lambda self: "meow"})
print(Cat().speak())

2. __init_subclass__ — the 80/20 Metaclass

Before reaching for a true metaclass, try __init_subclass__. It runs whenever a subclass is defined and handles the vast majority of real use-cases: registration, validation, auto-tagging.

Metaclass লেখার আগে __init_subclass__ ভাবুন। প্রতিবার subclass সংজ্ঞায়িত হলে এটি চলে — registration, validation, auto-tagging-এর মতো বেশিরভাগ কাজ এতেই হয়।
init_subclass.py
class Plugin:
    registry = []
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        Plugin.registry.append(cls)

class EmailPlugin(Plugin): pass
class SmsPlugin(Plugin): pass

print([p.__name__ for p in Plugin.registry])

3. A Real Metaclass

A metaclass inherits from type and overrides __new__ or __init__. Use it only when you need to transform the class body itself.

Metaclass type থেকে inherit করে এবং __new__ / __init__ override করে। শুধু তখনই ব্যবহার করুন যখন class-body নিজেই বদলাতে হবে।
metaclass.py
class UpperAttrMeta(type):
    def __new__(mcs, name, bases, ns):
        upper = {k.upper() if not k.startswith("__") else k: v
                 for k, v in ns.items()}
        return super().__new__(mcs, name, bases, upper)

class Config(metaclass=UpperAttrMeta):
    host = "localhost"
    port = 8080

print(Config.HOST, Config.PORT)
Tim Peters' wisdom: "Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't."

Tim Peters-এর কথা: "Metaclass ৯৯% ব্যবহারকারীর ভাবা দরকার নেই। প্রয়োজন আছে কিনা সন্দেহ হলে নেই।"

4. Descriptors — Controlled Attribute Access

A descriptor is any object that defines __get__, __set__ or __delete__. Python's property, staticmethod, and classmethod are all descriptors. You can use them to build typed, validated attributes.

Descriptor হলো এমন object যার __get__, __set__ বা __delete__ আছে। Python-এর property, staticmethod, classmethod সবই descriptor। এটি দিয়ে type-safe ও validated attribute বানানো যায়।
descriptor.py
class Positive:
    def __set_name__(self, owner, name):
        self.name = "_" + name
    def __get__(self, obj, owner=None):
        return getattr(obj, self.name)
    def __set__(self, obj, value):
        if value <= 0:
            raise ValueError(f"{self.name[1:]} must be positive")
        setattr(obj, self.name, value)

class Product:
    price    = Positive()
    quantity = Positive()
    def __init__(self, p, q):
        self.price, self.quantity = p, q

p = Product(100, 5)
print(p.price, p.quantity)
try:
    p.price = -1
except ValueError as e:
    print("caught:", e)

5. Class Decorators — The Middle Ground

A class decorator is a function that takes a class and returns a (usually modified) class. It's simpler than a metaclass and covers almost all practical transformations.

Class decorator একটি function যা class নিয়ে (প্রায়ই পরিবর্তিত) class ফেরত দেয়। Metaclass-এর চেয়ে সহজ এবং প্রায় সব প্রয়োজন মেটায়।
class_deco.py
def auto_repr(cls):
    def __repr__(self):
        attrs = ", ".join(f"{k}={v!r}" for k, v in vars(self).items())
        return f"{cls.__name__}({attrs})"
    cls.__repr__ = __repr__
    return cls

@auto_repr
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

print(Point(3, 4))

6. When to Use What

Which tool for the job? Attribute logic @property Descriptor Add methods / transform body Class decorator Subclass hook __init_subclass__ Rewrite class creation itself Metaclass (last resort) Figure 34.1 — সহজ থেকে জটিল দিকে যান: property → decorator → __init_subclass__ → metaclass।

7. Vocabulary (শব্দভাণ্ডার)

TermMeaningবাংলায়
MetaclassThe class of a class; default is type.Class-এর class; ডিফল্ট type।
DescriptorObject defining __get__/__set__.__get__/__set__-সম্পন্ন object।
__init_subclass__Hook run when a subclass is created.Subclass তৈরিতে চলা hook।
Class decoratorFunction taking & returning a class.Class নেয়, class ফেরত দেয় এমন function।
MROMethod Resolution Order.Method খোঁজার ক্রম।
__set_name__Hook to learn the attribute name the descriptor is bound to.Descriptor-এর attribute-নাম জানানোর hook।

8. Practice Problems

  1. Build a class dynamically with type(name, bases, ns) that has a method area returning 100.
    type(name, bases, ns) দিয়ে dynamically একটি class বানান — area method যা ১০০ ফেরত দেবে।
    ✨ Show Answer (উত্তর দেখুন)
    ans1.py
    Shape = type("Shape", (), {"area": lambda self: 100})
    print(Shape().area())
  2. Write a descriptor NonEmptyString that raises ValueError on empty strings.
    একটি descriptor NonEmptyString লিখুন — ফাঁকা string দিলে ValueError দেবে।
    ✨ Show Answer (উত্তর দেখুন)
    ans2.py
    class NonEmptyString:
        def __set_name__(s, o, n): s.n = "_"+n
        def __get__(s, o, t=None): return getattr(o, s.n)
        def __set__(s, o, v):
            if not v: raise ValueError(s.n)
            setattr(o, s.n, v)
    
    class User:
        name = NonEmptyString()
        def __init__(self, n): self.name = n
    
    print(User("Arif").name)
    try: User("")
    except ValueError as e: print("empty blocked", e)
  3. Use __init_subclass__ to auto-register every subclass in a list.
    __init_subclass__ দিয়ে প্রতিটি subclass-কে auto-register করুন।
    ✨ Show Answer (উত্তর দেখুন)
    ans3.py
    class Base:
        subs = []
        def __init_subclass__(cls, **kw):
            Base.subs.append(cls.__name__)
    class A(Base): pass
    class B(Base): pass
    print(Base.subs)
  4. Why is a metaclass rarely the right tool? Answer in two sentences.
    Metaclass কেন সচরাচর সঠিক tool নয় — দুই বাক্যে।
    ✨ Show Answer (উত্তর দেখুন)

    Answer: Metaclasses complicate inheritance and make code harder to read, debug and maintain. Simpler mechanisms — property, descriptors, class decorators, and __init_subclass__ — cover almost every real need and compose better.

    Metaclass inheritance জটিল করে, কোড পড়া-debug-maintain কঠিন করে। property, descriptor, class decorator ও __init_subclass__ — সহজ বিকল্পগুলো প্রায় সব বাস্তব প্রয়োজন পূরণ করে।

  5. Write a class decorator @final_log that prints a message each time an instance is created.
    একটি class decorator @final_log লিখুন — প্রতিটি instance তৈরিতে বার্তা প্রিন্ট করবে।
    ✨ Show Answer (উত্তর দেখুন)
    ans5.py
    def final_log(cls):
        original = cls.__init__
        def new_init(self, *a, **kw):
            print(f"creating {cls.__name__}")
            original(self, *a, **kw)
        cls.__init__ = new_init
        return cls
    
    @final_log
    class Widget:
        def __init__(self, n): self.n = n
    Widget("button"); Widget("slider")

Summary — Module 34

Everything in Python is an object, including classes — whose type is their metaclass. Descriptors (__get__/__set__) power property and typed attributes. Class decorators and __init_subclass__ handle 95% of metaprogramming; keep metaclasses for the rare deep case.

Python-এ সবই object — class-ও। Class-এর type হলো metaclass। Descriptor property ও typed attribute তৈরি করে। ৯৫% কাজ class decorator ও __init_subclass__-এ হয় — metaclass শুধু একান্ত প্রয়োজনেই।

Next Module → Algorithmic Problem Solving in Python (LeetCode style)।