היי חברים, ברוכים הבאים לקורס פייתון למתקדמים
ההנחה היא שאנחנו כבר יודעים איך קוד נראה, מהם משתנים, מערכים, לולאות, תנאים, ואפילו פונקציות. בהחלט הספקנו הרבה.
בקורס זה, אנו ניקח את כל הידע שצברנו, ונלמד להשתמש בו עם כלים נוספים.
בשיעור זה, אנו נלמד על מחלקות. באנגלית – Classes.
מחלקה היא תבנית קוד, שמיצגת לוגיקה כלשהי. מהתבנית הזו ניתן ליצור אובייקטים.
בואו נדמה זאת למכולת.
ובכן, בואו נחשוב שאנו כותבים תוכנה, שמטרתה היא לנהל מוצרים במכולת.
ברור לנו כמנהלי המכולת, שלמרות שישנם הרבה מוצרים, עדיין לכל מוצר יש:
בשביל לכתוב תוכנה שיהיה לנו קל לכתוב ולעבוד איתה, אנו נרצה להמציא איזושהי תבנית שתייצג מוצר.
תדמיינו שאנו בונים את רשימת המוצרים באקסל, ברור שבראש הטבלה אנו ניצור עמודות שכל עמודה תציין שם של מאפיין, כמו “שם המוצר”, “מחיר המוצר” וכן הלאה.
אז בתכנות, אנו נגדיר “מחלקה” בשם מוצר, המאפיינים של המחלקה יהיו השם, המחיר הברקוד וכו’.
לאחר שנגדיר מחלקה, שזה רק אפיון כללי למוצר (כמו ראש העמודות שבאקסל), אנו נוכל להתחיל ליצור מוצרים “אמיתיים”, שהם יהיו ה”מופעים” של המחלקה. (כמו השורות באקסל, שכל שורה תייצג בעצם מוצר).
כאשר בעל המכולת ירצה להכניס מוצר חדש למכולת, הוא יוכל להשתמש בתבנית (במחלקה) בצורה קלה.
בואו נראה איך עושים זאת.
מחלקה היא המסגרת הבסיסית של תכנות מונחה עצמים. זהו אוסף של משתנים (הנקראים שדות), מאפיינים ופונקציות (הנקראות שיטות) המאוגדים למבנה לוגי אחד ופועלים יחד. (ויקיפדיה)
בואו נצלול לכתיבה, ותוך כדי נבין טוב יותר.
הבה נגדיר מחלקה בשם מוצר.
הגדרת המחלקה תהיה כך:
class Product():
name = None
נשים לב לפרטים:
תחילה השתמשנו במילה השמורה class (מלשון מחלקה…)
אחר כך בחרנו את שם המחלקה Product (כמובן שיכולנו לבחור כל שם אחר).
בצמוד, הוספנו סוגריים עגולות, ולאחריהן נקודותיים.
בשורה הבאה, (תחת הזחה) יצרנו “מאפיין” או “משתנה” של המחלקה. קראנו לו name. הוא ייצג כמובן את השם של המוצר. רק שכעת אין לנו עדיין מוצר, זה רק טיפוס נתונים פוטנציאלי – תבנית.
ולכן גם את השם של המוצר אנו לא רוצים לבחור כעת, ולכן פשוט נאתחל אותו ב None – בכלום.
נוכל לדמיין את מה שעשינו כגיליון אקסל בשם product, שציינו בו “עמודה” בשם name. עדיין אין לנו שורות כלל.
מחוץ להגדרת המחלקה, נוכל ליצור מוצרים (“מופעים”) של המחלקה. (כמו שורות בטבלת האקסל)
היצירה תיעשה באופן שמזכיר קריאה לפונקציה… אנו ניצור משתנה חדש, לדוגמא X, שיכיל “עותק” או יותר נכון “מופע” של המחלקה. באופן הבא:
x = Product()
כעת X הוא מופע של המחלקה.
כרגע אין ל X שום מאפיין מעניין… (זה כאילו יצרנו שורה חדשה בטבלת האקסל, אך עוד מילאנו את הפרטים שלה)
מכיון שאנו יודעים שהמחלקה מכילה גם משתנה בשם name, זה אומר שזה מאפשר לנו להגדיר “שם” לכל מופע של המחלקה.
בשביל לעשות זאת, אנו ניגש לשדה name באמצעות X, באופן הבא:
x.name = "Sugar"
כעת X הוא מופע של המחלקה “מוצר”, וגם יש לו שם – סוכר. (שורה באקסל, שתחת העמודה name ציינו “סוכר”)
אם נרצה להדפיס את “שם המוצר” של המופע X, נעשה זאת בקלות כך:
print(x.name)
כשהגדרנו את המחלקה, יצרנו רק שדה אחד בשם name. כמובן שבדרך כלל יהיו לנו יותר משדה בודד…
בשביל לעשות זאת, אנו נוכל ליצור עוד משתנים. כך:
class Product():
name = None
barcode = None
price = None
sugar = Product()
sugar.name = "Sugar"
sugar.barkod = "345824"
sugar.price = 1.5
salt = Product()
salt.name = "Salt"
salt.barkod = "930043"
salt.price = 4.99
כעת יש לנו שני מוצרים (מופעים), שניהם “שייכים” לאותה מחלקה “מוצר”, אך לכל אחד נתונים שונים בשדות המאפיינים אותם.
אם נחזור לדוגמת האקסל, אז זה כאילו יצרנו שתי שורות חדשות, כל שורה מייצגת מוצר אחר. וכמובן שבכל שורה שמנו נתונים תחת העמודות הרלוונטיות..
צור מחלקה בשם person אשר מייצגת “אדם”. בשביל לייצג ‘אדם’ אנו נרצה שיהיו לו את השדות הבאים:
לאחר מכן, צור פונקציה אשר מקבלת את הפרמטרים: שם, מגדר, גיל, ות.ז,
הפונקציה יוצרת ‘מופע’ של המחלקה “אדם”, וכמובן מחזירה אותו…
לאחר מכן, השתמשו בפונקציה ליצירת ‘אדם’.
אנו נוכל להגדיר פונקציה שתהיה פונקציה של המחלקה.
פונקציה של מחלקה, זו “פעולה” ששייכת למחלקה.
מה זאת אומרת?
נמשיך עם הדוגמה של מחלקה המייצגת מוצר.
נניח שאנו רוצים ליצור פונקציה, אשר מחזירה את המחיר של המוצר בתוספת המע”מ.
ברור שמה שעל הפונקציה לעשות זה לקחת את המחיר של המוצר מתוך השדה price, ולהכפיל אותו ב 1.18 (בהנחה שהמע”מ הוא 18%)
בעיקרון פונקציה כזו אמורה לדעת לעבוד אך ורק בעולמם של ה”מוצרים”, ולכן במקום להגדיר סתם פונקציה כללית, נוכל להגדיר את הפונקציה בצורה כזו שהיא תהיה שייכת למחלקה.
איך עושים זאת?
אז בקוד הבא יצרנו שוב את המחלקה Product, עם השדות “שם” ו”מחיר”, אלא שהפעם הוספנו למחלקה גם “פונקציה” משלה:
class Product():
name = None
price = None
def plus_maam(self):
print("Calculation Taxes..")
result = self.price * 1.18 # Ma'am = 18%
return result
נשים לב מה עשינו:
אוקי, זה טוב ויפה.
איך כעת נוכל להשתמש בפונקציה הזו?
אז בשביל להשתמש בפונקציה של המחלקה, אנו חייבים שיהיה לנו קודם כל מופע שלה.
אז נניח שיש לנו מוצר (מופע) בשם salt, ואנו רוצים לדעת מה מחירו כולל מע”מ, אנו לכאורה נצטרך לקרוא לפונקציה, ולשלוח לה את המופע בעל השם salt (שיכנס לתוך ה-self)
salt = Product()
salt.name = "Salt"
salt.price = 4.99
final_price = plus_maam(salt)
אך לא, אם נעשה זאת אנו נקבל שגיאה.
הסיבה היא שהפונקציה plus_maam לא מוכרת למחשב. בשביל שהיא תהיה מוכרת למחשב, היא חייבת להופיע בהקשר של המחלקה.
כיוון שיש לנו ‘מופע’ של המחלקה, אנו נוכל באמצעות המופע salt לגשת לפונקציה:
final_price = salt.plus_maam(salt)
הסבר:
מכיוון שהפונקציה plus_maam לא מופיע בפני עצמה, אלא בהקשר של המופע (על ידי ציון שם המופע ואז נקודה), אז המחשב מכיר את הפונקציה ומבין למה הייתה כוונתנו.
אך לא, גם צורה זו איננה לגמרי תקינה, למה?
כיוון שאנו ניגשים לפונקציה דרך המשתנה salt, אנו לא צריכים יותר לשלוח את המשתנה salt בצורה מפורשת לפונקציה. הוא יעבור לשם אוטומאטית 🙂
ואם כך, כל מה שנצטרך לעשות זה פשוט:
final_price = salt.plus_maam()
כעת בתוך הפונקציה, המשתנה self יקבל את המופע salt. אך בצורה מרומזת.
מרומז – implicit באנגלית, ולא explicit – מפורש.
מונחים שכדאי שנכיר… הם חלק מהסלנג המקצועי בעולם התכנות.
אז שוב, מה עשינו?
יצרנו פונקציה ברמת המחלקה, ובשביל להשתמש בה, תחילה יצרנו מופע, ואז דרך המופע ביצענו קריאה לפונקציה.
מכיוון שהפונקציה הוגדרה לקבל רק פרמטר אחד – את המופע עצמו, אז בעת הקריאה לא נדרשנו לשלוח לה כלום. למה? כי המופע נשלח לפונקציה בצורה מרומזת בגלל עצם זה שהקריאה לפונקציה נעשתה דרכו.
אם נרצה שהפונקציה תקבל פרמטרים נוספים, למשל אם נרצה שהמע”מ שהפונקציה שלנו מחשבת, יהיה גם כן ארגומנט שישלח לפונקציה (ולא יהיה קבוע 18% כמו שעשינו), נוכל להגדיר את הפונקציה plus_maam באופן כזה:
def plus_maam(self, maam):
return self.price * (1 + maam)
מה עשינו? פשוט הוספנו בהגדרת הפונקציה פרמטר נוסף, בדיוק באותו אופן שעשינו זאת בפונקציות רגילות (שאינם חלק ממחלקה) בחלק הראשון של הקורס.
כעת, כאשר נשתמש במופע salt כדי לחשב את מחירו כולל המע”מ, נצטרך לשלוח לפונקציה רק את הערך של המע”מ, כי המשתנה עצמו כידוע, נשלח בצורה מרומזת.
אז בקיצור, נעשה כך:
final_price = salt.plus_maam(0.18)
הערה: אולי יהיה חכם מצידנו להגדיר ערך ברירת מחדל עבור המע”מ בדיוק כמו שלמדנו בעולם הפונקציות:
def plus_maam(self, maam=0.18):
. . .
צור מחלקה המייצגת “תיבה”.
המאפיינים של תיבה הם המידות שלה…
ולכן, תצטרך ליצור למחלקה שדות בשם: גובה, רוחב ואורך.
כמו כן דאג שלמחלקה תהיינה הפונקציות הבאות:
א. פונקציה המחשבת (ומחזירה) את השטח של הקוביה (רוחב כפול אורך)
ב. פונקציה המחשבת את הנפח של הקוביה (רוחב * גובה * אורך)
ג. פונקציה המדפיסה את כל הנתונים של הקוביה.
על פונקציה זו להדפיס את הרוחב הגובה והאורך, אך פרמטר אופציונאלי שיתקבל לפונקציה, עשוי לבקש ממנה להדפיס גם את הנפח שלה. (בעזרת הפונקציה הקודמת..)
הערה: כמו שניגשנו לשדות (משתנים) של מחלקה בתוך פונקציה באמצעות המשתנה self, אנו ניגש באופן דומה גם לפונקציות של המחלקה.
פונקצית איתחול – בנאי (באנגלית: constructor – מילה חשובה)
כאשר יצרנו אובייקט (או ‘מופע’) של מחלקה, ונתנו ערכים לשדות שלו, עשינו זאת בכמה שורות.
שורת יצירת האובייקט:
salt = Product()
salt.name = "Salt"
למעשה, ישנה דרך קצרה ונעימה יותר, שתאפשר לנו לקבוע את שם המוצר כבר בעת יצירתו.
באופן הבא:
salt = Product(name="Salt")
מה עשינו?
בתוך הסוגריים העגולות שאנו כותבים בעת יצירת מופע, הוספנו שם של שדה וערך.
נכון דרך מגניבה?
נוכל גם “לאתחל” כמה שדות, למשל:
salt = Product(name="Salt", price=5.5)
אבל כדי שכל הטוב הזה יקרה, אנו נהיה חייבים קודם להגדיר את המחלקה בצורה שתתמוך בכך.
איך עושים זאת?
אנו נצטרך ליצור פונקצית אתחול.
הפונקציה נראית כך:
class Product():
def __init__(self, name, price):
self.name = name
self.price = price
self.barkod = None
נשים לב לכמה דברים:
salt = Product(name="Salt", price=5.5)
salt = Product(x="Salt", y=5.5)
אני מקווה שברור לכם למה, אם לא, חזרו לפרק של הפונקציות 🙂
צור מחלקה המייצגת שולחן.
עליך ליצור לשולחן את השדות:
השולחנות שהמחלקה מייצגת, כולם מתוצרת איקאה, כך שלמחלקה גם יהיה שדה בשם company שיאותחל תמיד בשם IKEA.
כמו כן, צור למחלקה פונקצית איתחול (בנאי), שתאתחל את השדות אורך ורוחב ומחיר.
לשדה מחיר עליך לציין ערך ברירת מחדל של 100.
בנוסף, למחלקה תהיינה הפונקציות הבאות:
לסיום, צור קוד היוצר שני שולחנות, אחד עם מחיר “כדאי”, ואחד עם מחיר “לא כדאי”. (כאשר ה X לבדיקת כדאיות שווה 50)
השתמש בפונקציות של המחלקה בשביל להראות את הכדאיות…
class Table():
def __init__(self, length, width, price=100): # בנאי
self.company = 'IKEA'
self.length = length
self.width = width
self.price = price
def get_size(self):
return self.length * self.width
def is_worth(self, x):
size = self.get_size()
piece_price = self.price / size
return piece_price < x # return a Boolean value
# Testing class code:
table1 = Table(3, 2, 10)
table2 = Table(3, 2, 8000)
print(table1.is_worth(50))
print(table2.is_worth(50))
הערה: כתיבה מקצועית תיעשה על ידי ציון שם של מחלקה כך שיתחיל באות גדולה.
בנוסף, אם שם המחלקה מכיל יותר ממילה אחת, אנו נכתוב אותם בצפיפות, באופן הבא: MyClass
ולא על ידי קו תחתון (כמו פונקציות או משתנים) My_class.
ואם במנהגים עסקינן…
גם השימוש שלנו במילה self בשביל לציין את שם המשתנה המכיל את המופע, הוא מנהג… יכולנו להשתמש בשם אחר לכך.
אם אנו מתעניינים ואוהבים את ה “מנהגים” הללו, אנו נרצה להציץ במסמך הזה.
אעיר ואוסיף, שהמנהגים הללו הם אחד ההבדלים בין מתכנת מקצועי לשאינו כזה…
חבר’ה, יש לי עוד הרבה דברים לספר לכם על מחלקות.
יש מלא דברים שאפשר לעשות עם מחלקות שלא דיברנו עליהם, אני אנסה להכיר לכם את העיקריים שבהם שלא ציינו.
אז ראינו שמחלקה יכולה להכיל שדות, כמו “שם”, “מחיר” וכדומה.
למעשה, מחלקה מסוגלת להכיל גם “מילון” או “רשימה”.
למשל, מחלקה שמייצגת “חנות”, תוכל להכיל גם רשימה של מחרוזות, שמייצגת את שמות העובדים בחנות.
ומעבר לכך, מחלקה גם מסוגלת להכיל כשדה, גם “מופעים” של מחלקות אחרות.
למשל, נניח שאנו בונים תוכנית לניהול קניון.
אנו ניצור מחלקה בשם “איש” וכן מחלקה בשם “חנות”, אחד השדות של המחלקה “חנות” ניצור שדה בשם “מנהל”, וכך נוכל להציב בתוך המשתנה “מנהל”, מופע של המחלקה “איש”.
class Person:
def __init__(self, name, age):
self.name = age
self.age = name
class Store:
def __init__(self, name, manager):
self.manager = manager
self.name = name
עכשיו ניצור מופע של “איש”:
dave = Person(name=’Dave’, age=30)
zara = Store(name=’Zara’, manager=dave)
וכמו שמחלקה יכולה להכיל “מופע” של מחלקה אחרת, כך היא יכולה להחזיק גם “רשימה” של מופעים של מחלקה אחרת. (וכל קומבינציה אחרת שתעלו על דעתכם)
בוא ננסה לתרגל בעצמנו תרגיל פשוט:
נסו לכתוב את הפונקציות כך שיוכלו לקבל מוצר אחד או יותר, בשביל להוסיף/למחוק מהרשימה.
class Product:
def __init__(self, name, price, date):
self.name = name
self.price = price
self.date = date
class store():
def __init__(self):
self.products = []
def add_product(self, p):
self.products.append(p)
def remove_product(self, p):
self.products.remove(p)
בשביל לבדוק אם מה שעשינו תקין, נוכל לנסות להריץ את הקוד הבא:
s = store()
table = Product('Tabel', 150, "12/12/2019")
chair = Product('chair', 30, "12/12/2018")
s.add_product(table)
s.add_product(chair)
print(s.products)
[<__main__.Product object at 0x7ff11da86c10>, <__main__.Product object at 0x7ff11da86c50>]
הסיבה היא שמודפס לנו רשימה של “אובייקטים” מסוג “מוצר”.
אבל איך המחשב יידע כיצד להדפיס כל מוצר ברשימה?
בשביל שההדפסה תהיה יותר מובנת, נוכל להוסיף עבור כל מוצר את הפונקציה __repr__ שלמדנו מוקדם יותר בשיעור, ואז ההדפסה אוטומאטית תבצע מה שיוגדר בפונקציה הזו.
כאשר אנו ניצור תחת מחלקה פונקציות, נוכל לסווג אותן לשלושה סוגי פונקציות.
ובהרחבה:
class A:
@staticmethod
def staticmethod_example(x):
print(x)
קריאה לפונקציה הזו, תוכל להיות או בצורה רגילה דרך אובייקט כמו שראינו בסוג הראשון, או גם כאשר אין לנו אובייקט, דרך ציון שם המחלקה A, ואז נקודה, ושם הפונקציה:
A.example_function(5)
הסוג האחרון:
פונקציה שלא מבצעת פעולות ברמת המופע של המחלקה, כי אם ברמת המחלקה עצמה.
למשל, אם נרצה ליצור למחלקה פונקציה שמדפיסה למשל את כל השדות שיש למחלקה, תתאים לסוג הזה.
ואיך עושים זאת?
מכיוון שהפונקציה לא מבצעת פעולות ברמת אובייקט, אז מה שהיא צריכה לקבל זה את המחלקה עצמה, ולא אובייקט.
כאשר נוסיף מעל שורות הפונקציה את השורה: [email protected]
מאותו רגע, המשתנה הראשון שמתקבל לפונקציה (מה שהכרנו עד כה כ-self שמייצג מופע) יהיה כעת משתנה שיקבל את המחלקה עצמה. לכן גם לא נקרא לו self, אלא cls (מלשון קלאס – המחלקה עצמה)
בשביל לקרוא לפונקציה, נוכל לעשות זאת גם כן (כמו הסוג השני) בשני אופנים. או על ידי ציון שם המחלקה, או דרך אובייקט.
כדוגמה: (פונקציה שמדפיסה את כל השדות של המחלקה – על הדרך גם נלמד איך עושים זאת)
class A:
@classmethod
def classmethod_example(cls):
print([i for i in cls.__dict__.keys() if i[:1] != '_'])
הערה: לא נתעמק כרגע בלהבין את שורת ההדפסה שהפונקציה ביצעה (שמדפיסה את כל המשתנים והפונקציות של המחלקה), רק נציין שהשדות של המחלקה כמו גם הפונקציות, הן ברמת המחלקה עצמה. ולא ברמת אובייקט. (למעט כמובן שדות ש “נדחפו” לאובייקט, לא משנה אם בזמן ריצת הבנאי או סתם בקוד מחוץ למחלקה, בכל מקרה שדות אלו כבר לא יהיו שדות של המחלקה, והן לא תודפסנה בשורת ההדפסה הזו).
נתונה המחלקה הבאה:
class Product():
company = 'IKEA'
def __init__(self, name):
self.name = name
בקוד שלהלן, ישנה שורה אחת שאיננה תקינה.
נסה להבין מהי, ולמה.
print(Product.company)
print(Product.name)
print(Product('Table').name)
print(Product('Chair').company)
שורת הקוד שמנסה להדפיס את השם של המוצר, ללא יצירת מופע:
print(Product.name)
איננה תקינה.
הסיבה היא, שהמשתנה name לא באמת נוצר כל עוד לא יצרנו מופע של המחלקה.
נתראה בשיעור הבא 🙂