کلاسها و اشیا
در حال حاضر شما میدانید که چطور از توابع برای ساماندهی کدها و انواع توکار براس ساماندهی اطلاعات استفاده کنید.قدم بعدی یادگیری "برنامهنویسی شی گرا"ست که از انواع از پیش تعیین شده توسط برنامه نویس برای ساماندهی کد و اطلاعات استفاده میکند.برنامه نویسی شی گرا موضوع بسیار بزرگیست که ممکن است توضیح در مورد آن چند فصل به طول بیانجامد.
مثالهای آموزشی در این فصل در {آدرسی } و راهنمای تمرینها در {آدرس} در دسترس است.
۱۵.۱ انواع از پیشتعیین شده توسط برنامهنویس
ما در پایتون انواع توکار بسیاری داریم ، حالا ما میخواهیم که نوع جدیدی از انواع را تعریف کنیم.به عنوان مثال ، ما نوع جدیدی به نام Point میسازیم که معرفیکنندهی یک نقطه در یک فضای دو بعدیست.
در نشانه گذاری ریاضیاتی ، نقاط معمولاً در داخل پرانتز که با یک ویرگول از هم جدا شده اند ، نمایانگر محختصات هستند.برای مثال ،(۰,۰) نشاندهندهی مبدا و (x,y) نمایانگر نقطهی x از راست و y از بالا نسبت به مبدا ، میباشد.
در پایتون چندین راه برای نمایشدادن نقاط در پایتون وجود دارد:
- میتوان مختصات را با صورت جداگانه در دو متغیر x و y ذخیره کرد.
- میتوان مختصات را به صورت المانی در یک لیست یا لیست چندتایی ذخیره کرد .
- میتوان نوع جدیدی از نقاط به صورت اشیاء ساخت.
ساخت یک نوع جدید پیچیدهتر بقیه دیگر گزینههاست ، ولی دارای مزیتهایی هست که به زودی به آن خواهیم پرداخت.
یک نوع تعیین شده از کاربر یک کلاس نامیده میشود.ظاهر یک کلاس به صورت زیر است
class Point:
“””Represents a point in 2-D Space.”””
بخش بالا یک کلاس جدید به نام Point را نمایش میدهد.بدنهی آن یک Docstring است که توضیح میدهد که کلاس به چه کاری میآید.شما میتوانید متغیرها و متدها را در درون کلاس تعریف کنید ، اما ما بعداً به آن میپردازیم .
معرفی یک کلاس به نام Point که یک شیء از اشیا را میسازد.
>>> Point
<class ‘__main__.Point’>
چون Point در بالاترین مرحله معرفی شده ، "نام کامل" آن به صورت
__main__.Point
میباشد.
کلاس شی به منزلهی کارخانهای برای ساخت اشیاءمیباشد.برای ساخت یک نقطه ، میتوان ، در صورتی که یک تابع باشد ، آنرا صدا زد.
>>> blank = Point()
>>> blank
<__main__.Point object at 0xb7e9d3ac>
مقدار بازگشت داده شده ارجاعی به شیء در Point است که ما قبلاً آن را به blank متصل کرده بودیم.
ساخت یک شیء جدید ، نمونهسازی و شیء یک نمونه از کلاس نامیده میشود.
وقتی شما یک نمونه را چاپ میکنید،پایتون به شما میگوید که این نمونه به کدام کلاس تعلق دارد و در کجای حافظه ذخیره شده است .
( وجود پیشوند 0x نمایانگر آن است که آن عدد به صورت هگزادسیمال است)
هر شی نمونهای از برخی کلاسهاست ، پس "کلاس" و " نمونه" قابل تعویض است.ولی در این فصل من از "نمونه" استفاده میکنم تا نشان دهم که در مورد انواع از پیش تعیین شدهی برنامه نویس صحبت میکنم.
۱۵۰۲ خاصیتها
میتوان برای نمونهها مقادیری را با نشانهگذاری نقطه در نظر گرفت:
>>> blank.x = 3.0
>>> blank.y = 4.0
این نحو شباهت بسیاری به نحو انتخاب یک متغیر از ماژول ، مانند انتخاب math.pi ، یا string.whitespace.
در اینجا ما برای المانهای یک شی را برای نامگذاری ، مقادیری را در نظر میگیریم..
به عنوان یک اسم "AT-trib-ute" با تمرکز بر بخش اول کلمه تلفظ میشود ، که مخالف تلفظ "a-TRIB-ute" به عنوان یک فعل است .
دیاگرام ذیل نتیجهی این واگذاری را نشان میدهد.دیاگرام حالت
متغیر blank به یکی از شیهای Point اشاره میکند که هرکدام شامل دو خاصیت است.هر خاصیت به یک عدد با خاصیت شناور اشاره میکند.
شما میتوانید مقدار یک خاصیت را با نحو مشابهی بخوانید:
>>>blank,y
4.0
>>>x = blank.x
>>>x
3.0
عبارت blank.x به این معناست که "برو به شی blank و مقدار x را بگو" .به عنوان مثال ما مقدار یک متغیر به اسم x را در نظر میگیریم.هیچ تداخلی بین متغیر x و خاصیت x وجود ندارد
شما میتوانید از نشانه گذاری نقطه به عنوان بخشی از اصطلاح استفاده کنید:
>>> '(%g, %g)’ % (blank.x,blank.y)
‘(3.0, 4.0)’
>>> distance = math.sqrt(blank.x**2 + blank.y**2)
>>> distance
5.0
همچنین میتوان به از نمونه به عنوان آرگومان استفاده کرد .مثلاً:
def print_point(p):
print(‘(%g, %g)’ % (p.x, p.y))
عبارت print_point ، Point را به عنوان آرگومان گرفته و به عنوان نشانهگذاری ریاضی نمایش میدهد.برای فراخوانی آن میتوان blank را به عنوان یک آرگومان در نظر گرفت :
>>> print_point (blank)
(3.0, 4.0)
درون تابع ، p یک نام مستعار برای blank است که اگر p تغییر پیدا کند ، blank نیز تغییر میکند.
برای تمرین یک تابع با نام distance_between_points بنییسید که دو نقطه به عنوان آرگومان دریافت کند و فاصلهی بین آنها را نمایش دهد.
۱۵.۳ مستطیل
بعضی وقتها این واضح است که خاصیتهای یک شی چطور باید باشد ولی بعضی مواقع دیگر خود شما باید تصمیم بگیرید.برای مقال ،تصور کنید که شما در حال طراحی یک کلاس هستید که یک مستطیل را معرفی کند.کدام خاصیت را باید استفاده کنیم که مکان و ساز مستطیل را در نظر بگیرد ؟ شما میتوانید زوایا را در نظر نگیرید . برای یاده نگهداشتن اجزا میتوان فرض کرد که مستطیل هم عمودیست و هم افقی .
در اینجا حداقل دو احتمال وجود دارد.
- میتوان یک گوشه ، ارتفاع و عرض مستطیل را مشخص کرد
- میتوان دو گوشهی مخالف هم را در نظر گرفت
در اینجا سخت است که چه چیزی از دیگری بهتر است ، پس ما اولی را پیاده سازی میکنیم.برای مثال :
ما در اینجا یک کلاس را معرفی میکنیم :
شکل ۲-۱۵ دیاگرام شی
class Rectangle:
””” Represents a rectangle.
attributes : witdth, height, corner.
”””
docstring خصوصیات را لیست میکند :width و height عدد هستند و corner یک شی نقطه است که نقطه ی پایین سمت چپ را مشخص میکند.
برای نشان دادن یک مستطیل ، شما باید یک شی مستطیل ایجاد کنید و مقادیر ویژگی های شی مستطیل که ایجاد کرده اید را به شی اختصاص بدهید:
box = Rectangle ()
box.width = 100.0
box.height = 200.0
box.corner = Point ()
box.corner.x = 0.0
box.corner.y = 0.0
عبارت box.corner.x به این معنی است که "به شی ای که box به آن اشاره می کند برو و خاصیت corner را انتخاب کن ، حال به آن شی (corner) مراجعه کن و خاصیت x را انتخاب کن"
شکل ۲-۱۵ وضعیت شی را نشان می دهد. شی ای که خاصیت شی دیگری باشد ، درونی سازی شده ^1است.
۱۵-۴ نمونهها به عنوان مقدار
تابعها میتوانند نمونه بگردانند. برای مثال find_tracker یک چهارضلعی Rectangle میگیرد و یک نقطه Point برمیگرداند که مختصات مرکز چهارضلعی را در بردارد.
def find_center(rect):
p = Point()
p.x = rect.corner.x + rect.width/2
p.y = rec
return p
مثال زیر یک جعبه را به عنوان متغیر دریافت میکند و نقطه به دست آمده را در متغیر مرکز center ذخیره میکند.
>>> center = find_center(box)
>>> print_point(center)
(50, 100)
۱۵-۵ شیئها تغییرناپذیرند
میتوانید وضعیت یک شیئ را با تخصیص دادن یک مقدار به یکی از مشخصههایش تغییر دهید. برای مثال برای اینکه اندازه مستطیل را بدون تغییر مکانش، میتوانید مقدار طول width و عرض height را تغییر دهید:
box.width = box.width + 50
box.height = box.height + 100
شما همچنین میتوانید تابعهایی بنویسید که شیئها را تغییر میدهند. برای مثال grow_rectangle یک شیئ چهارضلعی و دو مقدار، dwidth و dheight را بگیرید و مقدارها را به طول و عرض مستطیل اضافه کند:
def grow_rectangle(rect, dwidth, dheight):
rect.width += dwidth
rect.height += dheight
مثال زیر اثر آن را نمایش میدهد:
>>> box.width, box.height
(150.0, 300.0)
>>> grow_rectangle(box, 50, 100)
>>> box.width, box.height
(200.0, 400.0)
درون تابع rect یک نام مستعار برای box است، بنابراین وقتی تابع rect را تغییر میدهد، جعبه تغییر میکند.
به عنوان تمرین یک تابع به نام move_rectangle که یک چهارضلعی و دو مقدار به نام dx و dy را میگیرد. این تابع باید مکان چهارضلعی را با اضافه کردن dx به مختصات x گوشه و اضافه کردن dy به مختصات y گوشه تغییر دهد.
۱۵-۶ کپی کردن
استفاده از نام مستعار باعث میشود خواندن برنامه سخت شود زیرا تغییرات در یک جا ممکن است اثرهای غیرمنتظرهای در جایی دیگر داشته باشد. مشکل بتوان حواسمان به تمام متغیرهایی باشد که ممکن است به یک شیئ مشخص اشاره کنند.
کپی کردن یک شیئ غالباً بدیلی برای مستعارسازی است. ماژول copy تابعی به نام copy دارد که میتواند هر شیئی را تکثیر کند:
>>> p1 = Point()
>>> p1.x = 3.0
>>> p1.y = 4.0
>>> import copy
>>> p2 = copy.copy(p1)
شیئهای p1 و p2 دربردارنده داده یکسانی هستند، اما آنها دو نقطه یکسان نیستند.
>>> print_point(p1)
(3, 4)
>>> print_point(p2)
(3, 4)
>>> p1 is p2
False
شکل ۱۵-۳
>>> p1 == p2
False
عملگر is اعلام میکند که p1 و p2 دو شیئ یکسان نیستند که همان چیزی است که انتظار داشتیم. اما شاید انتظار داشتید که == مقدار True برگرداند زیرا این دو نقطه مقدار یکسانی دربردارند. در این صورت دلسرد میشوید اگر بدانید که رفتار پیشفرض عملگر == در مورد نمونهها مشابه عملگر is است؛ این عملگر تشابه شیئها را بررسی میکند و نه برابری آنها را. این به این دلیل است که پایتون نمیداند در مورد تایپهای تعریف شده توسط برنامهنویس چه چیزی باید برابری به حساب آید. حداقل فعلاً نمیداند.
اگر از copy.copy برای تکثیر یک چهارضلعی استفاده کنید، متوجه خواهید شد که شیئ چهارضلعی را کپی میکند ولی نقطه جاسازیشده در آن را خیر.
>>> box2 = copy.copy(box)
>>> box2 is box
False
>>> box2.corner is box.corner
True
در شکل ۱۵-۳ میتوانید ببینید نمودار شیئ چه شکلی دارد. به این عمل کپی سطحی میگوییم زیرا شیئ و هر ارجاعی که دربردارد را کپی میکند، ولی شیئهای جاسازیشده در آن را خیر.
بیشتر اوقات این رفتاری نیست که مطلوبتان باشد. در این مثال فراخوانی grow_rectangle روی یکی از چهارضلعیها بر دیگری تاثیری نخواهد داشت ولی فراخوانی move_rectangle روی هر کدام از آنها بر هر دو اثر خواهد گذاشت! این رفتار سردرگمکننده و مستعد خطا است.
خوشبختانه ماژول کپی یک متد به نام deepcopy دارد که نه تنها شیئ را، بلکه شیئهایی که به آنها ارجاع میدهد، و شیئهایی که آنها به آن ارجاع میدهند و علی آخر کپی میکند. تعجب نخواهید کرد اگر بدانید به این عمل کپی عمیق میگویند.
>>> box3 = copy.deepcopy(box)
>>> box3 is box
False
>>> box3.corner is box.corner
False
شیئهای box3 و box دو شیئ کاملا مجزا هستند.
به عنوان تمرین، نسخهای از move_rectangle بنویسید که یک چهارضلعی جدید را ایجاد میکند و برمیگرداند به جای اینکه نمونه قدیمی را تغییر دهد.
۱۵-۷ رفع اشکال
زمانی که با شیئها شروع به کار میکنید، ممکن است به چند استثنای جدید برخورد کنید. اگر سعی کنید به مشخصهای دسترسی پیدا کنید که وجود ندارد، به AttributeError بر خواهید خورد:
>>> p = Point()
>>> p.x = 3
>>> p.y = 4
>>> p.z
AttributeError: Point instance has no attribute 'z'
اگر در مورد تایپ یک شیئ شک دارید، میتوانید بپرسید:
>>> type(p)
<class '__main__.Point'>
همچنین میتوانید از isinstance برای اینکه چک کنید آیا یک شیئ نمونهای از یک کلاس هست استفاده کنید:
>>> isinstance(p, Point)
True
اگر مطمئن نیستید آیا یک شیئ یک مشخصه خاص را دارد، میتوانید از تابع درونی hasattr استفاده کنید:
>>> hasattr(p, 'x')
True
>>> hasattr(p, 'z')
False
آرگومان اول میتواند هر شیئی باشد، آرگومان دوم یک رشته است که نام مشخصه را دربردارد.
همچنین برای اینکه ببینید آیا یک شیئ مشخصههایی که نیاز دارید را دربردارد، میتوانید از عبارت try استفاده کنید:
try:
x = p.x
except AttributeError:
x = 0
این رویکرد میتواند نوشتن تابعهایی که با تایپهای مختلف کار میکنند را سادهتر کند؛ در قسمت ۱۷-۹ در این مورد بیشتر صحبت خواهیم کرد.
۱۵-۸ واژهنامه
کلاس یک تایپ تعریف شده توسط برنامهنویس. تعریف کلاس یک شیئ کلاس جدید میسازد.
شیئ کلاس یک شیئ که اطلاعاتی در مورد تایپ تعریفشده توسط برنامهنویس را دربردارد. شیئ کلاس میتواند برای ساختن نمونههایی از این تایپ به کار رود.
نمونه یک شیئ که به یک کلاس تعلق دارد.
نمونهسازی ساختن یک شیئ جدید.
مشخصه یکی از مقدارهای نامگذاری شده که به یک شیئ مربوط است.
شیئ جاگذاریشده یک شیئ که به عنوان مشخصه یک شیئ دیگر ذخیره شده است.
کپی سطحی کپی کردن محتویات یک شیئ شامل هر ارجاعی به شیئهای جاگذاریشده که توسط تابع copy در ماژول copy اجرا میشود.
کپی عمیق کپی کردن محتویات یک شیئ به علاوه هر شیئ جاگذاریشده و هر شیئی که درون آنها جاگذاری شده و علی آخر که توسط تابع deepcopy در ماژول copy اجرا میشود.
نمودار شیئ یک نمودار که شیئها، مشخصههای آنها، و مقدارهای مشخصهها را نمایش میدهد.
۱۵-۹ تمرینها
تمرین ۱۵-۱ کلاسی را تعریف کنید به نام Circle با مشخصههای center و radius، که در آن center یک شیئ نقطه است و radius یک عدد است.
یک شیئ دایره نمونهسازی کنید که یک دایره با مرکز (۱۰۰، ۱۵۰) و شعاع ۷۵ را نمایش میدهد.
تابعی به نام point_in_circle تعریف کنید که یک دایره و یک نقطه را میگیرد و اگر نقطه داخل یا روی مرزهای دایره است، مقدار True برمیگرداند.
تابعی به نام rect_in_circle تعریف کنید که یک دایره و یک چهارضلعی را میگیرد و اگر چهارضلعی کاملا داخل یا روی مرزهای دایره است، مقدار True برمیگرداند.
تابعی به نام rect_circle_overlap تعریف کنید که یک دایره و یک چهارضلعی را میگیرد و اگر هر کدام از گوشههای چهارضلعی درون دایره قرار بگیرد، مقدار True برمیگرداند. یا برای نسخه چالشبرانگیزتر، مقدار True برگرداند اگر هر قسمتی از چهارضلعی درون دایره قرار بگیرد.
راهحل: http: // thinkpython2. com/ code/ Circle. py
تمرین ۱۵-۲ یک تابع به نام draw_rect بنویسید که یک شیئ لاکپشت و یک چهارضلعی میگیرد و از لاکپشت برای رسم چهارضلعی استفاده کند. برای نمونههایی از کاربرد شیئ لاکپشت به فصل ۴ رجوع کنید.
یک تابع به نام draw_circle بنویسید که یک شیئ لاکپشت و یک دایره میگیرد و دایره را رسم میکند.
راهحل: http: // thinkpython2. com/ code/ draw. py