درس ۱۷: شی گرایی (OOP) در پایتون: تعریف کلاس و ایجاد شی¶

Photo by Sarah Kilian
پیشتر مفهوم شیگرایی شرح داده شده است (درس پنجم). در این درس میخواهیم به بررسی چگونگی پیادهسازی این مفهوم در زبان برنامه نویسی پایتون بپردازیم. هنگام نگارش این درس فرض بر این بوده است که خوانندگان دروس پیش، بخصوص درس پنجم و دروس مربوط به توابع را مطالعه کردهاند.
این درس بر ارائه تعاریف مربوط به کلاس (Class) و شی (Object) از مفاهیم شیگرایی حاکم در زبان برنامهنویسی پایتون تمرکز دارد و مفاهیم باقی مانده در دروس آتی ارائه خواهند شد.
✔ سطح: متوسط
سرفصلها
برنامهنویسی شی گرا (Object-Oriented Programming)¶
همانطور که پیشتر نیز گفته شده است، پایتون یک زبان برنامهنویسی چند الگویی (multi-paradigm) است و از الگوهای مختلفی از جمله شی گرایی پشتیبانی میکند. شی گرایی یک الگوی برنامهنویسی یا روشی برای طراحی کدهای برنامه است.
در این شیوه، کدهای برنامه در قالب موجودیتهای کوچکی به نام کلاس (Class) به وجود میآیند. کلاسها چیزی نیستند جز ابزاری که توسط آن میتوان دادهها و عملیات مرتبط با یکدیگر را در یک دسته و جدا از سایر بخشهای کد قرار داد. با کمک کلاسها رفتار و عملکرد هر تکه از کد مشخص است و با سایر بخشهای کد تداخل پیدا نمیکند. ایجاد یک کلاس به معنی ایجاد یک نوع (Type) جدید در برنامه میباشد که میتوان چندین شی (Object) یا نمونه (Instance) از آن نوع ایجاد کرد. یک برنامه شیگرا حاصل ارتباط و تعامل اشیا مختلف ایجاد شده در آن است.
مفاهیم زیادی از پایتون تا پیش از این درس مطرح شده است، باید بدانیم که تمام آنها از پیادهسازی شی گرا پیروی میکردند. هر چیزی در پایتون یک شی است. انواع داده مانند اعداد، رشته، لیست یا دیکشنری همگی شی بودند - نمونههایی که از کلاسهای مربوط به خود ایجاد شدهاند. حتی تعریف تابع نیز به معنی ایجاد یک شی از کلاس متناطر آن بوده است. اما حالا میخواهیم نوع یا کلاسهای مورد نظر خودمان را در برنامه ایجاد و اشیایی از این کلاسها نمونه سازی کنیم. در ادامه به شرح این روند خواهیم پرداخت.
تعریف کلاس (Class)¶
در پایتون برای تعریف کلاس از کلمه کلیدی class
استفاده میگردد؛ همانند الگو پایین:
class ClassName:
<statement-1>
.
.
.
<statement-N>
کلمه کلیدی تعریف کلاس - class
- یک دستور اجراپذیر (Executable Statement) است، همانند دستور def
برای توابع. یک کلاس پیش از اجرای دستور خود هیچ تاثیری در برنامه ندارد. این شرایط سبب میشود که حتی بتوان یک کلاس را در میان بدنه دستور شرط (if
) یا درون بدنه یک تابع تعریف کرد.
بعد از کلمه کلیدی class
نام کلاس (به دلخواه کاربر) نوشته میشود. سطر نخست تعریف مانند تمام دستورات مرکب (Compound) که به صورت معمول در چند سطر نوشته میشوند و سرآیند دارند، به کاراکتر :
ختم میشود. از سطر دوم با رعایت یکنواخت تورفتگی دستورات بدنه کلاس نوشته میشوند:
>>> class MyClassName:
... pass
...
>>>
>>> type(MyClassName)
<class 'type'>
هر چیزی در پایتون یک شی است. حتی کلاسها، با اجرای دستور تعریف کلاس، یک شی از نوع 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) id
نیز مشخص است [اسناد پایتون] هر شی جدید از کلاس، identity یا هویتی مستقل از دیگر اشیا داشته و در مکانی جداگانه از حافظه قرار داده شده است.
>>> class Sample:
... pass
...
>>> obj = Sample()
>>> type(obj)
<class '__main__.Sample'>
>>> type(obj) == Sample
True
>>> isinstance(obj, Sample)
True
در زبان پایتون دو شیوه رایج برای بررسی نوع یک شی وجود دارد. یک راه استفاده از تابع آماده (built-in) type
است [اسناد پایتون] که پیشتر از آن استفاده میکردیم و راه دیگر استفاده از تابع آماده (built-in) isinstance
میباشد [اسناد پایتون] این تابع دو آرگومان میپذیرد که به ترتیب شی و نوع مورد نظر هستند، در صورتی که شی از نوع دریافت شده باشد، مقدار True
و در غیر این صورت False
برمیگرداند.
سازنده (Constructor)¶
در مبحث شیگرایی، هنگام ساخت یک شی (ایجاد یک نمونه جدید)، به صورت خودکار یک متد از داخل کلاس مورد نظر فراخوانی میشود. به این متد، سازنده (Constructor) گفته میشود. فراخوانی خودکار این متد به برنامهنویس این امکان را میدهد که در صورت تمایل بتواند چگونگی ایجاد شی جدید را مدیریت یا در همان هنگام ساخت، شخصیسازی نماید.
از طرفی هر کلاس در زبان برنامهنویسی پایتون شامل یک سری متد خاص میباشد که نام تمام آنها با دو کاراکتر خطزیرین (Underscore or Underline _
) شروع و نیز پایان مییابد همانند: __init__
- در کامیونیتی پایتون به دو کاراکتر خطزیرین در کنار هم به اصطلاح Dunder (Double underscores) گفته میشود - به این متدهای خاص در پایتون به اصطلاح Special Methods ،Dunder Methods یا Magic Methods گفته میشود. [اسناد پایتون] باید توجه داشت که تمام این متدها یک پیادهسازی پیشفرض در پایتون دارند و الزامی برای پیادهسازی از طرف برنامهنویس وجود ندارد.
در فرآیند نمونهسازی از یک کلاس پایتون، به ترتیب دو متد خاص درگیر هستند: __new__
[اسناد پایتون] و __init__
[اسناد پایتون]
متد __new__
در زمان ایجاد شی و دقیقا برای ایجاد شی فراخوانی میشود، خروجی این متد یک شی جدید از آن کلاس میباشد. این متد از نوع Static Method است - در بخش بعدی شرح داده خواهد شد - نخستین پارامتر این متد ،کلاسی است که قرار است از آن یک شی ایجاد گردد و پارامترهای دیگر که میتوانند حاوی مقادیری باشند که در زمان نمونهسازی ارسال شده است.
متد __init__
بلافاصله پس از اینکه شی جدید توسط متد __new__
ایجاد گردید و درست قبل از اینکه شی جدید از متد __new__
بازگردانده شود (returned)، فراخوانی میگردد. این متد از نوع Instance Method است - در بخش بعدی شرح داده خواهد شد - و بنابراین نخستین پارامتر این متد شی جاری است (همان شیای که توسط __new__
ایجاد گردیده است) و پارامترهای دیگر که برنامهنویس در زمان نمونهسازی جهت مقدار دهی در شی ارسال میکند - توجه داشته باشید که این متد خروجی ندارد (بدون دستور return یا بهتر بگوییم خروجی آن None است) و شی جدید حاصل خروجی متد __new__
خواهد بود.
متاسفانه برخی افراد تازه وارد در زبان پایتون و همینطور برخی آموزشها متد __init__
را به عنوان Constructor کلاسهای پایتون میدانند اما درست این است که در فرآیند نمونهسازی در زبان برنامهنویسی پایتون، دو متد __new__
و __init__
با یکدیگر کار میکنند و نقش سازنده (Constructor) را ایفا میکنند. متد __new__
شی را ایجاد (create) و متد __init__
آن را شحصیسازی (customize) میکند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class Sample: def __new__(cls, *args, **kwargs): print("__new__(), Has been called") print('cls: ', cls) print('args: ', args) print('kwargs: ', kwargs) # create new object obj = super().__new__(cls, *args, **kwargs) # return object return obj def __init__(self, x=0, y=0): print("__init__(), Has been called") print('self: ', self) self.x = x self.y = y sample_1 = Sample() print('-' * 30) sample_2 = Sample(3, 6) print('-' * 30) sample_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 و منطق و چگونگی پیادهسازی آن در زبان برنامهنویسی پایتون ارائه شده است. تمام موارد نا آشنایی که میبینید به تدریج شرح داده خواهند شد.
نکته
- زبان برنامهنویسی پایتون برخلاف برخی از زبانهای دیگر شیگرا به مانند Java، از امکان پیادهسازی چندین Constructor پشتیبانی نمیکند. البته برنامهنویس با روشهایی میتواند به صورت منطقی به هدف خود برسد!
- همانطور که بیان شد، هر کلاس پایتون یک پیادهسازی پیشفرض از دو متد
__new__
و__init__
دارد بنابراین الزامی به پیادهسازی دو متد__new__
و__init__
برای نمونهسازی از کلاس نیست. در اکثر مواقع__new__
پیادهسازی نمیشود اما زمانی که میخواهید در زمان نمونهسازی مقادیری در شی تنظیم نمایید، لازم است متد__init__
را پیادهسازی نمایید. - معمولا
__new__
زمانی پیادهسازی میشود که بخواهیم محدودیتهایی در ایجاد شی کلاس مورد نظر ایجاد کنیم. برای نمونه در پیادهسازی طرح Singleton [ویکیپدیا] یک کلاس. - ارسال آرگومان در زمان نمونهسازی شی یا همان پیادهسازی متد
__init__
به برنامهنویس این اطمینان را میدهد که شی جدید در یک وضعیت درست تنظیم شده است. - آرگومانهای متناظر با پارامترهای متد
__init__
(به جزself
که توسط مفسر پایتون مقداردهی میگردد) میبایست در زمان نمونهسازی و فراخوانی کلاس ارسال گردد.
اشیا با قابلیت فراخوانی (Callable Objects)¶
پیشتر دیدیم که میتوان توابع را فراخوانی نمود (درس دروازدهم)، با این کار بدنه تابع اجرا و خروجی متناسب دریافت میگردید. در این درس نیز مشاهده کردیم کلاسها نیز در پایتون توانایی فراخوانی دارند که با فراخوانی کلاس، یک شی از آن ایجاد میگردد. با استفاده از تابع callable
در پایتون میتوان تشخیص داد که آیا یک شی قابلیت فراخوانی دارد یا خیر [اسناد پایتون]، این تابع در صورتی که شی دریافتی قابلیت فراخوانی (callable) داشته باشد مقدار True
و در غیر این صورت False
برمیگرداند:
>>> 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
همانطور که از نمونه کد بالا مشخص است، اشیایی که از کلاسهای خودمان ایجاد میکنیم، بر خلاف خود کلاس قابلیت فراخوانی ندارند. در زبان پایتون میتوانیم این قابلیت را به اشیا کلاسهای خود اضافه نماییم.
همانطور که اشاره شد، کلاس در پایتون چندین متد خاص همانند __new__
و __init__
دارد که به تدریج به آنها آشنا خواهیم شد. یکی دیگر از این متدها __call__
میباشد [اسناد پایتون]. این متد نیز همانند متد __init__
از نوع Instance Method (بخش بعدی شرح داده شده است) میباشد که با پیادهسازی آن در کلاس، اشیای آن کلاس قابلیت فراخوانی پیدا خواهند کرد:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Sample: def __init__(self, x=0, y=0): print('------------------- Called __init__()') self.x = x self.y = y def __call__(self, x, y): print('------------------- Called __call__()') self.x = x self.y = y obj = Sample() print('object is callable:', callable(obj)) print('x =', obj.x) obj(5, 6) print('x =', obj.x) |
------------------- Called __init__()
object is callable: True
x = 0
------------------- Called __call__()
x = 5
سطر ۱۸ نمایش فراخوانی یک شی از کلاس Sample میباشد - درست به مانند یک تابع!
با فراخوانی یکی شی، به صورت خودکار متد __call__
فراخوانی و آرگومانهای نظیر ارسال میگردند.
از کاربردهای پیادهسازی متد __call__
و افزودن قابلیت فراخوانی به یک شی میتوان به ایجاد کلاس به عنوان دکوراتور (decorator) اشاره کرد (دروس آتی شرح داده خواهد شد) و همچنین کاربردهایی که نیاز میشود شی در زمان اجرا initialize یا مقداردهی دوباره داشته باشد، چرا که متد __init__
تنها یکبار در زمان نمونهسازی فراخوانی میگردد.
صفات (Attributes)¶
به بیانی ساده، متغیرهایی که به یک کلاس یا یک شی انتساب داده میشود صفت یا ویژگی یا Attribute خوانده میشوند. در بحث شی گرایی زبان برنامهنویسی پایتون دو نوع Attribute وجود دارد:
- Instance Attribute
- Class Attribute
Instance Attribute¶
به Attributeهای خاص یک شی گفته میشود. به هر شی در زبان برنامهنویسی پایتون میتوان با استفاده از سینتکس زیر یک Attribute انتساب داد:
object.attribute_name = value
1 2 3 4 5 6 7 | class Sample: pass sample = Sample() sample.a_new_attribute = 'A New Attribute!' print(sample.a_new_attribute) |
A New Attribute!
هر چیزی در پایتون یک شی است ولی ممکن است مفسر پایتون برای برخی اشیا محدودیتهایی در نظر گرفته باشد و شما نتوانید به هر شیای در پایتون Attribute اضافه نمایید. در این لحظه جا دارد اشاره شود به درس چهاردهم (بخش Function Attributes) که در واقع کاری جز افزودن Attribute به شی تابع نبود.
نکته
مقدار این دسته از Attributeها به ازای هر شی منحصربهفرد است. برای نمونه صفتهایی همچون نام، نامخانوادگی، سن و جنسیت برای هر یک از اشیای کلاس «شخص» قابل تعریف است. بدیهی است که هر نمونه شی از این کلاس میبایست شامل مقادیر منحصربهفردی از این Attributeها باشد.
برگردیم به مثال قبل که در آن ما یک کلاس به اسم Sample ایجاد (سطر ۱) و به یک شی از آن - پس از نمونهسازی (سطر ۳) - یک Attribute به نام a_new_attribute اضافه کردیم (سطر ۵). این شیوه افزودن Attribute به اشیای کلاسهایی که خودمان آنها را تعریف میکنیم چندان جالب نیست و ممکن است باعث بروز خطاهایی منطقی در برنامه گردد، بهتر است این کار توسط متد __init__
که در واقع initializer اشیا پایتون است، انجام پذیرد - به نمونه کدهای زیر توجه نمایید:
1 2 3 4 5 6 7 8 | class Sample: def __init__(self, attribute_value): self.a_new_attribute = attribute_value sample = Sample() print(sample.a_new_attribute) |
A New Attribute!
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Person: def __init__(self, first_name, last_name, age, gender): self.first_name = first_name self.last_name = last_name self.age = age self.gender = gender person_1 = Person('Kaneki', 'Ken', 18, 'male') person_2 = Person('Haise', 'Sasaki', 19, 'male') print(person_1.first_name) print(person_2.last_name) |
Kaneki
Sasaki
در ادامه شرح داده خواهد شد که پارامتر self
به شی جاری اشاره دارد و به صورت خودکار توسط مفسر پایتون مقداردهی میشود.
Class Attribute¶
به Attributeهای خاص یک کلاس گفته میشود و در واقع متغیرهایی است که درون کلاس و خارج از متدها تعریف میگردند. کاربرد این Attributeها به اشتراک گذاشتن یک یا چند مقدار یکسان در بین تمام اشیاست.
تمام اشیای یک کلاس به Class Attributeهای آن کلاس دسترسی دارند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | class Sample: class_attribute = 0 print('#' * 10, 'STEP#A') print('LINE 05:', Sample.class_attribute) # Instantiation sample_1 = Sample() sample_2 = Sample() print('#' * 10, 'STEP#B') print('LINE 12:', sample_1.class_attribute) print('LINE 13:', sample_2.class_attribute) print('#' * 10, 'STEP#C') # Change class_attribute for all objects Sample.class_attribute = 1 print('LINE 20:', sample_1.class_attribute) # Changed! print('LINE 21:', sample_2.class_attribute) # Changed! print('#' * 10, 'STEP#D') # WARNING!!! Create a new instance attribute sample_2.class_attribute = 2 print('LINE 28:', sample_1.class_attribute) print('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)¶
متدها در واقع توابعی هستند که داخل هر کلاس تعریف میشوند. هر کلاس پایتون میتواند شامل سه نوع متد باشد:
Instance Method
هر زمان در پیادهسازی یک کلاس، به شی جاری از کلاس یا Instance Attributeها نیاز داشتیم میبایست این نوع متد را پیادهسازی کنیم.
Class Method
هر زمان در پیادهسازی یک کلاس، به خود کلاس یا Class Attributeها نیاز داشتیم میبایست این نوع متد را پیادهسازی کنیم.
Static Method
هر زمان در پیادهسازی یک کلاس، به کلاس و به اشیای آن کلاس نیازی نداشتیم یا قصد پیادهسازی کاری مستقل از رفتار کلی کلاس مورد نظر داشتیم، میبایست این نوع متد را پیادهسازی کنیم.
1 2 3 4 5 6 7 8 9 10 11 12 | class Sample: def instance_method(self): pass @classmethod def class_method(cls): pass @staticmethod def static_method(): pass |
متد شی (Instance Method)¶
رایجترین نوع متد در پایتون است. برای ایجاد این متد نیازی به دکوراتور (Decorator - درس سیزدهم) نیست. همانطور که از نام این متد مشخص است این متد تنها از سوی اشیا یک کلاس قابل استفاده است. همانطور که پیشتر صحبت شد، هر شی از کلاس صفات خاص خود را دارد (Instance Attributes) که از این متدها میتوان برای دستیابی و دستکاری آنها استفاده کرد.
این نوع متد همواره میبایست حداقل یک پارامتر داشته باشد. پارامتر نخست که معمولا self
نامگذاری میشود حاوی شی جاری از کلاس است - در واقع همان شی ای که این متد را فراخوانی کرده است. این مقدار همواره از سوی مفسر پایتون ارسال میگردد و نیازی به ارسال از سوی برنامهنویس ندارد:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Sample: def __init__(self, char='*'): self.character = char def multiply_print(self, count=1): print(self.character * count) sample_1 = Sample() # Instantiating a new Object sample_1.multiply_print() sample_1.multiply_print(10) print('-' * 30) sample_2 = Sample('#') # Instantiating a new Object sample_2.multiply_print() sample_2.multiply_print(10) |
*
**********
------------------------------
#
##########
گفته شده که متد __init__
جزیی از مفهوم Constructor کلاسهای پایتون بوده و برای شخصیسازی یک شی در زمان ایجاد آن به کار میرود و کاربرد معمول آن افزودن Attribute به شی است. در نمونه کد بالا، این متد یک پارامتر char دریافت میکند - این پارامتر مقدار پیشفرض *
را دارد، بنابراین ارسال آرگومان متناظر برای آن اجباری نیست (تابع در پایتون - درس دوازدهم). با این کار میتوانیم در زمان نمونهسازی شی، یک Attribute با نام character در آن تعریف نماییم (سطر ۴). ما میخواهیم مقدار Attribute یا صفت character از هر شی را به تعداد دلخواه چاپ نماییم، از آنجا که این مقدار یک صفتِ متعلق به شی است و در ازای هر شی این مقدار میتواند متفاوت باشد پس ما برای این کار میبایست که یک Instance Method در بدنه کلاس تعریف کنیم (متد multiply_print
) - چرا که تنها در این صورت است که میتوانیم به self
دسترسی داشته باشیم و مقدار صفت character را از آن دستیابی کنیم.
نکته
Instance Methodها تنها میتوانند توسط اشیا فراخوانی شوند. روند فراخوانی یک متد توسط شی نیز به صورت نام شی + کاراکتر .
+ نام متد میباشد.
متد کلاس (Class Method)¶
این نوع متد همواره میبایست حداقل یک پارامتر داشته باشد. پارامتر نخست که معمولا cls
نامگذاری میشود حاوی کلاس جاری است - در واقع این متد هیچ اطلاعاتی از اشیا کلاس ندارد و تنها کلاس را میشناسد و Class Attributeها را دستیابی و دستکاری میکند. مقدار cls
نیز همانند self
همواره از سوی مفسر پایتون ارسال میگردد و نیازی به ارسال از سوی برنامهنویس ندارد. این متد با استفاده از دکوراتور (Decorator - درس سیزدهم) classmethod@
ایجاد میشود [اسناد پایتون]:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Student: school_name = 'My School' def __init__(self, name, family): self.name = name self.family = family @classmethod def school_info(cls): print(cls) return f'name: {cls.school_name}' print(Student.school_info()) print('-' * 30) print(Student('My Name', 'My Family').school_info()) |
<class '__main__.Student'>
name: My School
------------------------------
<class '__main__.Student'>
name: My School
نکته
این نوع متد (Class Method) را میتوان هم با استفاده از نام کلاس دستیابی کرد (سطر ۱۳) و هم با استفاده از اشیای آن کلاس (سطر ۱۵)، در واقع دکوراتور classmethod@
کارهای لازم برای نادیده گرفتن شی و ارسال مقدار پارامتر cls
را انجام میدهد.
متد ایستا (Static Method)¶
این نوع متد با استفاده از دکوراتور (Decorator - درس سیزدهم) staticmethod@
ایجاد میشود [اسناد پایتون]. این نوع متد پایتون، نه از اشیا اطلاعاتی دارد و نه حتی از کلاس. در واقع به این نوع متد، نه مقدار self
ارسال میشود و نه cls
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Student: school_name = 'My School' def __init__(self, name, family): self.name = name self.family = family @classmethod def school_info(cls): print(cls) return f'name: {cls.school_name}' @staticmethod def info(): return "This is a student class" print(Student.info()) print('-' * 30) print(Student('My Name', 'My Family').info()) |
This is a student class
------------------------------
This is a student class
نکته
این نوع متد (Static Method) را میتوان هم با استفاده از نام کلاس دستیابی کرد (سطر ۱۷) و هم با استفاده از اشیای آن کلاس (سطر ۱۹)، در واقع دکوراتور staticmethod@
کارهای لازم برای نادیده گرفتن شی و کلاس مربوط را انجام میدهد.
مقدار Hash یک شی و کاربرد آن در پایتون¶
به صورت کلی یک Hash در واقع عددی است که در ازای دادهای مشخص برآورد میگردد. دادههای مشابه دارای مقدار hash یکسانی خواهند بود و از طرفی یک تغییر جزئی در داده منجر به تولید یک مقدار hash کاملا متفاوت میشود. مقدار hash از یک تابع هَش [ویکیپدیا] به دست میآید که مسئولیت آن تبدیل داده ورودی به hash رمزگذاری شده است. واضح است که تعداد دادهها میتواند بسیار بیشتر از تعداد مقادیر قابل تولید hash باشد، بنابراین دو داده ممکن است مقدار hash یکسان داشته باشند که به آن Hash collision میگویند. در واقع اگر دو شی hash یکسان داشته باشند، لزوماً دارای ارزش یکسانی نیستند (برابر نیستند).
در زبان برنامهنویسی پایتون، تابع hash
[اسناد پایتون] یک شی hashable (قابل hash) را دریافت و مقدار 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
- گاهی ممکن است مقدار hash برابر با یک عدد منفی محاسبه گردد، مقدار hash منفی نیز در پایتون معتبر میباشد.
- با دوباره اجرا کردن برنامه یا اسکریپت ممکن است به نتایج دیگری از مقدار hash برسید. در واقع تظمین یکتایی مقدار hash تولید شده در پایتون تنها در ازای حیات هر proccess یا «اجرای برنامه» پابرجا خواهد بود.
شی hashable¶
گفتیم ورودی تابع hash
پایتون میبایست یک شی hashable باید. کدام اشیا در پایتون hashable هستند؟ تمامی اشیای که از نوع immutable (تغییرناپذیر - مراجعه شود به بخش دستهبندی از درس هشتم) هستند و همچنین اشیایی که متد خاص __hash__
[اسناد پایتون] را پیادهسازی کرده باشند:
>>> class Sample:
... pass
...
>>> obj = Sample()
>>> hash(obj)
-9223363243335467036
>>> obj.__hash__()
-9223363243335467036
متد __hash__
جزو متدهای خاص در پایتون میباشد و هر کلاسی که در پایتون ایجاد میکنید به صورت ضمنی یک پیادهسازی پیشفرض از این متد را شامل میشود.
کاربرد hash در پایتون¶
۱) ساختار Hash table [ویکیپدیا]
ساختمان داده دو نوع دیکشنری (dict) و مجموعه (set) در زبان برنامهنویسی پایتون بر پایه Hash table ایجاد شده است. در نتیجه سرعت دستیابی عناصر در آنها بسیار بیشتر از دستیابی در شی لیست (List) میباشد. در نوع داده دیکشنری، hash کلیدها محاسبه و از آن برای دستیابی مقدار مربوطه استفاده میشود، برای همین است که کلیدها در دیکشنری حتما میبایست از نوع hashable باشند ولی برای مقادیر هیچ محدودیتی وجود ندارد. نوع داده مجموعه نیز تنها میتواند شامل تعدادی شی hashable و یکتا (غیر تکراری) باشد.
۲) مقایسه دو شی
نکته
- اگر دو شی با یکدیگر برابر باشند، آنگاه مقدار hash آنها نیز برابر خواهد بود.
- اگر مقدار hash دو شی با یکدیگر برابر باشد، آنگاه ممکن است آن دو شی نیز با یکدیگر برابر باشند.
طی دروس آینده با مبحث Operator Overloading آشنا خواهید شد ولی در اینجا تنها کافی است بدانید که هرگاه دو شی توسط عملگر ==
مقایسه گردند، متد __eq__
[اسناد پایتون] به صورت خودکار فراخوانی خواهد شد. البته این متد نیز مانند باقی متدهای خاص پایتون، به صورت ضمنی یک پیادهسازی پیشفرض از خود دارد. در واقع خروجی این متد نتیجه مقایسه برابر بودن دو شی را برمیگرداند.
بین دو متد __eq__
و __hash__
روابطی حاکم است که باید بدانیم:
- اگر متد
__eq__
را پیادهسازی کنید ولی متد__hash__
را خیر، آنگاه اشیای کلاس مذکور hashable نخواهند بود. - اگر متد
__hash__
را پیادهسازی کردهاید، آنگاه بهتر است متد__eq__
را هم پیادهسازی نمایید. در غیر این صورت ممکن است در هنگام مقایسه اشیا خود دچار نتایج نامطلوب گردید. - در حالت پیشفرض پایتون،
True
بودن خروجی متد__eq__
برای دو شیx
وy
یعنیx == y
به معنی برقرار بودن دو شرط:x is y
وhash(x) == hash(y)
میباشد. - در حالت پیشفرض پایتون، تمام اشیای یک کلاس نابرابر و دارای مقدار hash متفاوت هستند، مگر اینکه ملاک مقایسه یک شی، خودش باشد.
به دو نمونه کد زیر توجه نمایید:
مقایسه شی در حالت پیشفرض:
1 2 3 4 5 6 7 8 9 10 11 | class Student: def __init__(self, name, score): self.name = name self.score = score obj_1 = Student('Saeid', 70) obj_2 = Student('Saeid', 90) print('Single Object :', obj_1 == obj_1) print('Same Objects :', obj_1 == Student('Saeid', 70)) print('Different Objects :', obj_1 == obj_2) |
Single Object : True
Same Objects : False
Different Objects : False
مقایسه شی به همراه شخصیسازی دو متد مذکور:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Student: def __init__(self, name, score): self.name = name self.score = score def __eq__(self, other): return self.name == other.name and self.score == other.score def __hash__(self): return hash((self.name, self.score)) obj_1 = Student('Saeid', 70) obj_2 = Student('Saeid', 90) print('Single Object :', obj_1 == obj_1) print('Same Objects :', obj_1 == Student('Saeid', 70)) print('Different Objects :', obj_1 == obj_2) |
Single Object : True
Same Objects : True
Different Objects : False
در این مثال هر دو Attribute کلاس Student از نوع immutable بودند، بنابراین از خود آنها برای مقایسه و محاسبه مقدار hash استفاده کردیم. به هر حال منطق پیادهسازی این دو متد بر اساس مسئله مطرح شده، بر عهده برنامهنویس میباشد. حتی میتوانید از مقدار تابع ()id
یا همان id(self)
بهره بگیرید.
😊 امیدوارم مفید بوده باشه