برنامه نویسی

متا کلاس در پایتون + ارث‌بری چندگانه در پایتون؛ ۲ مفهوم مهم

اگر با کلاس‌ها، اشیا، ارث‌بری و super() در پایتون آشنا شده‌اید، احتمالاً در پروژه‌های پیشرفته‌تر با دو مفهوم روبه‌رو می‌شوید که در نگاه اول کمی پیچیده به نظر می‌رسند: متا کلاس (Metaclass) و ارث‌بری چندگانه (Multiple Inheritance).

این دو مفهوم به هم مرتبط‌اند، اما یکسان نیستند. ارث‌بری چندگانه درباره این است که یک کلاس از چند کلاس والد ویژگی و رفتار دریافت کند؛ متا کلاس یک سطح بالاتر قرار دارد و مشخص می‌کند خودِ کلاس چگونه ساخته و کنترل شود.

در این آموزش، ابتدا هر مفهوم را جداگانه یاد می‌گیریم، سپس سراغ MRO، super()، مشکل Diamond و در نهایت ارتباط متا کلاس با ارث‌بری چندگانه می‌رویم. مثال‌ها نیز به‌گونه‌ای انتخاب شده‌اند که بتوانید کاربرد واقعی آن‌ها را در کد تشخیص دهید.

متا کلاس در پایتون چیست؟

برای درک متا کلاس، ابتدا باید یک سؤال ساده را جواب دهیم:

اگر یک کلاس خودش یک شیء باشد، چه چیزی آن کلاس را می‌سازد؟

در پایتون تقریباً همه چیز شیء است؛ از عدد و رشته گرفته تا خود کلاس‌ها.

مثلاً:

class User:

pass

user = User()

print(type(user))

print(type(User))

خروجی:

<class ‘__main__.User’>

<class ‘type’>

یعنی user یک نمونه از User است، اما خود User نیز یک شیء است که معمولاً نمونه‌ای از type محسوب می‌شود.

به زبان ساده:

Object → ساخته‌شده از Class

Class → ساخته‌شده از Metaclass

در حالت معمول، متا کلاس پیش‌فرض پایتون type است. مستندات رسمی Python نیز توضیح می‌دهد که کلاس‌ها به‌طور پیش‌فرض با type() ساخته می‌شوند. مستندات رسمی Python درباره Metaclass

type دقیقاً چه کاری انجام می‌دهد؟

می‌توانیم یک کلاس را به شکل سنتی بنویسیم:

class Person:

name = “Ali”

اما از نظر مفهومی، ساخت کلاس به چیزی شبیه این مربوط می‌شود:

Person = type(

“Person”,

(),

{“name”: “Ali”}

)

تابع type() در این حالت سه آرگومان می‌گیرد:

type(name, bases, namespace)

  • Name: نام کلاس
  • Bases: کلاس‌های والد
  • Namespace: ویژگی‌ها و متدهای کلاس

بنابراین type را می‌توان سازنده پیش‌فرض کلاس‌ها دانست.

متا کلاس سفارشی چگونه ساخته می‌شود؟

اگر بخواهیم فرآیند ساخت کلاس را تغییر دهیم، می‌توانیم از یک متا کلاس سفارشی استفاده کنیم.

معمولاً متا کلاس از type ارث‌بری می‌کند:

class MyMeta(type):

pass

حالا می‌توانیم کلاس خود را با آن بسازیم:

class User(metaclass=MyMeta):

pass

در اینجا:

User

↓

MyMeta

↓

type

یعنی User نمونه‌ای از MyMeta است.

custom metaclass

یک مثال کاربردی از متا کلاس

فرض کنید می‌خواهیم هنگام ایجاد هر کلاس، نام آن را بررسی کنیم:

class Meta(type):

def __new__(cls, name, bases, namespace):

print(“Creating:”, name)

return super().__new__(cls, name, bases, namespace)

class User(metaclass=Meta):

pass

class Product(metaclass=Meta):

pass

خروجی تقریباً چنین خواهد بود:

Creating: User

Creating: Product

نکته مهم این است که متا کلاس هنگام ساخته‌شدن کلاس وارد عمل می‌شود، نه هنگام ساخت نمونه‌ای از آن کلاس.

این تفاوت بسیار مهم است.

user = User()

در اینجا نمونه ساخته می‌شود.

اما:

class User:

pass

در این مرحله خود کلاس User در حال ساخته‌شدن است و متا کلاس می‌تواند در این فرآیند دخالت کند.

فرآیند ساخت کلاس در پایتون

طبق مستندات رسمی Python، هنگام اجرای تعریف یک کلاس، مراحلی مانند تعیین MRO، انتخاب متا کلاس مناسب، آماده‌سازی namespace، اجرای بدنه کلاس و در نهایت ساخت شیء کلاس انجام می‌شود. مستندات رسمی Data Model پایتون

به شکل ساده:

class User(…)

↓

تعیین Base Classها

↓

تعیین Metaclass

↓

آماده‌سازی namespace

↓

اجرای بدنه کلاس

↓

ساخت Class Object

این موضوع نشان می‌دهد متا کلاس فقط یک «کلاس عجیب برای حرفه‌ای‌ها» نیست؛ بلکه در سطح پایین‌تر، بخشی از مکانیزم ساخت کلاس در پایتون است.

ساخت کلاس در پایتون

__new__ در متا کلاس چه کاربردی دارد؟

یکی از رایج‌ترین جاهایی که متا کلاس سفارشی می‌شود، متد __new__ است:

class Meta(type):

def __new__(cls, name, bases, namespace):

print(f”Creating class: {name}”)

return super().__new__(

cls,

name,

bases,

namespace

)

سپس:

class User(metaclass=Meta):

age = 20

متا کلاس قبل از ایجاد نهایی User می‌تواند namespace آن را بررسی یا تغییر دهد.

مثلاً:

class Meta(type):

def __new__(cls, name, bases, namespace):

if “name” not in namespace:

raise TypeError(

f”{name} must define name”

)

return super().__new__(

cls,

name,

bases,

namespace

)

class User(metaclass=Meta):

name = “Ali”

اما:

class Product(metaclass=Meta):

pass

باعث خطا می‌شود.

این قابلیت می‌تواند برای اعتبارسنجی یا ایجاد قراردادهای خاص در یک مجموعه از کلاس‌ها مفید باشد.

__prepare__ چیست؟

یکی از بخش‌های کمتر توضیح‌داده‌شده متا کلاس‌ها، __prepare__ است.

این متد قبل از اجرای بدنه کلاس برای آماده‌کردن namespace مورد استفاده قرار می‌گیرد.

مثلاً:

class Meta(type):

@classmethod

def __prepare__(cls, name, bases):

print(“Preparing:”, name)

return {}

def __new__(cls, name, bases, namespace):

print(“Creating:”, name)

return super().__new__(

cls,

name,

bases,

namespace

)

این مکانیزم در PEP 3115 به‌صورت رسمی توضیح داده شده است. این PEP مشخص می‌کند که متا کلاس می‌تواند قبل از اجرای بدنه کلاس namespace مورد استفاده برای تعریف اعضای کلاس را آماده کند. PEP 3115؛ مستند رسمی متا کلاس‌ها در Python

البته در بیشتر پروژه‌های معمولی نیازی به پیاده‌سازی __prepare__ ندارید.

prepare in python

چه زمانی واقعاً به متا کلاس نیاز داریم؟

این یکی از مهم‌ترین سؤال‌هاست.

اگر تازه متا کلاس‌ها را یاد گرفته‌اید، ممکن است تصور کنید استفاده از آن‌ها نشانه حرفه‌ای‌تر بودن کد است. اما معمولاً چنین نیست.

در بسیاری از پروژه‌ها راه‌حل‌هایی مانند:

  • تابع
  • Decorator
  • __init_subclass__
  • Composition
  • کلاس پایه معمولی

ساده‌تر و قابل نگهداری‌تر هستند.

مستندات رسمی Python برای متا کلاس‌ها کاربردهایی مانند frameworkها، interface checking، logging، ساخت خودکار property و موارد مشابه را مطرح می‌کند. کاربردهای Metaclass در مستندات رسمی Python

یک قانون عملی

اگر مسئله شما این است:

«وقتی یک کلاس ساخته شد، می‌خواهم رفتار خاصی داشته باشد.»

ابتدا این گزینه‌ها را بررسی کنید:

Decorator

↓

__init_subclass__

↓

Base Class

↓

Metaclass

متا کلاس معمولاً باید آخرین گزینه باشد، نه اولین گزینه.

ارث‌بری چندگانه در پایتون چیست؟

در ارث‌بری معمولی، یک کلاس معمولاً از یک کلاس پایه استفاده می‌کند:

class Animal:

def eat(self):

print(“Eating”)

class Dog(Animal):

pass

اما پایتون اجازه می‌دهد یک کلاس از چند والد ارث‌بری کند:

class Walker:

def walk(self):

print(“Walking”)

class Swimmer:

def swim(self):

print(“Swimming”)

class Duck(Walker, Swimmer):

pass

حالا:

duck = Duck()

duck.walk()

duck.swim()

هر دو متد در دسترس هستند.

ساختار کلاس به شکل زیر است:

Walker

\

Duck

/

Swimmer

این قابلیت Multiple Inheritance نام دارد.

multiple inheritance

مشکل اصلی ارث‌بری چندگانه چیست؟

مشکل زمانی ایجاد می‌شود که چند کلاس والد، متدی با نام یکسان داشته باشند.

class A:

def hello(self):

print(“A”)

class B:

def hello(self):

print(“B”)

class C(A, B):

pass

حالا:

c = C()

c.hello()

کدام hello() اجرا می‌شود؟

پاسخ با MRO مشخص می‌شود.

MRO در پایتون چیست؟

MRO مخفف:

Method Resolution Order

است؛ یعنی ترتیب جست‌وجوی پایتون برای پیدا کردن متدها و ویژگی‌ها.

می‌توانیم آن را ببینیم:

print(C.__mro__)

یا:

print(C.mro())

احتمالاً نتیجه‌ای مشابه این خواهد بود:

C → A → B → object

پس وقتی می‌نویسیم:

c.hello()

پایتون ابتدا C را بررسی می‌کند، سپس A، بعد B و در نهایت object.

پایتون برای حل MRO در ارث‌بری چندگانه از الگوریتم C3 Linearization استفاده می‌کند. راهنمای رسمی Python درباره MRO توضیح می‌دهد که این ترتیب باید ویژگی‌هایی مانند حفظ ترتیب محلی والدها و یکنواختی سلسله‌مراتب را رعایت کند. راهنمای رسمی Python درباره MRO

چرا super() در Multiple Inheritance مهم است؟

یکی از اشتباهات رایج این است که تصور کنیم:

super()

یعنی «کلاس والد من».

در واقع در ارث‌بری چندگانه، super() به کلاس بعدی در MRO مراجعه می‌کند.

مثال:

class A:

def hello(self):

print(“A”)

super().hello()

class B:

def hello(self):

print(“B”)

super().hello()

class C(A, B):

def hello(self):

print(“C”)

super().hello()

اگر C را اجرا کنیم:

C().hello()

ترتیب می‌تواند چنین باشد:

C

A

B

در اینجا super() در هر مرحله MRO را دنبال می‌کند.

این موضوع برای طراحی درست Cooperative Multiple Inheritance بسیار مهم است.

mro

مشکل Diamond در ارث‌بری چندگانه

یکی از مشهورترین مشکلات Multiple Inheritance، ساختار Diamond است.

A

/ \

B C

\ /

D

کد:

class A:

def hello(self):

print(“A”)

class B(A):

pass

class C(A):

pass

class D(B, C):

pass

ممکن است تصور کنیم A دوبار بررسی می‌شود، اما MRO پایتون این مسئله را مدیریت می‌کند.

print(D.__mro__)

ترتیبی شبیه این دریافت می‌کنیم:

D → B → C → A → object

این یکی از دلایل اهمیت MRO در پایتون است.

مقایسه متا کلاس و ارث‌بری چندگانه

ویژگی متا کلاس ارث‌بری چندگانه
هدف اصلی کنترل ساخت کلاس ترکیب رفتار چند کلاس
سطح فعالیت سطح کلاس سلسله‌مراتب کلاس‌ها
ابزار مهم type MRO
مفهوم کلیدی metaclass= class C(A, B)
کاربرد رایج Frameworkها و ساخت خودکار کلاس‌ها Mixins و ترکیب رفتار
ریسک اصلی پیچیدگی و درگیری متا کلاس‌ها MRO و وابستگی پیچیده
جایگزین‌های ساده‌تر Decorator، __init_subclass__ Composition، Mixins

ارتباط متا کلاس و Multiple Inheritance

این قسمت از نظر عملی بسیار مهم است.

فرض کنید دو کلاس پایه داشته باشیم:

class MetaA(type):

pass

class MetaB(type):

pass

class A(metaclass=MetaA):

pass

class B(metaclass=MetaB):

pass

حالا می‌خواهیم:

class C(A, B):

pass

مشکل ممکن است در Metaclass Resolution ایجاد شود.

چرا؟

زیرا C باید یک متا کلاس مناسب داشته باشد که با متا کلاس‌های والدهایش سازگار باشد.

مستندات رسمی Python توضیح می‌دهد که در زمان ساخت کلاس، پایتون باید مناسب‌ترین متا کلاس را از متا کلاس صریح و متا کلاس‌های کلاس‌های پایه انتخاب کند؛ اگر هیچ متا کلاس سازگاری وجود نداشته باشد، تعریف کلاس با TypeError شکست می‌خورد. بخش Determining the appropriate metaclass در مستندات Python

این همان چیزی است که به آن Metaclass Conflict گفته می‌شود.

Metaclass

یک اشتباه رایج: استفاده بی‌دلیل از Metaclass

فرض کنید فقط می‌خواهید هنگام ایجاد زیرکلاس، یک پیام چاپ شود.

ممکن است کسی متا کلاس بنویسد:

class Meta(type):

def __new__(cls, name, bases, namespace):

print(name)

return super().__new__(

cls,

name,

bases,

namespace

)

اما اگر هدف فقط اجرای یک رفتار هنگام ایجاد زیرکلاس باشد، در بسیاری از موارد __init_subclass__ انتخاب ساده‌تری است:

class Base:

def __init_subclass__(cls, **kwargs):

super().__init_subclass__(**kwargs)

print(“Created:”, cls.__name__)

class User(Base):

pass

بنابراین قبل از استفاده از Metaclass از خودتان بپرسید:

آیا واقعاً باید فرآیند ساخت کلاس را کنترل کنم؟

اگر جواب «نه» است، احتمالاً متا کلاس انتخاب مناسبی نیست.

تجربه عملی: برای کدام مسئله کدام ابزار را انتخاب کنیم؟

فرض کنید در یک پروژه چهار نیاز مختلف داریم.

نیاز اول: افزودن رفتار به چند کلاس

از:

Decorator

استفاده کنید.

نیاز دوم: اجرای منطق هنگام ساخت زیرکلاس

ابتدا:

__init_subclass__

را بررسی کنید.

نیاز سوم: ترکیب چند قابلیت مستقل

مثلاً:

class LoggerMixin:

…

class CacheMixin:

…

class Service(LoggerMixin, CacheMixin):

…

اینجا Multiple Inheritance می‌تواند منطقی باشد.

نیاز چهارم: کنترل عمیق فرآیند ساخت کلاس

در این حالت:

Metaclass

می‌تواند گزینه مناسبی باشد.

این تفکیک باعث می‌شود کد بیش از حد پیچیده نشود.

کدام ابزار برای کدام مسئله

چگونه MRO را در پروژه واقعی بررسی کنیم؟

هر وقت با ارث‌بری چندگانه مواجه شدید، حدس نزنید.

مستقیماً MRO را ببینید:

print(MyClass.__mro__)

یا:

MyClass.mro()

مثلاً:

class A:

pass

class B(A):

pass

class C(A):

pass

class D(B, C):

pass

print(D.mro())

با این کار دقیقاً می‌بینید پایتون چه ترتیبی را برای جست‌وجوی متدها انتخاب کرده است.

این کار در زمان Debug کردن خطاهای super() بسیار مفید است.

چند نکته مهم برای نوشتن Multiple Inheritance سالم

اگر از ارث‌بری چندگانه استفاده می‌کنید، چند اصل را رعایت کنید:

۱. والدها را بیش از حد به هم وابسته نکنید

کلاس‌هایی که به‌شدت به ترتیب خاصی از MRO وابسته‌اند، نگهداری دشوارتری دارند.

۲. در زنجیره super() همکاری کنید

اگر طراحی شما بر پایه Cooperative Multiple Inheritance است، زنجیره super() را بی‌دلیل قطع نکنید.

۳. MRO را بررسی کنید

این کد را فراموش نکنید:

print(MyClass.__mro__)

۴. برای ترکیب ساده رفتار، Composition را هم بررسی کنید

همیشه لازم نیست برای استفاده از چند قابلیت، چندکلاسه‌کردن انجام دهید.

۵. متا کلاس را فقط برای مسئله‌ای واقعی استفاده کنید

پیچیده‌تر بودن راه‌حل، به معنی حرفه‌ای‌تر بودن آن نیست.

جمع‌بندی

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

در مقابل، ارث‌بری چندگانه اجازه می‌دهد یک کلاس از چند کلاس پایه ارث‌بری کند. مهم‌ترین مفاهیم مرتبط با آن MRO و super() هستند.

رابطه این دو زمانی جدی‌تر می‌شود که کلاس‌های پایه متا کلاس‌های متفاوتی داشته باشند؛ در چنین شرایطی ممکن است با Metaclass Conflict مواجه شوید.

برای استفاده عملی، این قانون ساده را به خاطر بسپارید:

ترکیب رفتارها

↓

Composition / Mixins

تغییر رفتار زیرکلاس‌ها

↓

__init_subclass__

کنترل ساخت کلاس

↓

Metaclass

هدف یادگیری این مفاهیم نباید صرفاً حفظ کردن Syntax باشد. مهم‌تر این است که بدانید چه زمانی نباید از آن‌ها استفاده کنید.

 

کوانتوم سافت

تیم حرفه ای و متخصص کوانتوم سافت، مقالات بروز برای آشنایی کاربران عزیز به زبان فارسی ارائه می دهد تا در جریان ترندهای شگفت انگیز تکنولوژی و هوش مصنوعی باشید.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

نوشته های مشابه

دکمه بازگشت به بالا