Skip to content

מה זה dataclass?

  • hypo69 

מה זה dataclass?

dataclass — זהו דקורטור, שהוצג בפייתון 3.7, המייצר אוטומטית שיטות מיוחדות (כגון __init__, __repr__, __eq__ ואחרות) עבור מחלקות המשמשות בעיקר כקונטיינרים לנתונים. זה חוסך ממך את הצורך לכתוב הרבה קוד תבניתי.

למה להשתמש ב-dataclass?

  1. קיצור קוד: במקום להגדיר ידנית שיטות __init__, __repr__, __eq__ וכו', אתה פשוט מצהיר על שדות הנתונים, ו-dataclass יעשה את כל השאר.
  2. שיפור קריאות: מחלקות הופכות לתמציתיות ומובנות יותר, מכיוון שהן מתמקדות בנתונים ולא ביישום הטכני.
  3. הפחתת שגיאות: קוד שנוצר אוטומטית אמין יותר בדרך כלל מקוד שנכתב ידנית.
  4. האצת פיתוח: תוכל ליצור מחלקות לעבודה עם נתונים מהר יותר, מבלי לבזבז זמן על שגרה.

כיצד להשתמש ב-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.

דוגמאות לשימוש בפרמטרים

  1. השבתת שיטת __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'
        
  2. הגדרת סדר, הוספת שיטת 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__)

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *