منابع ترجمه · Think Python · فصل ۱۴

کلاس‌ها و توابع (فصل ۱۴ Think Python)

۱۸ دقیقه مطالعه · به‌روزرسانی ۲ آبان ۱۴۰۵

منبع اصلی: Think Python, Chapter 14: Classes and Functions — Allen B. Downey

💡 این صفحه مثل یک نوت‌بوک Jupyter است: متغیر و تابعی که در یک سلول تعریف شود، در سلول‌های بعدی همین صفحه در دسترس است. سلول‌ها را به ترتیب اجرا کنید، یا با «⏮ تا اینجا» همهٔ سلول‌های قبلیِ اجرانشده را یک‌جا اجرا کنید.

دو نکته درباره‌ی این ترجمه: (۱) کتاب اصلی در مثال «اشیاء تغییرپذیرند» نام یک فیلم مشهور را می‌آورد؛ من آن را با «یک فیلم» جایگزین کرده‌ام، بدون تغییر در مثال. (۲) خروجی‌های نمایش‌داده‌شده‌ی زیر کدها را با اجرای همین کدها به دست آورده‌ام. آدرس حافظه‌ای که هنگام چاپ یک شیء نمایش داده می‌شود در هر اجرا فرق می‌کند.

تا اینجا می‌دانید چگونه از توابع برای سازمان‌دهی کد و از نوع‌های داخلی برای سازمان‌دهی داده استفاده کنید. قدم بعدی برنامه‌نویسی شیءگرا (object-oriented programming) است، که از نوع‌های تعریف‌شده توسط برنامه‌نویس برای سازمان‌دهی هم کد و هم داده استفاده می‌کند.

برنامه‌نویسی شیءگرا موضوع بزرگی است، پس تدریجی پیش می‌رویم. در این فصل با کدی شروع می‌کنیم که «idiomatic» نیست – یعنی از نوعی نیست که برنامه‌نویسان باتجربه می‌نویسند – اما نقطه‌ی شروع خوبی است. در دو فصل بعد از امکانات بیشتری برای نوشتن کد idiomatic‌تر استفاده می‌کنیم.

۱۴.۱. نوع‌های تعریف‌شده توسط برنامه‌نویس

از نوع‌های داخلی زیادی در پایتون استفاده کرده‌ایم – حالا نوع جدیدی تعریف می‌کنیم. به‌عنوان اولین مثال، نوعی به نام Time می‌سازیم که یک زمان از روز را نشان می‌دهد. نوع تعریف‌شده توسط برنامه‌نویس کلاس (class) هم نامیده می‌شود. تعریف کلاس این‌طور به نظر می‌رسد:

class Time:
    """Represents a time of day."""

سرآیند نشان می‌دهد کلاس جدید Time نام دارد. بدنه یک docstring است که توضیح می‌دهد کلاس برای چیست. تعریف یک کلاس یک شیء کلاس (class object) می‌سازد.

شیء کلاس مثل کارخانه‌ای برای ساختن شیءهاست. برای ساختن یک شیء Time، Time را مثل یک تابع فراخوانی می‌کنید.

lunch = Time()

نتیجه یک شیء جدید است که نوعش __main__.Time است، که __main__ نام ماژولی است که Time در آن تعریف شده.

type(lunch)
__main__.Time

وقتی یک شیء را چاپ می‌کنید، پایتون به شما می‌گوید از چه نوعی است و کجای حافظه ذخیره شده (پیشوند 0x یعنی عدد بعدی در مبنای شانزده است).

print(lunch)
<__main__.Time object at 0x7f3e502dfa10>

ساختن یک شیء جدید نمونه‌سازی (instantiation) نامیده می‌شود و شیء یک نمونه (instance) از کلاس است.

۱۴.۲. صفت‌ها (Attributes)

یک شیء می‌تواند متغیرهایی داشته باشد که صفت (attribute) نامیده می‌شوند (در انگلیسی تأکید روی هجای اول تلفظ می‌شود، مثل “AT-trib-ute”، نه هجای دوم، مثل “a-TRIB-ute”). می‌توانیم صفت‌ها را با نماد نقطه بسازیم.

lunch.hour = 11
lunch.minute = 59
lunch.second = 1

این مثال صفت‌هایی به نام hour، minute و second می‌سازد که ساعت، دقیقه و ثانیه‌ی زمان 11:59:01 را دارند، که تا جایی که به من مربوط است، وقت ناهار است.

نمودار زیر وضعیت lunch و صفت‌هایش را بعد از این نسبت‌دهی‌ها نشان می‌دهد.

(در کتاب اصلی اینجا نمودار رسم شده است: متغیر lunch که به یک شیء Time اشاره می‌کند؛ این شیء سه صفت hour با مقدار ۱۱، minute با مقدار ۵۹ و second با مقدار ۱ دارد.)

متغیر lunch به یک شیء Time اشاره می‌کند که سه صفت دارد. هر صفت به یک عدد صحیح اشاره می‌کند. نمودار وضعیتی مثل این – که یک شیء و صفت‌هایش را نشان می‌دهد – نمودار شیء (object diagram) نامیده می‌شود.

می‌توانید مقدار یک صفت را با عملگر نقطه بخوانید.

lunch.hour
11

می‌توانید یک صفت را به‌عنوان بخشی از هر عبارتی به کار ببرید.

total_minutes = lunch.hour * 60 + lunch.minute
total_minutes
719

و می‌توانید عملگر نقطه را در یک عبارت داخل f-string به کار ببرید.

f'{lunch.hour}:{lunch.minute}:{lunch.second}'
'11:59:1'

اما دقت کنید که مثال قبل در قالب استاندارد نیست. برای درست کردنش، باید صفت‌های minute و second را با صفر پیشرو چاپ کنیم. می‌توانیم این کار را با گسترش عبارت‌های داخل آکولاد با یک مشخص‌کننده‌ی قالب (format specifier) انجام دهیم. در مثال زیر، مشخص‌کننده‌های قالب نشان می‌دهند minute و second باید با دست‌کم دو رقم و در صورت لزوم با یک صفر پیشرو نمایش داده شوند.

f'{lunch.hour}:{lunch.minute:02d}:{lunch.second:02d}'
'11:59:01'

از این f-string برای نوشتن تابعی استفاده می‌کنیم که مقدار یک شیء Time را نمایش می‌دهد. می‌توانید یک شیء را به روش معمول به‌عنوان آرگومان بدهید. برای مثال، تابع زیر یک شیء Time را به‌عنوان آرگومان می‌گیرد.

def print_time(time):
    s = f'{time.hour:02d}:{time.minute:02d}:{time.second:02d}'
    print(s)

وقتی آن را فراخوانی می‌کنیم، می‌توانیم lunch را به‌عنوان آرگومان بدهیم.

print_time(lunch)
11:59:01

۱۴.۳. شیءها به‌عنوان مقدار بازگشتی

توابع می‌توانند شیء برگردانند. برای مثال، make_time پارامترهایی به نام hour، minute و second می‌گیرد، آن‌ها را به‌عنوان صفت در یک شیء Time ذخیره می‌کند و شیء جدید را برمی‌گرداند.

def make_time(hour, minute, second):
    time = Time()
    time.hour = hour
    time.minute = minute
    time.second = second
    return time

شاید تعجب‌آور باشد که پارامترها همان نام صفت‌ها را دارند، اما این روش رایجی برای نوشتن چنین تابعی است. این‌طور از make_time برای ساختن یک شیء Time استفاده می‌کنیم.

time = make_time(11, 59, 1)
print_time(time)
11:59:01

۱۴.۴. شیءها تغییرپذیرند

فرض کنید به سانس یک فیلم می‌روید که ساعت 9:20 PM شروع می‌شود و 92 دقیقه، یعنی یک ساعت و 32 دقیقه، طول می‌کشد. فیلم چه ساعتی تمام می‌شود؟

اول شیء Time‌ای می‌سازیم که زمان شروع را نشان می‌دهد.

start = make_time(9, 20, 0)
print_time(start)
09:20:00

برای پیدا کردن زمان پایان، می‌توانیم صفت‌های شیء Time را با اضافه کردن مدت فیلم تغییر دهیم.

start.hour += 1
start.minute += 32
print_time(start)
10:52:00

فیلم ساعت 10:52 PM تمام می‌شود.

این محاسبه را در یک تابع کپسوله می‌کنیم و آن را تعمیم می‌دهیم تا مدت فیلم را در سه پارامتر hours، minutes و seconds بگیرد.

def increment_time(time, hours, minutes, seconds):
    time.hour += hours
    time.minute += minutes
    time.second += seconds

این مثالی است که اثرش را نشان می‌دهد.

start = make_time(9, 20, 0)
increment_time(start, 1, 32, 0)
print_time(start)
10:52:00

نمودار پشته‌ی زیر وضعیت برنامه را درست قبل از اینکه increment_time شیء را تغییر دهد نشان می‌دهد.

(در کتاب اصلی اینجا نمودار رسم شده است: قاب __main__ با متغیر start و قاب increment_time با پارامترهای time، hours با مقدار ۱، minutes با مقدار ۳۲ و seconds با مقدار ۰؛ هر دوی start و time به یک شیء Time با صفت‌های ۹، ۲۰ و ۰ اشاره می‌کنند.)

داخل تابع، time نام مستعار start است، پس وقتی time تغییر می‌کند، start هم تغییر می‌کند.

این تابع کار می‌کند، اما بعد از اجرایش، متغیری به نام start برایمان می‌ماند که به شیئی اشاره می‌کند که زمان پایان را نشان می‌دهد، و دیگر شیئی که زمان شروع را نشان دهد نداریم. بهتر است start را بدون تغییر بگذاریم و شیء جدیدی برای نشان دادن زمان پایان بسازیم. می‌توانیم این کار را با کپی کردن start و تغییر کپی انجام دهیم.

۱۴.۵. کپی کردن

ماژول copy تابعی به نام copy دارد که می‌تواند هر شیئی را تکثیر کند. می‌توانیم آن را این‌طور وارد کنیم.

from copy import copy

برای دیدن اینکه چطور کار می‌کند، با یک شیء Time جدید که زمان شروع فیلم را نشان می‌دهد شروع می‌کنیم.

start = make_time(9, 20, 0)

و یک کپی می‌سازیم.

end = copy(start)

حالا start و end داده‌ی یکسانی دارند.

print_time(start)
print_time(end)
09:20:00
09:20:00

اما عملگر is تأیید می‌کند که آن‌ها یک شیء نیستند.

start is end
False

ببینیم عملگر == چه می‌کند.

start == end
False

شاید انتظار داشته باشید == مقدار True بدهد چون شیءها داده‌ی یکسانی دارند. اما برای کلاس‌های تعریف‌شده توسط برنامه‌نویس، رفتار پیش‌فرض عملگر == همان رفتار عملگر is است – یکسانی (identity) را بررسی می‌کند، نه هم‌ارزی (equivalence) را.

۱۴.۶. توابع خالص

می‌توانیم از copy برای نوشتن توابع خالصی استفاده کنیم که پارامترهایشان را تغییر نمی‌دهند. برای مثال، این تابعی است که یک شیء Time و یک مدت‌زمان به ساعت، دقیقه و ثانیه می‌گیرد. از شیء اصلی کپی می‌گیرد، با increment_time کپی را تغییر می‌دهد و آن را برمی‌گرداند.

def add_time(time, hours, minutes, seconds):
    total = copy(time)
    increment_time(total, hours, minutes, seconds)
    return total

این‌طور از آن استفاده می‌کنیم.

end = add_time(start, 1, 32, 0)
print_time(end)
10:52:00

مقدار بازگشتی شیء جدیدی است که زمان پایان فیلم را نشان می‌دهد. و می‌توانیم تأیید کنیم که start بدون تغییر مانده است.

print_time(start)
09:20:00

add_time یک تابع خالص (pure function) است چون هیچ‌یک از شیءهایی را که به‌عنوان آرگومان به آن داده شده تغییر نمی‌دهد و تنها اثرش برگرداندن یک مقدار است.

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

به‌طور کلی، پیشنهاد می‌کنم هر جا معقول است توابع خالص بنویسید و فقط اگر مزیت قانع‌کننده‌ای هست به توابع ناخالص متوسل شوید. این رویکرد سبک برنامه‌نویسی تابعی (functional programming style) نامیده می‌شود.

۱۴.۷. نمونه‌ی اولیه و وصله (Prototype and patch)

در مثال قبل، increment_time و add_time به نظر کار می‌کنند، اما اگر مثال دیگری را امتحان کنیم، می‌بینیم که کاملاً درست نیستند.

فرض کنید به سالن می‌رسید و متوجه می‌شوید فیلم ساعت 9:40 شروع می‌شود، نه 9:20. این چیزی است که هنگام محاسبه‌ی زمان پایان به‌روزشده پیش می‌آید.

start = make_time(9, 40, 0)
end = add_time(start, 1, 32, 0)
print_time(end)
10:72:00

نتیجه زمان معتبری نیست. مشکل این است که increment_time حالت‌هایی را که تعداد ثانیه‌ها یا دقیقه‌ها بیش از 60 می‌شود مدیریت نمی‌کند.

این نسخه‌ی بهبودیافته‌ای است که بررسی می‌کند آیا second از 60 بیشتر یا مساوی آن است – اگر بله، minute را افزایش می‌دهد – بعد بررسی می‌کند آیا minute از 60 بیشتر یا مساوی آن است – اگر بله، hour را افزایش می‌دهد.

def increment_time(time, hours, minutes, seconds):
    time.hour += hours
    time.minute += minutes
    time.second += seconds

    if time.second >= 60:
        time.second -= 60
        time.minute += 1

    if time.minute >= 60:
        time.minute -= 60
        time.hour += 1

درست کردن increment_time add_time را هم که از آن استفاده می‌کند درست می‌کند. پس حالا مثال قبل درست کار می‌کند.

end = add_time(start, 1, 32, 0)
print_time(end)
11:12:00

اما این تابع هنوز درست نیست، چون آرگومان‌ها ممکن است بزرگ‌تر از 60 باشند. برای مثال، فرض کنید مدت اجرا به‌جای 1 ساعت و 32 دقیقه، 92 دقیقه داده شود. ممکن است add_time را این‌طور فراخوانی کنیم.

end = add_time(start, 0, 92, 0)
print_time(end)
10:72:00

نتیجه زمان معتبری نیست. پس رویکرد متفاوتی را با استفاده از تابع divmod امتحان می‌کنیم. از start کپی می‌گیریم و با افزایش صفت minute آن را تغییر می‌دهیم.

end = copy(start)
end.minute = start.minute + 92
end.minute
132

حالا minute برابر 132 است، که 2 ساعت و 12 دقیقه است. می‌توانیم از divmod برای تقسیم بر 60 و برگرداندن تعداد ساعت‌های کامل و تعداد دقیقه‌های باقی‌مانده استفاده کنیم.

carry, end.minute = divmod(end.minute, 60)
carry, end.minute
(2, 12)

حالا minute درست است و می‌توانیم ساعت‌ها را به hour اضافه کنیم.

end.hour += carry
print_time(end)
11:12:00

نتیجه زمان معتبری است. می‌توانیم همین کار را با hour و second هم بکنیم و کل فرایند را در یک تابع کپسوله کنیم.

def increment_time(time, hours, minutes, seconds):
    time.hour += hours
    time.minute += minutes
    time.second += seconds
    
    carry, time.second = divmod(time.second, 60)
    carry, time.minute = divmod(time.minute + carry, 60)
    carry, time.hour = divmod(time.hour + carry, 24)

با این نسخه‌ی increment_time، add_time حتی وقتی آرگومان‌ها از 60 بیشتر باشند درست کار می‌کند.

end = add_time(start, 0, 90, 120)
print_time(end)
11:12:00

این بخش طرح توسعه‌ی برنامه‌ای را نشان می‌دهد که نمونه‌ی اولیه و وصله (prototype and patch) می‌نامم. با یک نمونه‌ی اولیه‌ی ساده شروع کردیم که برای مثال اول درست کار می‌کرد. بعد آن را با مثال‌های دشوارتر آزمایش کردیم – وقتی خطایی پیدا کردیم، برنامه را برای رفع آن تغییر دادیم، مثل وصله زدن به لاستیکی که پنچر شده است.

این رویکرد می‌تواند مؤثر باشد، به‌ویژه اگر هنوز درک عمیقی از مسئله ندارید. اما اصلاح‌های تدریجی می‌توانند کدی بسازند که بی‌دلیل پیچیده است – چون با حالت‌های خاص زیادی سروکار دارد – و غیرقابل‌اعتماد است – چون دانستن اینکه همه‌ی خطاها را پیدا کرده‌اید دشوار است.

۱۴.۸. توسعه‌ی طراحی‌محور (Design-first development)

یک طرح جایگزین توسعه‌ی طراحی‌محور (design-first development) است، که قبل از ساختن نمونه‌ی اولیه برنامه‌ریزی بیشتری لازم دارد. در فرایند طراحی‌محور، گاهی بینشی سطح بالا درباره‌ی مسئله برنامه‌نویسی را خیلی آسان‌تر می‌کند.

در این مورد، بینش این است که می‌توانیم یک شیء Time را عددی سه‌رقمی در مبنای ۶۰ – که مبنای شصتگانی (sexagesimal) هم نامیده می‌شود – در نظر بگیریم. صفت second «ستون یکان»، صفت minute «ستون شصت‌ها» و صفت hour «ستون سی‌وشش‌صدها» است. وقتی increment_time را نوشتیم، در واقع در مبنای ۶۰ جمع می‌کردیم، به همین دلیل بود که باید از یک ستون به ستون بعدی رقم نقلی (carry) می‌بردیم.

این مشاهده رویکرد دیگری برای کل مسئله پیشنهاد می‌کند – می‌توانیم شیءهای Time را به عدد صحیح تبدیل کنیم و از این واقعیت که پایتون می‌داند چطور حساب عددهای صحیح را انجام دهد بهره ببریم.

این تابعی است که یک Time را به عدد صحیح تبدیل می‌کند.

def time_to_int(time):
    minutes = time.hour * 60 + time.minute
    seconds = minutes * 60 + time.second
    return seconds

نتیجه تعداد ثانیه‌ها از ابتدای روز است. برای مثال، 01:01:01 یعنی 1 ساعت، 1 دقیقه و 1 ثانیه از ابتدای روز، که مجموع 3600 ثانیه، 60 ثانیه و 1 ثانیه است.

time = make_time(1, 1, 1)
print_time(time)
time_to_int(time)
01:01:01
3661

و این تابعی است که در جهت مخالف می‌رود – تبدیل یک عدد صحیح به شیء Time – با استفاده از تابع divmod.

def int_to_time(seconds):
    minute, second = divmod(seconds, 60)
    hour, minute = divmod(minute, 60)
    return make_time(hour, minute, second)

می‌توانیم آن را با تبدیل مثال قبل به Time آزمایش کنیم.

time = int_to_time(3661)
print_time(time)
01:01:01

با استفاده از این توابع، می‌توانیم نسخه‌ی مختصرتری از add_time بنویسیم.

def add_time(time, hours, minutes, seconds):
    duration = make_time(hours, minutes, seconds)
    seconds = time_to_int(time) + time_to_int(duration)
    return int_to_time(seconds)

خط اول آرگومان‌ها را به یک شیء Time به نام duration تبدیل می‌کند. خط دوم time و duration را به ثانیه تبدیل می‌کند و جمعشان می‌زند. خط سوم مجموع را به شیء Time تبدیل می‌کند و برمی‌گرداند.

این‌طور کار می‌کند.

start = make_time(9, 40, 0)
end = add_time(start, 1, 32, 0)
print_time(end)
11:12:00

از بعضی جهات، تبدیل از مبنای ۶۰ به مبنای ۱۰ و برگشت دشوارتر از سروکار داشتن مستقیم با زمان‌هاست. تبدیل مبنا انتزاعی‌تر است؛ شهود ما برای کار با مقدارهای زمان بهتر است.

اما اگر بینش لازم را داشته باشیم که زمان‌ها را مثل عددهای مبنای ۶۰ در نظر بگیریم – و برای نوشتن توابع تبدیل time_to_int و int_to_time تلاش کنیم – برنامه‌ای می‌گیریم که کوتاه‌تر، خواناتر، اشکال‌زدایی‌اش آسان‌تر و قابل‌اعتمادتر است.

افزودن ویژگی‌ها در آینده هم آسان‌تر است. برای مثال، تصور کنید دو شیء Time را از هم کم کنید تا مدت بین آن‌ها را پیدا کنید. رویکرد ساده‌لوحانه پیاده‌سازی تفریق با قرض گرفتن است. استفاده از توابع تبدیل آسان‌تر و با احتمال بیشتر درست است.

از قضا، گاهی دشوارتر یا کلی‌تر کردن یک مسئله آن را آسان‌تر می‌کند، چون حالت‌های خاص کمتر و فرصت خطا کمتر می‌شود.

۱۴.۹. اشکال‌زدایی

پایتون چند تابع داخلی دارد که برای آزمودن و اشکال‌زدایی برنامه‌هایی که با شیءها کار می‌کنند مفیدند. برای مثال، اگر مطمئن نیستید یک شیء از چه نوعی است، می‌توانید بپرسید.

type(start)
__main__.Time

همچنین می‌توانید از isinstance برای بررسی اینکه آیا یک شیء نمونه‌ای از یک کلاس مشخص است استفاده کنید.

isinstance(end, Time)
True

اگر مطمئن نیستید یک شیء صفت مشخصی دارد یا نه، می‌توانید از تابع داخلی hasattr استفاده کنید.

hasattr(start, 'hour')
True

برای گرفتن همه‌ی صفت‌ها و مقدارهایشان در یک دیکشنری، می‌توانید از vars استفاده کنید.

vars(start)
{'hour': 9, 'minute': 40, 'second': 0}

ماژول structshape، که در فصل ۱۱ دیدیم، با نوع‌های تعریف‌شده توسط برنامه‌نویس هم کار می‌کند.

download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/structshape.py');
from structshape import structshape

t = start, end
structshape(t)
'tuple of 2 Time'

۱۴.۱۰. واژه‌نامه

برنامه‌نویسی شیءگرا (object-oriented programming): سبکی از برنامه‌نویسی که از شیءها برای سازمان‌دهی کد و داده استفاده می‌کند.

کلاس (class): نوع تعریف‌شده توسط برنامه‌نویس. تعریف کلاس یک شیء کلاس جدید می‌سازد.

شیء کلاس (class object): شیئی که یک کلاس را نشان می‌دهد – نتیجه‌ی تعریف کلاس است.

نمونه‌سازی (instantiation): فرایند ساختن شیئی که به یک کلاس تعلق دارد.

نمونه (instance): شیئی که به یک کلاس تعلق دارد.

صفت (attribute): متغیری وابسته به یک شیء، که متغیر نمونه (instance variable) هم نامیده می‌شود.

نمودار شیء (object diagram): نمایش گرافیکی یک شیء، صفت‌هایش و مقدارهای آن‌ها.

مشخص‌کننده‌ی قالب (format specifier): در f-string، تعیین می‌کند مقدار چگونه به رشته تبدیل شود.

تابع خالص (pure function): تابعی که پارامترهایش را تغییر نمی‌دهد و اثری جز برگرداندن مقدار ندارد.

سبک برنامه‌نویسی تابعی (functional programming style): شیوه‌ای از برنامه‌نویسی که تا حد ممکن از توابع خالص استفاده می‌کند.

نمونه‌ی اولیه و وصله (prototype and patch): شیوه‌ای برای توسعه‌ی برنامه با شروع از یک پیش‌نویس خام و افزودن تدریجی ویژگی‌ها و رفع باگ‌ها.

توسعه‌ی طراحی‌محور (design-first development): شیوه‌ای برای توسعه‌ی برنامه با برنامه‌ریزی دقیق‌تر از نمونه‌ی اولیه و وصله.

۱۴.۱۱. تمرین‌ها

# This cell tells Jupyter to provide detailed debugging information
# when a runtime error occurs. Run it before working on the exercises.

%xmode Verbose

۱۴.۱۱.۱. از یک دستیار مجازی بپرسید

در این فصل واژگان جدید زیادی هست. گفت‌وگو با یک دستیار مجازی می‌تواند درکتان را مستحکم‌تر کند. این پرسش‌ها را در نظر بگیرید:

  • “What is the difference between a class and a type?” (تفاوت کلاس و نوع چیست؟)

  • “What is the difference between an object and an instance?” (تفاوت شیء و نمونه چیست؟)

  • “What is the difference between a variable and an attribute?” (تفاوت متغیر و صفت چیست؟)

  • “What are the pros and cons of pure functions compared to impure functions?” (مزایا و معایب توابع خالص در مقایسه با توابع ناخالص چیست؟)

چون تازه با برنامه‌نویسی شیءگرا شروع کرده‌ایم، کد این فصل idiomatic نیست – از نوعی نیست که برنامه‌نویسان باتجربه می‌نویسند. اگر برای تمرین‌ها از دستیار مجازی کمک بگیرید، احتمالاً امکاناتی می‌بینید که هنوز پوشش نداده‌ایم. به‌طور خاص، احتمالاً متدی به نام __init__ را می‌بینید که برای مقداردهی اولیه‌ی صفت‌های یک نمونه به کار می‌رود.

اگر این امکانات برایتان معنا دارد، از آن‌ها استفاده کنید. اما اگر نه، صبور باشید – به زودی به آن‌ها می‌رسیم. در این بین، ببینید می‌توانید تمرین‌های زیر را فقط با امکاناتی که تا الان پوشش داده‌ایم حل کنید.

همچنین در این فصل یک نمونه از مشخص‌کننده‌ی قالب را دیدیم. برای اطلاعات بیشتر بپرسید: “What format specifiers can be used in a Python f-string?” (چه مشخص‌کننده‌های قالبی را می‌توان در f-string پایتون به کار برد؟)

۱۴.۱۱.۲. تمرین

تابعی به نام subtract_time بنویسید که دو شیء Time بگیرد و فاصله‌ی بین آن‌ها را بر حسب ثانیه برگرداند – با فرض اینکه دو زمان در یک روز هستند.

این یک طرح کلی از تابع برای شروع است.

def subtract_time(t1, t2):
    """Compute the difference between two times in seconds.
    
    >>> subtract_time(make_time(3, 2, 1), make_time(3, 2, 0))
    1
    >>> subtract_time(make_time(3, 2, 1), make_time(3, 0, 0))
    121
    >>> subtract_time(make_time(11, 12, 0), make_time(9, 40, 0))
    5520
    """
    return None
# Solution goes here

می‌توانید از doctest برای آزمودن تابع‌تان استفاده کنید.

from doctest import run_docstring_examples

def run_doctests(func):
    run_docstring_examples(func, globals(), name=func.__name__)

run_doctests(subtract_time)

۱۴.۱۱.۳. تمرین

تابعی به نام is_after بنویسید که دو شیء Time بگیرد و اگر زمان اول دیرتر از زمان دوم در روز است True و در غیر این صورت False برگرداند.

این یک طرح کلی از تابع برای شروع است.

def is_after(t1, t2):
    """Checks whether `t1` is after `t2`.
    
    >>> is_after(make_time(3, 2, 1), make_time(3, 2, 0))
    True
    >>> is_after(make_time(3, 2, 1), make_time(3, 2, 1))
    False
    >>> is_after(make_time(11, 12, 0), make_time(9, 40, 0))
    True
    """
    return None
# Solution goes here

می‌توانید از doctest برای آزمودن تابع‌تان استفاده کنید.

run_doctests(is_after)

۱۴.۱۱.۴. تمرین

این تعریفی برای کلاس Date است که یک تاریخ را نشان می‌دهد – یعنی سال، ماه و روز ماه.

class Date:
    """Represents a year, month, and day"""
  1. تابعی به نام make_date بنویسید که year، month و day را به‌عنوان پارامتر بگیرد، یک شیء Date بسازد، پارامترها را به صفت‌ها نسبت دهد و شیء جدید را برگرداند. شیئی بسازید که ۲۲ ژوئن ۱۹۳۳ را نشان می‌دهد.

  2. تابعی به نام print_date بنویسید که یک شیء Date بگیرد، با f-string صفت‌ها را قالب‌بندی کند و نتیجه را چاپ کند. اگر آن را با Date‌ای که ساختید آزمایش کنید، نتیجه باید 1933-06-22 باشد.

  3. تابعی به نام is_after بنویسید که دو شیء Date را به‌عنوان پارامتر بگیرد و اگر اولی بعد از دومی می‌آید True برگرداند. شیء دومی بسازید که ۱۷ سپتامبر ۱۹۳۳ را نشان می‌دهد و بررسی کنید که آیا بعد از شیء اول می‌آید.

راهنمایی: ممکن است نوشتن تابعی به نام date_to_tuple مفید باشد که یک شیء Date بگیرد و چندتایی‌ای شامل صفت‌هایش به ترتیب سال، ماه، روز برگرداند.

می‌توانید از این طرح کلی تابع برای شروع استفاده کنید.

def make_date(year, month, day):
    return None
# Solution goes here

می‌توانید از این مثال‌ها برای آزمودن make_date استفاده کنید.

birthday1 = make_date(1933, 6, 22)
birthday2 = make_date(1933, 9, 17)

می‌توانید از این طرح کلی تابع برای شروع استفاده کنید.

def print_date(date):
    print('')
# Solution goes here

می‌توانید از این مثال برای آزمودن print_date استفاده کنید.

print_date(birthday1)

می‌توانید از این طرح کلی تابع برای شروع استفاده کنید.

def is_after(date1, date2):
    return None
# Solution goes here
# Solution goes here

می‌توانید از این مثال‌ها برای آزمودن is_after استفاده کنید.

is_after(birthday1, birthday2)  # should be False
is_after(birthday2, birthday1)  # should be True