• בלוג
  • איך לשמור שלא יוסיפו לכם Attribute בטעות למחלקה בפייתון

איך לשמור שלא יוסיפו לכם Attribute בטעות למחלקה בפייתון

18/11/2020

פוסט זה כולל טיפ קצר לעבודה יעילה עם Python. אם אתם רוצים ללמוד פייתון יותר לעומק אני ממליץ על קורס Python כאן באתר.
הקורס כולל עשרות שיעורי וידאו והמון תרגול מעשי וילמד אתכם Python בצורה מקצועית מההתחלה ועד הנושאים המתקדמים.

בפוסט שעלה לא מזמן ב Python Tips הציע יהונתן להגדיר slots כדי לבנות אוביקטים קטנים יותר. תוצאת לוואי מעניינת של הפיצ'ר היא שהוא מונע מאנשים להוסיף בטעות Attribute לאוביקט.

אם אתם משתמשים ב Type Hints (מה זה אם!? ברור שאתם משתמשים ב Type Hints), תשמחו לשמוע שיש דרך פשוטה יותר לקבל את אותה תוצאת לוואי בצורה שגם מסתדרת טוב עם ירושה, גם מאפשרת וידוא טיפוסים וגם כוללת כמה פינוקים ברמת הקוד. אני מדבר על הדקורטור dataclass. בואו נראה איך הוא עובד.

נניח שיש לנו מחלקה בשם Person ולכל אוביקט מסוג Person אנחנו רוצים להגדיר את השדות:

  1. שדה id מסוג int.
  2. שדה name מסוג str.
  3. שדה friends מסוג מערך של Person.

הקוד באמצעות Type Hints יראה כך:

from typing import List

class Person:
    id: int
    name: str
    friends: List['Person']

ורק עד לפה אנחנו כבר מקבלים המון עזרה מ mypy כשמנסים לעשות שטויות שאסור עם Person:

p = Person()
x = Person()
y = Person()

# Works great
p.id = 0
p.name = 'test'
p.friends = [x, y]

# Fails: 10 is not a Person
p.friends.append(10)

# Fails: foo is not a valid attribute
p.foo = 'bar'

הפעלה של mypy על הקוד נותנת:

a.py:22: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "Person"
a.py:25: error: "Person" has no attribute "foo"

ואפשר היה לסיים פה את הפוסט אבל מה שבאמת משדרג את הגישה זה הדקורייטור dataclass. ברגע שאוסיף אותו אני מקבל:

  1. פונקציית __init__ אוטומטית שמקבלת את כל הפרמטרים שהגדרתי למחלקה

  2. פונקציית __repr__ אוטומטית שמחזירה ייצוג ידידותי של האוביקט

  3. פונקציית __eq__ שבודקת שיוויון לפי כל אחד מהשדות

  4. פונקציה בשם dataclasses.asdict שלוקחת כל Data Class ומחזירה dict עם כל המאפיינים שלו.

סך הכל אחרי המרה לשימוש ב dataclass המחלקה שלי נראית כך:

from typing import List
from dataclasses import dataclass, field
import dataclasses


@dataclass
class Person:
    id: int
    name: str
    friends: List['Person'] = field(default_factory=list)


p = Person(0, 'foo')
x = Person(1, 'bar')
y = Person(2, 'buz')

print(dataclasses.asdict(p))

בזכות פונקציית __init__ שנוצרה אוטומטית אני יכול ליצור אוביקטים של Person ולהעביר אליהם את כל הפרמטרים עבורם לא מוגדר ערך ברירת מחדל (ויש Type Hint שמוודא את זה), ואני יכול לקרוא ל asdict כדי לקבל את id, name ו friends בתוך dict.

למידע נוסף על dataclasses שווה לקרוא את דף התיעוד בקישור: https://docs.python.org/3/library/dataclasses.html