فصل ۷: ماژول‌ها، کتابخانه‌ی استاندارد و شیءگرایی

کلاس و شیء: __init__، self، متدها و property

داده و رفتار، کنار هم

تا این‌جا سفارش را با 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 بنویسید.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.