איך לשמור שלא יוסיפו לכם Attribute בטעות למחלקה בפייתון
פוסט זה כולל טיפ קצר לעבודה יעילה עם Python. אם אתם רוצים ללמוד פייתון יותר לעומק אני ממליץ על קורס Python כאן באתר.
הקורס כולל עשרות שיעורי וידאו והמון תרגול מעשי וילמד אתכם Python בצורה מקצועית מההתחלה ועד הנושאים המתקדמים.
בפוסט שעלה לא מזמן ב Python Tips הציע יהונתן להגדיר slots כדי לבנות אוביקטים קטנים יותר. תוצאת לוואי מעניינת של הפיצ'ר היא שהוא מונע מאנשים להוסיף בטעות Attribute לאוביקט.
אם אתם משתמשים ב Type Hints (מה זה אם!? ברור שאתם משתמשים ב Type Hints), תשמחו לשמוע שיש דרך פשוטה יותר לקבל את אותה תוצאת לוואי בצורה שגם מסתדרת טוב עם ירושה, גם מאפשרת וידוא טיפוסים וגם כוללת כמה פינוקים ברמת הקוד. אני מדבר על הדקורטור dataclass. בואו נראה איך הוא עובד.
נניח שיש לנו מחלקה בשם Person ולכל אוביקט מסוג Person אנחנו רוצים להגדיר את השדות:
- שדה id מסוג int.
- שדה name מסוג str.
- שדה 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. ברגע שאוסיף אותו אני מקבל:
-
פונקציית
__init__
אוטומטית שמקבלת את כל הפרמטרים שהגדרתי למחלקה -
פונקציית
__repr__
אוטומטית שמחזירה ייצוג ידידותי של האוביקט -
פונקציית
__eq__
שבודקת שיוויון לפי כל אחד מהשדות -
פונקציה בשם 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