GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing pythonpersian.com — درس ۱۷: شی گرایی (OOP) در پایتون: تعریف کلاس و ایجاد شی — کتاب پایتون به پارسی
"نه مرگ آنقدر ترسناک است و نه زندگی آنقدر شیرین که انسان برای این دو شرفش را بدهد" نامتان جاوید باد درس ۱۷: شی گرایی (OOP) در پایتون: تعریف کلاس و ایجاد شی¶
Photo by Sarah Kilian¶ پیشتر مفهوم شیگرایی شرح داده شده است (درس پنجم). در این درس میخواهیم به بررسی چگونگی پیادهسازی این مفهوم در زبان برنامه نویسی پایتون بپردازیم. هنگام نگارش این درس فرض بر این بوده است که خوانندگان دروس پیش، بخصوص درس پنجم و دروس مربوط به توابع را مطالعه کردهاند. این درس بر ارائه تعاریف مربوط به کلاس (Class) و شی (Object) از مفاهیم شیگرایی حاکم در زبان برنامهنویسی پایتون تمرکز دارد و مفاهیم باقی مانده در دروس آتی ارائه خواهند شد. ✔ سطح: متوسط برنامهنویسی شی گرا (Object-Oriented Programming)¶همانطور که پیشتر نیز گفته شده است، پایتون یک زبان برنامهنویسی چند الگویی (multi-paradigm) است و از الگوهای مختلفی از جمله شی گرایی پشتیبانی میکند. شی گرایی یک الگوی برنامهنویسی یا روشی برای طراحی کدهای برنامه است. در این شیوه، کدهای برنامه در قالب موجودیتهای کوچکی به نام کلاس (Class) به وجود میآیند. کلاسها چیزی نیستند جز ابزاری که توسط آن میتوان دادهها و عملیات مرتبط با یکدیگر را در یک دسته و جدا از سایر بخشهای کد قرار داد. با کمک کلاسها رفتار و عملکرد هر تکه از کد مشخص است و با سایر بخشهای کد تداخل پیدا نمیکند. ایجاد یک کلاس به معنی ایجاد یک نوع (Type) جدید در برنامه میباشد که میتوان چندین شی (Object) یا نمونه (Instance) از آن نوع ایجاد کرد. یک برنامه شیگرا حاصل ارتباط و تعامل اشیا مختلف ایجاد شده در آن است. مفاهیم زیادی از پایتون تا پیش از این درس مطرح شده است، باید بدانیم که تمام آنها از پیادهسازی شی گرا پیروی میکردند. هر چیزی در پایتون یک شی است. انواع داده مانند اعداد، رشته، لیست یا دیکشنری همگی شی بودند - نمونههایی که از کلاسهای مربوط به خود ایجاد شدهاند. حتی تعریف تابع نیز به معنی ایجاد یک شی از کلاس متناطر آن بوده است. اما حالا میخواهیم نوع یا کلاسهای مورد نظر خودمان را در برنامه ایجاد و اشیایی از این کلاسها نمونه سازی کنیم. در ادامه به شرح این روند خواهیم پرداخت. تعریف کلاس (Class)¶در پایتون برای تعریف کلاس از کلمه کلیدی class ClassName: <statement-1> . . . <statement-N> کلمه کلیدی تعریف کلاس - بعد از کلمه کلیدی >>> class MyClassName: ... pass ... >>> >>> type(MyClassName) <class 'type'> هر چیزی در پایتون یک شی است. حتی کلاسها، با اجرای دستور تعریف کلاس، یک شی از نوع نکته پیشنهاد PEP 8: برای نوشتن نام کلاس از شیوه CapitalizedWords استفاده شود. نکته کلاسها نیز همانند توابع حوزه (Scope) خود را دارند - درس دوازدهم. با تعریف هر کلاس یک حوزه محلی جدید در برنامه پایتونی تعریف میگردد. نکته یک کلاس، تعریف کننده صفات (ویژگیها) - که به عنوان Attribute شناخته میشوند - و رفتارهای (عملیات) - که به عنوان Method شناخته میشوند - اشیایی است که از آن ایجاد خواهد شد. در واقع نام کلاس معرف نوع (Type) اشیای خود است. به بیانی سادهتر، هر کلاس میتواند شامل تعدادی تابع و متغیر در داخل بدنه خود باشد. که به متغیرها: Attribute و به توابع: Method گفته میشود. هر یک از این Attributeها و Methodها انواعی دارند که در ادامه شرح داده خواهد شد. توجه داشته باشید که تعریفهایی با سینتکس زیر نیز از کلاس در زبان برنامهنویسی پایتون صحیح میباشند. به طور کلی «پرانتر» در جلوی نام کلاس، زمانی قرار میگیرد که قصد پیادهسازی مفهوم وراثت (درس بعد) را داشته باشیم، در غیر این صورت نیازی به قرار دادن پرانتز نمیباشد: >>> class DerivedClassName(BaseClassName): ... pass ... >>> >>> class MyClassName(): ... pass ... >>> نمونهسازی (Instantiation)¶به عملیات ایجاد یک شی از کلاس، نمونهسازی (Instantiation) گفته میشود. کلاس چیزی جز تکه کدی نوشته شده نیست و جایی در حافظه ندارد، این اشیا ایجاد شده از کلاس هستند که در حافظه (Memory) قرار میگیرند. نمونهسازی از یک کلاس در زبان پایتون به صورت زیر انجام میشود: >>> class Sample: ... pass ... >>> >>> sample_object = Sample() # Instantiation >>> type(sample_object) <class '__main__.Sample'> در زبان برنامهنویسی پایتون با فراخوانی نام کلاس - همچون فراخوانی یک تابع - یک شی از آن کلاس ایجاد میگرد. از هر کلاس میتوان بینهایت نمونهسازی داشت. هر شی از یک کلاس، حوزه (Scope) مخصوص به خود را دارد که جدا از دیگر اشیا آن کلاس خواهد بود. بنابراین اشیا هر کلاس کاملا مستقل و ایزوله (isolated) از یکدیگر هستند. >>> class Sample: ... pass ... >>> >>> obj_1 = Sample() >>> obj_2 = Sample() >>> id(obj_1) 139936512966840 >>> id(obj_2) 139936512967008 همانطور که از خروجی تابع آماده (built-in) >>> class Sample: ... pass ... >>> obj = Sample() >>> type(obj) <class '__main__.Sample'> >>> type(obj) == Sample True >>> isinstance(obj, Sample) True در زبان پایتون دو شیوه رایج برای بررسی نوع یک شی وجود دارد. یک راه استفاده از تابع آماده (built-in) سازنده (Constructor)¶در مبحث شیگرایی، هنگام ساخت یک شی (ایجاد یک نمونه جدید)، به صورت خودکار یک متد از داخل کلاس مورد نظر فراخوانی میشود. به این متد، سازنده (Constructor) گفته میشود. فراخوانی خودکار این متد به برنامهنویس این امکان را میدهد که در صورت تمایل بتواند چگونگی ایجاد شی جدید را مدیریت یا در همان هنگام ساخت، شخصیسازی نماید. از طرفی هر کلاس در زبان برنامهنویسی پایتون شامل یک سری متد خاص میباشد که نام تمام آنها با دو کاراکتر خطزیرین (Underscore or Underline در فرآیند نمونهسازی از یک کلاس پایتون، به ترتیب دو متد خاص درگیر هستند: متد متد متاسفانه برخی افراد تازه وارد در زبان پایتون و همینطور برخی آموزشها متد 1class Sample: 2 3 def __new__(cls, *args, **kwargs): 4 print("__new__(), Has been called") 5 print('cls: ', cls) 6 print('args: ', args) 7 print('kwargs: ', kwargs) 8 9 # create new object 10 obj = super().__new__(cls, *args, **kwargs) 11 12 # return object 13 return obj 14 15 def __init__(self, x=0, y=0): 16 print("__init__(), Has been called") 17 print('self: ', self) 18 self.x = x 19 self.y = y 20 21 22sample_1 = Sample() 23print('-' * 30) 24sample_2 = Sample(3, 6) 25print('-' * 30) 26sample_3 = Sample(x=3, y=6) __new__(), Has been called
cls: <class '__main__.Sample'>
args: ()
kwargs: {}
__init__(), Has been called
self: <__main__.Sample object at 0x7fb4580a6470>
------------------------------
__new__(), Has been called
cls: <class '__main__.Sample'>
args: (3, 6)
kwargs: {}
__init__(), Has been called
self: <__main__.Sample object at 0x7fb4580a64e0>
------------------------------
__new__(), Has been called
cls: <class '__main__.Sample'>
args: ()
kwargs: {'x': 3, 'y': 6}
__init__(), Has been called
self: <__main__.Sample object at 0x7fb005453438>
این مثال صرفا جهت نمایش نقش Constructor و منطق و چگونگی پیادهسازی آن در زبان برنامهنویسی پایتون ارائه شده است. تمام موارد نا آشنایی که میبینید به تدریج شرح داده خواهند شد. نکته
اشیا با قابلیت فراخوانی (Callable Objects)¶پیشتر دیدیم که میتوان توابع را فراخوانی نمود (درس دروازدهم)، با این کار بدنه تابع اجرا و خروجی متناسب دریافت میگردید. در این درس نیز مشاهده کردیم کلاسها نیز در پایتون توانایی فراخوانی دارند که با فراخوانی کلاس، یک شی از آن ایجاد میگردد. با استفاده از تابع >>> def function(): ... pass ... >>> callable(function) True >>> class SampleClass: ... pass ... >>> callable(SampleClass) True >>> obj = SampleClass() >>> callable(obj) False >>> obj() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'SampleClass' object is not callable همانطور که از نمونه کد بالا مشخص است، اشیایی که از کلاسهای خودمان ایجاد میکنیم، بر خلاف خود کلاس قابلیت فراخوانی ندارند. در زبان پایتون میتوانیم این قابلیت را به اشیا کلاسهای خود اضافه نماییم. همانطور که اشاره شد، کلاس در پایتون چندین متد خاص همانند 1class Sample: 2 3 def __init__(self, x=0, y=0): 4 print('------------------- Called __init__()') 5 self.x = x 6 self.y = y 7 8 def __call__(self, x, y): 9 print('------------------- Called __call__()') 10 self.x = x 11 self.y = y 12 13 14obj = Sample() 15print('object is callable:', callable(obj)) 16print('x =', obj.x) 17 18obj(5, 6) 19print('x =', obj.x) ------------------- Called __init__() object is callable: True x = 0 ------------------- Called __call__() x = 5 سطر ۱۸ نمایش فراخوانی یک شی از کلاس Sample میباشد - درست به مانند یک تابع! با فراخوانی یکی شی، به صورت خودکار متد از کاربردهای پیادهسازی متد صفات (Attributes)¶به بیانی ساده، متغیرهایی که به یک کلاس یا یک شی انتساب داده میشود صفت یا ویژگی یا Attribute خوانده میشوند. در بحث شی گرایی زبان برنامهنویسی پایتون دو نوع Attribute وجود دارد:
Instance Attribute¶به Attributeهای خاص یک شی گفته میشود. به هر شی در زبان برنامهنویسی پایتون میتوان با استفاده از سینتکس زیر یک Attribute انتساب داد: object.attribute_name = value 1class Sample: pass 2 3sample = Sample() 4 5sample.a_new_attribute = 'A New Attribute!' 6 7print(sample.a_new_attribute) A New Attribute! هر چیزی در پایتون یک شی است ولی ممکن است مفسر پایتون برای برخی اشیا محدودیتهایی در نظر گرفته باشد و شما نتوانید به هر شیای در پایتون Attribute اضافه نمایید. در این لحظه جا دارد اشاره شود به درس چهاردهم (بخش Function Attributes) که در واقع کاری جز افزودن Attribute به شی تابع نبود. نکته مقدار این دسته از Attributeها به ازای هر شی منحصربهفرد است. برای نمونه صفتهایی همچون نام، نامخانوادگی، سن و جنسیت برای هر یک از اشیای کلاس «شخص» قابل تعریف است. بدیهی است که هر نمونه شی از این کلاس میبایست شامل مقادیر منحصربهفردی از این Attributeها باشد. برگردیم به مثال قبل که در آن ما یک کلاس به اسم Sample ایجاد (سطر ۱) و به یک شی از آن - پس از نمونهسازی (سطر ۳) - یک Attribute به نام a_new_attribute اضافه کردیم (سطر ۵). این شیوه افزودن Attribute به اشیای کلاسهایی که خودمان آنها را تعریف میکنیم چندان جالب نیست و ممکن است باعث بروز خطاهایی منطقی در برنامه گردد، بهتر است این کار توسط متد 1class Sample: 2 3 def __init__(self, attribute_value): 4 self.a_new_attribute = attribute_value 5 6sample = Sample() 7 8print(sample.a_new_attribute) A New Attribute! 1class Person: 2 3 def __init__(self, first_name, last_name, age, gender): 4 self.first_name = first_name 5 self.last_name = last_name 6 self.age = age 7 self.gender = gender 8 9person_1 = Person('Kaneki', 'Ken', 18, 'male') 10person_2 = Person('Haise', 'Sasaki', 19, 'male') 11 12print(person_1.first_name) 13print(person_2.last_name) Kaneki Sasaki در ادامه شرح داده خواهد شد که پارامتر Class Attribute¶به Attributeهای خاص یک کلاس گفته میشود و در واقع متغیرهایی است که درون کلاس و خارج از متدها تعریف میگردند. کاربرد این Attributeها به اشتراک گذاشتن یک یا چند مقدار یکسان در بین تمام اشیاست. تمام اشیای یک کلاس به Class Attributeهای آن کلاس دسترسی دارند: 1class Sample: 2 class_attribute = 0 3 4print('#' * 10, 'STEP#A') 5print('LINE 05:', Sample.class_attribute) 6 7# Instantiation 8sample_1 = Sample() 9sample_2 = Sample() 10 11print('#' * 10, 'STEP#B') 12print('LINE 12:', sample_1.class_attribute) 13print('LINE 13:', sample_2.class_attribute) 14 15print('#' * 10, 'STEP#C') 16 17# Change class_attribute for all objects 18Sample.class_attribute = 1 19 20print('LINE 20:', sample_1.class_attribute) # Changed! 21print('LINE 21:', sample_2.class_attribute) # Changed! 22 23print('#' * 10, 'STEP#D') 24 25# WARNING!!! Create a new instance attribute 26sample_2.class_attribute = 2 27 28print('LINE 28:', sample_1.class_attribute) 29print('LINE 29:', sample_2.class_attribute) # instance attribute!!! ########## STEP#A LINE 05: 0 ########## STEP#B LINE 12: 0 LINE 13: 0 ########## STEP#C LINE 20: 1 LINE 21: 1 ########## STEP#D LINE 28: 1 LINE 29: 2 نکته مقدار Class Attributeها هم با استفاده از نام کلاس قابل دستیابی است (سطر ۵) و هم با استفاده از هر یک از اشیا آن کلاس (سطرهای ۱۲ و ۱۳). نکته برای تغییر مقدار Class Attribute در داخل کلاس از Class Method - در ادامه شرح داده میشود - استفاده میشود و در بیرون کلاس با استفاده از نام کلاس به صورت زیر (سطر ۱۸): ClassName.class_attribute = new_value نکته برای تغییر مقدار Class Attributeها، از شی استفاده نکنید، با این کار تنها یک Instance Attribute برای آن شی ایجاد میگردد (سطر ۲۶). متد (Method)¶متدها در واقع توابعی هستند که داخل هر کلاس تعریف میشوند. هر کلاس پایتون میتواند شامل سه نوع متد باشد:
1class Sample: 2 3 def instance_method(self): 4 pass 5 6 @classmethod 7 def class_method(cls): 8 pass 9 10 @staticmethod 11 def static_method(): 12 pass متد شی (Instance Method)¶رایجترین نوع متد در پایتون است. برای ایجاد این متد نیازی به دکوراتور (Decorator - درس سیزدهم) نیست. همانطور که از نام این متد مشخص است این متد تنها از سوی اشیا یک کلاس قابل استفاده است. همانطور که پیشتر صحبت شد، هر شی از کلاس صفات خاص خود را دارد (Instance Attributes) که از این متدها میتوان برای دستیابی و دستکاری آنها استفاده کرد. این نوع متد همواره میبایست حداقل یک پارامتر داشته باشد. پارامتر نخست که معمولا 1class Sample: 2 3 def __init__(self, char='*'): 4 self.character = char 5 6 def multiply_print(self, count=1): 7 print(self.character * count) 8 9 10sample_1 = Sample() # Instantiating a new Object 11 12sample_1.multiply_print() 13sample_1.multiply_print(10) 14 15print('-' * 30) 16 17sample_2 = Sample('#') # Instantiating a new Object 18 19sample_2.multiply_print() 20sample_2.multiply_print(10) * ********** ------------------------------ # ########## گفته شده که متد نکته Instance Methodها تنها میتوانند توسط اشیا فراخوانی شوند. روند فراخوانی یک متد توسط شی نیز به صورت نام شی + کاراکتر متد کلاس (Class Method)¶این نوع متد همواره میبایست حداقل یک پارامتر داشته باشد. پارامتر نخست که معمولا 1class Student: 2 school_name = 'My School' 3 4 def __init__(self, name, family): 5 self.name = name 6 self.family = family 7 8 @classmethod 9 def school_info(cls): 10 print(cls) 11 return f'name: {cls.school_name}' 12 13print(Student.school_info()) 14print('-' * 30) 15print(Student('My Name', 'My Family').school_info()) <class '__main__.Student'> name: My School ------------------------------ <class '__main__.Student'> name: My School نکته این نوع متد (Class Method) را میتوان هم با استفاده از نام کلاس دستیابی کرد (سطر ۱۳) و هم با استفاده از اشیای آن کلاس (سطر ۱۵)، در واقع دکوراتور متد ایستا (Static Method)¶این نوع متد با استفاده از دکوراتور (Decorator - درس سیزدهم) 1class Student: 2 school_name = 'My School' 3 4 def __init__(self, name, family): 5 self.name = name 6 self.family = family 7 8 @classmethod 9 def school_info(cls): 10 print(cls) 11 return f'name: {cls.school_name}' 12 13 @staticmethod 14 def info(): 15 return "This is a student class" 16 17print(Student.info()) 18print('-' * 30) 19print(Student('My Name', 'My Family').info()) This is a student class ------------------------------ This is a student class نکته این نوع متد (Static Method) را میتوان هم با استفاده از نام کلاس دستیابی کرد (سطر ۱۷) و هم با استفاده از اشیای آن کلاس (سطر ۱۹)، در واقع دکوراتور مقدار Hash یک شی و کاربرد آن در پایتون¶به صورت کلی یک Hash در واقع عددی است که در ازای دادهای مشخص برآورد میگردد. دادههای مشابه دارای مقدار hash یکسانی خواهند بود و از طرفی یک تغییر جزئی در داده منجر به تولید یک مقدار hash کاملا متفاوت میشود. مقدار hash از یک تابع هَش [ویکیپدیا] به دست میآید که مسئولیت آن تبدیل داده ورودی به hash رمزگذاری شده است. واضح است که تعداد دادهها میتواند بسیار بیشتر از تعداد مقادیر قابل تولید hash باشد، بنابراین دو داده ممکن است مقدار hash یکسان داشته باشند که به آن Hash collision میگویند. در واقع اگر دو شی hash یکسان داشته باشند، لزوماً دارای ارزش یکسانی نیستند (برابر نیستند). در زبان برنامهنویسی پایتون، تابع >>> a = 5 >>> hash(a) 5 >>> hash(5) 5 >>> hash(999999999999999999) 999999999999999999 >>> hash(99999999999999999999999999999999999999) 244469275760665570 >>> a = 'saeid' >>> hash(a) 4007074958086188072 >>> hash('PYTHON') -6387242471900568301 >>> hash('PYTHoN') -6457932607787762593
شی hashable¶گفتیم ورودی تابع >>> class Sample: ... pass ... >>> obj = Sample() >>> hash(obj) -9223363243335467036 >>> obj.__hash__() -9223363243335467036 متد کاربرد hash در پایتون¶۱) ساختار Hash table [ویکیپدیا] ساختمان داده دو نوع دیکشنری (dict) و مجموعه (set) در زبان برنامهنویسی پایتون بر پایه Hash table ایجاد شده است. در نتیجه سرعت دستیابی عناصر در آنها بسیار بیشتر از دستیابی در شی لیست (List) میباشد. در نوع داده دیکشنری، hash کلیدها محاسبه و از آن برای دستیابی مقدار مربوطه استفاده میشود، برای همین است که کلیدها در دیکشنری حتما میبایست از نوع hashable باشند ولی برای مقادیر هیچ محدودیتی وجود ندارد. نوع داده مجموعه نیز تنها میتواند شامل تعدادی شی hashable و یکتا (غیر تکراری) باشد. ۲) مقایسه دو شی نکته
طی دروس آینده با مبحث Operator Overloading آشنا خواهید شد ولی در اینجا تنها کافی است بدانید که هرگاه دو شی توسط عملگر بین دو متد
به دو نمونه کد زیر توجه نمایید: مقایسه شی در حالت پیشفرض: 1class Student: 2 def __init__(self, name, score): 3 self.name = name 4 self.score = score 5 6obj_1 = Student('Saeid', 70) 7obj_2 = Student('Saeid', 90) 8 9print('Single Object :', obj_1 == obj_1) 10print('Same Objects :', obj_1 == Student('Saeid', 70)) 11print('Different Objects :', obj_1 == obj_2) Single Object : True Same Objects : False Different Objects : False مقایسه شی به همراه شخصیسازی دو متد مذکور: 1class Student: 2 def __init__(self, name, score): 3 self.name = name 4 self.score = score 5 6 def __eq__(self, other): 7 return self.name == other.name and self.score == other.score 8 9 def __hash__(self): 10 return hash((self.name, self.score)) 11 12obj_1 = Student('Saeid', 70) 13obj_2 = Student('Saeid', 90) 14 15print('Single Object :', obj_1 == obj_1) 16print('Same Objects :', obj_1 == Student('Saeid', 70)) 17print('Different Objects :', obj_1 == obj_2) Single Object : True Same Objects : True Different Objects : False در این مثال هر دو Attribute کلاس Student از نوع immutable بودند، بنابراین از خود آنها برای مقایسه و محاسبه مقدار hash استفاده کردیم. به هر حال منطق پیادهسازی این دو متد بر اساس مسئله مطرح شده، بر عهده برنامهنویس میباشد. حتی میتوانید از مقدار تابع 😊 امیدوارم مفید بوده باشه |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |