چند ریختی در پایتون – توضیح پلی مورفیسم به زبان ساده
«برنامهنویسی شیگرایانه» (Object-Oriented Programming | OOP) دارای ۴ ویژگی ضروری است. «مفاهیم انتزاعی» (Abstraction)، «کپسولهسازی» (Encapsulation)، «وراثت» (Inheritance)، و «چند ریختی» (Polymorphism) اصول چهارگانهای هستند که شیگرایی بدون آنها نه فایدهای دارد نه قابلیت پیادهسازی به شکل حرفهای پیدا میکند. مجله فرادرس در این مطلب یکی از این اصول چهارگانه به نام چند ریختی را بررسی کرده است. در این مطلب از مجله فرادرس به چند ریختی در پایتون خواهیم پرداخت، اینکه چیست، چه جزییاتی دارد و چگونه پیادهسازی میشود و بهطور کلی تلاش میکنیم نکتهای ناگفته از مطلب چند ریختی باقینماند. اما به عبارت کوتاه، چند ریختی، توانایی بر عهده گرفتن چند شکل مختلف را توسط متدها یا عملگرها تعریف میکند. چند ریختی در پایتون به ما اجازه میدهد، متدی را در کلاس فرزند تعریف کنیم، درحالی که در کلاس والد نیز متدی با همان نام وجود دارد.
- میآموزید چند ریختی چیست و چگونه در برنامهنویسی پایتون به کار میرود.
- یاد میگیرید چند ریختی چگونه در توابع، اشیا و متدهای توکار پایتون اعمال میشود.
- با چند ریختی مبتنی بر وراثت و مزایای بازنویسی متدها آشنا میشوید.
- میتوانید چند ریختی را در متدهای کلاسی و نمونه کدهای مرتبط تشخیص دهید.
- یاد میگیرید سربارگذاری متد و عملگرها چگونه در پایتون پیادهسازی میشود.
- Duck Typing و نقش آن در پیادهسازی چند ریختی در پایتون را میآموزید.


چند ریختی چیست؟
کلمه چند ریختی، Polymorphism از دو کلمه یونانی Poly به معنی «چَند» و Morphism به معنی «شکلها» گرفته شده است. در دنیای زبانهای برنامهنویسی این کلمه به این معنا است که از یک تابع برای انواع مختلف داده میتوان استفاده کرد. این روش، کد نویسی را بیشتر شهودی میکند و همینطور باعث آسانتر شدن طراحی کدها هم میشود.
در پایتون روشهای مختلفی برای تعریف چند ریختی وجود دارد. در ادامه به بررسی تعریف چند ریختی در پایتون خواهیم پرداخت و نحوه کار کردن با تکنیک چند ریختی را بررسی خواهیم کرد.
چند ریختی در پایتون
کلاس فرزند همه متدهای کلاس والد را به ارث میبرد. اگرچه، در بعضی موقعیتها، متدی که از کلاس والد به ارث رسیده است بهطور کامل با کلاس فرزند همخوانی ندارد. در همچنین موقعیتهایی مجبور خواهید بود تا متد والد را در کلاس فرزند دوباره پیادهسازی کنید. چند ریختی در پایتون توانایی یک شیئی برای پذیرش چند شکل مختلف است. به عبارت سادهتر، چند ریختی به فعالیتی اجازه میدهد که به چند روش مختلف اجرا شود.

در زبان پایتون، روشهای متفاوتی برای استفاده از چند ریختی وجود دارند. میتوانید از توابع مختلف، کلاسها، متدهای کلاسی یا اشیا برای تعریف پلیمورفیسم استفاده کنید. در ادامه به بررسی تک تک این روشها برای تعریف چند ریختی در پایتون خواهیم پرداخت.
چند ریختی در تابع توکار len
«تابع توکار» (Built-In Function) len() طول اشیا را بسته به نوع آنها محاسبه میکند. اگر شیئی از نوع رشته باشد، تابع تابع len در پایتون تعداد کارکترها را برمیگرداند و اگر شیئی از نوع لیست باشد تعداد آیتمهای درون لیست را میشمارد و برمیگرداند.
تابع len()با هر شیئی بسته به نوع کلاس آن شی رفتار میکند.
نمونه کد
در مثالی که در نمونه کد زیر آوردهایم، روش کار این تابع نمایش داده شده است.
خروجی کد بالا به صورت زیر میشود.
3
10توجه کنید که تابع len() در برخورد با نوع داده دیکشنری تعداد کلیدها را برمیگرداند که نمیتوان گفت تعداد کل آیتمها هستند. چون مقدار هر کلید در دیکشنری خود، میتواند یک دیکشنری دیگر باشد.
چند ریختی با وراثت
چند ریختی در پایتون بهطور غالب در کنار وراثت استفاده میشود. در وراثت در پایتون، کلاس فرزند ویژگیها و متدهای کلاس والد را به ارث میبرد. کلاس موجود به عنوان کلاس پایه یا کلاس والد شناخته میشود و کلاس جدیدی که از این کلاس ارث بری خواهد کرد به عنوان زیرکلاس، کلاس فرزند یا کلاس مشتق شده شناخته خواهد شد.
با استفاده از تکنیک «بازنویسی متد» (Method Overriding)، چند ریختی به ما اجازه میدهد که متدهایی را در کلاس فرزند تعریف کنیم که همنام متدهایی هستند که در کلاس والد وجود دارند. به این فرایند پیادهسازی دوباره متدهایی که از کلاس والد به کلاس فرزند به ارث رسیدهاند، بازنویسی متد یا همان Method Overriding میگویند.

مزایای قابلیت بازنویسی متد
دو امتیاز مهم را این قابلیت در اختیار برنامه نویسان قرار میدهد.
- زمانی که با ایجاد تغییر در متدهای به ارث رسیده، میخواهیم عملکرد متدها را گسترش دهیم، این قابلیت بسیار مفید و موثر است یا وقتی متدی که از کلاس والد به ارث رسیده است نیازهای کلاس فرزند را برآورده نمیکند، قادر به پیادهسازی دوباره همان متد اما به روش متفاوتی، در کلاس فرزند هستیم.
- هنگامی که کلاس والد چندین زیرکلاس دارد و یکی از آن کلاسهای فرزند میخواهد که متد را بازنویسی کند. کلاسهای فرزند دیگر میتوانند از متدی که از کلاس والد به ارث بردهاند، بدون هیچ تغییر و مشکلی استفاده کنند. باتوجه به این، دیگر نیازی به اعمال تغییر در کدهای کلاس والد وجود ندارد.
در چند ریختی، وقتی که متدی را فراخوانی میکنیم، پایتون اول نوع کلاس اشیا را بررسی میکند و سپس متد مناسب با آن نوع کلاس را اجرا میکند. برای مثال، اگر کلاس خودرو را ایجاد کنید و برای این کلاس متد سرعتspeed() را تعریف کنید سپس از این کلاس دو کلاس فرزند به نامهای ماشین کشاورزی و ماشین مسابقه ارثبری کنند. وقتی که از کلاس ماشین مسابقه شیئی ایجاد کنید و تابع speed() را فراخوانی کنید، درنتیجه پایتون نیز متد speed() را از کلاس ماشین مسابقه فراخوانی میکند نه متد speed() مربوط به کلاس خودرو یا کلاس ماشین کشاورزی.
در ادامه به روش کار قابلیت بازنویسی متدها با استفاده از مثال خواهیم پرداخت.
مثال به همراه کد برای بازنویسی متد
در مثالی که آوردهایم، کلاس وسیله نقلیه به نام «Vehicle» را به عنوان والد و کلاس ماشین سواری و کامیون را به ترتیب به نامهای «Car» و «Truck» به عنوان کلاسهای فرزند تعریف کردهایم. اما هر وسیله نقلیهای میتواند ظرفیت مسافر، سرعت و وزن و غیره متفاوتی مخصوص به خود داشته باشد. درنتیجه میتوانیم چند نمونه متد یکسان با نام یکسان در هر کلاس داشته باشیم که هرکدام پیادهسازی مخصوص به خود را دارند. کدهایی که طبق این روش نوشته شوند میتوانند در طول زمان گسترشداده شوند و به آسانی نگهداری شوند.

خروجی این کد به صورت زیر میشود.
Details: Car x1 Red 20000
Car max speed is 240
Car change 7 gear
Details: Truck x1 white 75000
Vehicle max speed is 150
Vehicle change 6 gearهمانطور که میتوانید ببینید، بر طبق قابلیت چند ریختی، مفسر پایتون متوجه میشود که متدهایmax_speed() وchange_gear() برای شی خودرو بازنویسی شدهاند. پس، از متد تعریف شده در کلاس فرزند Car استفاده میکند. از طرف دیگر، متدshow() در کلاس فرزند Carبازنویسی نشده است، پس این متد از کلاس Vehicle اجرا میشود.
در پایتون می توانیم رفتارهای پیش فرض توابع توکار را نیز تغییر دهیم. برای مثال توابع توکاری مثل len() ، abs() وdivmod() را هم میتوانیم گسترش دهیم و هم میتوان رفتارشان را در کلاس خودمان تغییر دهیم. این کار را با دوباره تعریف کردن این توابع انجام میدهیم.
برای مشاهده نمونهای از بازتعریف توابع توکار به کد زیر دقت کنید. در این مثال تابع len() را بازتعریف کردهایم.
خروجی کد بالا به صورت زیر میشود.
Redefine length
4چند ریختی در متدهای کلاسی
وقتی اشیا متفاوتی را که متدهای یکسانی دارند کنار هم دستهبندی میکنیم، استفاده از پلی مورفیسم در رابطه با متدهای کلاسی بسیار مفید است. میتوانیم آنها را به لیست یا تاپلی اضافه کنیم و بدون آنکه نیاز به بررسی نوع اشیا، قبل از فراخوانی متدهای آنها داشته باشیم، از آنها استفاده کنیم. در مقابل، پایتون نوع اشیا را در زمان اجرا بررسی و متد درست را فرخوانی خواهد کرد. بنابراین، میتوانیم بدون اینکه نگران این باشیم که شی طرف حساب ما از چه کلاسی ایجاد شده است، متدها را فراخوانی کنیم. فرض میکنیم که این متدها در هر کلاسی وجود دارند.
پایتون به کلاسهای متفاوت اجازه میدهد که متدهایی با نام یکسان داشته باشند. در پایین مراحل انجام آزمایش برای بررسی نحوه فراخوانی متدها توسط پایتون را فهرست کردهایم.
- ابتدا میخواهیم کلاس متفاوتی به همان روش با اضافه کردن متدهای یکسان در چندین کلاس طراحی کنیم.
- سپس از هر کلاس شیئی ایجاد خواهیم کرد.
- سپس همه اشیا را به تاپلی اضافه خواهیم کرد.
- در پایان، با استفاده از حلقه for روی این تاپل پیمایش خواهیم کرد و متدهای اشیا را بدون بررسی کردن کلاس آنها فراخوانی خواهیم کرد.
نمونه کد برای چند ریختی در متدهای کلاسی
در مثالی که در زیر آوردهایم، توابع fuel_type() و max_speed() متدهای نمونه ایجاد شده در هر دو کلاس هستند.
خروجی کد بالا به این صورت میشود.
Petrol
Max speed 350
Diesel
Max speed is 240همانطور که میبینید، دو کلاس فراریFerrari و بی ام وBMW را ایجاد کردهایم. این کلاسها متدهای یکسانی را به عنوان نمونه برای تست، به نامهای max_speed() و fuel_type() دارند. اگرچه، نه این دو کلاس را به هم مربوط کردهایم و نه از وراثت استفاده کردهایم.
ما دو شی مختلف را در یک تاپل جمع کردهایم و درون آن با استفاده از متغیر car پیمایش خواهیم کرد. از آنجا که در هر دو کلاس متدهای هم نامی ایجاد کردهایم، استفاده از چند ریختی هنوز ممکن است زیرا پایتون در ابتدا نوع کلاسها را بررسی میکند و سپس متدهای موجود در هر کلاس را اجرا میکند.
چندریختی درباره توابع و اشیا
روی تابعی که هر شی را بتواند به عنوان پارامتر بپذیرد و متدهای آن شی را بدون بررسی نوع کلاس آن اجرا کند نیز میتوانیم چند ریختی را پیادهسازی کنیم. با استفاده از این قابلیت، بجای تکرار فراخوانی متدها، میتوانیم فعالیت همه اشیا را با استفاده از تابع یکسانی فراخوانی کنیم.
به مثالی که در ادامه زدهایم دقت کنید.
خروجی کدهایی که در بالا آوردهایم، به این شکل میشود.
Petrol
Max speed 350
Diesel
Max speed is 240چند ریختی در متدهای توکار
تابع «توکار» (Built-In) reversed(obj) متغیرهای پیمایشپذیر را میپذیرد و محتویات درون آنها را برعکس میکند و برمیگرداند. برای مثال اگر رشتهای را به آن بدهید، از آخر به اول آن را بصورت برعکس در میآورد. اما اگر لیستی از رشتهها را به آن تابع بدهید، آن لیست –شی پیمایشپذیر- را با معکوس کردن چیدمان عناصر داخل آن به شما برمیگرداند. این بار دیگر رشتهها را بهصورت مجزا معکوس نخواهد کرد.
در زیر خواهیم دید که یک متد توکار چگونه اشیایی را که نوع دادههای گوناگونی دارند، پردازش می کنند.
خروجی این کد به این شکل میشود.
Reverse string
e v i t a n Y P
Reverse list
Kelly Jessa Emma انواع روش های پیاده سازی چند ریختی در پایتون
کلمه پلی مورفیسم به معنای این است که متدها میتوانند اشیا را بسته به نوع کلاس یا نوع دادهای که می گیرند پردازش کنند. در پایتون ۴ روش برای پیادهسازی چند ریختی وجود دارند که در زیر فهرست کردهایم.
- پیادهسازی چندریختی به روش Duck Typing
- پیادهسازی چندریختی به روش «سربارگزاری متد» (Method Overloading)
- پیادهسازی چندریختی به روش «سربارگزاری عملگر» (Operator Overloading)
- پیادهسازی چندریختی به روش «بازنویسی متد» (Method Overriding)
پیادهسازی چندریختی به روش بازنویسی متد را بالاتر توضیح دادهایم. در ادامه مطلب به بررسی باقی روشها خواهیم پرداخت.
Duck Typing در پایتون چیست؟
روش Duck Typing مفهومی است که بیان میکند، نوع شی فقط در زمان اجرای برنامه اهمیت پیدا میکند و نیازی نیست که قبل از اجرای هر عملیاتی روی شی، نوع آن را یادآوری کنیم. همینکه کاری که میخواهیم انجام دهد، کافیست. به اصطلاح برنامهنویسان «اگر مانند یک اردک، کووَک کووَک کند، پس اردک است.»

در پایتون مفهومی به نام «نوعدهی پویا» (Dynamic Typing) وجود دارد. میتوانیم نوع متغیر یا شی را بعدا مورد نظر قرار دهیم. ایده پشت کار این است که نیازی به دانستن نوع متد برای فراخوانی متد موجود داخل اشیا ندارید، اگر متدی برای شیئی تعریف شده، کافی است. میتوانید آن متد را فراخوانی کنید.
لطفا به مثال زیر توجه کنید.
خروجی کد بالا به صورت زیر میباشد.
Geeks are working on the code...شاید کد بالا کمی گمراهکننده باشد. توجه کنید که در ابتدا دو کلاس GFGCoder و Geeks تعریف شدهاند. سپس از هر دو کلاس اشیای به ترتیب executor و ide ساخته شدهاند. شیide متدcode() را فراخوانی کرده و شیexecutor را به عنوان ورودی به آن متد ارسال کرده است.
سربارگزاری متدها
به فرایند فراخوانی متدهای دارای نام یکسان که پارامترهای متفاوتی میپذیرند «سربارگزاری متد» (Method Overloading) میگویند. زبان برنامهنویسی پایتون فنآوری سربازگزاری متد را پشتیبانی نمیکند. پایتون فقط آخرین متدی که تعریف شده است را درنظر میگیرد. حتی اگر متد را «بازنویسی» (Overloading) کرده باشید. اگر سعی در سربارگزاری متدها کنید، پایتون خطای TypeError را به شما برمیگرداند.
برای مثال به کدی که در ادامه آمده، توجه کنید.
برای غلبه بر مشکل فوق، میتوانیم ازروشهای مختلفی استفاده کنیم تا قابلیت Overload کردن متدها را در پایتون بدست آوریم. در پایتون برای Overload کردن متدهای کلاسی، لازم است منطق متدها را طوری بنویسیم که اجراهای مختلف کد داخل تابع وابسته به پارامترهایی باشد که به تابع داده میشود.
برای مثال، تابع توکار range() ،۳ پارامتر مختلف را میپذیرد و با توجه به تعداد پارامترهایی که به تابع ارسال میکنیم، نتایج مختلفی را ارائه میدهد. در واقع به این تابع به ۳ روش گوناگون میتوان پارامتر ارسال کرد که هر روش ارسال باعث میشود تابع range()رفتار متفاوتی نسبت به پارامترها داشته باشد.
برای نمونه به کدی که در زیر آمده، توجه کنید.
خروجی کد بالا به صورت زیر میشود.
0, 1, 2, 3, 4,
5, 6, 7, 8, 9,
2, 4, 6, 8, 10,فرض میکنیم که متدی به نام area() داریم که کارش محاسبه مساحت مربع و مستطیل است. این متد با توجه به تعداد پارامترهایی که به عنوان ورودی به آن ارسال میشود، مساحت را محاسبه میکند.
- اگر یک پارامتر ارسال شود، متد بهصورت خودکار مساحت مربع را محاسبه خواهد کرد.
- اگر دو پارامتر به متد ارسال شود، در آن حالت بهصورت خودکار مساحت مستطیل حاسبه خواهد شد.
مثال زیر متدی چند ریختی در پایتون است که توسط کاربر تعریف شده است.
خروجی مثال بالا به شکل زیر خواهد بود.
Area of Square is: 25
Area of Rectangle is: 15سربارگزاری عملگرها در پایتون
«سربارگزاری عملگر» (Operator Overloading) به معنی این است که بسته به عملوندهایی که استفاده میکنیم، رفتار پیشفرض عملگر مورد استفاده تغییر کند. به عبارت دیگر، میتوانیم از عملگری یکسان برای هدفهای مختلف استفاده کنیم.
برای مثال، عملگر + وقتی که همراه با اعداد استفاده میشود، عمل جمع ریاضی را انجام خواهد داد. به همین ترتیب وقتی که با عملوندهایی از جنس رشته مورد استفاده قرار گیرد باید عمل الحاق یا چسباندن رشتهها را به هم انجام دهد.

عملگر + برای انجام عملیات مختلف روی نوع دادههای مجزا از هم، مورد استفاده قرار میگیرد. این مورد یکی از سادهترین مظاهر چند ریختی در پایتون است.
برای درک بهتر مطلبی که در بالا مورد اشاره قرار گرفت، لطفا به کد زیر توجه کنید.
خروجی این کد به صورت زیر میشود.
300
JessRoy
[10, 20, 30, 'jessa', 'emma', 'kelly']سربارگزاری عملگر + برای اشیا سفارشی کاربر
فرض کنید که دو شی داریم که توسط کلاسی که خود تعریف کردهایم ایجاد شدهاند. اکنون میخواهیم که این دو شی را به کمک عملگر باینری + با یکدیگر جمع ببندیم. البته توجه دارید که اگر این عملیات جمع را انجام دهیم، با خطا روبهرو خواهیم شد زیرا کامپایلر پایتون دو شی سفارشی را با هم جمع نمیکند. مثالی که در ادامه زدهایم را برای بررسی جزییات بیشتر ببینید.
خروجی کد بالا به صورت زیر خواهد بود.
TypeError: unsupported operand type(s) for +: 'Book' and 'Book'اگرچه که میتوانیم عملگر+ را برای کار کردن با اشیا سفارشی Overload کنیم. پایتون چندین تابع مخصوص یا به اصطلاح جادویی را فراهم میکند که وقتی با عملگرهای اختصای روبهرو میشوند، بهطورخودکار فراخوانی میشوند. برای مثال، زمانی که از عملگر+ استفاده میکنیم، متد جادویی __add__() به صورت خودکار فراخوانی میشود. عملگر داخلی + با استفاده از متد __add__() پیادهسازی شده است. برای استفاده از عملگر + به منظور جمع کردن اشیا سفارشی، مجبوریم که این متد را داخل کلاس خودمان بازنویسی کنیم.
به مثال زیر توجه کنید.
خروجی کد بالا بهصورت زیر خواهد بود.
Total number of pages: 700سربارگزاری عملگر *
عملگر* برای اجرای عملیات ضرب مورد استفاده قرار میگیرد. برای نمونه، روش Overload کردن این عملگر را برای محاسبه میزان حقوق کارمندان در دورههای زمانی خاصی پیادهسازی خواهیم کرد. عملگر داخلی * با استفاده از متد__mul__() پیادهسازی شده است.
لطفا به پیادهسازی زیر دربار این متد توجه کنید.
خروجی این کد به صورت زیر خواهد بود.
Wroked for 50 days
salary is: 40000متدهای جادویی
«متدهای جادویی» (Magic Methods) در پایتون به متدهای خاصی گفته میشود که در هر طرف نامشان دو «خط زیر» (Underscore) وجود دارد. این متدها بهصورت توکار برای پایتون طراحی شدهاند و عمده مصرف اینها برای پیادهسازی عملیات «سربارگزاری» (Overloading) در پایتون است. به منظور این کار متدهای جادویی مختلفی وجود دارند. نامهای متدهای جادویی که برای سربارگزاری عملگرهای ریاضی، عملگرهای انتسابی و عملگرهای رابطهای استفاده میشوند را در جدولهایی در زیر آوردهایم.
در ابتدا با «عملگرهای ریاضی» (Mathematical Operator) یا عملگرهای محاسبهای شروع میکنیم.
| نام عملگر | نماد | متد جادویی |
| جمع | + | __add__(self, other) |
| منها | - | __sub__(self, other) |
| ضرب | * | __mul__(self, other) |
| تقسیم | / | __div__(self, other) |
| تقسیم صحیح | // | __floordiv__(self,other) |
| باقیمانده | ٪ | __mod__(self, other) |
| توان | ** | __pow__(self, other) |
جدول بعدی متعلق به «عملگرهای انتسابی» (Assignment Operator) است.
| نام عملگر | نماد | متد جادویی |
| «انتساب افزایشی» (Increment) | += | __iadd__(self, other) |
| «انتساب کاهشی» (Decrement) | -= | __isub__(self, other) |
| «انتساب ضربی» (Product) | *= | __imul__(self, other) |
| تقسیم | /= | __idiv__(self, other) |
| باقیمانده | %= | __imod__(self, other) |
| توان | **= | __ipow__(self, other) |
و جدول آخر به «عملگرهای رابطهای» (Relational Operators) تعلق دارد.
| نام عملگر | نماد | متد جادویی |
| کوچکتر | < | __lt__(self, other) |
| برزگتر | > | __gt__(self, other) |
| کوچکتر یا مساوی | <= | __le__(self, other) |
| بزرگتر یا مساوی | >= | __ge__(self, other) |
| مساوی بودن یا نه | برابری | == | __eq__(self, other) |
| نامساوی بودن یا نه | نابرابری | != | __ne__(self, other) |
جمع بندی
چند ریختی در پایتون، عنصر بسیار مهمی در برنامهنویسی شیگرایانه است. چندریختی باعث میشود که پایتون توانایی حل مسائل متنوعتر را با عملکرد بهتری داشته باشد. به برنامهنویسان کمک میکند که با کمک تنها یک عملگر یا تابع، چندین وظیفه مختلف را انجام دهند. همچنین آنها را قادر میسازد که وقتی متدها را در کلاسهای مختلفی تعریف میکنند یا به منظور کاملا مجزایی آنها را بازتعریف میکنند، از نامهای تکراری استفاده کنند.
آموختن روشهای استفاده از پلی مورفیسم در پایتون برای استفاده در متدهای کلاسی، توابع یا عملگرها ممکن است با چالشهایی همراه باشد اما اولین بار که به استفاده از این روش بپردازید، به برنامهنویس حرفهایتری تبدیل خواهید شد. در این مطلب از مجله فرادرس، سعی کردیم که این تکنیک برنامهنویسی را به شفافترین و کاملترین شکل ممکن بیان کنیم تا هم برای برنامهنویسهای تازه کار قابل درک باشد و هم برای برنامهنویسان حرفهای مفید.













بسیار عالی و جامع بود.