מה זה dataclass
?
dataclass
— זהו דקורטור, שהוצג בפייתון 3.7, המייצר אוטומטית שיטות מיוחדות (כגון __init__, __repr__, __eq__ ואחרות) עבור מחלקות המשמשות בעיקר כקונטיינרים לנתונים. זה חוסך ממך את הצורך לכתוב הרבה קוד תבניתי.
למה להשתמש ב-dataclass
?
- קיצור קוד: במקום להגדיר ידנית שיטות __init__, __repr__, __eq__ וכו', אתה פשוט מצהיר על שדות הנתונים, ו-
dataclass
יעשה את כל השאר. - שיפור קריאות: מחלקות הופכות לתמציתיות ומובנות יותר, מכיוון שהן מתמקדות בנתונים ולא ביישום הטכני.
- הפחתת שגיאות: קוד שנוצר אוטומטית אמין יותר בדרך כלל מקוד שנכתב ידנית.
- האצת פיתוח: תוכל ליצור מחלקות לעבודה עם נתונים מהר יותר, מבלי לבזבז זמן על שגרה.
כיצד להשתמש ב-dataclass
?
ראשית, עליך לייבא את הדקורטור dataclass
מהמודול dataclasses
:
from dataclasses import dataclass
לאחר מכן, אתה מסמן את המחלקה בדקורטור @dataclass
, ומגדיר את שדות הנתונים כמשתני מחלקה רגילים עם הערות סוג:
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
בדוגמה זו, Point
— זוהי dataclass
, שיש לה שני שדות: x
ו-y
, שניהם מסוג שלם. dataclass
תיצור אוטומטית:
- בנאי __init__, המאפשר ליצור מופעים של המחלקה, לדוגמה Point(1, 2).
- __repr__, המחזיר ייצוג מחרוזתי של האובייקט, לדוגמה Point(x=1, y=2).
- __eq__, המאפשר להשוות אובייקטים, לדוגמה Point(1, 2) == Point(1, 2).
דוגמה לשימוש פשוט
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
# יצירת מופע של המחלקה
point1 = Point(1, 2)
point2 = Point(1, 2)
point3 = Point(3, 4)
# פלט
print(point1) # יוציא: Point(x=1, y=2)
print(point1 == point2) # יוציא: True
print(point1 == point3) # יוציא: False
אפשרויות dataclass
dataclass
מספקת מספר פרמטרים להתאמה אישית של ההתנהגות:
init
: אםTrue
(ברירת מחדל), נוצרת שיטת __init__. אםFalse
, שיטת __init__ לא נוצרת.repr
: אםTrue
(ברירת מחדל), נוצרת שיטת __repr__. אםFalse
, שיטת __repr__ לא נוצרת.eq
: אםTrue
(ברירת מחדל), נוצרת שיטת __eq__. אםFalse
, שיטת __eq__ לא נוצרת.order
: אםTrue
, נוצרות שיטות השוואה (__lt__, __le__, __gt__, __ge__). ברירת המחדל היאFalse
.unsafe_hash
: אםFalse
(ברירת מחדל), שיטת __hash__ לא נוצרת. אםTrue
, שיטת __hash__ תיווצר, ו-dataclass
תהפוך לניתנת לגיבוב (hashable).frozen
: אםTrue
, מופעי המחלקה יהיו בלתי ניתנים לשינוי (לקריאה בלבד). ברירת המחדל היאFalse
.
דוגמאות לשימוש בפרמטרים
-
השבתת שיטת __repr__ והפיכת המחלקה לבלתי ניתנת לשינוי
from dataclasses import dataclass @dataclass(repr=False, frozen=True) class Point: x: int y: int # יצירת מופע של המחלקה point1 = Point(1, 2) # פלט print(point1) # יוציא: <__main__.Point object at 0x...> (מכיוון ש-__repr__ לא מוגדר) # שינוי מופע יגרום לשגיאה try: point1.x = 10 except Exception as e: print(e) # יוציא: cannot assign to field 'x'
-
הגדרת סדר, הוספת שיטת hash והפיכת המחלקה לבלתי ניתנת לשינוי
from dataclasses import dataclass @dataclass(order=True, unsafe_hash=True, frozen=True) class Point: x: int y: int # יצירת מופע של המחלקה point1 = Point(1, 2) point2 = Point(3, 4) point3 = Point(1, 2) # פלט print(point1 < point2) # יוציא: True print(point1 == point3) # יוציא: True # כעת ניתן להשתמש במחלקה כמפתח מילון my_dict = {point1: "first", point2: "second"} print(my_dict) # יוציא: {Point(x=1, y=2): 'first', Point(x=3, y=4): 'second'}
ערכי ברירת מחדל
תוכל להגדיר ערכי ברירת מחדל לשדות:
from dataclasses import dataclass
@dataclass
class Point:
x: int = 0
y: int = 0
# יצירת מופע של המחלקה
point1 = Point()
point2 = Point(1, 2)
# פלט
print(point1) # יוציא: Point(x=0, y=0)
print(point2) # יוציא: Point(x=1, y=2)
בעת יצירת מופע של המחלקה, אם לא הועברו ערכים, ייעשה שימוש בערך ברירת המחדל.
שימוש ב-dataclass
עם טיפוסים ניתנים לשינוי
היזהר בעת שימוש בטיפוסי נתונים ניתנים לשינוי (רשימות, מילונים) כערכי ברירת מחדל. הם ייווצרו רק פעם אחת וישמשו את כל מופעי המחלקה:
from dataclasses import dataclass
from typing import List
@dataclass
class BadExample:
items: List[int] = []
bad1 = BadExample()
bad2 = BadExample()
bad1.items.append(1)
print(bad1.items) # יוציא: [1]
print(bad2.items) # יוציא: [1]
כדי למנוע זאת, השתמש ב-dataclasses.field
וב-default_factory
:
from dataclasses import dataclass, field
from typing import List
@dataclass
class GoodExample:
items: List[int] = field(default_factory=list)
good1 = GoodExample()
good2 = GoodExample()
good1.items.append(1)
print(good1.items) # יוציא: [1]
print(good2.items) # יוציא: []
דיאגרמה
הנה דיאגרמה המציגה את המושגים העיקריים של dataclass
:
classDiagram
class DataClass {
<>
+init: bool = True
+repr: bool = True
+eq: bool = True
+order: bool = False
+unsafe_hash: bool = False
+frozen: bool = False
--
+__init__(...)
+__repr__()
+__eq__(...)
+__lt__(...)
+__le__(...)
+__gt__(...)
+__ge__(...)
+__hash__()
}
class UserDefinedClass {
<>
+field1: type
+field2: type
+field3: type = defaultValue
+field4: type = field(default_factory=...)
}
DataClass <|-- UserDefinedClass
dict(), __dir__() ותכונות נוספות של dataclass
- dict() לא עובד ישירות עם מופעי
dataclass
. כדי להמיר למילון, עליך להשתמש בשיטות ידניות או בספריות צד שלישי. - __dir__() מחזיר רשימה של כל התכונות והשיטות של האובייקט, כולל שיטות ושדות שנוצרו על ידי
dataclass
. - __dataclass_fields__ ו-__dataclass_params__ מספקים מטא-נתונים על שדות ופרמטרים של
dataclass
.
1. dict() בהקשר של dataclass
אין תמיכה אוטומטית ב-dict(). עליך להשתמש בשיטה ידנית או ב-__dict__:
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
person = Person("Alice", 30)
# המרה ידנית
person_dict = {field.name: getattr(person, field.name) for field in dataclasses.fields(Person)}
print(person_dict) # {'name': 'Alice', 'age': 30}
# או דרך __dict__
person_dict = person.__dict__
print(person_dict) # {'name': 'Alice', 'age': 30}
2. __dir__() ב-dataclass
השיטה __dir__() מחזירה רשימה של כל התכונות והשיטות של האובייקט:
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
def distance(self):
return (self.x**2 + self.y**2)**0.5
point = Point(1, 2)
print(dir(point))
# ['__class__', '__dataclass_fields__', '__dataclass_params__', ..., 'distance', 'x', 'y']
3. תכונות נוספות של dataclass
- __dataclass_fields__: מילון עם מידע על שדות ה-
dataclass
. - __dataclass_params__: מידע על הפרמטרים (כמו frozen=True).
from dataclasses import dataclass, fields
@dataclass
class Point:
x: int = 0
y: int = 0
print(Point.__dataclass_fields__)
print(Point.__dataclass_params__)