מחלקה class

February 23, 2019

מחלקה class

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

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

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

באמצעות יצירת מחלקה (class), פייתון מאפשרת בניית מודל הכולל מבנים גדולים ומורכבים או למעשה יצירת מפעל שיכול לסייע למתכנת ליצור במהירות פרט או בשמו המקצועי מופע (instance) שהוא כמו ייצור בעל תכונות ויכולות שהגדרנו במחלקה, והוא יהיה דומה ליתר המופעים שאותה מחלקה מסוגלת לייצר, למעט בחלקים שנגדיר כמיוחדים לכל אחד מהפרטים (או המופעים) במחלקה. המחלקה מאפשרת לנו ליצור מופע במהירות, והוא יקבל מיד את כל התכונות (attributes) והפונקציות (methods) שהוגדרו במחלקה (פונקציה בתוך מחלקה נקראת גם מתודה)– תכונה יכולה להיות צבע למשל ומתודה יכולה לעשות פעולה כמו הדפסה הכפלה וכיוב'.

בדוגמא של חידת שמונה המלכות, אנו יכולים לבנות מודל של מלכה על לוח שחמט, ולאחר שבנינו את המודל, יש לנו מפעל קטן, אשר בשורת קוד אחת, הופך כל משתנה למלכה עם כל התכונות וכל הפונקציות שמלכה יכולה לבצע. כך אנו יכולים בין רגע להפוך את Q1 למלכה הראשונה, Q2 למלכה השנייה וכו'

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

 

class BlueMen ():
    color="blue"
    def m (self):
        print("moshe")             
    def y (self):
        print ("yossi")

x=BlueMen()
x.m()                      >>>   moshe
x.y()                        >>>   yossi
print(x.color)         >>>   blue

 

ונסביר - אנו מגדירים מחלקה באמצעות שימוש במילה class ולאחריה אנו בוחרים שם למחלקה BlueMen. בפייתון קיימת מוסכמה ששמות מחלקות מתחילים באות גדולה. אנו בחרנו בשם BlueMen ובתוך המחלקה שלנו לכל הפרטים שניצור תהיה תכונה (attribute) משותפת – הם יהיו כחולים blue)). כמו כן, בנינו שתי מתודות (שהן פונקציות בתוך המחלקה), האחת נקראת m והיא מדפיסה את המילה moshe והשנייה נקראת y  והיא מדפיסה את המילה yossi.

המילה self (גם היא מוסכמה ואפשר להשתמש במילה אחרת)  בכל אחת מהמתודות שבנינו (מתודה m ומתודה y) מייצגת את הפרט הספציפי או המופע (instance) שאנו נייצר בעתיד באמצעות המחלקה שלנו. היות שבשלב בניית המפעל אנו לא יודעים איזה שם ספציפי יקרא לכל אחד מהפרטים שניצור באמצעותו, אנו קוראים לו self. בהמשך, לאחר שניצור פרט ונקרא לו x נוכל להשתמש ב- x כדי למצוא תכונות או להפעיל פונקציות בהתאם למה שהגדרנו במחלקה. כדאי לשים לב שאין פקודת print תחת הגדרת color כך שאם רוצים לראות על המסך את התכונה הזאת, אנו צריכים לבקש זאת באמצעות הפקודה print בניגוד למתודות m ו- y שכבר כוללות את הפקודה print.

לאחר שבנינו את המחלקה – אנו יכולים בהקלדה של שורה אחת ליצור מופע בשם x שיקבל את כל תכונות המחלקה BlueMen ועושים זאת באמצעות סימן = מהצד השני של המשתנה שבחרנו למופע. לאחר שיצרנו את המופע x, השימוש בתכונות ובפונקציות הוא פשוט. מוסיפים אחרי ה-x נקודה ואת שם המתודה ובום – היא עושה מה שהיא אמורה לעשות, קרי ()x.m מדפיסה את המילה moshe ו- ()x.y מדפיסה את המילה yossi וכל מי שירצה לדעת מה צבעו של האובייקט יוכל להדפיס את x.color ולראות שהאובייקט שלנו הוא בעל תכונה משונה – הוא כחול. עוד דבר שכדאי לשים אליו לב הוא שפונקציות מסויימות יוצאות לפועל רק כאשר יש סוגריים (ריקים) אחריהם, וזה האופן שבו קוראים לפונקציה, יש לכך הסבר מתקדם יותר המשתייך לשלב אחר.

לאחר שהקמנו את המחלקה אנו יכולים לייצר מופעים רבים בהשמה פשוטה של שם המחלקה ()BlueMen שבחרנו, לתוך משתנה נניח y. מרגע זה כל מה שיכולנו לבצע עם x נוכל לבצע גם עם y.

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

למעלה ראינו מחלקה של אנשים כחולים שאינה מקבלת פרמטרים כלשהם מהמשתמש. כעת נראה מחלקות המיועדות לייצירת פריטים, שהמשתמש צריך להזין, עם הקמת הפריטים (istances), נתונים כאלה ואחרים המשתנים בין פרט לפרט (בשפה מקצועית- בין מופע למופע, ובאנגלית from one instance to another).

פונקציית __init__() במחלקה

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

 

class Customer:
  def __init__(self, name, age):
    self.name = name
    self.age = age

    self.character="always right"
  def welcome(self):
    print("welcome",self.name,"!")

j=Customer ("jack",47)
j.welcome()                        >>>   welcome jack !
print (j.name)                     >>>   jack

print (j.age)                        >>>   47

print (j.character)               >>>   always right

 

פונקציית ()__init__ שייכת לקבוצת הפונקציות המיוחדות (special methods) בפייתון והיא נרשמת עם שני קווים תחתונים לפני ואחרי המילה, במקרה הזה המילה היא init. פונקציות אלה נקראות לעיתים dunders שזה קיצור של double underscore על שם הקו התחתון הכפול המאפיין אותן או slots. פונקציות אלה מיועדות לקריאה על ידי המערכת (מכאן גם חוסר האסתטיות היחסית של אופן הרישום) והיא מיועדת להגדיר את האובייקט (שניצור בעתיד באמצעות המפעל הקטן שלנו).       למה לא הסתפקו בקו תחתון אחד ? התשובה היא שיש ביטויים שנכתבים עם קו תחתון אחד לפני המילה (בלי אחד אחרי) ויש כאלה שיש גם קו אחד לפני וקו אחד אחרי והם מיועדים לתפקידים אחרים.

לאחר שבנינו מחלקה המגדירה לקוח (Customer), המפעל שלנו מוכן. כעת אנו מכריזים על המשתנה j כלקוח ששמו jack וגילו הוא 27 באמצעות השורה (j=Customer ("jack",47  , מאותו רגע, j  נקרא מופע (instance) של המחלקה Customer, והוא יקבל מיידית שלל תכונות ויכולות המוקנות לחברים במחלקה Customer. כך למשל מאותו רגע jack, כמו אשתי, תמיד צודק, תכונה זו תהיה משותפת לכל הלקוחות (המופעים), משום שזו תכונה שאינה ספציפית רק ללקוח מסוים.

אנו רואים ש- self מהמפעל הופך לאובייקט בשם j אשר חלק מהתכונות שלו הן תכונות משתנות כמו שם וגיל, וכל לקוח יקבל שם וגיל שונים, לעומת character שאינו מצוי ברשימת המשתנים משום שהוא קבוע – כל הלקוחות תמיד צודקים (always right).

בנוסף, במחלקה שלנו קיימת מתודה שמבצעת פעולה מסוימת, היא מדפיסה ברכה אישית (עם שם המשתמש הספציפי) ברגע שמפעילים אותה ()j.welcome – מקבלים ! welcome jack .

נדגים מדוע הפעולות שעובדות בתכנות הפונקציונאלי לא עובדות באותו האופן במחלקה (class) – נוסיף למחלקה שלנו משתנה נוסף customersNumber המשותף לכל המופעים היות שהוא ברמת המחלקה, שמאפשר לספור את מספר המופעים שיצרנו במחלקה. וניצור שני מופעים (שני לקוחות שונים), j ו- m. כשנבדוק כמה מופעים יש לנו נקבל את המספר 2. אולם גם אחרי שנמחק מופע אחד (נפטר את העובד), זה לא מסתדר אוטומטית, עדיין אנו רואים ספירה של שני מופעים, למרות שמחקנו אחד.

 

class Customer:
  customersNumber=0
  def __init__(self, name, age):
    self.name = name
    self.age = age
    self.character="always right"
    Customer.customersNumber +=1
  def welcome(self):
    print("welcome",self.name,"!")

j=Customer("jack",47)
m=Customer("mark",26)

print(Customer.customersNumber)        >>>   2
del(m)
print(Customer.customersNumber)        >>>   2

 

 

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

כך למשל, כדי לתקן את הבאג ולאפשר לנו לפטר עובד מהמערכת, אחרי שאנו מבצעים פקודת del, בקוד תכנות המחלקה נוסיף מתודה מיוחדת (__del__) שתאפשר גם פעולות מחיקה –

 

class Customer:
  customersNumber=0
  def __init__(self, name, age):
    self.name = name
    self.age = age
    self.character="always right"
    Customer.customersNumber +=1


  def welcome(self):
    print("welcome",self.name,"!")


  def __del__(self):
    Customer.customersNumber -= 1

j=Customer("jack",47)
m=Customer("mark",26)

print(Customer.customersNumber)               >>>   2
del(m)
print(Customer.customersNumber)               >>>   1

 

 

איך היינו עושים זאת בתכנות פונקציונאלי? מוחקים באמצעות פקודת del אפשר במילון ואפשר ברשימה כמו שרואים למטה -

 

dic = {'a':'b', 'c':'d'}
del dic['a']
print(dic)               >>>   {'c': 'd'}

lista=["a","b","c"]
del(lista[1])
print(lista)              >>>   ['a', 'c']

 


 

 

 

 

כדי ש del- (כמו פקודות טריוויאליות אחרות) תעבוד כמצופה ממנה בתוך מחלקות, אנו צריכים לבנות את היכולת הזאת באמצעות פונקציות מיוחדות כמו (__del__).

אפילו עניין פשוט כמו להדפיס שם למופע (instance) שלנו יכול להיות מורכב (אבל שווה את הטרחה) ומחייב פונקציה מיוחדת – נסתכל על הפונקציה המיוחדת (__str__) בלעדיה לא נקבל משהו מובן כאשר נבקש להדפיס את האובייקט שלנו –

 

class Coordinate:
    def __init__(self, letter, num):
        self.letter = letter
        self.num = num

c =Coordinate('c', 35))

print(c)

 

>>>

<__main__.Coordinate object at 0x00000234DEF81828>

 

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

 

class Coordinate:
    def __init__(self, letter, num):
        self.letter = letter
        self.num = num
    def __str__ (self):
        return self.letter+str(self.num)

y =Coordinate('a', 8)
z=Coordinate("b",52)
print(y)                          >>>   a8
print(z)                          >>>   b52

 

 

מה שעשינו הוא לבקש מהתוכנה, שבכל פעם שאנו מבקשים להדפיס את האובייקט שלנו, אנו רוצים לראות מחרוזת המורכבת מהאות (letter) שהכנסנו עם הקמת המופע, בצירוף המספר (num) שאף הוא הוזן עם הקמת המופע. כמובן שכדי לחבר מחרוזת (string) עם מספר (integer) אנו צריכים להפוך את המספר למחרוזת באמצעות הפקודה ()str, כך ש- (str(self.num הוא בעצם מחרוזת ולא integer,  והוא כמו כל אות אחרת ולכן אפשר להדביק אותו באמצעות סימן + פשוט למחרוזת אחרת. כך אנו מקבלים אובייקט z למשל שאנו מבינים שמכיל את הקואורדינטה b52. יכולנו כמובן לבחור בדרכי ייצוג אחרות לתוכן האובייקט z, כל דבר שיבהיר לנו במה מדובר.

ישנה פונקציה מיוחדת שעושה דבר דומה מאוד והיא נקראת (__repr__). ההבדלים בינה לבין פקודת (__str__) אינה מעניינו של שלב זה (אבל כדאי לדעת שזה קיים).

כמה פונקציות מיוחדות יש? הרבה מאוד. ניתן לקרוא אודותיהן באתר של פייתון או במקומות אחרים ברשת (=מרשתת=אינטרנט).

Please reload

Please reload

רעננו את הדף והקליקו למעבר לנושא הבא: