بهزاد دبیری
بهزاد دبیری
خواندن ۵ دقیقه·۱ سال پیش

Metaprogramming with Metaclasses in Python


توی این قسمت قراره بیشتر با Metaprogramming و Metaclas ها اشنا بشیم

در ابتدا، کلمه Metaprogramming شاید یک چیز عجیب و بیگانه به نظر برسه، اما اگر تا به حال با دکوراتورها یا متاکلاس ها کار کرده باشید ، باید بگم که شما در تمام مدت آنجا مشغول فرابرنامه نویسی بودید. به طور خلاصه، میشه گفت فرابرنامه‌نویسی کدی است که کد را دستکاری می‌کنه. اینجا قراره در مورد متاکلاس ها بحث کنیم، چرا و چه زمانی باید از آنها استفاده کنیم و چه جایگزین هایی وجود دارد. این یک مبحث نسبتاً پیشرفته پایتون است و پیش نیاز زیر مورد انتظار است :

  • OOP concept in Python
  • decorators in python

Metaclasses

توی پایتون هرچیزی یک تایپی داره .به عنوان مثال، اگه متغیری داریم که دارای یک مقدار صحیح هست، نوع آن int است. با استفاده از تابع ()type می توانید نوع هر چیزی را بدست آورید.

num = 23

print("Type of num is:", type(num))

>>>Type of num is: <class 'int'>

list = [1, 2, 4]

print("Type of list is:", type(list))

Type of list is: <class 'list'>

name = "Atul"

print("Type of name is:", type(name))

Type of name is: <class 'str'>

هر تایپی در پایتون با کلاس تعریف میشه.توی مثال بالا برخلاف ++C یا جاوا که int، char، float انواع داده های اولیه هستند، در پایتون آنها اشیایی از کلاس int یا کلاس str هستند.بنابراین می توانیم با ایجاد کلاسی از آن نوع، یک نوع جدید بسازیم. به عنوان مثال، ما می توانیم با ایجاد یک کلاس Student نوع جدیدی از Student ایجاد کنیم.

class Student:

pass

stu_obj = Student()

print("Type of stu_obj is:", type(stu_obj))

Type of stu_obj is: <class '__main__.Student'>

یک کلاس نیز یک شیء است، و درست مانند هر شیء دیگری، نمونه ای از چیزی به نام متاکلاس است. یک کلاس خاص یا همون(type) این class objects ها را ایجاد می کند. type class یک پیش فرض متاکلاس است که وظیفه ساخت کلاس ها را بر عهده دارد. در مثال بالا، اگر بخواهیم نوع کلاس Student را بفهمیم، خروجی ما type خواهد بود.

class Student:

pass

print("Type of Student class is:", type(Student))

Type of Student class is: <class 'type'>

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

class test:

pass

test.x = 45

test.str = lambda self: print('Hello')

my_obj = test()

print(my_obj.x)

my_obj.str()

45 Hello

خلاصه مطالب بالا میشه :

Object => instance of ====> Class => instance of====> Metaclass

متاکلاس مسئول تولید کلاس‌ها است، بنابراین می‌توانیم متاکلاس‌های سفارشی خود را بنویسیم تا نحوه تولید کلاس‌ها را با انجام اقدامات اضافی یا تزریق کد تغییر دهیم.معمولاً ما به متاکلاس های سفارشی نیاز نداریم اما گاهی اوقات لازم است.مشکلاتی وجود دارد که راه‌حل‌های متاکلاس و غیر متاکلاسی برای آنها در دسترس است (که اغلب ساده‌تر هستند) اما در برخی موارد، فقط متاکلاس می‌تواند مشکل را حل کند. ما در این مقاله به چنین مشکلی خواهیم پرداخت.

ساخت متا کلاس سفارشی:

برای ایجاد متاکلاس سفارشی ما، متاکلاس سفارشی ما باید type متاکلاس را به ارث برده و اصطلاحا override بشه.

خب ()__new__ چیه : این متدی است که قبل از ()__init__فراخوانی می شود. شی را ایجاد می کند و آن را
برمی گرداند. ما می تونیم override بکنیم این متد رو تا بتونه نحوه ایجاد object ها رو کنترل بکنه.

خب ()__init__ چیه : این متد فقط object ایجاد شده را که به عنوان پارامتر ارسال می شود مقداردهی اولیه می کند.

ما می توانیم با استفاده از تابع ()type به طور مستقیم کلاس ایجاد کنیم. می توان آن را به روش های زیر فراخوانی کرد

1- هنگامی که تنها با یک آرگومان فراخوانی می شود،type را برمی گرداند

2- هنگامی که با سه پارامتر فراخوانی می شود، یک کلاس ایجاد می کند:

  • نام کلاس (class name)
  • تاپل دارای کلاس های پایه است که توسط کلاس به ارث برده می شود (Tuple having base classes inherited by class)
  • دیکشنری کلاس (class dictionary): به عنوان یک فضای نام محلی برای کلاس عمل می کند که با متدها و متغیرهای کلاس پر شده است . به مثال زیر توجه کیند:

def test_method(self):

print("This is Test class method!")

class Base:

def myfun(self):

print("This is inherited method!")

Test = type('Test', (Base, ), dict(x="abc", my_method=test_method))

print("Type of Test class: ", type(Test))

test_obj = Test()

print("Type of test_obj: ", type(test_obj))

test_obj.myfun()

test_obj.my_method()

print(test_obj.x)

Type of Test class: <class 'type'> Type of test_obj: <class '__main__.Test'> This is inherited method! This is Test class method! abc

حالا بیایید بدون استفاده مستقیم از ()type یک متاکلاس ایجاد کنیم.در مثال زیر، ما یک متاکلاس MultiBases ایجاد می کنیم که بررسی می کند آیا کلاس ایجاد شده از بیش از یک base class ارث بری کرده و اگه اینطور نباشه یک خطا ایجاد می کند.

class MultiBases(type):

def __new__(cls, clsname, bases, clsdict):

if len(bases)>1:

raise TypeError("Inherited multiple base classes!!!")

return super().__new__(cls, clsname, bases, clsdict)

class Base(metaclass=MultiBases):

pass

class A(Base):

pass

class B(Base):

pass

class C(A, B):

pass

Traceback (most recent call last): File &quot<stdin>&quot, line 2, in <module> File &quot<stdin>&quot, line 8, in __new__ TypeError: Inherited multiple base classes!!!

نکته آخر اینکه بیشتر اوقات ما از متاکلاس استفاده نمی کنیم، معمولاً برای چیزهای پیچیده استفاده می شود

  • همانطور که در مثال بالا دیدیم، متاکلاس ها سلسله مراتب وراثت را منتشر می کنند. همه زیر کلاس ها را نیز تحت تاثیر قرار خواهد داد. اگر چنین شرایطی داریم باید از متاکلاس ها استفاده کنیم.
  • اگر بخواهیم کلاس را به طور خودکار تغییر دهیم، وقتی ایجاد شد، از متاکلاس استفاده می کنیم.
  • برای توسعه API، ممکن است از متاکلاس ها استفاده کنیم

یه نقل قولی از Tim Peters راجب این مبحث هست که جالبه :

متاکلاس ها جادوی عمیق تری هستند که 99 درصد کاربران هرگز نباید نگران آن باشند. اگر تعجب می کنید که آیا به آنها نیاز دارید یا خیر، نیازی ندارید (افرادی که واقعاً به آنها نیاز دارند با اطمینان می دانند که به آنها نیاز دارند و نیازی به توضیح در مورد دلیل آنها ندارند).

میدونم یه مقداری طولانی شد ولی امیدوارم مفید باشه?




lt classprint typeکلاسtype test
شاید از این پست‌ها خوشتان بیاید