کتاب یک بایت از پایتون.فصل یازدهم.برنامه نویسی شیء گرا
از PyLearn.com
فهرست مندرجات |
برنامه نویسی شیء گرا
معرفی
تا بدین جا در تمامی برنامه ها ، برای طراحی برنامه های مان ، از توابع و یا بلاک هایی از دستوراتی که داده ها را دستکاری می کردند استفاده می کردیم ، این روش در برنامه نویسی بعنوان برنامه نویسی رویه گرا ( procedure-oriented ) شناخته می شود . شیوه ی دیگری از ساماندهی برنامه ها وجود دارد که داده و عملیات بروی داده را در کنار هم ترکیب می کند و این دو را در داخل آن چیزی قرار می دهد که شیء نامیده می شود . این شیوه از برنامه نویسی بعنوان الگوی برنامه نویسی شیء گرا شناخته می شود . اکثر مواقع ممکن است که شما از برنامه نویسی ساخت یافته استفاده کنید ولی زمانی که قصد نوشتن برنامه های بزرگ و یا راه حلی که بیان اش در این روش مناسب تر است را دارید ، می توانید از شیوه برنامه نویسی شیء گرا استفاده کنید .
کلاس ها و اشیاء دو مفهوم اساسی در برنامه نویسی شیء گرا هستند . یک کلاس یک نوع جدید را ایجاد می کند ، در حالیکه اشیاء نمونه هایی از آن کلاس هستند . بدین معنی که ، اینکه شما می توانید متغیرهایی از نوع int داشته باشید ، به تعبیری دیگر مثل آنست که بگوئید ، متغیرهایی که اعداد صحیح را ذخیره می کنند ، متغیرهایی هستند که نمونه هایی (اشیاء ایی ) از کلاس int هستند .
نکاتی برای برنامه نویسان C/C++/Java/C#
توجه داشته باشید که حتی اعداد صحیح هم به مانند اشیاء ( اشیایی از کلاس int ) رفتار می کنند . این برخلاف C++ و جاوا است (البته پیش از نسخه 1.5 ) که اعداد صحیح از نوع های اولیه اصلی هستند . برای جزئیات بیشتر در مورد این کلاس help(int) را ملاحظه کنید .
برنامه نویسان C# و Java 1.5 با این مفهوم آشنا تر هستند ، چرا که شبیه به مفهوم boxing و unboxing در این زبان ها هست .
اشیاء می توانند داده ها را با استفاده از متغیرهای معمولی که به شیء تعلق دارد ، ذخیره کنند . متغیرهایی که به یک شیء یا یک کلاس تعلق دارند ، فیلد نامیده می شوند . همچنین اشیاء می توانند عملیاتی را با استفاده از توابعی که متعلق به کلاس هستند ، انجام دهند . این چنین توابعی method هایی از کلاس نامیده می شوند . این اصطلاحات بسیار مهم هستند ، چرا که به ما کمک می کند تا بین توابع و متغیرهایی که بصورت جدا از هم تعریف می شوند با توابع و متغیرهایی که متعلق به یک کلاس و یا شیء هستند ، تفاوت قائل بشویم . بطور مشترک ، فیلد ها و متدها می توانند بعنوان مشخصه/ویژگی هایی (attributes) از کلاس اشاره شوند .
فیلدها بر دو قسم اند : یک قسم آنهایی که می توانند به هر شیء/ نمونه ایی از کلاس تعلق بگیرند ، و یک قسم دیگر آنهایی که می توانند به کلاس خودشان تعلق بگیرند . قسم اول instance variables و قسم دوم class variables نامیده می شوند .
کلاس با استفاده از کلمه کلیدی class ایجاد می شود ، فیلدها و متدهای کلاس در یک بلاک تورفته فهرست می شوند .
self
متدهای کلاس تنها یک تفاوت مشخص با توابع معمولی دارند ، آنها می بایست دارای یک اسم اضافی باشند که بعنوان اولین آرگومان به فهرست پارامترهای متد اضافه شود ، اما شما هنگام فراخوانی متد نباید مقداری به آن ارسال کنید ،چرا که پایتون خود این کار را انجام می دهد . این متغیر خاص به شیء خودش رجوع می کند ، و رسم هست که نام self به آن داده شود .
هرچند که می توانید هر اسمی به این پارامتر بدهید ، اما به شدت پشنهاد می شود که از نام self استفاده کنید – هر نام دیگری بطور معلوم ناخوشایند است . مزیت های زیادی هست که از یک نام استاندارد استفاده بشود – هر خواننده ی برنامه های شما فورا آن را تشخیص می دهد و حتی اگر از نام self استفاده کنید IDE های (محیط های توسعه یکپارچه) مخصوص نیز می توانند به شما کمک کنند .
نکاتی برای برنامه نویسان C++/Java/C#
self در پایتون همان اشاره گر self در C++ و مرجع this در جاوا و C# است .
حتما کنجکاو هستید که پایتون به چه صورت مقدار self را ارسال می کند و چرا شما نیازی به مقداردهی به آن ندارید . با یک مثال این موضوع را شفاف می کنیم ، فرض کنید کلاسی به اسم MyClass و نمونه ایی از این کلاس به نام MyObject دارید . هنگامی که متدی از این کلاس را بعنوان نمونه MyObject.method(arg1,arg2) صدا می زنید . این کد بصورت خودکار توسط پایتون به MyClass.method(Myobject,arg1,arg2) تبدیل می شود - این تمام آن چیزی بود که self بطور خاص داشت . این مطلب همچنین بدین معنی ست که اگر متدی داشته باشید که هیچ آرگومانی بعنوان ورودی نمی پذیرد ، باز شما می بایست آرگومان self را برای آن تعریف نمائید .
کلاس ها
یک کلاس ِ تا حد ممکن ساده در مثال زیر آورده شده است .
ایجاد یک کلاس : مثال 11.1 . ایجاد یک کلاس :
#!/usr/bin/python # Filename: simplestclass.py class Person: pass # An empty block p = Person() print p
خروجی :
$ python simplestclass.py <__main__.Person instance at 0xf6fcb18c>
نحوه ی عملکرد این مثال
ما یک کلاس جدید را با استفاده از دستور class و بدنبال آن با آوردن نام کلاس و متعاقبا با یک بلاک تو رفته از دستورات که بدنه کلاس را شکل می دهد ایجاد کردیم . در این مثال ، ما بلاک خالی تو رفته را تنها با یک دستور pass نشان دادیم . سپس ، یک شیء/نمونه از این کلاس را با استفاده از نام کلاس همراه با یک جفت پرانتز باز وبسته ، ایجاد کردیم . ( در بخش بعدی درباره نمونه سازی بیشتر خواهیم آموخت ) . وسپس برای اثبات صحت کارمان به سادگی با چاپ کردن متغیر، از نوع آن مطمئن می شویم ، چرا که به ما نشان می دهد که یک نمونه از کلاس Person ، در ماژول __main__ داریم . توجه داشته باشید که آدرس حافظه کامپیوتری که شیء در آن ذخیره شده است نیز چاپ می شود . این آدرس در کامپیوترهر کسی متفاوت هست ، چرا که پایتون می تواند شیء را در هر کجایی از حافظه که فضای خالی یافت ذخیره کند .
متدهای شیء
ما درباره اشیاء / کلاس ها صحبت کردیم و گفتیم که می توانند متدهایی مشابه به توابع داشته باشند ، تنها با این استثناء که یک آرگومان اضافی به نام self دارند . حال می خواهیم مثال هایی دراین رابطه داشته باشیم .
استفاده از متدهای اشیاء
مثال 11.2 . به کارگیری متدهای اشیاء
#!/usr/bin/python # Filename: method.py class Person: def sayHi(self): print 'Hello, how are you?' p = Person() p.sayHi() # This short example can also be written as Person().sayHi()
خروجی
$ python method.py Hello, how are you?
نحوه ی عملکرد این مثال
در این مثال در عمل با self آشنا شدیم . دقت کنید که متد SayHi پارامتری نمی گیرد ولی با این حال self را در تعریف تابع خود دارد .
متد __init__
تعدادی از نام های متدها هستند که برای کلاس های پایتون معنی و مفهوم خاصی دارند . یکی از آن نام ها __init__ هست ، که اکنون می خواهیم به مفهوم آن پی ببریم . متد __init__ ، به مجرد اینکه یک شیء از کلاس نمونه سازی می شود ، اجرا می شود . این متد برای هر کار اولیه ایی که می خواهید با شیء تان انجام دهید ، مناسب است . به دو زیرخطی که در ابتدا و انتهای نام این متد هست دقت داشته باشید .
به کارگیری متد __init__
مثال 11.3 . استفاده از متد __init__
#!/usr/bin/python
# Filename: class_init.py
class Person:
def __init__(self, name):
self.name = name
def sayHi(self):
print 'Hello, my name is', self.name
p = Person('Swaroop')
p.sayHi()
# This short example can also be written as Person('Swaroop').sayHi()
خروجی :
$ python class_init.py Hello, my name is Swaroop
نحوه ی عملکرد این مثال
در اینجا ، ما متد __init__ را طوری تعریف کردیم که یک پارامتر به اسم name بپذیرد ( در کنارپارامتر self معمول و همیشگی ) . سپس تنها یک فیلد جدید به نام name ایجاد کردیم . دقت کنید که این ها متغیرهایی متفاوت هستند ، هر چند که نام های شان مثل هم است . علامت نقطه گذاشته شده باعث می شود که بین آنها تمایز قائل شویم . نکته حائز اهمیت این است که ما بطور صریح متد __init__ را فراخوانی نکردیم ، اما آرگومان های آن را در حین ایجاد یک نمونه از کلاس ، در بین پرانتزهای نام کلاس به آن فرستادیم . این مفهوم خاص این متد هست . اکنون ما می توانیم از فیلد self.name در تمامی متدهای مان استفاده کنیم ، که این کار در متد SayHi مشخص هست .
نکته ایی برای برنامه نویسان C++/Java/C#
متد __init__ در قیاس می تواند مثل سازندها در C++ ، C# و جاوا باشد .
متغیرهای شیء و متغیرهای کلاس
تا بدین جا درباره عملیاتی که بخشی از اشیاء و کلاس هستند بحث شد ، اکنون ما می خواهیم درباره ی داده که بخش دیگری از اشیاء و کلاس هستند بحث کنیم . در واقع ، داده ها چیز خاصی به جز متغیرهای معمولی که به فضای نامی اشیاء و کلاس ها bound شده اند ، نیستند ، بدین معنی که نام های این متغیرها تنها در داخل این کلاس ها و اشیاء معتبر هستند .
دو نوع ازفیلد ها وجود دارند – متغیرهای کلاس و متغیرهای شیء ، که طبقه بندی شان به ترتیب ، منوط براین است که آیا متغیرها متعلق به کلاس هستند و یا به شیء .
متغیرهای کلاس به این جهت به اشتراک گذاشته شده اند تا برای تمامی اشیاء ی (نمونه ها) کلاس قابل دسترس باشند . تنها یک کپی از متغیر کلاس وجود دارد و هنگامی که هر شیء ایی تغییری در یک متغیر کلاس بدهد ، این تغییر به درستی در تمامی نمونه های دیگر منعکس می شود .
متغیرهای شیء ،متعلق به هر شیء/نمونه منحصر به فرد از یک کلاس هستند . در این حالت ، هر شیء کپی مخصوص به خودش را از فیلد دارد ، بدین معنی که آنها اشتراک گذارده نمی شوند و رابطه ایی بین نام های یکسان آن ها در کلاس های یکسان از نمونه های متفاوت وجود ندارد . یک مثال باعث فهم آسان این مسئله خواهد شد .
استفاده از متغیرهای شیء و کلاس
مثال 11.4 . به کارگیری متغیرهای شیء و کلاس
#!/usr/bin/python
# Filename: objvar.py
class Person:
'''Represents a person.'''
population = 0
def __init__(self, name):
'''Initializes the person's data.'''
self.name = name
print '(Initializing %s)' % self.name
# When this person is created, he/she
# adds to the population
Person.population += 1
def __del__(self):
'''I am dying.'''
print '%s says bye.' % self.name
Person.population -= 1
if Person.population == 0:
print 'I am the last one.'
else:
print 'There are still %d people left.' % Person.population
def sayHi(self):
'''Greeting by the person.
Really, that's all it does.'''
print 'Hi, my name is %s.' % self.name
def howMany(self):
'''Prints the current population.'''
if Person.population == 1:
print 'I am the only person here.'
else:
print 'We have %d persons here.' % Person.population
swaroop = Person('Swaroop')
swaroop.sayHi()
swaroop.howMany()
kalam = Person('Abdul Kalam')
kalam.sayHi()
kalam.howMany()
swaroop.sayHi()
swaroop.howMany()
خروجی
$ python objvar.py (Initializing Swaroop) Hi, my name is Swaroop. I am the only person here. (Initializing Abdul Kalam) Hi, my name is Abdul Kalam. We have 2 persons here. Hi, my name is Swaroop. We have 2 persons here. Abdul Kalam says bye. There are still 1 people left. Swaroop says bye. I am the last one.
نحوه ی عملکرد این مثال
این مثال نسبتا بزرگی ست ولی ماهیت متغیرهای کلاس و شیء را برای ما روشن می کند . در اینجا ، population متعلق به کلاس Person هست و بنابراین یک متغیر کلاس هست . متغیر name متعلق به شیء ( که با استفاده از self نسبت دهی صورت گرفته است) هست و از اینرو یک متغیر شیء است.
بنابراین ما به متغیر کلاس population بصورت Person.population رجوع می کنیم و نه به صورت self.population . توجه داشته باشید در صورتیکه یک متغیر شیء ، نام یکسانی با متغیر کلاس داشته باشد ، متغیر کلاس را خواهد پوشاند ! در یک متد از شیء ، با استفاده از نشان self.name به متغیر شیء name رجوع می کنیم . تفاوت ساده بین متغیرهای کلاس و متغیرهای شیء را به خاطر بسپارید .
مشخص هست که متد __init__ برای ایجاد کارهای مقدماتی و اولیه ی نمونه ی Person به کار گرفته شده است . در این متد ، ما مقدار population را به اندازه یک واحد افزایش می دهیم ، چرا که یک شخص جدید به جمعیت ما اضافه می شود . همچنین واضح هست که مقدار self.name مختص هر شیء است و ماهیت متغیر شیء را برای ما روشن می کند .
به یاد داشته باشید که ، می بایست به متغیرها و متدهای یک شیء یکسان تنها با استفاده از متغیر self رجوع کنید . که این مطلب ، ارجاع به ویژگی ( attribute reference ) نامیده می شود .
همچنین در این مثال ، ما می بینیم که از docstring ها، هم برای کلاس و هم برای متدها استفاده کردیم . می توانیم در زمان اجرا به docstring مربوط به کلاس با استفاده از Person.__doc__ و به docstring مربوط به متد از طریق Person.sayHi.__doc__ دسترسی داشته باشیم .
به مانند متد خاص __init__ ، متد ویژه دیگری به نام __del__ نیز وجود دارد که هنگامیکه یک شیء در حال از بین رفتن هست ، فراخوانی می شود ، بدین معنی که آن شیء از این پس مورد استفاده قرار نمی گیرد و برای استفاده مجدد از آن بخش از حافظه ، به سیستم برگردانده می شود . ما در این متد به سادگی مقدار Person.population را یک واحد کاهش دادیم .
متد __del__ وقتی که شیء ایی بیش ازاین نیازی به استفاده ازش نیست اجرا می شود و وقتی که این متد اجرا شود دیگر ضمانتی به آن شیء نیست ، اگر که شما می خواهید بطور صریح این کار را انجام بدهید ، می بایست از دستور del بصورتی که در مثال های قبلی آورده شده بود ، استفاده کنید .
نکته ایی برای برنامه نویسان C++/Java/C# در پایتون تمامی اعضای یک کلاس ( شامل اعضای داده ) عمومی ( public ) هستند و تمامی متدها ، مجازی virtual ) ) هستند .
یک استثناء : اگر نام اعضای داده را بصورت دو زیرخط چسپبیده به نام تعریف کنید ، مثل privatevar __ پایتون از دستکاری نام (name-mangling) بهره می برد تا بطور موثری آن متغیر را خصوصی ( private) سازد.
[مترجم : پس این متغیر از طریق نام classname._classname__privatevar همچنان قابل دسترس و عمومی ست . ]
از اینرو طبق عرف ، هر متغیری که تنها در داخل خود کلاس و یا اشیاء مورد استفاده قرارمی گیرد ، می بایست نامش با یک زیرخط شروع شود ، و تمامی دیگر نام ها عمومی هستند و می توانند توسط دیگر کلاس ها / اشیاءها استفاده شوند . به خاطر داشته باشید که این تنها یک عرف هست و اجباری از طرف پایتون برای آن نیست . ( البته به جز برای نام هایی با پیشوند دو زیر خط ) .
همچین ، دقت کنید که متد __del__ در قیاس می توان گفت که همان مفهوم مخرب (destructor) را در زبان های بالا دارد.
وراثت
یکی از مزایای اصلی برنامه نویسی شیء گرا ، قابلیت استفاده مجدد از کد هست ، و یکی از روش های رسیدن به آن از طریق مکانیسم وراثت است . ارث بری می تواند بصورت بهتری اینگونه تصور شود که پیاده سازی یک رابطه نوع و زیر نوع ما بین کلاس ها است .
فرض کنید که می خواهید برنامه ایی بنویسید که اطلاعات مربوط به اساتید و دانشجو یک دانشکده را نگه دارد . این موجودیت ها دارای چندین ویژگی رایج همچون نام ، سن و آدرس هستند . همچنین دارای چندین ویژگی مخصوص به خود همچون حقوق ، کلاس درس ، مرخصی برای اساتید و شهریه و نمره برای دانشجویان هستند .
شما می توانید دو کلاس مستقل برای هر کدام از این ها ایجاد کنید و آنها را پردازش کنید ولی اضافه شدن یک ویژگی جدید رایج ، به معنی اضافه کردن این ویژگی به هر دو کلاس مستقل است . که این مطلب به سرعت باعث عدم اداره کردن آسان کلاس ها می شود .
روش بهتری هم هست ، و آن اینکه یک کلاس رایج به نام SchoolMember ایجاد کنیم و سپس دو کلاس به نام دانشجو و استاد داشته باشیم که از این کلاس ارث ببرند . بدین معنی که این دو ، زیر- نوع هایی از این نوع (کلاس) رایج هستند و سپس ما می توانیم ویژگی های مختص به خودشان را به این زیر– نوع ها اضافه کنیم.
این شیوه مزایای بسیاری دارد . اگر ما هرعملیاتی را در کلاس SchoolMember اضافه کنیم و یا تغییر بدهیم ، این تغییر بطور خودکار در زیر- نوع ها هم به خوبی منعکس می شود ، برای مثال ، شما می توانید برای هر دو موجودیت اساتید و دانشجو یک فیلد شماره کارت ( ID ) جدید اضافه کنید و این کار را به سادگی ، تنها با اضافه کردن آن به کلاس SchoolMember انجام بدهید . البته ، تغییر در یک زیرنوع تاثیری بروی دیگر زیرنوع ها ندارد . مزیت دیگر آن اینست که اگر شما به یک کلاس استاد و یا دانشجو بعنوان یک شیء SchoolMember رجوع کنید می تواند در مواردی همچون شمارش تعداد اعضای دانشکده مفید باشد . این چندریختی نامیده می شود ، که در آن یک زیر- نوع می تواند جانشین هر مکانی شود که انتظار می رود یک نوع والد درآن مکان قرار گیرد ، بدین معنی که یک شیء می تواند بعنوان یک نمونه از کلاس والد رفتار کند .
همچنین مشخص هست که ما از کد کلاس والد استفاده مجدد می کنیم و نیازی به تکرار آن در کلاس های متفاوت نیست ، و همینطوری که ما در همین جا داشتیم ، و در کلاس هایی مستقل از آن استفاده بردیم .
در این وضعیت کلاس SchoolMember ، بعنوان کلاس پایه (base class) و یا فوق کلاس (superclass) شناخته می شود ، و کلاس های استاد و دانشجو کلاس های مشتق شده(derived classes) و یا زیرکلاس (subclasses) نامیده می شوند .
حالا ما این مثال را بعنوان یک برنامه خواهیم دید .
به کارگیری وراثت
مثال 11.5. استفاده از وراثت
#!/usr/bin/python
# Filename: inherit.py
class SchoolMember:
'''Represents any school member.'''
def __init__(self, name, age):
self.name = name
self.age = age
print '(Initialized SchoolMember: %s)' % self.name
def tell(self):
'''Tell my details.'''
print 'Name:"%s" Age:"%s"' % (self.name, self.age),
class Teacher(SchoolMember):
'''Represents a teacher.'''
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age)
self.salary = salary
print '(Initialized Teacher: %s)' % self.name
def tell(self):
SchoolMember.tell(self)
print 'Salary: "%d"' % self.salary
class Student(SchoolMember):
'''Represents a student.'''
def __init__(self, name, age, marks):
SchoolMember.__init__(self, name, age)
self.marks = marks
print '(Initialized Student: %s)' % self.name
def tell(self):
SchoolMember.tell(self)
print 'Marks: "%d"' % self.marks
t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 22, 75)
print # prints a blank line
members = [t, s]
for member in members:
member.tell() # works for both Teachers and Students
خروجی
$ python inherit.py (Initialized SchoolMember: Mrs. Shrividya) (Initialized Teacher: Mrs. Shrividya) (Initialized SchoolMember: Swaroop) (Initialized Student: Swaroop) Name:"Mrs. Shrividya" Age:"40" Salary: "30000" Name:"Swaroop" Age:"22" Marks: "75"
نحوه ی عملکرد این مثال
برای استفاده از وراثت ، ما نام های کلاس پایه را در یک چندتایی به دنبال نام کلاس در تعریف کلاس مشخص می کنیم . سپس ، مشاهده می کنیم که متد __init__ از کلاس پایه با استفاده از متغیر self بطور صریح فراخوانی می شود ، بدین ترتیب که مقدمات اولیه کلاس پایه را که بخشی از شیء است انجام می دهیم . این بسیار مهم است که به خاطر داشته باشیم که پایتون بطور خودکار سازنده ی کلاس پایه را فراخوانی نمی کند ، و شما می بایست بطور صریح آن را برای خودتان صدا بزنید .
همچنین می بینیم که فراخوانی متدهای کلاس پایه در زیرکلاس ها با پیشوند نام کلاس پایه همراه با اسم متد آن و سپس فرستادن متغیر self بعنوان اولین آرگومان در کنار دیگرآرگومان ها صورت می گیرد .
توجه کنید که، هنگامیکه متد Tell از کلاس SchoolMember را به کار می بریم با نمونه هایی از Teacher و Studentمی توانیم همان طوری رفتار کنیم که با نمونه های SchoolMember رفتار می کنیم .
همچنین ، روشن هست که متد Tell از زیرنوع صدا زده می شود و نه متد Tell از کلاس SchoolMember . یک راه برای درک این مطلب ، این است که پایتون همیشه شروع جستجو برای متدها را در نوعی (کلاسی) که در آن هست آغاز می کند ، سپس اگر نتواند متد مورد نظر را در آن نوع (کلاس) پیدا کند ، ادامه جستجو را در متدهای متعلق به کلاس های پایه اش شروع می کند ، بصورت یکی یکی و به ترتیبی که در چندتایی در تعریف کلاس مشخص شده اند .
نکته ایی در مورد یک اصطلاح – اگر در زمان تعریف ارث بری ، بیش از یک کلاس در چندتایی ارث بری فهرست شده باشد ، آن را وراثت چندگانه ( multiple inheritance ) می نامند .
خلاصه
ما اکنون مفاهیم متنوعی از کلاس ها و اشیاء و همین طور اصطلاحات گوناگون مربوط به آنها را مورد بررسی قرار دادیم . همچنین مزایا و معایب برنامه نویسی شیء گرا را دیدیم . پایتون قویاً شیء گرا و قابل فهم است . این مفاهیم بطور قابل ملاحظه ایی در جای خودش به شما کمک بیشتری خواهد کرد .
در فصل بعدی ، درباره مواجهه با ورودی / خروجی و چگونگی دسترسی به فایل ها در پایتون خواهیم آموخت .

