כיוונון - מתי משתמשים ב @abstractmethod

קצת מבולבלת…
בדוגמא במחברת 2 של Animal השתמשנו בזה גם מעל ה init
אבל בדוגמא של השחמט במחלקת Piece זה לא נמצא מעל האיניט
בשני המקרים מחלקת העל היא מחלקה מופשטת ובשני המקרים מחלקות הבת לא צריכות להוסיף כלום לinit.
ניסיתי להבין מהתיעוד אבל לא לגמרי הבנתי מה החוקיות.

לפי מה שאני הבנתי, משתמשים ב-@abstractmethod רק בפעולות במחלקת-העל שאנחנו רוצים שמחלקות הבת יהיו חייבות לממש בתוכן.

לפי מחברת 2:

  • תתי־המחלקות שיורשות מהמחלקה המופשטת, חייבות לממש את כל הפעולות שמוגדרות במחלקת־העל כמופשטות.

שימי לב שחלק מהמחלקות שמייצגות כלים בדוגמת השח-מט יורשות ממחלקת Piece ולא מממשות פונקציית init בתוכן (למשל מחלקות King, Queen, Rook, Bishop). לעומתן, במחלקת Pawn נדרש להוסיף תכונה שמגדירה מהו “צעד קדימה”, ולכן הפונקציה init ממומשת בתוכה ונוספת לה התכונה self.forward.

לייק 1

משתמשים ב @abstractmethod כאשר את רוצה לומר:
בsub classes שיווצרו מהמחלקה הזו - הפונקציות שיהיו מתחת לדקורייטור זה יהיו חייבות להיות מבוצעות.

כלומר אם שמת את זה מעל ל str משמע בכל תתי המחלקות יהיה חייב ! להיות מימוש (כתוב) של הפונקציה str
כלומר התת מחלקה תכיל בהכרח

def __str__(self):
    ...

ברגע שלא שמים את זה, באים לומר שזה בסדר מבחינתך שהתת מחלקה תירש את הפונקציה מהמחלקה הסופר כמו שהיא, ואת לא מחייבת אותה לבצע מימוש נוסף. בתוך התת מחלקה.

כך שאם לא שמו ב init - משמע אפשר להשתמש ב__init__ שמקבלים מהירושה ללא צורך לממש אותו בתת מחלקה עצמה. כלומר היא עומדת בפני עצמה … ואפשר ליצור אובייקטים אשר את הפעולה הזו יורשים מהמחלקה האם.

לסיכום: כל פונקציה ש “לא עומדת בפני עצמה” (אומר חייבת להיות ממומשת מהתת מחלקה) נעטר אותה בדקורייטור הזה.

2 לייקים

אז למה:

  1. שוב, בדוגמא של הAnimal כן מימשו את init? זה בדיוק אותה סיטואציה
  2. מחלקות בת לא היו מחויבות לממש את שאר המתודות של מחלקת העל שהיו מסומנות על ידי @abstractmethod ?

בנוגע ל:

  • 1: זה שם לטובת ההסברה. כדי להראות שאם יש את @abstractmethod על ה init אז בהכרח הוא יופיע גם בכל התת מחלקות.
  • 2: לא. את יכולה למחוק את הדקורייטור ואת כל ה init מכל אחת מהתת מחלקות ולראות שזה עובד :slight_smile:

למה? לגבי 2, יש דוגמא במחברת אפילו, מה קורה אם במחלקת אב יש מעל פעולה את ה abstractmethod@ מחלקת הבת תהיה חייבת לכלול את כל הפעולות המסומנות, אחרת תתקבל שגיאה, זה ממש מופיע במחברת, המחלקה היורשת לא חייבת להשתמש בהן, היא חייבת לכלול אותן, -לכתוב אותן בתוכה.

זו השגיאה שקיבלתי כאשר שמתי את המתודה בפעולת האתחול של מחלקת האם ולא במחלקת הבת:

TypeError: Can't instantiate abstract class B with abstract methods __init__

לא הבנת @zurikarat כמוני או לא הבנת את מה ש @aviadamar אמר?

ההסבר שלך @aviadamar ב1 מאוד מבהיר לי. אבל 2, בתשובה למה שאמרת, אז למה שמנו את זה אם זה עובד גם בלי?

אתן דוגמא;
מחלקת האם:

from abc import ABC, abstractmethod


class A(ABC):
	@abstractmethod
	def __init__(self, a):
		self.a = a

ומחלקת הבת (כאן היא ללא פעולת האתחול, תריצי ותראי):

class B(A):
	def func(self):
		return 5

זה דוגמא … לא שמו את זה כי בהכרח צריך, שמו את זה כדי להראות שזה מחייב אותנו.
הרי התרגלנו למצבים בהם יורשים ולא בהכרח משתמשים ב init אז רצו להראות בדוגמא הזו שגם אם אין צורך קיומי עדין אם משים את זה זה מחייב את השאר ליישם את הפונקציה.

תתקבל השגיאה שציינתי למעלה,
מכיוון שמחלקת הבת חייבת לכלול בתוכה את הפעולות שסומנו ע"י המתודה. חייבת.

אני מרגישה שהכל קצת מבולגן.
ממש אשמח אם סגל יתייחסו ויסבירו

אולי זה יעזור

או זה

יש מלא דברים באינטרנט :slight_smile: , אפשר לקרוא גם על התיעוד של המתודה, זה גם יעזור

קראתי את התיעוד, לא מובן.
מה שהבנתי מהלינק הראשון:
זה משפיע רק על המתודה הספציפית שהוא מעליו ואם רוצים שזה יהיה ממומש במחלקה מסוימת צריך לממש אותו שם עם משהו בפנים.
יש מצב שזה גם היה כתוב ככה במחברת ואז התבלבלתי עם כל שאר הדברים שקרו שם.
מאיך שהדברים התגלגלו חשבתי שזה אומר שאם שמים מעל הinit אז גם צריך מעל שאר המתודות במחלקה ושכל מחלקה שיורשת צריכה לממש את כל מה שיש מעליו את זה.

אוקי נראה לי שהבנתי תודה לשניכם @aviadamar @zurikarat :slight_smile:

לייק 1

אני אתן לך הסבר: עבור שתי מחלקות A ו B

להלן דוגמא למחלקות האלה כאשר B יורשת את A אולם A היא לא אבסטרקטית:

class A:
    def __init__(self, name):
        self.name = name

class B(A):
    pass

b = B('Aviad')
print(b.name)

פלט:

Aviad

אפשר לראות כי אין לנו פה את העניין של הדקורייטור ולכן המחלקה B יכולה לאתחל את עצמה באמצעות init של המחלקה האם A … אין אנו מחוייבים ליישים את init גם ב B.

דוגמא 2:

from abc import ABC, abstractmethod

class A(ABC):
    @abstractmethod
    def __init__(self, name):
        self.name = name
    
class B(A):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
    
b = B(name='Aviad')
print(b.name)

פלט:

Aviad

במקרה הזה אנו רואים כי A היא מחלקה אבסטרקטית וB יורשת ממנה. כמעט כמו מקודם. אולם פה מוגדר במחלקה כי הפונקציה init היא פונקציה שלא עומדת בזכות עצמה ולכן תהיה מחוייבת להיות מיושמת במחלקות היורשות ממנה. (באמצעות הדקורייטור @abstractmethod)

וכמו שאנחנו רואים B אכן צריכה לממש את הפונקציה init בתוכה. אם היינו מנסים כמו קודם לעשות ללא ה init בתוך B הייתה חוזרת לנו שגיאה:

TypeError: Can't instantiate abstract class B with abstract methods __init__

במקרה הזה כמובן שאין לזה יותר מדי היתרונות.
להן דוגמא 3:

from abc import ABC, abstractmethod

class A(ABC):
    @abstractmethod
    def __init__(self, name):
        self.name = name
    
class B(A):
    def __init__(self, age, **kwargs):
        super().__init__(**kwargs)
        self.age = age

b = B(32, name='Aviad')
print(b.name)
print(b.age)

פלט:

Aviad
32

גם במקרה הזה כמו בשני, A מחלקה אבסטרקטית, וB יורשת ממנה ומחוייבת ליישם את init בתוכה.
אולם פה בגלל שיש לנו מימוש של init אנו יכולים גם להרחיב את כמות הארגומנטים שיש לנו ב B. במקרה זה הוספנו “גיל”.

זה יהיה בדיוק כמו לממש את הדוגמא הראשונה בצורה הנ"ל:

class A:
    def __init__(self, name):
        self.name = name

class B(A):
    def __init__(self, name, age):
        super().__init__(name)
        self.age = age
        

b = B('Aviad', 32)
print(b.name)
print(b.age)

פלט:

Aviad
32

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

a = A('Aviad')
print(a.name)

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

Aviad

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

a = A('Aviad')

שגיאה:

TypeError: Can't instantiate abstract class A with abstract methods __init__

למה להשתמש באבסטרקטית ? זה במצבים ספציפית שיש לנו מופע על שאנחנו לא רוצים ליצור אותו אלא רק את תתי המחלקות שלו (למשל חיה: וסוגי חיות, פרח, וסוגי פרחים … וכו … הסבר נוסף יש במחברת)

בכל מקרה (לדעתיהאישית*) לזרוק את “אני אשמח שהסגל יסביר” נשמע לי קצת וותרני במיוחד אחרי שמנסים לעזור פה. אם לא היינו יודעים את התשובה לא היינו מנסים. אולם כמו כן, יש המון חומר באינטרנט ואת המחברות שאני מאמין שקריאה שלהם חוזרת אחרי שעוזרים פה בפורום עושה את העבודה.

7 לייקים

לגבי מה שאמרת על התגובה לגבי הסגל, קצת קשה להבין כשכל פעם מישהו אחר נותן הסבר על משהו שהוא לא בסדר התגובות, כלומר אחרי שהתחלת אז מישהו אחר התחיל להסביר וזה היה לי קצת לא מסונכרן.
קשה לנהל ‘שיחה’ כשכל פעם לוקח לאנשים זמן לכתוב ואז התגובות לא מסודרות אז זה מבלבל.
זה היה קצת overwhelming אז היה לי קשה לעקוב. בסוף הבנתי והודתי וההסבר שלך בסוף עשה לי עוד יותר סדר.

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

3 לייקים

אני חלילה לא שופט ואם זה הרושם שקיבלת, אז חשוב לי שתביני שזה לא המצב.
אני שמח לעזור בדברים שאני מבין ואני עושה את זה בכיף זה עוזר גם לי.
לא הייתי משקיע בתגובה כזו אם לא היה חשוב לי לעזור.
גם לי יש דברים שממש לא עוברי אצלי בקלות ואני נעזר באחרים פה :slight_smile:

אני כן מאמין באנשים ואני חושב שאפשר לנסות להתמודד גם בלי הסגל בהרבה מקרים.
אני אקח לתשומת ליבי את עניין הביקורתיות, משתדל בדרך כלל לא להישמע ככה במיוחד שאין לי כוונה כזו.
שמח שהסדר לך החומר בסוף זו המטרה.
:heart:

לייק 1