اگر داری پایتون یاد میگیری، احتمالاً تا الان با کلاس‌ها (Classes) و شی‌گرایی حسابی رفیق شدی. میدونی که کلاس ‌ها مثل یه نقشه یا قالب هستن که از روشون آبجکت‌ها (Objects) رو میسازیم. اما تا حالا از خودت پرسیدی "خود این کلاس ‌ها از روی چی ساخته میشن؟"  اینجاست که پای یه مفهوم خفن و کمی ترسناک به اسم متاکلاس (Metaclass) میاد وسط. بیا با هم بدون کلمات قلمبه‌ سلمبه بریم تو دل این ماجرا.

همه‌چیز در پایتون یک آبجکت است! (حتی خود کلاس‌ها)

تو زبان‌ های دیگه مثل C++ یا جاوا، کلاس فقط یه تیکه کده که کامپایل میشه. اما تو پایتون، خود کلاس هم یه آبجکته! یعنی وقتی تو یه کلاس به اسم Car می‌نویسی، پایتون تو پس ‌زمینه یه آبجکت به اسم Car تو مموری میسازه. چون کلاس خودش یه آبجکته، پس حتماً باید توسط یه چیزی ساخته شده باشه، درسته؟ اون "چیز"، همون متا کلاس ماست.

رئیس بزرگ کیه؟ آشنایی با type

احتمالاً تابع type() رو میشناسی. معمولاً ازش استفاده میکنیم تا ببینیم تایپ یه متغیر چیه:

print(type(10))       # <class 'int'>
print(type("Hello"))  # <class 'str'>

حالا بیا یه کلاس بسازیم و ببینیم تایپ خود کلاس چیه:

class MyClass:
    pass
print(type(MyClass))  # <class 'type'> ! سورپرایز !

بله! تایپ خود کلاس‌ ها، چیزی نیست جز type. در واقع، type متا کلاس پیش ‌فرض تو پایتونه. همون‌طور که کلاس ‌ها  آبجکت میسازن، type هم کلاس میسازه.

جدول مقایسه: آبجکت، کلاس و متا کلاس 

برای اینکه تو ذهنت قشنگ جا بیفته، بیا این سه تا سطح رو تو یه جدول با هم مقایسه کنیم:

سطح اسم مفهوم وظیفه‌ش چیه؟ مثال تو دنیای واقعی کی اون رو می‌سازه؟
سطح ۱ آبجکت (Object) نمونه ‌سازی از روی نقشه ماشین پرایدِ سفیدِ من کلاس (Class)
سطح ۲ کلاس (Class) نقشه ساخت آبجکت ‌ها نقشه مهندسی ساخت پراید متاکلاس (Metaclass)
سطح ۳ متاکلاس (Metaclass) کارخونه ساخت کلاس‌ ها! شرکتی که استاندارد های نقشه رو تعیین می‌کنه خودش (type)

چطوری متاکلاس اختصاصی خودمون رو بسازیم؟ 

گاهی اوقات دوست داریم وقتی یه کلاس داره ساخته میشه، تو کارش دخالت کنیم. مثلاً فرض کن میخوایم یه قانونی بذاریم که "اسم تمام متغیرهای این کلاس باید با حروف بزرگ نوشته بشن". برای این کار باید یه متاکلاس بسازیم که از type ارث‌بری کنه و متد __new__ رو دستکاری کنیم:

class UpperCaseMeta(type):
    # این متد قبل از ساخته شدن کلاس اجرا میشه
    def __new__(cls, name, bases, dct):
        uppercase_attr = {}
        for key, value in dct.items():
            # اگه اسم متغیر با __ شروع نمیشه (مثل __init__)، بزرگش کن
            if not key.startswith('__'):
                uppercase_attr[key.upper()] = value
            else:
                uppercase_attr[key] = value
# حالا کلاس رو با ویژگی‌های جدید می‌سازیم
        return super().__new__(cls, name, bases, uppercase_attr)

حالا چطور ازش استفاده کنیم؟ خیلی راحت، تو تعریف کلاس بهش میگیم از این متاکلاس استفاده کن:

class MyCustomClass(metaclass=UpperCaseMeta):
    my_variable = "Salam Okyan!"
# حالا بیا تستش کنیم:
obj = MyCustomClass()
# print(obj.my_variable)  # این ارور میده! چون این متغیر دیگه وجود نداره
print(obj.MY_VARIABLE)    # خروجی: Salam Okyan!

دیدی چی شد؟ متاکلاس ما قبل از اینکه کلاس کاملاً متولد بشه، رفت تو دلش و اسم متغیر رو بزرگ کرد!

کجا ها از متاکلاس استفاده میشه؟ 

شاید با خودت بگی "خب که چی؟ چرا باید لقمه رو دور سرم بچرخونم؟". راستش رو بخوای، تو کدهای روزمره خیلی کم پیش میاد به متاکلاس نیاز پیدا کنی. اما تو ساخت فریمورک‌های بزرگ به شدت کاربرد داره:

  1. جنگو (Django ORM): وقتی تو جنگو یه مدل برای دیتابیس مینویسی (مثلاً name = models.CharField())، جنگو با استفاده از متاکلاس‌ها این متغیر های ساده رو تبدیل به فیلد های پیچیده دیتابیس میکنه.
  2. الگوی سینگلتون (Singleton): وقتی بخوای از یه کلاس فقط و فقط یه دونه آبجکت تو کل برنامه ساخته بشه.
  3. اعتبارسنجی (Validation): وقتی می‌خوای مطمئن بشی توسعه ‌دهنده‌ های دیگه که دارن از کد های تو ارث ‌بری می‌کنن، حتماً یه سری متد خاص رو پیاده‌سازی کردن.

 هشدار مهم از زبان خالقان پایتون!

تیم پیترز (Tim Peters)، یکی از خدایان پایتون، یه جمله خیلی معروف درباره متاکلاس‌ها داره:

"متاکلاس‌ها جادوی عمیقی هستند که ۹۹ درصد کاربران هرگز نباید نگران آن‌ها باشند. اگر شک دارید که آیا به آن‌ها نیاز دارید یا نه، پس قطعاً به آن‌ها نیاز ندارید!" بنابراین، فقط وقتی برو سراغشون که داری یه فریم‌ورک می‌نویسی یا راه حل ساده‌تری برای مشکلت وجود نداره. 

جمع‌ بندی

پرونده‌ ی متاکلاس ‌ها رو هم با هم بستیم! اگر بخوایم کل این مقاله رو  تو یک جمله خلاصه کنیم، باید بگیم: متاکلاس ‌ها ابزاری برای برنامه‌نویسیِ برنامه‌نویسی هستن! یعنی ما کدی مینویسیم تا رفتار کد های دیگه (کلاس‌ها) رو قبل از تولدشون کنترل کنیم.