ساختن و به‌کارگیری یک کلاس

برنامه ‌نویسی شیء‌گرا یکی از موثّ 79 رترین رویکردها برای نوشتن نرم‌افزار است. در برنامه‌نویسی شیءگرا شما کلاس‌هایی می‌سازید که بیانگر مسائل دنیای واقعی هستند و سپس شیءها را بر اساس آن‌ها ایجاد می‌کنید. با ساختنِ یک کلاس رفتاری عمومی را برای دسته‌ای از اشیاء تعیین می‌کنید.

زمانی که از یک کلاس، چند شیءِ جداگانه می‌سازیم، هر شیء به طور خودکار به این رفتار عمومی مجهّز می‌شود؛ پس از آن می‌توانید به هر کدام از اشیاء هر ویژگی یا خصلتی که خواستید اضافه کنید. این که وضعیت‌های دنیای واقعی با برنامه‌نویسی شیءگرا چگونه به خوبی مدل می‌شوند، موجب شگفتی شما خواهد شد.

ساختن شیء از روی یک کلاس را نمونه‌سازی گویند و شما با نمونه‌های یک کلاس کار خواهید کرد. در این فصل کلاس‌ها را ایجاد کرده و نمونه‌هایی از روی آن‌ها می‌سازید. نوع اطّلاعاتی را که ممکن است در نمونه‌ها ذخیره شود، تعیین و همچنین کنش‌هایی را که می‌توان با این نمونه‌ها انجام داد، تعریف می‌کنید. کلاس‌هایی را هم برای گسترشِ کارآمدیِ کلاس‌های موجود ایجاد خواهید کرد تا کلاس‌های مشابه بتوانند به طور موثّری کد را بین خود به اشتراک بگذارند. کلاس‌های خود را در ماژول‌ها ذخیره و کلاس‌هایی را که دیگر برنامه‌نویسان نوشته‌اند در فایل‌های برنامه‌ی خود وارد می‌کنید.

درک برنامه‌نویسی شیءگرا به شما کمک می‌کند که جهان را از روزنه‌ی چشم یک برنامه‌نویس ببینید. این شما را یاری می‌کند که کد خود را واقعاً درک کنید و نه تنها بفهمید که در خط به خط برنامه چه اتّفاقی در حال رخ دادن است بلکه مفاهیم بزرگ‌تری را در ورای آن نیز درک کنید. درکِ منطق پشتِ کلاس‌ها شما را برای فکر کردنِ منطقی تربیت می‌کند تا بتوانید برنامه‌هایی بنویسید که تقریباً هر مشکلی را که به آن برمی‌خورید حل کند.

همچنان‌که به تدریج به چالش‌های پیچیده‌تری برمی‌خورید، کلاس‌ها کار را برای شما و دیگر برنامه‌نویسانی که با شما کار می‌کنند، ساده‌تر می‌کنند. زمانی که شما و برنامه‌نویسان دیگر بر اساس یک نوع منطقِ مشترک برنامه می‌نویسید، قادر خواهید بود که کار یکدیگر را ساده‌تر درک کنید. برنامه‌های شما برای مشارکت‌کنندگان بسیاری قابل درک خواهد بود که به همگان امکان می‌دهد که دستاورد بیشتری داشته باشند.

شما می‌توانید تقریباً هر چیزی را با کلاس‌ها مدل کنید. کار را با نوشتن کلاسی ساده به نام Dog شروع می‌کنیم که نماینده‌ی یک سگ است — نه یک سگ خاص بلکه هر نوع سگی. درباره‌ی سگ‌های خانگی چه می‌دانیم؟ همه‌ی آن‌ها نام و سن دارند. همچنین می‌دانیم که بیشترِ آن‌ها می‌نشینند و غلت می‌زنند. این دو قطعه اطّلاعات (نام و سن) و آن دو رفتار (نشستن و غلت زدن) در کلاس Dog جای می‌گیرند زیرا بین بیشتر سگ‌ها مشترک‌اند. این کلاس به پایتون می‌گوید که چگونه شی‌ای را برای نمایش یک سگ بسازد. بعد از این که کلاس ما نوشته شد، از آن برای ساختن نمونه‌های جداگانه که هر کدام بیانگر یک سگ خاص هستند، استفاده می‌کنیم.

ساختن کلاس Dog

هر نمونه‌ای که از کلاس Dog ساخته شود یک نام (name) و یک سن (age) ذخیره می‌کند و به هر سگ دو قابلیّت نشستن (sit()) و غلت زدن (roll_over()) خواهد داد:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Dog:
    """A simple attempt to model a dog."""

    def __init__(self, name, age):
        """Initialize name and age attributes."""
        self.name = name
        self.age = age

    def sit(self):
        """Simulate a dog sitting in response to a command."""
        print(f"{self.name} is now sitting.")

نکته‌های فراوانی برای توضیح وجود دارد امّا نگران نباشید. این ساختار را در سراسر این فصل خواهید دید و برای عادت کردن به آن وقتِ زیادی دارید. کلاسی به نام Dog تعریف می‌کنیم. بنا بر قرارداد، در پایتون نام‌هایی که با حرف اوّل بزرگ نوشته می‌شوند، بیانگر نام کلاس‌ها هستند. هیچ پرانتزی در تعریف کلاس وجود ندارد چون آن را از اوّل ایجاد می‌کنیم. یک docstring برای شرح عملکرد کلاس می‌نویسیم.

متد __init__()

به تابعی که بخشی از یک کلاس باشد متد می‌گویند. هر چیزی که درباره‌ی تابع‌ها آموخته‌اید در مورد متدها هم صدق می‌کند، فعلاً تنها تفاوت عملی بین این دو، نحوه‌ی فراخوانی متدهاست. متد __init__() متد ویژه‌ای است که پایتون هر بار که نمونه‌ای بر اساس کلاس Dog می‌سازد، آن را به طور خودکار اجرا می‌کند. این متد دو زیرخط در ابتدا و دو زیرخط در انتها دارد، این قراردادی است که مانع تداخل نام‌ پیش‌فرض متدهای پایتون و نام متدهای شما می‌شود. مطمئن شوید که حتماً دو زیرخط در دو طرف __init__() گذاشته‌اید. اگر فقط یکی بگذارید، متد هنگام استفاده از کلاس به طور خودکار فراخوانی نخواهد شد که ممکن است به بروز خطاهایی بیانجامد که تشخیص آن‌ها مشکل است.

تابع __init__() را طوری تعریف می‌کنیم که سه پارامتر داشته باشد: self، name و age. پارامتر self در تعریفِ متد الزامی است و باید پیش از دیگر پارامترها ذکر شود. این پارامتر باید در تعریفِ متد آورده شود زیرا زمانی‌که پایتون بعداً (هنگامِ ساختن یک نمونه از Dog) این متد را فراخوانی می‌کند، به طور خودکار نشان‌وند self را به آن انتقال می‌دهد. فراخوانی هر کدام از متدهای مربوط به یک نمونه به طور خودکار نشان‌وند self را انتقال می‌دهد که در واقع ارجاعی به خودِ نمونه است و به یک نمونه‌ی مجزّا امکان می‌دهد به مشخّصه‌ها و متدهای یک کلاس دسترسی داشته باشد. در زمانِ ساختنِ نمونه‌ای از Dog، پایتون متد __init__() را از کلاس Dog فراخوانی می‌کند. یک نام و یک سن را به عنوان نشان‌وند به Dog() می‌دهیم؛ self به طور خودکار منتقل می‌شود و نیازی به ذکر آن نیست. هر بار خواستیم نمونه‌ای از کلاس Dog بسازیم، تنها مقدارهای دو پارامتر آخر، name و age را ارائه می‌کنیم.

دو متغیّرِ تعریف‌شده در __init__() هر دو پیش‌وند self دارند. هر متغیّری که پیش‌وندِ self داشته باشد در دسترسِ تمام متدهای کلاس خواهد بود و ما نیز از82 طریقِ نمونه‌هایی که از کلاس ساخته می‌شود به این متغیّرها دسترسی داریم. خط self.name = name مقداری را که به پارامتر name داده شده به متغیّر name نسبت می‌دهد و سپس این متغیّر، به نمونه‌ای که ساخته می‌شود الصاق می‌شود. چنین فرآیندی برای self.age = age هم طی می‌شود. به متغیّری که به این شکل و از طریق نمونه‌ها در دسترس است مشخّصه می‌گویند.

در کلاس Dog دو متد دیگر نیز تعریف شده است: sit() و roll_over(). چون این متدها برای اجرا شدن به اطّلاعات اضافی نیاز ندارند آن را تنها با یک پارامتر، self، تعریف می‌کنیم. نمونه‌هایی که بعداً می‌سازیم به این متدها دسترسی خواهند داشت. به بیانِ دیگر آن‌ها می‌توانند بنشینند یا غلت بزنند. فعلاً sit() و roll_over() کار زیادی نمی‌کنند. آن‌ها تنها پیامی چاپ می‌کنند که سگ در حال نشستن یا غلت زدن است. امّا این مفهوم را می‌توان به وضعیّت‌های واقعی بسط داد: اگر این کلاس بخشی از یک بازی رایانه‌ای واقعی بود، این متدها کدی را شامل می‌شدند که باعث می‌شد سگی انیمیشنی بنشیند یا غلت بزند. اگر این کلاس برای کنترل کردن رباتی نوشته بود، این متدها حرکت‌هایی را طوری هدایت می‌کردند که به نشستن یا غلت زدن سگِ رباتی منجر می‌شوند.

ساختن یک نمونه از یک کلاس

به کلاس به عنوان مجموعه‌ای دستورالعمل‌ها برای چگونگی ساختنِ یک نمونه نگاه کنید. کلاس Dog مجموعه‌ای از دستورالعمل‌هاست که به پایتون می‌گوید چگونه نمونه‌های جداگانه‌ای بسازد که هر کدام بیانگر سگِ خاصّی هستند.

اکنون نمونه‌ای که نماینده‌ی سگ خاصّی باشد می‌سازیم:

1
2
3
4
5
6
7
8
class Dog:
    --snip--

my_dog = Dog('Willie', 6)

print(f"My dog's name is {my_dog.name}.")

print(f"My dog is {my_dog.age} years old.")

کلاس Dog در اینجا همانی است که در مثال قبلی تعریف کردیم. از پایتون می‌خواهیم که سگی به نام 'Willie' با ۶ سال سن بسازد. هنگامی که پایتون این خط را می‌خواند، متد __init__() را در Dog با نشان‌وندهای 'Willie' و 6 فراخوانی می‌کند. متد __init__() نمونه‌ای بیانگر این سگِ خاص می‌سازد و مشخّصه‌های name و age را با توجه به مقادیرِ ارائه شده، تعیین می‌کند. سپس پایتون نمونه‌ای را که بیانگر این سگ است برمی‌گرداند. این نمونه را به متغیّر my_dog نسبت می‌دهیم. قرارداد نام‌گذاری اینجا سودمند است: معمولاً می‌توانیم فرض کنیم که نام‌هایی مانند Dog که حرف اوّل آن‌ها بزرگ است به یک کلاس و آن‌هایی که با حروف کوچک نوشته می‌شوند، مانند my_dog، به یک نمونه از آن کلاس اشاره دارند.

دسترسی به مشخّصه‌ها

برای دسترسی به مشخّصه‌های یک نمونه، از نمادگذاریِ نقطه استفاده می‌کنید. به این ترتیب به مقدار مشخّصه‌ی name از نمونه‌ی my_dog دسترسی پیدا می‌کنیم:

1
my_dog.name

در پایتون نماد نقطه بسیار به کار می‌رود. این دستور نشان می‌دهد که چگونه پایتون مقدار یک مشخّصه را پیدا می‌کند. اینجا پایتون به نمونه‌ی my_dog نگاه می‌کند و سپس مشخّصه‌ی name را که به آن وابسته است پیدا می‌کند. این همان مشخّصه‌ای است که در کلاس Dog به عنوان self.name مورد ارجاع قرار گرفت. از همین رویکرد برای کار کردن با مشخّصه‌ی age استفاده کرده‌ایم.

خروجی این کد خلاصه‌ای است که درباره‌ی my_dog می‌دانیم:

1
2
3
My dog's name is Willie.

My dog is 6 years old.

فراخوانی متدها

پس از ساختن نمونه‌ای از کلاس Dog، می‌توانیم از نماد نقطه برای فراخواندن هر کدام از متدهای تعریف‌شده در Dog استفاده کنیم. اکنون کاری می‌کنیم که سگ ما بنشیند یا غلت بزند:

1
2
3
4
5
6
7
8
class Dog:
    --snip--

my_dog = Dog('Willie', 6)

my_dog.sit()

my_dog.roll_over()

برای فراخوانیِ یک متد، نامِ نمونه (در این مورد my_dog) و نام متدی را که می‌خواهید فراخوانی کنید بنویسید و بین آن‌ها یک نقطه بگذارید. زمانی که پایتون my_dog.sit() را می‌خواند به دنبال متد sit() در کلاس Dog می‌گردد و کد آن را اجرا می‌کند. پایتون خط my_dog.roll_over() را به همین ترتیب تفسیر می‌کند.

اکنون ویلی آن‌چه را به او فرمان داده‌ایم اجرا می‌کند:

1
2
3
Willie is now sitting.

Willie rolled over!

این نوع نوشتن بسیار سودمند است. زمانی که مشخّصه‌ها و متدها نام‌های گویایی داشته باشند، مانند name، age، sit() و roll_over()، می‌توانیم به سادگی به آن‌چه کد قرار است انجام دهد پی ببریم هر چند قبلاً هرگز آن را ندیده باشیم.

ساختن چند نمونه

می‌توانید هر تعداد نمونه که بخواهید از یک کلاس بسازید. بیاید سگ دومی به نام your_dog بسازیم:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Dog:
    --snip--

my_dog = Dog('Willie', 6)

your_dog = Dog('Lucy', 3)

print(f"My dog's name is {my_dog.name}.")

print(f"My dog is {my_dog.age} years old.")

my_dog.sit()

print(f"\nYour dog's name is {your_dog.name}.")

print(f"Your dog is {your_dog.age} years old.")

your_dog.sit()

در این مثال سگی به نام ویلی و سگی دیگر به نام لوسی ساخته‌ایم. هر سگ نمونه‌ی جداگانه‌ی خود را با مجموعه‌ی مشخّصه‌های مخصوص به خود دارد و قادر به انجام کارهای مشترکی است:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
My dog's name is Willie.

My dog is 6 years old.

Willie is now sitting.

Your dog's name is Lucy.

Your dog is 3 years old.

Lucy is now sitting.

حتّی اگر از یک نام و سن برای سگ دوم استفاده می‌کردیم، باز هم پایتون یک نمونه‌ی جداگانه از کلاس Dog می‌ساخت. شما می‌توانید هر تعداد نمونه که خواستید از یک کلاس بسازید، تا زمانی که به هر نمونه یک نام متغیّر یکتا بدهید یا نمونه‌ جای یکتایی را در یک لیست یا دیکشنری اشغال کند.

خودآزمایی

۹-۱. رستوران: کلاسی به نام Restaurant بسازید. متد __init__() در Restaurant باید دو مشخّصه را ذخیره کند: restaurant_name و cuisine_type. متدی به نام describe_restaurant() بسازید که این دو دسته اطّلاعات را چاپ کند، متدی هم به نام open_restaurant() بسازید که پیامی برای باز کردنِ رستوران چاپ کند.

نمونه‌ای از کلاس خود به نام restaurant بسازید. دو مشخّصه را جداگانه چاپ کنید و سپس دو متد آن را فرابخوانید.

۹-۲. سه رستوران: از کلاسی که در تمرین ۹-۱ تعریف کردید شروع کنید. سه نمونه‌ی مختلف از این کلاس بسازید و describe_restaurant() را برای هر نمونه فراخوانی کنید.

۹-۳. کاربران: کلاسی به نام User بسازید. دو مشخّصه به نام‌های first_name و last_name بسازید و سپس چند مشخّصه‌ی دیگر که معمولاً در نمایه‌ی یک کاربر ذخیره می‌شود به آن اضافه کنید. متدی به نام describe_user() بسازید که خلاصه‌ای از اطّلاعات یک کاربر را چاپ می‌کند. متد دیگری به نام greet_user() بسازید که یک پیام احوال‌پرسی شخصی برای کاربر چاپ می‌کند.

چندین نمونه که هر کدام بیانگر کاربر متفاوتی است بسازید و برای هر کدام هر دو متد را فراخوانی کنید.