کلاسهایی که با پایتون هماهنگاند
وراثت
وراثت یعنی کلاس جدید، همهچیز کلاس والد را به ارث ببرد و فقط تفاوتها را تعریف کند. فرش ابریشمی همان فرش است، با یک ویژگی اضافه و قیمتگذاری متفاوت:
class SilkRug(Rug):
def __init__(self, code, width_cm, length_cm, price_per_sqm, raj: int):
super().__init__(code, width_cm, length_cm, price_per_sqm) # کار والد را انجام بده
self.raj = raj
def total_price(self) -> int:
base = super().total_price()
return round(base * 1.4) if self.raj >= 60 else base
s = SilkRug("KSH-777", 150, 225, 20_000_000, raj=70)
print(isinstance(s, Rug)) # True — فرش ابریشمی، فرش هم هست
وراثت را فقط برای رابطهی واقعی «... یک نوع ... است» به کار ببرید. برای «... یک ... دارد» (سفارش یک مشتری دارد) از ترکیب استفاده کنید: شیء مشتری را بهعنوان ویژگی در سفارش بگذارید. سلسلهمراتب عمیق وراثت، کد را شکننده میکند.
متدهای dunder
متدهایی که با دو زیرخط شروع و تمام میشوند (double underscore، «dunder») به کلاس شما اجازه میدهند با عملگرها و توابع داخلی پایتون کار کند:
class Rug:
# ... __init__ و بقیه مثل درس قبل
def __repr__(self) -> str: # برای برنامهنویس: دیباگ، لاگ، REPL
return f"Rug({self.code!r}, {self.width_cm}, {self.length_cm})"
def __str__(self) -> str: # برای کاربر: print و f-string
return f"فرش {self.code} — {self.area:g} متر مربع"
def __eq__(self, other) -> bool: # عملگر ==
if not isinstance(other, Rug):
return NotImplemented
return self.code == other.code
| متد | با چه چیزی فعال میشود |
|---|---|
__str__ | print(r)، str(r)، f-string |
__repr__ | REPL، نمایش داخل لیست، repr(r)، {r!r} |
__eq__ | == و in |
__lt__ | < و در نتیجه sorted |
__len__ | len(obj) و truthiness |
اگر فقط یکی را تعریف میکنید، __repr__ باشد؛ print در نبود __str__ از آن استفاده میکند.
dataclass: کلاس داده بدون کد تکراری
نوشتن __init__، __repr__ و __eq__ برای هر کلاس داده خستهکننده است. دکوراتور @dataclass همه را از روی فیلدهای annotateشده خودکار میسازد:
from dataclasses import dataclass, field, asdict
@dataclass
class Order:
code: str
customer: str
area: float
price: int
tags: list[str] = field(default_factory=list) # نه tags: list = []
status: str = "new"
def __post_init__(self):
if self.price <= 0:
raise ValueError("قیمت نامعتبر")
o = Order("KSH-101", "مریم کاشانی", 6, 57_000_000)
print(o) # Order(code='KSH-101', customer='مریم کاشانی', ..., status='new')
print(asdict(o)) # dict آماده برای json.dumps
print(o == Order("KSH-101", "مریم کاشانی", 6, 57_000_000)) # True
@dataclass(frozen=True, order=True)
class Size:
width: int
length: int # تغییرناپذیر، قابل مرتبسازی و قابل استفاده بهعنوان کلید dict
نکتههایی که کمتر کسی میداند
- تعریف
__eq__بدون__hash__، شیء را unhashable میکند: دیگر نمیتوانید آن را در set یا بهعنوان کلید dict بگذارید.frozen=Trueدر dataclass این مشکل را حل میکند. - برگرداندن
NotImplemented(نه False) در__eq__به پایتون اجازه میدهد مقایسه را از طرف دیگر امتحان کند؛ خود NotImplemented یک مقدار ویژه است، نه استثنا. field(default_factory=list)همان راهحل دام «پیشفرض mutable» در فصل ۵ است؛ dataclass اصلاً اجازهیtags: list = []را نمیدهد و ValueError میدهد.Order(**row)یک dict خواندهشده از JSON یا CSV را مستقیم به dataclass تبدیل میکند، به شرط اینکه کلیدها با نام فیلدها یکی باشند.@dataclass(slots=True)(3.10+) مصرف حافظهی هر شیء را کم میکند و غلط تایپی در نام ویژگی (o.custmer = ...) را به خطا تبدیل میکند.