این مار خوش خط و خال
آموزش کار با زبان برنامه‌نويسی پايتون (بخش چهارم)
به‌واسطه قولی که در قسمت پیشین آموزش کار با زبان برنامه‌نويسی پايتون داده بودیم، این شماره را به بررسی برنامه‌نویسی شیء‌گرا و به‌ویژه شیء‌گرایی در پایتون اختصاص داده‌ایم.

1606683296_1_0.gif


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


برای مطالعه قسمت‌های قبلی سلسله مقالات آموزش زبان برنامه‌نویسی پایتون اینجا کلیک کنید


 

انسان ناخودآگاه و به شیوه‌ای نوستالژیک سعی دارد پدیده‌های تازه اطرافش را به موارد شناخته شده ربط داده یا تبدیل کند و آن‌ها را بر اساس قوانین شناخته شده توضیح دهد. به عنوان مثال، اتومبيل‌ها را با صفاتی مانند خشمگین، هیولا و.... بخواند، برنامه‌های مخرب کامپیوتری را ویروس بنامد و حتی در برنامه‌نویسی سعی کند همه‌چیز یک برنامه را به شیء تبدیل کند. گویی در این دنیای صفر و یکی هم مانند زندگی روزمره با خرده‌ریزهای فیزیکی سروکار دارد. بیشتر شما به یقین اصطلاح برنامه نویسی شی‌ءگرا یا OOP (سرنام Object Oriented Programming) را شنیده‌اید. این اصطلاح به‌طور‌معمول در برابر اصطلاح برنامه‌نویسی تابع‌گرا یا Functional مطرح می‌شود و به نوعی از برنامه‌نویسی اشاره دارد که به‌صورت خلاصه سعی دارد مجموعه داده‌های دارای خواص و عملکرد یکسان را به عنوان یک نوع‌داده یا DataType طبقه‌بندی کرده و از این راه مدیریت کد را ساده‌تر کند.
میانه راه
نكته جالب توجه اين كه شما استفاده از اشیا، کلاس‌ها، متدها و خلاصه تمام جنبه‌های برنامه‌نویسی شیء‌گرا را زمانی که نخستین خط کد به زبان پایتون را نوشته‌اید، شروع کرده‌اید. بنابراين، شما اکنون در میانه راه هستید! تمام اجزاي پایتون بر‌اساس سیستم شیء‌گرا نوشته شده‌اند. در پایتون هرچیزی، حتی یک مقدار عددی نظیر 1/67 نیز یک شیء است. اما تمام این هیاهو بر سر چیست؟ کلاس و شیء چیستند؟ 
در تعاریف برنامه‌نویسی «شیء» مجموعه‌ای از داده‌ها است که دارای تعدادی خصوصیت یا Attribute و تعدادی عملکرد یا Method است. خواص، نگه‌دارنده مشخصات و وضعیت شیء هستند و متدها،  قابلیت‌ها و عملیات ممکن روی یک شیء را نشان می‌دهند. کلاس نیز درواقع دستورالعمل ساخت یک شیء جدید است. اگر یک اتومبیل را به عنوان یک شیء در نظر بگیریم، دارای خصوصیاتی نظیر نام، مدل، حداکثر سرعت، تاریخ ساخت و... خواهد بود. عملیات استارت زدن، حرکت به جلو، ترمز گرفتن و... نیز متدهای این شیء به شمار می‌آیند. در این حالت مستندات و طرح‌های موجود در کارخانه، که اتومبیل از روی آن‌ها ساخته می‌شود، کلاس این شیء خواهد بود.

مطلب پیشنهادی

ساده‌ترین زبان‌های برنامه‌نویسی ویژه افراد تازه‌کار
این زبان‌ها به شما در درک بهتر برنامه‌نویسی کمک می‌کنند
 به خط فرمان پایتون بروید و متغیر s را برابر “ABCD” (به بزرگ بودن تمام حروف دقت کنید) تعریف کنید. می‌دانید که این متغیر از نوع رشته خواهد بود. به‌صورت دقیق‌تر s يك شیء (Object) از نوع (Class) رشته است. دستور (dir(s را تایپ کنید. همان‌گونه که در شکل 1 می‌بینید، فهرست تمام خواص و عملکردهای شیء s برای شما نمایش داده خواهد شد. برای استفاده از خواص و متدها، از نماد نقطه‌گذاری معمول استفاده می‌کنیم. تنها توجه داشته باشید که برای فراخوانی متدها به علامت () نیاز خواهیم داشت.. به عنوان نمونه دستور s.__doc__ را تایپ کنید. (علامت __ از دو زیر خط پیوسته تشکیل شده است). همان‌طور که می‌بینید این خاصیت توضیحات مربوط به شیء رشته را چاپ خواهد کرد. اکنون از دستور () s.isupper استفاده کنید.  این یکی از متدهای شیء رشته است که اگر تمام حروف رشته بزرگ باشند، مقدار True  وگرنه False را بازخواهد گرداند. اگر این متد را بدون پرانتز فراخواني كنيد، پایتون تنها نوع و آدرس آن را برای شما نمایش خواهد داد. از() dir می‌توان برای استخراج خواص و متدهای تمام اشيا، داده‌ها، توابع، ماجول‌ها و... استفاده کرد. 
 
شکل 1- فهرست خواص و متدهای شییء
 
class Point:
x=0
y=0
p1= Point()
p2= Point()
p1.x=7
p2.x=9
p1.name = “Start Point”
print p1.x , p2.x
print p1.name
print p1
 
فهرست 1-  تعريف نوع داده‌ای جديد برای شیء نقطه
 
کاربرد
استفاده از سیستم شیء‌گرا در برنامه‌ها مزیت‌های فراوانی را به همراه خواهد داشت. ابتدا این‌که اجزای برنامه به سیستم ادراکی روزمره ما نزدیک‌تر شده و به همین دلیل درک منطق برنامه‌نویسی در بسیاری موارد ساده‌تر از حالت تابعی (روشی که در برنامه‌های قسمت‌های قبل به‌کار می‌بردیم) خواهد بود. همچنین در این روش به دلیل پیاده‌سازی متدها و داده‌ها در خود شیء، نیاز به انتقال اطلاعات بین توابع مختلف و ارجاع‌های متعدد از میان خواهد رفت و دیگر این‌که با فراهم شدن امکان ارث‌بری خصوصیات میان اشیا، پیاده سازی اشیا جدید و کلاس‌های پیچیده راحت‌تر می‌شود.
فرض کنید با برنامه‌ای سروکار داریم که قرار است مشخصات و روابط هندسی میان تعدادی نقطه را پردازش و محاسبه كند. به مفهوم ریاضی نقطه فکر کنید که عبارت است از دو عدد (مختصات) که در مجموع به عنوان یک شیء واحد در نظر گرفته می‌شوند. در پیاده‌سازی برنامه و به عنوان یک راه‌حل ساده می‌توانید مختصات نقاط را در یک توپل یا یک لیست قرار دهید. اگرچه این کار می‌تواند تا حدودی نیاز برنامه را برآورده کند، اما برای کار با داده‌های زیاد، اصلاً مناسب نخواهد بود. بهترین کار تعریف یک نوع داده (Data Type) اختصاصی جدید برای کار با نقطه است. فهرست 1 را در ویرایشگر دلخواه خود وارد کرده، با نام Points01.py ذخیره کرده و آن را اجرا کنید. خروجی باید همانند شکل 2 باشد.
شکل 2- خروجی برنامه فهرست 1
 
در خط اول با کلمه کلیدی class و سپس نام نوع داده جدید، آن را تعریف کرده‌ایم. در خطوط بعدی، مختصات طول و عرض در داخل خود این کلاس تعریف شده و برابر صفر مقدار داده شده‌اند. تعاریف کلاس به‌طورمعمول در ابتدای برنامه و بعد از دستورات import آورده می‌شوند و رسم بر این است که نام کلاس‌های جدید همواره با حروف بزرگ آغاز شوند. در خطوط5 ,6 دو متغیر جدید از نوع نقطه تعریف شده‌ و سپس مقدار x و y آن‌ها دست‌کاری شده است.  تعریف متغیر از یک نوع جدید را به اصطلاح وهله‌سازی، نمونه‌سازی یا Instantiation می‌نامند. در خطوط 12 تا 14 مقادیر برخی خصوصیات این اشیا چاپ خواهد شد. همان‌گونه که در خط 10 مشاهده می‌کنید، به کمک نماد نقطه‌گذاری می‌توان داده جدیدی (نظیر نام) را نیز به یک نمونه از یک کلاس اضافه کرد. اگر تلاش کنید تا خود شیء p1 را چاپ کنید، پایتون نوع یا کلاس سازنده آن را باز خواهد گرداند. 
به سادگی می‌توان نمونه‌های یک شیء را  همانند یک مقدار یا یک متغیر به یک تابع پاس کرد. فایل قبلی را همانند فهرست 2 ویرایش کرده و با نام Points02.py ذخیره کنید. پس از اجرای این کد خروجی برنامه همانند شکل 3 خواهد بود. 
 
class Point:
x=0
y=0
#### Functions
def PrintPoint(p):
text=»Point at %d,%d» %(p.x,p.y)
print text
def MoveLeft(p,how_much):
p.x = p.x - how_much
def MoveRight(p,how_much):
p.x = p.x + how_much       
#### Instantiation    
p1= Point()
p1.x = 5
p1.y = 9
 
p2= Point()
p2.x = -9
p2.y = 4
 
PrintPoint(p1)
PrintPoint(p2)
#### Moving points
MoveLeft(p1,3)
MoveRight(p2,10)
#### Printing results
print
print “P1 after moving . . . “
PrintPoint(p1)
print “P2 after moving . . . “
PrintPoint(p2)
فهرست 2- تعريف توابعی برای كار با انواع داده جديد
 
شکل 3- خروجی برنامه فهرست 2
در خط 5 تابعی تعریف کرده‌ایم که یک نقطه را به عنوان ورودی گرفته و با استخراج مقادیر x   و y،  آن را در قالب بهتری نسبت به دستور print چاپ می‌کند. توابع Move Left  و/ Move Right  هم نقطه را در جهت X چپ‌و راست مي‌كنند. توجه داشته باشید که تابع تعریف شده هیچ اطلاعی از نوع یا مقداری که در آینده به آن ارجاع خواهد شد ندارد و فعلاً برنامه‌نویس باید کنترل کند که آیا مقدار پاس شده با کارکرد تابع همخوانی دارد یا خیر. می‌توانید برای امتحان یک مقدار عددی را به تابع پاس کنید و نتیجه را ببینید. همچنین در این قطعه کد توابعی تعریف شده‌اند که نقطه را به اندازه مشخصی به چپ یا راست می‌برند و نتیجه عملیات آن‌ها توسط خطوط . . . .  چاپ خواهد شد.

مطلب پیشنهادی

چرا یادگیری پایتون برای برنامه‌نویسان تازه‌کار کاملا ایده‌آل است؟
پایتون زبانی با ترکیب نحوی ساده و کارایی بالا
کمی پیشرفته‌تر
اما همان‌گونه که از ابتدا گفتیم، یکی از مهم‌ترین قابلیت‌های شیء‌گرایی، انتقال توابع و محاسبات و اطلاعات به داخل خود شیء است. در واقع ما می‌توانیم توابع تعریف شده در فهرست 2 را به داخل خود کلاس Point منتقل کنیم تا نیازی به پاس کردن نقطه‌ها به توابع مختلف وجود نداشته باشد. فهرست3 را وارد کرده و با نام Points03.py ذخیره کنید. نتیجه اجرای این کد در شکل 4 آورده شده است.
 
class Point:
def __init__(self,input_x,input_y):
self.x = input_x
self.y = input_y
def PrintPoint(self):
text=”Point at %d,%d” %(self.x,self.y)
print text
def MoveLeft(self,how_much):
self.x = self.x - how_much
def MoveRight(self,how_much):
self.x = self.x + how_much    
#### Instantiation    
p1= Point(6,13)
p2= Point(9,11)
####  
p1.PrintPoint()
p2.PrintPoint()
#### Moving points
p1.MoveLeft(3)
p2.MoveRight(10)
#### Printing results
print
print “P1 after moving . . . “
p1.PrintPoint()
print “P2 after moving . . . “
p2.PrintPoint()
فهرست 3- تعريف متدها در يك كلاس
در این تعریف جدید از کلاس Point توابع مورد نیاز برای دستکاری یک نقطه به داخل خود کلاس منتقل شده‌اند. در خط2 شما یکی از اساسی‌ترین توابع یک کلاس یعنی __init__  را مشاهده می‌کنید. هربار که نمونه جدیدی از یک کلاس ساخته می‌شود، این تابع اجرا خواهد شد. اگر نمونه‌های شما در همان هنگام ساخت احتیاج به مقداردهی اولیه یا تنظیم برخی ویژگی‌ها داشته باشند، محل انجام تمام این عملیات بدنه تابع __init__   است. به خاطر داشته باشید که معرفی آرگومان‌های تابع __init__  (به جز self) در هنگام ایجاد یک نمونه جدید از کلاس الزامی است. (خطوط 13 و 14 را ببينيد)
با این سیستم انتقال توابع به داخل کلاس، خطوط 13 تا 19 از فهرست 2 به سادگی به خطوطي شبيه 13 و 14 از فهرست 3 تبدیل می‌شود. نکته‌ای که به یقین توجه شما را جلب کرده، آرگومانی به نام self است. این آرگومان همواره به خود شیء اشاره می‌کند. اگرچه این آرگومان باید در تمام توابع موجود در کلاس آورده شود، اما هنگام فراخوانی این توابع، به وارد کردن آن نيازي نیست و پایتون به‌صورت خودكار، خود شیء را به تابع ارسال می‌کند. از کلمه self در تمام قسمت‌های تعریف یک کلاس برای ارجاع به خود شیء استفاده می‌شود. 

شکل 4-  خروجی برنامه فهرست 3

شکل 5 - خروجی برنامه فهرست 5
 
تابع خاص دیگری که در این کد وجود دارد __str__ است که تنها آرگومان آن هم خود شیء، یعنی self است. همان‌گونه که در ابتدای بحث اشاره شد، پایتون زبانی کاملاً شی‌ء گراست و تمام اجزای آن در حقیقت شیء هستند. هنگامی که شما شیء، متغیر یا عبارتی را در برابر کلمه کلیدی print قرار می‌دهید، پایتون در واقع تنها متد __str__ آن شیء را فراخوانی می‌کند. بنابراین، به جای تعریف یک تابع مستقل PrintPoint با تعریف __str__ می‌توانیم رفتار دستور print را برای شیء موردنظرمان يعني نقطه، به دلخواه تغییر دهیم. 
به همین ترتيب، ما در انتهای کد به سادگی از کلمه کلیدی print استفاده کرده‌ایم که درواقع مقدار برگشتی تابع __str__  را نمایش خواهد داد. سایر توابع با همان فرمت قبل و فقط با افزوده شدن یک آرگومان self مورد استفاده قرار گرفته‌اند. به خاطر داشته باشید که پس از این کار، این توابع متعلق به شیء نقطه هستند و برای فراخوانی آن‌ها باید از روش نشانه‌گذاری نقطه‌ای استفاده کرد.

مطلب پیشنهادی

چرا پایتون و #C هنوز نمی‌توانند جایگاه جاوا، C و ++C را به‌دست آورند؟
پایتون و #C از محدودیت‌های ریشه‌ای خود ضربه می‌خورند
بار اضافی
برای انجام عملیات ریاضی نظیر جمع و ضرب هم می‌توان مانند مثال قبلی توابعی جداگانه ایجاد کرد یا برای ساده‌تر شدن کار با اشیا می‌توان عملگرهای معمول ریاضی را به گونه‌ای تغییر داد که با اشیا نیز کار کنند. به عنوان مثال، اگر بخواهید عمل جمع بین دو شیء نقطه را برای پایتون تعریف کنید، کافی است تابعی با نام __add__  را مانند فهرست 4 در تعریف کلاس آن بگنجانید. 
در این‌صورت هنگامی که پایتون با عبارتی نظیر p1+p2 روبه‌رو شود که در آن p1 و p2 هردو از جنس نقطه باشند، تنها متد(p1.__add__(p2  را اجرا خواهد کرد. این کار را باردهی اضافی عملگر یا Operator Overloading می‌نامند. برای باردهی اضافي تفریق، ضرب و تقسیم باید به ترتیب توابعی با نام‌های __sub__  ، __mul__  و __div__ را در کلاس موردنظر پیاده کنیم. برای بررسی تساوی یا بزرگ‌تر و کوچک‌تر بودن هم باید تابعی با نام __cmp__ را تعریف و تنظیم کرد. 
 
def __add__(self,Second_Point):
result_x = self.x + Second_Point.x
result_y = self.y + Second_Point.y
return Point(result_x,result_y)
 
فهرست 4- بار دهی اضافي عملگر جمع
اشیاي مرکب
اشیا می‌توانند در ترکیب با یکدیگر به ساخت اشیا جدید و پیچیده‌تر کمک کنند. به عنوان مثال، اگر مدل‌سازی یک مستطیل مدنظر باشد، برای تعیین مشخصات آن می‌توانیم از نقطه گوشه و طول اضلاع آن استفاده کنیم. در این‌صورت کافی‌ است کلاس Rectangle را ایجاد کنیم و برای نگه‌داری اطلاعات مربوط به گوشه مستطیل از یک شیء نقطه استفاده کنیم. کد فهرست 5 را تایپ، با نام Rectangle.py ذخیره و اجرا کنید. نتیجه خروجی باید همانند شکل 5 باشد. همان‌گونه که در خط 11 مشاهده می‌کنید، برای نگه‌داری نقطه گوشه مستطیل از یک شیء نقطه استفاده شده است. همین‌طور به خط 15 نیز توجه کنید که برای ساخت خروجی __str__  مستطیل تابع __str__  مربوط به نقطه گوشه هم فراخوانده شده است.
 
class Point:
def __init__(self,input_x,input_y):
self.x = input_x
self.y = input_y
def __str__(self):
text=»Point at %d,%d» %(self.x,self.y)
return text
 
class Rectangle:
def __init__(self,x,y,w,h):
self.corner = Point(x,y)
self.width = w
self.height = h
def __str__(self):
text = “Rectangle corner is %s” %self. corner.__str__()
text += “\nWidth = %d \t Height = %d” %(self.  width , self.height)
return text
r1= Rectangle(2,3,10,12)
print “r1 is:\n” , r1
r2=r1
print “r2 is: \n” , r2
r2.width = 40
print “\nr2 changed to \n” , r2
print “\nr1 changed too !! \n” , r1
 
فهرست 5 - تعريف شیء مستطيل (توجه كنيد كه خطوط 15، 16 و 17، 18 بايد در يك سطر تايپ شود) 
 
نکته‌ای که همیشه باید درباره اشیا به یاد داشته باشید این است که استفاده از علامت مساوی برای انتساب یک شیء به شیء دیگر، درواقع به محل آن شیء در حافظه ارجاع می‌دهد. 
به عبارت دیگر، در خط شماره 22  r2 مستطیلی جداگانه نیست، بلکه دقیقاً به همان محل نگه‌داری  r1 در حافظه اشاره می‌کند و بنابراین، هر تغییری در r2 یا r1 (همانند خط شماره 24) به هردوی آن‌ها اعمال می‌شود. برای تهیه کپی‌های مجزا از اشیا، باید از ماجولی به نام copy استفاده کنیم. 
این ماجول دو متد بسیار پرکاربرد دارد؛ یکی متد copy و دیگری متد deepcopy. تفاوت این دو متد در هنگام برخورد با اشیا جاسازی شده در یک شیء مرکب مشخص می‌شود. به شکل 6 توجه کنید. 
این دیاگرام وضعیت r1 و r2 را در حالت‌های مختلف نشان می‌دهد. همان‌گونه که مشاهده می‌کنید با استفاده از عبارت r2=r1 به طور عملي شیء جدیدی ایجاد نخواهد شد، بلکه شیء r2  تنها به محل r1 در حافظه اشاره خواهد کرد. در صورت استفاده از متد ()copy.copy  اگرچه r2 یک شیء جدید خواهد شد، اما همانند r1 برای نگه‌داری نقطه گوشه همچنان به محل شیء p1 در حافظه اشاره خواهد کرد.  يعني اشيا داخل شيء اصلي كپي نمي‌شوند.بنابراین، تغییر گوشه مستطیل r2 باعث تغییر r1 هم خواهد شد و برعكس. 
اما استفاده از متد() copy.deepcopy نه تنها خود شیء که تمام ارجاعات و اشیا موجود در آن را نیز به‌صورت مستقل کپی خواهد کرد.
 
class Point:
def __init__(self,input_x,input_y):
self.x = input_x
self.y = input_y
def __str__(self):
text=”Point at %d,%d” %(self.x,self.y)
return text
def MoveLeft(self,how_much):
self.x = self.x - how_much
def MoveRight(self,how_much):
self.x = self.x + how_much    
class NewPoint(Point):
def MoveUp(self,how_much):
self.y = self.y + how_much
 
p=NewPoint(5,6)
print p ,”\n”
 
p.MoveLeft(2)
print p ,”<-- Moved left\n”
 
p.MoveUp(5)
print p ,’’<-- Moved Up\n”
فهرست 6- ارث‌بری یک کلاس جدید از کلاس موجود

شکل 6- حالت‌های متفاوت تكثير اشيا

شکل 7-  خروجی فهرست 6
ارث‌بری خصوصیات
هر شیء در پایتون می‌تواند والد شیء دیگری باشد و تمام یا برخی خصوصیات خود را برای این شیء جدید به ارث بگذارد. برای درک این موضوع کد فهرست 6 را تایپ کرده و با نام Points05.py ذخیره و اجرا کنید. خروجی این قطعه کد باید همانند شکل 7 باشد. 
توجه کنید که همه اشیا، حتی آن‌هایی‌که خود شما به‌صورت مستقل ایجاد می‌کنید، در نهایت فرزند يك شي‌ء مادر به نام object هستند و خصوصیاتی نظیر __doc__ و __module__   را از آن به ارث می‌برند. در واقع object والد تمام اشیا پایتون است. همان‌گونه که در خط 12 مشاهده می‌کنید، برای مشخص کردن والد یک کلاس جدید کافی‌ است نام کلاس والد را پس از نام کلاس جدید در یک پرانتز ذکر کنید. در این مثال کلاس NewPoint تمام خصوصیات و متدهای کلاس Point (همانند MoveLeft) را به ارث برده است. این کلاس متد جدیدی (MoveUp) دارد که والد آن، یعنی Point فاقد آن است.  اگر قصد تغییر یکی از متدهای کلاس والد را در این کلاس جدید دارید، کافی است آن‌ متد را بازنویسی کنید. 
در قسمت بعدی به کمک سیستم شیء گرا و نرم‌افزار Boa Constructor به سراغ ساخت برنامه‌های گرافیکی خواهیم رفت.

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

ثبت اشتراک نسخه کاغذی ماهنامه شبکه     
ثبت اشتراک نسخه آنلاین

 

کتاب الکترونیک +Network راهنمای شبکه‌ها

  • برای دانلود تنها کتاب کامل ترجمه فارسی +Network  اینجا  کلیک کنید.

کتاب الکترونیک دوره مقدماتی آموزش پایتون

  • اگر قصد یادگیری برنامه‌نویسی را دارید ولی هیچ پیش‌زمینه‌ای ندارید اینجا کلیک کنید.

ایسوس

نظر شما چیست؟