Faragiri

همهٔ زبان‌ها

آموزش کلاس و شیبرنامه‌نویسی شی‌گرا

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

۴بخش
۳دقیقه خواندن
۴عمق

دو جای نوشتن، یک نام

صفتی که در بدنه کلاس است و صفتی که روی self است

هر سطر این جدول برای هر دو معنی دارد، و سطر آخر است که یکی را خطرناک می‌کند.

صفت کلاسیک مقدار، مشترک میان همه نمونه‌ها
صفت نمونهیک مقدار جدا برای هر نمونه
کجا نوشته می‌شوددر بدنه کلاسروی self، معمولا در __init__
چند نسخه داردیکی، هر چند نمونه که باشدبه تعداد نمونه‌ها
با انتساب روی نمونهعوض نمی‌شودهمین است که ساخته می‌شود
در vars نمونهدیده نمی‌شوددیده می‌شود
اگر فهرست باشدappend همه را عوض می‌کندهر نمونه فهرست خودش را دارد

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

در بدنه یک کلاس چیزی که می‌نویسید یک مقدار است که همه نمونه‌ها آن را می‌بینند. چیزی که داخل __init__ روی self می‌گذارید یک مقدار جدا برای هر نمونه است. این کل تفاوت است و همه رفتارهای بعدی از آن می‌آید.

  • class Peyk:
  • shahr = "Karaj" یک مقدار، مشترک میان همه پیک‌ها
  • def __init__(self, kod): self.kod = kod یک مقدار به‌ازای هر پیک

با دو نمونه، Peyk.shahr = "Qazvin" هر دو را عوض می‌کند، چون هیچ‌کدام نسخه خودشان را ندارند و هر دو همان یک مقدار را می‌خوانند. حالا a.shahr = "Hashtgerd" بنویسید: فقط همان یکی عوض می‌شود و Peyk.shahr دست‌نخورده می‌ماند. آن انتساب کلاس را عوض نکرد، یک صفت نمونه ساخت، و می‌شود دیدش: 'shahr' in a.__dict__ جواب می‌دهد True و برای نمونه دیگر False.

و همین‌جاست که فهرست مشترک آدم را می‌گیرد

یک فهرست خالی در بدنه کلاس، مثل tahvil = []، همان قاعده را دارد و همان یک شی است برای همه. اما این بار انتسابی در کار نیست: a.tahvil.append("S-901") صفت تازه‌ای نمی‌سازد، همان فهرست مشترک را عوض می‌کند. نتیجه‌اش این است که b.tahvil هم S-901 را دارد، برای پیکی که هیچ‌وقت آن سفارش را نگرفت. تفاوت میان خواندن و انتساب اینجا کاملا معکوس نتیجه می‌دهد.

قاعده‌ای که این را جمع می‌کند: هر چیزی که می‌شود در جا عوضش کرد، فهرست و دیکشنری و مجموعه، باید داخل __init__ ساخته شود. بدنه کلاس فقط جای مقدارهای تغییرناپذیر است، یعنی عدد و رشته و چندتایی. همین تله در مقدار پیش‌فرض یک تابع هم هست و دلیلش یکی است: ورودی، خروجی، و جایی که دامنه نام تمام می‌شود.

self یک کلمه کلیدی نیست

پایتون نمونه را به‌عنوان اولین آرگومان به متد پاس می‌دهد و نام آن آرگومان قرارداد است نه قانون. برای همین متدی که self نگیرد با پیامی درباره تعداد آرگومان‌ها می‌شکند و نه با پیامی درباره کلاس، و برای همین Peyk.tahvil_bede(a) و a.tahvil_bede() دقیقا یک کار می‌کنند. فهمیدن این یک چیز، نصف سردرگمی درباره متدها را تمام می‌کند.

جایی که کار گیر می‌کند، کلاسی است که فقط جای متغیر است

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

ابزار، و سالی که این حرف درست است

در سال ۲۰۲۶ dataclasses در کتابخانه استاندارد و رایگان است و جای نوشتن __init__ و __repr__ و مقایسه به دست را گرفته؛ نکته‌ای که مستقیما به این صفحه مربوط است این است که مقدار پیش‌فرض تغییرپذیر را رد می‌کند و به‌جای یک باگ خاموش، همان لحظه خطا می‌دهد و field(default_factory=list) را می‌خواهد. برای دیدن اینکه یک صفت روی نمونه است یا روی کلاس، vars(a) در خود مفسر کافی است و فقط صفت‌های نمونه را نشان می‌دهد.

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

کنار این

بالای این