توابع ویژه در پایتون (dunder methods)

پایتون یکی از زبان های برنامه نویسی مشهور و پراستفاده ی دنیای امروز است. شاید بسیاری از ما "سینتکس آسان" یا "آسان تر بودن" را در مورد پایتون زیاد شنیده باشیم. این تعاریف شاید به نحوی درست باشند، اما از طرفی دیگر همین سادگی ها ممکن است باعث شوند تا ما نتوانیم از تمام پتانسیل این زبان قدرتمند استفاده کنیم. بسیاری از آموزش های موجود پایتون حدالمکان از تمرکز بر روی ویژگی هایی که فقط مختص پایتون هستند اجتناب می کنند و سعی دارند پایتون را در محدوده ی مشترک تعریف شده برای تمامی زبان های برنامه نویسی ارایه دهند.توابع ویژه ی پایتون یا dunder method ها یکی از ویژگی های زیبا و بسیار مفید پایتون هستند که به ما امکان نوشتن کدهای کاربردی و اصطلاحا پایتونیک را می دهند. dunder مخفف double underscore before and after است! اگر کمی کد پایتون زده باشید، حتما با چنین توابعی که بین دو جفت underline قرار می گیرند روبرو شده اید.اولین چیزی که باید در مورد توابع ویژه بدانیم این است که آنها اساسا با هدف فراخوانی شدن توسط مفسر پایتون هستند و ما برنامه نویسان باید از فراخوانی مستقیم آن ها خودداری کنیم. کاربرد این متد ها برای ما آن است که با "پیاده سازی" آن ها میتوانیم ویژگی های خاصی برای آبجکت های خود ایجاد کنیم.در بسیاری از زبان های برنامه نویسی، برای بدست آوردن طول یک آبجکت iterable میتوان از object.length یا object.size یا توابعی شبیه به این استفاده کرد. اما در پایتون ما به صورت len(object) عمل میکنیم.اگر برای آبجکت مورد نظر، تابع ویژه ی __len__ تعریف شده باشد، با فراخوانی len(object) مفسر پایتون این تابع ویژه را فراخوانی کرده و مقداری که این تابع برمیگرداند را برمیگرداند.دقت کنید که برای بدست آوردن طول آبجکتی میتوان از __object.__len نیز استاده کرد اما همانطور که گفته شد، این تابع به منظور فراخوانی شدن توسط مفسر پایتون استفاده می شود نه برنامه نویس.سبد میوه!حال به پیاده سازی یک کلاس برای نشان دادن و کار کردن با یک سبد میوه میپردازیم و سعی خواهیم کرد با پیاده سازی توابع ویژه، برای کلاس خود ویژگی های خاصی را ایجاد کنیم.import random class FruitBasket: def __init__(self): self.fruits = [random.choice(['orange', 'banana', 'grape', 'apple', 'cherry']) for _ in range(5)]هنگام ساختن هر آبجکت از کلاس فوق، 5 میوه (میتوانند تکراری باشند) به صورت تصادفی انتخاب شده و داخل صفت fruits قرار میگیرند.حال بیایید آبجکتی از کلاس زیر بسازیم و طول آن را بدست بیاوریم. basket_1 = FruitBasket() print(len(basket_1)) >>> TypeError: object of type 'FruitBasket' has no len()همانطور که انتظار میرفت، با خطا مواجه شدیم.میتوان یک property method با نام len برای این کلاس نوشت که طول fruits را برمیگرداند و از object.len برای بدست آوردن تعداد میوه ها استفاده کرد. اما رویکرد پایتونیک تر و تمیز تر، پیاده سازی تابع ویژه ی __len__ است:import random class FruitBasket: def __init__(self): self.fruits = [random.choice(['orange', 'banana', 'grape', 'apple', 'cherry']) for _ in range(5)] def __len__(self): return len(self.fruits) basket_1 = FruitBasket() print(len(basket_1))حال خروجی کد فوق 5 خواهد بود. درواقع کلاس ما با پیاده سازی تابع ویژه ی __len__ به تابع len که یک تابع built in در زبان پایتون است پاسخ خواهد داد.برنامه نویسی که با این ویژگی ها آشنا نیست، شاید در کد خود برای استفاده از آبجکت های این کلاس، از len(object.fruits) استفاده میکرد. یا مثلا اگر میخواست روی لیست میوه ها پیمایش کند، یا میوه ای را انتخاب کند، باز هم به صفت fruits مراجعه میکرد و مستقیما با آن کار می کرد. اما زبان پایتون برای چنین اعمالی هم امکاناتی در اختیار ما گذاشته است:import random class FruitBasket: def __init__(self): self.fruits = [random.choice(['orange', 'banana', 'grape', 'apple', 'cherry']) for _ in range(5)] def __len__(self): return len(self.fruits) def __getitem__(self, position): return self.fruits[position]به کد بالا دقت کنید. تابع ویژه ی دیگری به اسم __getitem__ به کلاسمان اضافه کردیم. با اضافه کردن این کد کوتاه، هم اکنون کلاس ما تمام ویژگی های یک sequence استاندارد پایتون را ساپورت می کند!برای مثال میتوانیم میوه ی سوم را اینگونه انتخاب کنیم:objct[2]یا میتوانیم slice ای از سبد را انتخاب کنیم:object[1:4]حتی میتوانیم سبد خود را پیمایش کنیم!:for item in object: print(item)میتوانیم میوه ای را به تصادف انتخاب کنیم:random.choice(object)با همین چند خط کد میتوان فواید و مزیت های بسیار چنین رویکردی را دید و درک کرد. توجه کنید که هم اکنوان کلاس ما به بسیاری از توابع کتابخانه های دیگر نیز که با sequence های پایتون کار میکنند (مانند random) هم پاسخگو است.بردار ساده!حال میخواهیم کلاسی ساده برای نشان دادن بردار های ساده ی دو بعدی را پیاده سازی کنیم و توابع ویژه ی دیگری در پیاده سازی این کلاس استفاده کنیم. حال که کمی با توابع ویژه آشنا شده ایم اینبار تمام کد مربوط به کلاس را نوشته و نکات لازم را مرور میکنیم.class Vector: def __init__(self, x, y): self.x = x self.y = y def __add__(self, other_vector): x = self.x + other_vector.x y = self.y + other_vector.y return Vector(x, y) def __mul__(self, number): return Vector(self.x * number, self.y * number) def __repr__(self): return f'Vector({self.x!r}, {self.y!r})'کلاس بالا یک بردار ساده ی ریاضی است که برای ساختن آبجکتی از آن باید یک جفت x و y را به آن پاس دهیم.حال دو بردار میسازیم. vector_1 = Vector(1, 3) vector_2 = Vector(2, 4)تابع __add__ به آبجکت های کلاس ما این اجازه را میدهد که با بردار های دیگر جمع شوند. و تابع __mul__ اجازه می دهد که آبجکت های کلاس ما قابلیت ضرب شدن در یک عدد را داشته باشند.تابع __repr__ باعث میشود که آبجکت های ما به تابع print پایتون پاسخ دلخواه ما را بدهند. بدین ترتیب ما میتوانیم با پرینت کردن آبجکت های خود، اطلاعات مفیدی بدست آوریم.به تکه کدهای زیر و خروجی آن ها دقت کنید.>>>print(vector_1) >>>Vector(1, 3)>>> print(vector_1 + vector_2) >>> Vector(3, 7)>>>print(vector_2 * 4) >>>Vector(8, 16)کلام آخربدین ترتیب ما توانستیم با رویکرد هایی تمیز و پایتونیک، ویژگی های خاصی را برای کلاس های خود پیاده سازی کنیم و از آن ها استفاده کنیم. پایتون توابع ویژه ی بسیاری دارد و در اینجا سعی کردیم با مفهوم کلی آن ها و رویکرد استفاده از آن ها آشنا شویم. شمارا دعوت میکنم که با دیگر توابع ویژه ی پایتون نیز آشنا شوید. امیدوارم این مطلب برایتان مفید بوده باشد.موفق و پیروز باشید.