Dataclasses — البرمجة الكائنية
Dataclasses
كثير من الأصناف هدفها الأساسي حمل البيانات: منتج، نقطة، طلب. كتابة __init__ و __repr__ و __eq__ لكل منها مملّ ومعرّض للأخطاء. @dataclass تولّدها تلقائياً من تعريف الحقول.
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
quantity: int = 0يولّد تلقائياً:
- __init__(self, name, price, quantity=0)
- __repr__: Product(name='قلم', price=1.5, quantity=0)
- __eq__: مقارنة الحقول.
أنواع الحقول (: str) مطلوبة لكي تتعرف dataclass على الحقل، لكن Python لا تتحقق منها عند التشغيل؛ هي توثيق وأداة لمحررات الكود.
القيم الافتراضية القابلة للتعديل
from dataclasses import field
@dataclass
class Order:
items: list = [] # ❌ ValueError
items: list = field(default_factory=list) # ✅ قائمة جديدة لكل كائنلو سُمح بـ [] لتشاركت كل الطلبات نفس القائمة. dataclass تمنع هذا الخطأ صراحة.
خيارات مفيدة
| الخيار | التأثير |
|---|---|
| @dataclass(frozen=True) | الكائن غير قابل للتعديل، ويصلح مفتاحاً في قاموس |
| @dataclass(order=True) | يولّد < و > للمقارنة والترتيب حسب الحقول بالترتيب |
| field(repr=False) | إخفاء حقل من التمثيل النصي (مثل كلمة مرور) |
__post_init__ للتحقق
def __post_init__(self):
if self.price < 0:
raise ValueError("السعر لا يمكن أن يكون سالباً")تُستدعى بعد __init__ المولّد مباشرة.
إضافة دوال
dataclass صنف عادي، يمكنك إضافة دوال وخصائص:
@property
def total(self):
return self.price * self.quantitydataclass أم namedtuple أم dict؟
| الأداة | متى |
|---|---|
| dict | بيانات مؤقتة بسيطة أو قادمة من JSON |
| namedtuple | سجل صغير ثابت خفيف |
| dataclass | بنية بيانات حقيقية في برنامجك، مع دوال أو تحقق |
تمرين
أنشئ dataclass باسم Book (العنوان، المؤلف، السنة، تقييمات كقائمة) مع خاصية محسوبة للتقييم المتوسط، وتحقق في __post_init__ أن السنة منطقية، ورتّب قائمة كتب حسب السنة.
from dataclasses import dataclass, field
from typing import List
# بدون dataclass (الطريقة القديمة)
class PersonOld:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def __repr__(self):
return f"Person(name='{self.name}', age={self.age})"
def __eq__(self, other):
return (self.name == other.name and
self.age == other.age and
self.email == other.email)
# مع dataclass (الطريقة الجديدة)
@dataclass
class Person:
name: str
age: int
email: str = "غير محدد" # قيمة افتراضية
# الاستخدام
p1 = Person("أحمد", 25)
p2 = Person("أحمد", 25)
print(p1) # Person(name='أحمد', age=25, email='غير محدد')
print(p1 == p2) # True
# مع قائمة (استخدم field)
@dataclass
class Student:
name: str
grade: int
subjects: List[str] = field(default_factory=list)
def add_subject(self, subject):
self.subjects.append(subject)
student = Student("محمد", 10)
student.add_subject("رياضيات")
student.add_subject("علوم")
print(student)
# Frozen (غير قابل للتغيير)
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(3.0, 4.0)
# p.x = 5 # FrozenInstanceError!
# مع الترتيب
@dataclass(order=True)
class Product:
name: str = field(compare=False) # لا يُستخدم في المقارنة
price: float
products = [
Product("قلم", 2.5),
Product("دفتر", 5.0),
Product("ممحاة", 1.0)
]
products.sort()
for p in products:
print(p)
# Post-init للمعالجة بعد الإنشاء
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False) # لا يُمرر في __init__
def __post_init__(self):
self.area = self.width * self.height
rect = Rectangle(4, 5)
print(rect.area) # 20.0اختبر فهمك