بنام خدا
آموزش پایتون – درس 7
کلاس ها و اشیا - 1
مبحث مهم بعدی در برنامه نویسی پایتون بحث کلاس و اشیا می باشد . من توضیح مختصری در مورد برنامه نویسی شی گرا ارائه می کنم در صورتی که آشنایی با مبحث کلاس و اشیا و برنامه نویسی شی گرا ندارید پیشنهاد می کنم از منابع موجود برنامه نویسی شی گرا استفاده کنید . ولی توضیحات ارائه شده در اینجا نیز تا حدودی شما را با این سبک برنامه نویسی و روشها و ویژگی های آن آشنا خواهد ساخت .
تا کنون با انواع داده های مختلف آشنا شدید و توابعی را نیز نوشته و به کار بردید . همه این توابع و متغییر ها مستقل بوده و بطور مجزا مورد استفاده قرار می گرفتند . این روش که به برنامه نویسی ساخت یافته یا مبتنی بر روال و توابع جدا می باشد کلیت برنامه را به روالها و اجزا کوچکتر تقسیم می کند و باعث آسانتر شدن اشکال زدایی و استفاده مجدد از کد های نوشته شده می شود اما برنامه هایی موسوم به اسپاگتی کد را تولید می کند یعنی برنامه هایی که کد ها همانند ضرف حاوی ماکارونی بهم ریخته و درهم بود و سروته برنامه مشخص نمی باشد . گام بعدی در برنامه نویسی که یک الهام از طبیعت ( OOP (Object Oriented Programming یا همان برنامه نویسی شی گرا است این سبک برنامه نویسی یک برنامه را همانند طبیعت به اشیا سطح بالا تقسیم می کند و هر شی قسمتی از مساله ای را که به دنبال حل آن هستید مدل سازی می کند در این حالت اشیا با یکدیگر در حال تعامل می باشند به نحوی که اجزا و اشیا برنامه همه باهم روند برنامه را پیش می برند . در این صورت ، یک برنامه شی گرا به صورت مدل و شبیه سازی زنده از مساله ای خواهد بود که در صدد حل آن هستید . رکن اساسی برنامه نویسی شی گرا کلاس می باشد که در واقع یک طرح کلی از یک موجودیت مجزا می باشد و اشیا با توجه به آن ایجاد می گردند . یک کلاس شامل متغییر و توابع می باشد و این دو رکن اساسی برنامه در یک موجودیت واحد به نام کلاس جمع شده است . در مثال های قبل شما با این مبحث آشنا شده و حتی از آن استفاده کردید . برای مثال وقتی یک رشته تعریف می کنید این رشته همانند زبانی چون C فقط شامل کاراکتر ها نمی باشد بلکه یک شی از کلاس رشته می باشد و متغییر های عضوی چون مقدار رشته نسبت داده شده و توابع یا اصطلاحا متد هایی را داراست . برای مثال وقتی شما رشته ای را بر قرار دادن یک نقطه بعد از آن و نوشتن عبارتی چون ()upper آن را به حروف بزرگتر تبدیل می کنید در واقع از تابی عضو کلاس و شی رشته استفاده کرده اید . شما اطلاعی از نحوه کار و نوع رابطه این تابع با دیگر تابع ها و متغییر های داخل کلاس رشته ندارید ولی نحوه استفاده از آن را می دانید که این موضوع یکی از جنبه های مثبت برنامه نویسی شی گرا است . متد ها یا توابع در این سبک برنامه نویسی به عنوان یک رفتار و عمل برای کلاسی خاص می باشند که شما کافی است طریقه استفاده از آن را بدانید ! همانطور که قبلا هم گفته شد شما می توانید با دستور dir عناصر (هم متغییر و هم تابع) عضو یک کلاس را مشاهده کنید .
name="Tux-World.Com"
dir(name)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__','__ge__', '__getattribute__', '__getitem__', '__getnewargs__', __getslice__','__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__','__mul__', '__ne__', '__new__', '__reduce__', __reduce_ex__', '__repr__','__rmod__', '__rmul__', '__setattr__', '__str__', 'capitalize', 'center', 'count','decode', 'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum','isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust','lower', 'lstrip', 'replace', 'rfind', 'rindex', rjust', 'rsplit', 'rstrip','split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate','upper', 'zfill']
متغییر ها و توابعی که به ' __ ' شروع شده اند عناصر خصوصی یا private کلاس می باشند یعنی شما مجاز به استفاده از آنها نیستید اما می توانید ایتفاده کنید ! در واقع پایتون مانع استفاده شما از این عناصر نمی شود و این بستگی به انتخاب شما دارد . در مثال زیر ابتدا نوع عنصر __len__ از کلاس رشته را بررسی می کنیم . چون نوع آن تابع می باشد آن را اجرا می کنیم . عدد برگشت داده شده همان طول متغییر است . تابع len هم از همین متغییر هر کلاس استفاده می کند به همین دلیل است که می تواند طول داده های پیچیده را نیز مشخص کند .
>>> type (name.__len__)
<type 'method-wrapper'>
>>> name.__len__()
13
ادامه درس را با بیان مثالی کلی و کامل کردن آن ادامه می دهیم . فرض کنید می خواهید از یک یخچال در برنامه خود استفاده کنید ! شبیه سازی یک یخچال درست مثل حالت واقعی آن ، بسیار مفید و کارا خواهد بود و این همان کاری هست که برنامه نویسی شی گرا انجام می دهد . فرض کنید می خواهید اعمالی چون اضافه کردن غذا ، برداشتن آن ، اطلاع از موجود بودن غذا یا میوه ای خاص و یا قرار دادن یا برداشتن بیش از یک چیز در یک زمان خاص را انجام دهید . به عبارت دیگر وقتی شما برنامه ای می نویسید که در آن از اشیا دنیای واقعی استفاده می کنید شبیه سازی عینی آن بصورت یک عنصر واحد به حل مسئله و تسهیل برنامه نویسی کمک شایانی خواهد کرد .
تعریف کلاس :
زمانی که شما قصد ایجاد یک کلاس و نوشتن کد ها و بخش های مختلف آن را دارید کافی است فقط از عبارت کلاس باضافه نام کلاس استفاده کنید و همانند سایر قطعه های کد آن را با دو نقطه پایان دهید . و سپس شروع به نوشتن کد کنید . در مرحله بعدی می توانید یک نمونه از این کلاس (شی) را ایجاد . اسم خاصی را به آن نصبت دهید سپس از طریق این اسم می توانید به عناصر (متغییر و متد) داخل آن شی (کلاس) دسترسی داشته باشید .
در مثال زیر ساده ترین کلاس ممکنه را می بینید که فقط شامل توضیحات می باشد .
Class Fridge:
"""This class implements a fridge where ingredients can be added and removed individually, or in groups."""
توضیح اینکه هر متنی پس از تعریف کلاس نوشته شود به عنوان مستندات آن کلاس بوده و تاثیری بر اجرای آن ندارد و می توانید به این مستندات از طریق متفییر __doc__ آن کلاس دسترسی داشته باشد .
ایجاد یک شی از کلاس تعریف شده :
کد کلاس Fridge را در یک فایل نوشته و آن را بوسیله دستور python -i اجرا کنید . شما اکنون می توانید یک نمونه از کلاس تعریف شده را به همراه اسم آن و پارانتز بازو بسته ایجاد کنید :
>>> f = Fridge()
فعلا این کلاس و درنتیجه ای شی خالی می باشد . البته به این معنی نیست که بی کاربرد است . شما الان هم می توانید آن را تغییر دهید !! در ثانی استفاده از کلاس ها در مبحث استثنا ها کاربرد دارد که در درسهای آینده مفصل بحث خواهد شد . و اما استفاده از شی خالی f و مشاهده مستندات آن :
>>> f.items = {}
>>> f.items["milk"] = 2
>>> f.items["milk"]
2
>>> f.__doc__
'This class implements a fridge where ingredients can be added and removed individually, or in groups.'
اکنون باید به طراحی کلاس یخچال بپردازید به این منظور باید هدف خود از این کار و نیز کاربرد دقیق کلاس خود را مشخص کنید در اولین گام باید بتوانیم عناصری را در یخچال قرار دهیم . روشهای متعددی موجود است افزودن یک عدد از یک شی و یا چندین موجودیت با تعداد مختلفو به وسیله نوع داده دیکشنری .
در مرحله بعدی شما باید بتوانید اشیایی را از آن بردارید و نیاز دارید همانند موقع قرار دادن اشیا یک عدد از یک عنصر با بصورت گروهی این کار را انجام دهید .
اعمال و خواسته (تابع یا متد) های دیگری نیز مورد نیاز است تا این یخچال کامل شود . به متد هایی که شی را در دسترس قرار می دهد و بوسیله انها با شی تعامل می کنید رابط نام دارند . چون نحوه ارتباط درون کلاس و شی را با محیط بیرون مشخص می کنند . مثال خود را کاملتر می کنیم :
class Fridge:
"""This class implements a fridge where ingredients can be added and re moved individually, or in groups. The fridge will retain a count of every ingredient added or removed, and will raise an error if a sufficient quan tity of an ingredient isn't present.
Methods:
has(food_name[, quantity])-checks if the string food_name is in the fridge.
Quantity will be set to 1 if you don't specify a number.
has_various(foods)-checks if enough of every food in the dictionary is in the fridge
add_one(food_name) - adds a single food_name to the fridge
add_many(food_dict) - adds a whole dictionary filled with food
get_one(food_name) - takes out a single food_name from the fridge
get_many(food_dict) - takes out a whole dictionary worth of food.
get_ingredients(food) - If passed an object that has the __ingredients__
method, get_many will invoke this to get the list of ingredients.
"""
def __init__(self, items={}):
"""Optionally pass in an initial dictionary of items"""
if type(items) != type({}):
raise TypeError,"Fridge requires a dictionary not %s"% type(items)
self.items = items
return
همانطور که مشاهده می کنید برای توابع نیز توضیحاتی نوشته ایم . که انجام این عمل توصیه می گردد .
تابه __init__ همان تابع سازنده می باشد این تابع هنگام تعریف کلاس فعال شده و کارهایی را که در داخل آن تعریف شده انجام می دهد . این تابع دو پارامتر دارد . اولی self می باشد که معادل this در دیگر زباهای برنامه نویسی می باشد و به خود شی اشاره می کند و از آن می توان به عناصر داخل کلاس دسترسی داشت . به عبارت دیگر در داخل هر تابعی که خواستید به یکی از عناصر خود کلاس دسترسی یابید باید این متغییر را به عنوان پارامتر ارسال کنید و مثل تابع بالا از آن به همراه جدا کننده نقطه برای دسترسی به عناصر عضو همین شی استفاده کنید . در صورتی که در زبانهای شی گرای دیگری چون ++C این کار نیاز نیست . پس در پایتون شما می توانید در داخل یک تابع ، توابع و متغییرهایی هم اسم با متغییر ها و متد های عضو همان کلاس تعریف و استفاده کنید بدون اینکه تداخلی باهم داشته باشند . که در مثال بالا مشهود است و دو نوع متغییر با نام items موجود است یکی به عنوان ورودی و دیگری عضو کلاس .
هنگام نوشتن توابع عضو و متد های یک کلاس باید بیشترین دقت را داشته باشید برای مثال نوشتن توابعی چون برداشتن یک شی از یخچال ، برداشتن دو شی از یخچال و برداشتن چند شی از یخچال باعث تکرار کد های تکراری ، بالا رفتن حجم برنامه و پایین آمدون کارایی و مشکل شدن استفاده از آن خواهد بود و به عبارت کلی تر بهینه نخواهد بود . پس می توان برای مثال قسمت مشترک این متد ها را در یک متد خصوصی و درونی پیاده شازی کرده و در بقیه فقط از آن استفاده کرد . پس بهتر است در مثال ذکر شده یک تابع درونی با نام add_multi__ نوشته شود که دو پارامتر (یکی اسم شی و دیگری تعداد آن) را دریافت کرده و آنها را به محتویات یخچال اضافه کند . در ادامه مثال ذکر شده را کاملتر خواهد شد ولی از تکرار کد های قبلی اجتناب می شود فقط چون تورفتگی در پایتون مهم است تعریف کلاس را که در اینجا مبنای سایر کد ها می باشد ذکر می کنیم .
class Fridge:
# the docstring and intervening portions of the class would be here, and
# __add_multi should go afterwards.
def __add_multi(self, food_name, quantity):
"""
__add_multi(food_name, quantity) - adds more than one of a
food item. Returns the number of items added
This should only be used internally, after the type checking has been
done
"""
if not self.items.has_key(food_name):
self.items[food_name] = 0
self.items[food_name] = self.items[food_name] + quantity
خوب اکنون موقع نوشتن دو تابع دیگر یعنی افزودن یک عدد از یک شی و چندین مقدار مختلف است که هر دو از تابع پایه و درونی تهریف شده بالا ( add_multi__ ) استفاده خواهند کرد .
def add_one(self, food_name):
"""
add_one(food_name) - adds a single food_name to the fridge returns True
Raises a TypeError if food_name is not a string.
"""
if type(food_name) != type(""):
raise TypeError,"add_one requires a string, not %s" %type(food_name)
else:
self.__add_multi(food_name, 1)
return True
def add_many(self, food_dict):
"""
add_many(food_dict) - adds a whole dictionary filled with food as keys and
quantities as values.
returns a dictionary with the removed food.
raises a TypeError if food_dict is not a dictionary
returns False if there is not enough food in the fridge.
"""
if type(food_dict) != type({}):
raise TypeError,"add_many requires a dictionary, not %s" % food_dict
for item in food_dict.keys():
self.__add_multi(item, food_dict[item])
return
اضافه کردن یک عنصر بسیار شبیه به تابع دوم می باشد . و در هر دو از یک کد مشترک و تابع درونی add_multi__ استفاده شده است . اگر از تابع add_one استفاده شود ابتدا نوع آن بررسی می شود تا حتما رشته باشد ( چون نوع های دیگر هم می توانند فرستاده شوند ) سپس تابع درونی add_multi__ را با همان متغییر به همراه عدد یک فراخوانی و در نتیجه شی در یخچال قرار می گیرد . در حالت دوم یعنی امکان اضافه کردن چندین مقدار همزمان ابتدا نوع آن تست می شود در صورتی که دیکشنری نبود پیغام خطایی نمایش داده می شود و در غیر اینصورت با یک حلقه به کلید های آن دسترسی یافته و سپس همان کلید را با مقدار آن را به تابع add_multi__ ارسال می کنیم . همانگونه که مشاهده می کنید از طریق دستور raise که جزو توابع مدیریت استثناها و خطاهای برنامه نویسی می باشد یک پیام انتخابی به همراه نوع خاصی از ارور (یعنی TypeError) نمایش داده شده و برنامه خاتمه می یابد . چند مثال :
>>> f = Fridge({"eggs":6, "milk":4, "cheese":3})
>>> f.items
{'cheese': 3, 'eggs': 6, 'milk': 4}
>>> f.add_one("grape")
True
>>> f.items
{'cheese': 3, 'eggs': 6, 'grape': 1, 'milk': 4}
>>> f.add_many({"mushroom":5, "tomato":3})
>>> f.items
{'tomato': 3, 'cheese': 3, 'grape': 1, 'mushroom': 5, 'eggs': 6, 'milk': 4}