داده و رفتار، کنار هم
تا اینجا سفارش را با dict نشان میدادیم و توابع جداگانه رویش کار میکردند. وقتی داده و رفتارِ مربوط به آن زیاد شود، کلاس آنها را یکجا جمع میکند. کلاس نقشه یا قالب است و شیء (instance) نمونهای ساختهشده از آن نقشه. «فرش» کلاس است؛ «فرش KSH-101 به ابعاد ۲×۳» یک شیء.
class Rug:
def __init__(self, code: str, width_cm: int, length_cm: int, price_per_sqm: int):
self.code = code
self.width_cm = width_cm
self.length_cm = length_cm
self.price_per_sqm = price_per_sqm
def area(self) -> float:
return self.width_cm * self.length_cm / 10_000
def total_price(self) -> int:
return round(self.area() * self.price_per_sqm)
r = Rug("KSH-101", 200, 300, 9_500_000)
print(r.code, r.area(), f"{r.total_price():,}") # KSH-101 6.0 57,000,000
__init__ و self
__init__ «مقداردهی اولیه» است و هنگام ساختن شیء (Rug(...)) خودکار صدا زده میشود. self خودِ شیء در حال ساخت یا استفاده است؛ پایتون آن را خودکار بهعنوان اولین آرگومان میفرستد. r.total_price() در واقع همان Rug.total_price(r) است. هر چیزی که با self.x = ... تعریف شود، ویژگی (attribute) آن شیء است و هر شیء نسخهی خودش را دارد.
property: ویژگی محاسباتی و اعتبارسنجی
مساحت در واقع یک ویژگی است، نه یک «عمل». با @property میتوانیم آن را بدون پرانتز بخوانیم و با setter جلوی مقدار نامعتبر را بگیریم:
class Rug:
def __init__(self, code, width_cm, length_cm, price_per_sqm):
self.code = code
self.width_cm = width_cm
self.length_cm = length_cm
self.price_per_sqm = price_per_sqm # از setter عبور میکند
@property
def area(self) -> float:
return self.width_cm * self.length_cm / 10_000
@property
def price_per_sqm(self) -> int:
return self._price_per_sqm
@price_per_sqm.setter
def price_per_sqm(self, value: int) -> None:
if value <= 0:
raise ValueError("قیمت باید مثبت باشد")
self._price_per_sqm = value
r = Rug("KSH-101", 200, 300, 9_500_000)
print(r.area) # 6.0 — بدون پرانتز
r.price_per_sqm = -5 # ValueError
زیبایی property این است که میتوانید اول ویژگی ساده بنویسید و بعدها، بدون تغییر حتی یک خط از کد استفادهکننده، اعتبارسنجی اضافه کنید. به همین دلیل در پایتون برخلاف جاوا getter و setter دستی (get_price()) نمینویسیم.
ویژگی کلاس در برابر ویژگی شیء
| ویژگی شیء | ویژگی کلاس | |
|---|---|---|
| تعریف | داخل متد با self.x = ... | مستقیم در بدنهی کلاس |
| اشتراک | هر شیء جدا | بین همهی شیءها مشترک |
| کاربرد | کد، ابعاد، قیمت | ثابتها: VAT = 0.10 |
نکتههایی که کمتر کسی میداند
- ویژگی کلاس تغییرپذیر (مثل
items = []در بدنهی کلاس) بین همهی شیءها مشترک است؛ افزودن به سبد یک مشتری، در سبد همه ظاهر میشود. لیست را در__init__بسازید. - زیرخط ابتدای نام (
_price_per_sqm) فقط قرارداد «دست نزنید» است و پایتون جلوی دسترسی را نمیگیرد؛ پایتون «private» واقعی ندارد. - property را بدون setter تعریف کنید تا فقطخواندنی شود:
r.area = 5خطای AttributeError میدهد. vars(r)دیکشنری ویژگیهای شیء را نشان میدهد؛ برای دیباگ سریع عالی است.- متدی که به self نیاز ندارد ولی منطقاً مال کلاس است را با
@staticmethodو سازندهی جایگزین (مثلRug.from_dict(d)) را با@classmethodبنویسید.