בוקר חדש, השמש הפציעה והחלטתם שצברתם מספיק ידע בקורס כדי לפתוח רשת חברתית משלכם, בשם צ'יקצ'וק.
אתם משליכים את מחברות הפייתון מהחלון ומתחילים לתכנת במרץ את המערכת שתעזור לכם לנהל את המשתמשים.
לכל משתמש יש את התכונות הבאות:
בחרו סוג משתנה שיאפשר לכם לאחסן בנוחות את הנתונים הללו.
צרו שני משתמשים לדוגמה, והשתמשו בסוג המשתנה שבחרתם.
חשוב!
פתרו לפני שתמשיכו!
לפני שנציג את פתרון השאלה, נעמיק מעט ברעיון הכללי שעומד מאחורי הדוגמה הזו.
כל משתמש שניצור הוא מעין אסופת תכונות – במקרה שלנו התכונות הן שם פרטי, שם משפחה, כינוי וגיל.
לכל תכונה יהיה ערך המתאים לה, ויחד הערכים הללו יצרו משתמש אחד.
נמצא עוד דוגמאות לאסופות תכונות שכאלו:
חשבו על עוד 3 דוגמאות לעצמים שאפשר לתאר כערכים עם אסופת תכונות.
חשוב!
פתרו לפני שתמשיכו!
ניצור שני משתמשים לדוגמה לפי תכונותיהם שהוצגו לעיל:
In [ ]:
user1 = {
'first_name': 'Christine',
'last_name': 'Daaé',
'nickname': 'Little Lotte',
'age': 20,
}
user2 = {
'first_name': 'Elphaba',
'last_name': 'Thropp',
'nickname': 'Elphie',
'age': 19,
}
תוכלו ליצור בעצמכם פונקציה שיוצרת משתמש חדש?
זה לא מסובך מדי:
In [ ]:
def create_user(first_name, last_name, nickname, current_age):
return {
'first_name': first_name,
'last_name': last_name,
'nickname': nickname,
'age': current_age,
}
# נקרא לפונקציה כדי לראות שהכל עובד כמצופה
new_user = create_user('Bayta', 'Darell', 'Bay', 24)
print(new_user)
נוכל גם לממש פונקציות שיעזרו לנו לבצע פעולות על כל אחד מהמשתמשים.
לדוגמה: הפונקציה describe_as_a_string תקבל משתמש ותחזיר לנו מחרוזת שמתארת אותו,
והפונקציה celeberate_birthday תקבל משתמש ותגדיל את גילו ב־1:
In [ ]:
def describe_as_a_string(user):
first_name = user['first_name']
last_name = user['last_name']
full_name = f'{first_name} {last_name}'
nickname = user['nickname']
age = user['age']
return f'{nickname} ({full_name}) is {age} years old.'
def celebrate_birthday(user):
user['age'] = user['age'] + 1
print(describe_as_a_string(new_user))
celebrate_birthday(new_user)
print("--- After birthday")
print(describe_as_a_string(new_user))
הצלחנו לערוך את ערכו של user['age']
מבלי להחזיר ערך, כיוון שמילונים הם mutable.
אם זה נראה לכם מוזר, חזרו למחברת על mutability ו־immutability.
בשלב הזה בתוכניתנו קיימות קבוצת פונקציות שמטרתן היא ניהול של משתמשים ושל תכונותיהם.
נוכל להוסיף למשתמש תכונות נוספות, כמו דוא"ל ומשקל, לדוגמה,
או להוסיף לו פעולות שיהיה אפשר לבצע עליו, כמו הפעולה eat_bourekas, שמוסיפה לתכונת המשקל של המשתמש חצי קילו.
אף על פי שהרעיון נחמד, ככל שנרבה להוסיף פעולות ותכונות, תגבר תחושת האי־סדר שאופפת את הקוד הזה.
קל לראות שהקוד שכתבנו מפוזר על פני פונקציות רבות בצורה לא מאורגנת.
במילים אחרות – אין אף מבנה בקוד שתחתיו מאוגדות כל הפונקציות והתכונות ששייכות לטיפול במשתמש.
הבעיה תצוף כשנרצה להוסיף לתוכנה שלנו עוד מבנים שכאלו.
לדוגמה, כשנרצה להוסיף לצ'יקצ'וק יכולת לניהול סרטונים – שתכונותיהם אורך סרטון ומספר לייקים, והפעולה עליהם היא היכולת לעשות Like לסרטון.
הקוד לניהול המשתמש והקוד לניהול הסרטונים עלולים להתערבב, יווצרו תלויות ביניהם וחוויית ההתמצאות בקוד תהפוך ללא נעימה בעליל.
החוסר באיגוד התכונות והפונקציות אף מקשה על הקורא להבין לאן שייכות כל אחת מהתכונות והפונקציות, ומה תפקידן בקוד.
מי שמסתכל על הקוד שלנו לא יכול להבין מייד ש־describe_as_a_string מיועדת לפעול רק על מבנים שנוצרו מ־create_user.
הוא עלול לנסות להכניס מבנים אחרים ולהקריס את התוכנית, או גרוע מכך – להיתקל בבאגים בעתיד, בעקבות שימוש לא נכון בפונקציה.
במהלך המחברת ראינו דוגמאות למבנים שהגדרנו כאוספים של תכונות ושל פעולות.
משתמש באפליקציית צ'יקצ'וק, לדוגמה, מורכב מהתכונות שם פרטי, שם משפחה, כינוי וגיל, ומהפעולות "חגוג יום הולדת" ו"תאר כמחרוזת".
נורה עשויה להיות מורכבת מהתכונות צבע ומצב (דולקת או לא), ומהפעולות "הדלק נורה" ו"כבה נורה".
מחלקה היא דרך לתאר לפייתון אוסף כזה של תכונות ושל פעולות, ולאגד אותן תחת מבנה אחד.
אחרי שתיארנו בעזרת מחלקה אילו תכונות ופעולות מאפיינות עצם מסוים, נוכל להשתמש בה כדי לייצר כמה עצמים כאלו שנרצה.
נדמיין מחלקה כמו שבלונה – תבנית שמתארת אילו תכונות ופעולות מאפיינות סוג עצם מסוים.
מחלקה שעוסקת במשתמשים, לדוגמה, תתאר עבור פייתון מאילו תכונות ופעולות מורכב כל משתמש.
בעזרת אותה מחלקת משתמשים (או שבלונת משתמשים, אם תרצו), נוכל ליצור משתמשים רבים.
כל משתמש שניצור באמצעות השבלונה ייקרא "מופע" (או Instance) – יחידה אחת, עצמאית, שמכילה את התכונות והפעולות שתיארנו.
אנחנו נשתמש במחלקה שוב ושוב כדי ליצור כמה משתמשים שנרצה, בדיוק כמו שנשתמש בשבלונה.
יש עוד הרבה מה להגיד והרבה מה להגדיר, אבל נשמע שמתחתי אתכם מספיק.
בואו ניגש לקוד!
ראשית, ניצור את המחלקה הפשוטה ביותר שאנחנו יכולים לבנות, ונקרא לה User.
בהמשך המחברת נרחיב את המחלקה, והיא תהיה זו שמטפלת בכל הקשור במשתמשים של צ'יקצ'וק:
In [ ]:
class User:
pass
ניסינו ליצור את המבנה הכי קצר שאפשר, אבל class
חייב להכיל קוד.
כדי לעקוף את המגבלה הזו, השתמשנו במילת המפתח pass
, שאומרת לפייתון "אל תעשי כלום".
בקוד שלמעלה השתמשנו במילת המפתח class
כדי להצהיר על מחלקה חדשה.
מייד לאחר מכן ציינו את שם המחלקה שאנחנו רוצים ליצור – User במקרה שלנו.
שם המחלקה נתון לחלוטין לבחירתנו, והמילה User לא אומרת לפייתון שום דבר מיוחד. באותה המידה יכולנו לבחור כל שם אחר.
הדבר שחשוב לזכור הוא שהמחלקה היא לא המשתמש עצמו, אלא רק השבלונה שלפיה פייתון תבנה את המשתמש.
אמנם כרגע המחלקה User ריקה ולא מתארת כלום, אבל פייתון עדיין תדע ליצור משתמש חדש אם נבקש ממנה לעשות זאת.
נבקש מהמחלקה ליצור עבורנו משתמש חדש. נקרא לה בשמה ונוסיף סוגריים, בדומה לקריאה לפונקציה:
In [ ]:
user1 = User()
כעת יצרנו משתמש, ואנחנו יכולים לשנות את התכונות שלו.
מבחינה מילולית, נהוג להגיד שיצרנו מופע (Instance) או עצם (אובייקט, Object) מסוג User, ששמו user1.
השתמשנו לשם כך במחלקה בשם User.
נשנה את תכונות המשתמש.
כדי להתייחס לתכונה של מופע כלשהו בפייתון, נכתוב את שם המשתנה שמצביע למופע, נקודה, ואז שם התכונה.
אם נרצה לשנות את התכונה – נבצע אליה השמה:
In [ ]:
user1.first_name = "Miles"
user1.last_name = "Prower"
user1.age = 8
user1.nickname = "Tails"
נוכל לאחזר את התכונות הללו בקלות, באותה הצורה:
In [ ]:
print(user1.age)
ואם נבדוק מה הסוג של המשתנה user1, מצפה לנו הפתעה נחמדה:
In [ ]:
type(user1)
איזה יופי! המחלקה גרמה לכך ש־User הוא ממש סוג משתנה בפייתון עכשיו.
קחו לעצמכם רגע להתפעל – יצרנו סוג משתנה חדש בפייתון!
אם כך, המשתנה user1 מצביע על מופע של משתמש, שסוגו User.
ננסה ליצור מופע נוסף, הפעם של משתמש אחר:
In [ ]:
user2 = User()
user2.first_name = "Harry"
user2.last_name = "Potter"
user2.age = 39
user2.nickname = "BoyWhoLived1980"
ונשים לב ששני המופעים מתקיימים זה לצד זה, ולא דורסים את הערכים זה של זה:
In [ ]:
print(f"{user1.first_name} {user1.last_name} is {user1.age} years old.")
print(f"{user2.first_name} {user2.last_name} is {user2.age} years old.")
המצב הזה מתקיים כיוון שכל קריאה למחלקה User יוצרת מופע חדש של משתמש.
כל אחד מהמופעים הוא ישות נפרדת שמתקיימת בזכות עצמה.
צרו מחלקה בשם Point שמייצגת נקודה.
צרו 2 מופעים של נקודות: אחת בעלת x שערכו 3 ו־y שערכו 1, והשנייה בעלת x שערכו 4 ו־y שערכו 1.
חשוב!
פתרו לפני שתמשיכו!
שמות מחלקה ייכתבו באות גדולה בתחילתם, כדי להבדילם מפונקציות וממשתנים רגילים.
אם שם המחלקה מורכב מכמה מילים, האות הראשונה בכל מילה תהא אות גדולה. בשם לא יופיעו קווים תחתונים.
לדוגמה, מחלקת PopSong.
יצירת מחלקה ריקה זה נחמד, אבל זה לא מרגיש שעשינו צעד מספיק משמעותי כדי לשפר את איכות הקוד מתחילת המחברת.
לדוגמה, אם אנחנו רוצים להדפיס את הפרטים של משתמש מסוים, עדיין נצטרך לכתוב פונקציה כזו:
In [ ]:
def describe_as_a_string(user):
full_name = f'{user.first_name} {user.last_name}'
return f'{user.nickname} ({full_name}) is {user.age} years old.'
print(describe_as_a_string(user2))
הפונקציה עדיין מסתובבת לה חופשייה ולא מאוגדת תחת אף מבנה – וזה בדיוק המצב שניסינו למנוע.
למזלנו הפתרון לבעיית איגוד הקוד הוא פשוט. נוכל להדביק את קוד הפונקציה תחת המחלקה User
:
In [ ]:
class User:
def describe_as_a_string(user):
full_name = f'{user.first_name} {user.last_name}'
return f'{user.nickname} ({full_name}) is {user.age} years old.'
user3 = User()
user3.first_name = "Anthony John"
user3.last_name = "Soprano"
user3.age = 61
user3.nickname = "Tony"
בתא שלמעלה הגדרנו את הפונקציה describe_as_a_string בתוך המחלקה User.
פונקציה שמוגדרת בתוך מחלקה נקראת פעולה (Method), שם שניתן לה כדי לבדל אותה מילולית מפונקציה רגילה.
למעשה, בתא שלמעלה הוספנו את הפעולה describe_as_a_string לשבלונה של המשתמש.
מעכשיו, כל מופע חדש של משתמש יוכל לקרוא לפעולה describe_as_a_string בצורה הבאה:
In [ ]:
user3.describe_as_a_string()
חדי העין שמו ודאי לב למשהו מעט משונה בקריאה לפעולה describe_as_a_string.
הפעולה מצפה לקבל פרמטר (קראנו לו user), אבל כשקראנו לה בתא האחרון לא העברנו לה אף ארגומנט!
זהו קסם ידוע ונחמד של מחלקות: כשמופע קורא לפעולה כלשהי – אותו מופע עצמו מועבר אוטומטית כארגומנט הראשון לפעולה.
לדוגמה, בקריאה user3.describe_as_a_string()
, המופע user3 הועבר לתוך הפרמטר user של describe_as_a_string.
המוסכמה היא לקרוא תמיד לפרמטר הקסום הזה, זה שהולך לקבל את המופע, בשם self.
נשנה את ההגדרה שלנו בהתאם למוסכמה:
In [ ]:
class User:
def describe_as_a_string(self):
full_name = f'{self.first_name} {self.last_name}'
return f'{self.nickname} ({full_name}) is {self.age} years old.'
user3 = User()
user3.first_name = "Anthony John"
user3.last_name = "Soprano"
user3.age = 61
user3.nickname = "Tony"
user3.describe_as_a_string()
טעות נפוצה היא לשכוח לשים self כפרמטר הראשון בפעולות שנגדיר.
צרו פעולה בשם describe_as_a_string עבור מחלקת Point שיצרתם.
הפעולה תחזיר מחרוזת בצורת (x, y).
חשוב!
פתרו לפני שתמשיכו!
הפיסה החסרה בפאזל היא יצירת המופע.
אם נרצה ליצור משתמש חדש, עדיין נצטרך להציב בו תכונות אחת־אחת – וזה לא כזה כיף.
נשדרג את עצמנו ונכתוב פונקציה שקוראת ל־User ויוצרת מופע עם כל התכונות שלו:
In [ ]:
def create_user(first_name, last_name, nickname, current_age):
user = User()
user.first_name = first_name
user.last_name = last_name
user.nickname = nickname
user.age = current_age
return user
user4 = create_user('Daenerys', 'Targaryen', 'Mhysa', 23)
print(f"{user4.first_name} {user4.last_name} is {user4.age} years old.")
אבל הגדרה שכזו, כמו שכבר אמרנו, סותרת את כל הרעיון של מחלקות.
הרי המטרה של מחלקות היא קיבוץ כל מה שקשור בניהול התכונות והפעולות תחת המחלקה.
נעתיק את create_user לתוך מחלקת User, בשינויים קלים:
user = User()
ו־return user
.
In [ ]:
class User:
def describe_as_a_string(self):
full_name = f'{self.first_name} {self.last_name}'
return f'{self.nickname} ({full_name}) is {self.age} years old.'
def create_user(self, first_name, last_name, nickname, current_age):
self.first_name = first_name
self.last_name = last_name
self.nickname = nickname
self.age = current_age
עכשיו נוכל ליצור משתמש חדש, בצורה החביבה והמקוצרת הבאה:
In [ ]:
user4 = User()
user4.create_user('Daenerys', 'Targaryen', 'Mhysa', 23)
user4.describe_as_a_string()
מינרווה מקגונגל יצאה לבילוי לילי בסמטת דיאגון,
ואחרי לילה עמוס בשתיית שיכר בקלחת הרותחת, היא מעט מתקשה לחזור להוגוורטס.
הוסיפו את הפעולות create_point ו־distance למחלקת הנקודה שיצרתם.
הפעולה create_point תקבל כפרמטרים x ו־y, ותיצוק תוכן למופע שיצרתם.
הפעולה distance תחזיר את המרחק של מקגונגל מהוגוורטס, הממוקם בנקודה (0, 0).
נוסחת המרחק היא חיבור בין הערכים המוחלטים של נקודות ה־x וה־y.
לדוגמה:
x = 5, y = 3הוא 8.
x = 0, y = 3הוא 3.
x = -3, y = 3הוא 6.
x = -5, y = 0הוא 5.
x = 0, y = 0הוא 0.
ודאו שהתוכנית שלכם מחזירה Success! עבור הקוד הבא:
In [ ]:
current_location = Point()
current_location.create_point(5, 3)
if current_location.distance() == 8:
print("Success!")
כדי להקל אפילו עוד יותר על המלאכה, בפייתון יש פעולות קסם (Magic Methods).
אלו פעולות עם שם מיוחד, שאם נגדיר אותן במחלקה, הן ישנו את ההתנהגות שלה או של המופעים הנוצרים בעזרתה.
__str__
נתחיל, לדוגמה, מהיכרות קצרה עם פעולת הקסם __str__
(עם קו תחתון כפול, מימין ומשמאל לשם הפעולה).
אם ננסה סתם ככה להמיר למחרוזת את user4 שיצרנו קודם לכן, נקבל בהלה והיסטריה:
In [ ]:
user4 = User()
user4.create_user('Daenerys', 'Targaryen', 'Mhysa', 23)
str(user4)
פייתון אמנם אומרת דברים נכונים, כמו שמדובר באובייקט (מופע) מהמחלקה User ואת הכתובת שלו בזיכרון, אבל זה לא באמת מועיל.
כיוון שפונקציית ההדפסה print, מאחורי הקלעים, מבקשת את צורת המחרוזת של הארגומנט שמועבר אליה,
גם קריאה ל־print ישירות על user4 תיצור את אותה תוצאה לא ססגונית:
In [ ]:
print(user4)
המחלקה שלנו, כמובן, כבר ערוכה להתמודד עם המצב.
בזכות הפעולה describe_as_a_string שהגדרנו קודם לכן נוכל להדפיס את פרטי המשתמש בקלות יחסית:
In [ ]:
print(user4.describe_as_a_string())
אבל יש דרך קלה עוד יותר!
ניחשתם נכון – פעולת הקסם __str__
.
נחליף את השם של הפעולה describe_as_a_string, ל־__str__
:
In [ ]:
class User:
def __str__(self):
full_name = f'{self.first_name} {self.last_name}'
return f'{self.nickname} ({full_name}) is {self.age} years old.'
def create_user(self, first_name, last_name, nickname, current_age):
self.first_name = first_name
self.last_name = last_name
self.nickname = nickname
self.age = current_age
user5 = User()
user5.create_user('James', 'McNulty', 'Jimmy', 49)
print(user5)
ראו איזה קסם! עכשיו המרה של כל מופע מסוג User למחרוזת היא פעולה ממש פשוטה!
בתא שלמעלה, הגדרנו את פעולת הקסם __str__
.
הפעולה מקבלת כפרמטר את self, המופע שביקשנו להמיר למחרוזת,
ומחזירה לנו מחרוזת שאנחנו הגדרנו כמחרוזת שמתארת את המופע.
הגדרת פעולת הקסם __str__
עבור מחלקה מסוימת מאפשרת לנו להמיר מופעים למחרוזות בצורה טבעית.
__init__
פעולת קסם חשובה אף יותר, ואולי המפורסמת ביותר, נקראת __init__
.
היא מאפשרת לנו להגדיר מה יקרה ברגע שניצור מופע חדש:
In [ ]:
class User:
def __init__(self):
print("New user has been created!")
def __str__(self):
full_name = f'{self.first_name} {self.last_name}'
return f'{self.nickname} ({full_name}) is {self.age} years old.'
def create_user(self, first_name, last_name, nickname, current_age):
self.first_name = first_name
self.last_name = last_name
self.nickname = nickname
self.age = current_age
user5 = User()
user5.create_user('Lorne', 'Malvo', 'Mick', 23)
print(user5)
בדוגמת הקוד שלמעלה הגדרנו את פעולת הקסם __init__
, שתרוץ מייד כשנוצר מופע חדש.
החלטנו שברגע שייווצר מופע של משתמש, תודפס ההודעה New user has been created!.
הכיף הגדול ב־__init__
הוא היכולת שלה לקבל פרמטרים.
נוכל להעביר אליה את הארגומנטים בקריאה לשם המחלקה, בעת יצירת המופע 🤯
In [ ]:
class User:
def __init__(self, message):
self.creation_message = message
print(self.creation_message)
def __str__(self):
full_name = f'{self.first_name} {self.last_name}'
return f'{self.nickname} ({full_name}) is {self.age} years old.'
def create_user(self, first_name, last_name, nickname, current_age):
self.first_name = first_name
self.last_name = last_name
self.nickname = nickname
self.age = current_age
user5 = User("New user has been created!") # תראו איזה מגניב
user5.create_user('Lorne', 'Malvo', 'Mick', 58)
print(user5)
print(f"We still have the message: {user5.creation_message}")
בתא שלמעלה הגדרנו שפעולת הקסם __init__
תקבל כפרמטר הודעה להדפסה.
ההודעה תישמר בתכונה creation_message השייכת למופע, ותודפס מייד לאחר מכן.
את ההודעה העברנו כארגומנט בעת הקריאה לשם המחלקה, User, שיוצרת את המופע.
ואם כבר יש לנו משהו שרץ כשאנחנו יוצרים את המופע... והוא יודע לקבל פרמטרים...
אתם חושבים על מה שאני חושב?
בואו נשנה את השם של create_user ל־__init__
!
בצורה הזו נוכל לצקת את התכונות למופע מייד עם יצירתו, ולוותר על קריאה נפרדת לפעולה שמטרתה למלא את הערכים:
In [ ]:
class User:
def __init__(self, first_name, last_name, nickname, current_age):
self.first_name = first_name
self.last_name = last_name
self.nickname = nickname
self.age = current_age
print("Yayy! We have just created a new instance! :D")
def __str__(self):
full_name = f'{self.first_name} {self.last_name}'
return f'{self.nickname} ({full_name}) is {self.age} years old.'
user5 = User('Lorne', 'Malvo', 'Mick', 58)
print(user5)
איגדנו את יצירת תכונות המופע תחת פעולה אחת, שרצה כשהוא נוצר.
הרעיון הנפלא הזה נפוץ מאוד בשפות תכנות שתומכות במחלקות, ומוכרת בשם פעולת אתחול (Initialization Method).
זו גם הסיבה לשם הפעולה – המילה init נגזרת מהמילה initialization, אתחול.
שפצו את מחלקת הנקודה שיצרתם, כך שתכיל __init__
ו־__str__
.
חשוב!
פתרו לפני שתמשיכו!
צ'יקצ'וק שמה את ידה על פרטי המשתמשים של הרשת החברתית המתחרה, סניילצ'אט.
רשימת המשתמשים נראית כך:
In [ ]:
snailchat_users = [
['Mike', 'Shugarberg', 'Marker', 36],
['Hammer', 'Doorsoy', 'Tzweetz', 43],
['Evan', 'Spygirl', 'Odd', 30],
]
נניח, לכאורה בלבד, שאנחנו רוצים להעתיק את אותה רשימת משתמשים ולצרף אותה לרשת החברתית שלנו.
קחו דקה וחשבו איך הייתם עושים את זה.
זכרו שקריאה למחלקה User היא ככל קריאה לפונקציה אחרת,
ושהמופע שחוזר ממנה הוא ערך בדיוק כמו כל ערך אחר.
נוכל ליצור רשימת מופעים של משתמשים. לדוגמה:
In [ ]:
our_users = []
for user_details in snailchat_users:
new_user = User(*user_details) # Unpacking – התא הראשון עובר לפרמטר התואם, וכך גם השני, השלישי והרביעי
our_users.append(new_user)
print(new_user)
בקוד שלמעלה יצרנו רשימה ריקה, שאותה נמלא במשתמשים שנגנוב שנשאיל מסניילצ'אט.
נעביר את הפרטים של כל אחד מהמשתמשים המופיעים ב־snailchat_users, ל־__init__
של User,
ונצרף את המופע החדש שנוצר לתוך הרשימה החדשה שיצרנו.
עכשיו הרשימה our_users היא רשימה לכל דבר, שכוללת את כל המשתמשים החדשים שהצטרפו לרשת החברתית שלנו:
In [ ]:
print(our_users[0])
print(our_users[1])
print(our_users[2])
צרו את רשימת כל הנקודות שה־x וה־y שלהן הוא מספר שלם בין 0 ל־6.
לדוגמה, רשימת כל הנקודות שה־x וה־y שלהן הוא בין 0 ל־2 היא:
[(0, 0), (0, 1), (1, 0), (1, 1), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]
חשוב!
פתרו לפני שתמשיכו!
נסקור כמה דוגמאות כדי לוודא שבאמת הבנו כיצד מתנהגות מחלקות.
נגדיר את מחלקת User שאנחנו מכירים, ונצרף לה את הפעולה celebrate_birthday, שכזכור, מגדילה את גיל המשתמש ב־1:
In [ ]:
class User:
def __init__(self, first_name, last_name, nickname, current_age):
self.first_name = first_name
self.last_name = last_name
self.nickname = nickname
self.age = current_age
def celebrate_birthday(self):
age = age + 1
def __str__(self):
full_name = f'{self.first_name} {self.last_name}'
return f'{self.nickname} ({full_name}) is {self.age} years old.'
ניסיון ליצור מופע של משתמש ולחגוג לו יום הולדת יגרום לשגיאה.
תוכלו לנחש מה תהיה השגיאה עוד לפני שתריצו?
In [ ]:
user6 = User('Winston', 'Smith', 'Jeeves', 39)
user6.celebrate_birthday()
ניסינו לשנות את המשתנה age – אך הוא אינו מוגדר.
כדי לשנות את הגיל של המשתמש שיצרנו, נהיה חייבים להתייחס ל־self.age
.
אם לא נציין במפורש שאנחנו רוצים לשנות את התכונה age ששייכת ל־self, פייתון לא תדע לאיזה מופע אנחנו מתכוונים.
נתקן:
In [ ]:
class User:
def __init__(self, first_name, last_name, nickname, current_age):
self.first_name = first_name
self.last_name = last_name
self.nickname = nickname
self.age = current_age
def celebrate_birthday(self):
self.age = self.age + 1
def __str__(self):
full_name = f'{self.first_name} {self.last_name}'
return f'{self.nickname} ({full_name}) is {self.age} years old.'
user6 = User('Winston', 'Smith', 'Jeeves', 39)
print(f"User before birthday: {user6}")
user6.celebrate_birthday()
print(f"User after birthday: {user6}")
באותה המידה, תכונות שהוגדרו כחלק ממופע לא מוגדרות מחוצה לו.
אפשר להשתמש, לדוגמה, בשם המשתנה age מבלי לחשוש לפגוע בתפקוד המחלקה או בתפקוד המופעים:
In [ ]:
user6 = User('Winston', 'Smith', 'Jeeves', 39)
print(user6)
age = 10
print(user6)
כדי לשנות את גילו של המשתמש, נצטרך להתייחס אל התכונה שלו בצורת הכתיבה שלמדנו:
In [ ]:
user6.age = 10
print(user6)
שגיאה שמתרחשת לא מעט היא פנייה לתכונה או לפעולה שלא קיימות עבור המופע.
לדוגמה:
In [ ]:
class Dice:
def __init__(self, number):
if 1 <= number <= 6:
self.is_valid = True
dice_bag = [Dice(roll_result) for roll_result in range(7)]
יצרנו רשימת קוביות וביצענו השמה כך ש־dice_bag תצביע עליה.
כעת נדפיס את התכונה is_valid של כל אחת מהקוביות:
In [ ]:
for dice in dice_bag:
print(dice.is_valid)
הבעיה היא שהקוביה הראשונה שיצרנו קיבלה את המספר 0.
במקרה כזה, התנאי בפעולת האתחול (__init__
) לא יתקיים, והתכונה is_valid לא תוגדר.
כשהלולאה תגיע לקובייה 0 ותנסה לגשת לתכונה is_valid, נגלה שהיא לא קיימת עבור הקובייה 0, ונקבל AttributeError.
נתקן:
In [ ]:
class Dice:
def __init__(self, number):
self.is_valid = (1 <= number <= 6) # לא חייבים סוגריים
dice_bag = [Dice(roll_result) for roll_result in range(7)]
for dice in dice_bag:
print(dice.is_valid)
במחברת זו רכשנו כלים לעבודה עם מחלקות ועצמים, ולייצוג אוספים של תכונות ופעולות.
כלים אלו יעזרו לנו לארגן טוב יותר את התוכנית שלנו ולייצג ישויות מהעולם האמיתי בצורה אינטואיטיבית יותר.
נהוג לכנות את עולם המחלקות בשם "תכנות מונחה עצמים" (Object Oriented Programming, או OOP).
זו פרדיגמת תכנות הדוגלת ביצירת מחלקות לצורך חלוקת קוד טובה יותר,
ובתיאור עצמים מהעולם האמיתי בצורה טובה יותר, כאוספים של תכונות ופעולות.
תכנות מונחה עצמים הוא פיתוח מאוחר יותר של פרדיגמת תכנות אחרת שאתם כבר מכירים, הנקראת "תכנות פרוצדורלי".
פרדיגמה זו דוגלת בחלוקת הקוד לתתי־תוכניות קטנות (מה שאתם מכירים כפונקציות), כדי ליצור קוד שמחולק טוב יותר וקל יותר לתחזוק.
פייתון תומכת הן בתכנות פרוצדורלי והן בתכנות מונחה עצמים.
__init__
ו־__str__
.
כתבו מחלקה המייצגת נתיב תקין במערכת ההפעלה חלונות.
הנתיב מחולק לחלקים באמצעות התו / או התו \.
החלק הראשון בנתיב הוא תמיד אות הכונן ואחריה נקודתיים.
החלקים שנמצאים אחרי החלק הראשון, ככל שיש כאלו, הם תיקיות וקבצים.
דוגמאות לנתיבים תקינים:
המחלקה תכלול את הפעולות הבאות:
In [ ]:
import os
class Path:
def __init__(self, path):
self.fullpath = path
self.parts = list(self.get_parts())
def get_parts(self):
current_part = ""
for char in self.fullpath:
if char in r"\/":
yield current_part
current_part = ""
else:
current_part = current_part + char
if current_part != "":
yield current_part
def get_drive_letter(self):
return self.parts[0].rstrip(":")
def get_dirname(self):
path = "/".join(self.parts[:-1])
return Path(path)
def get_basename(self):
return self.parts[-1]
def get_extension(self):
name = self.get_basename()
i = name.rfind('.')
if 0 < i < len(name) - 1:
return name[i + 1:]
return ''
def is_exists(self):
return os.path.exists(str(self))
def normalize_path(self):
normalized = "\\".join(self.parts)
return normalized.rstrip("\\")
def info_message(self):
return f"""
Some info about "{self}":
Drive letter: {self.get_drive_letter()}
Dirname: {self.get_dirname()}
Last part of path: {self.get_basename()}
File extension: {self.get_extension()}
Is exists?: {self.is_exists()}
""".strip()
def __str__(self):
return self.normalize_path()
EXAMPLES = (
r"C:\Users\Yam\python.jpg",
r"C:/Users/Yam/python.jpg",
r"C:",
r"C:\\",
r"C:/",
r"C:\Users/",
r"D:/Users/",
r"C:/Users",
)
for example in EXAMPLES:
path = Path(example)
print(path.info_message())
print()
מחלקת המוצר בצ'יקצ'וק החליטה להוסיף פיצ'ר שמאפשר למשתמשים ליצור סקרים, וכרגיל כל העבודה נופלת עליכם.
כתבו מחלקה בשם Poll שמייצגת סקר.
פעולת האתחול של המחלקה תקבל כפרמטר את שאלת הסקר, וכפרמטר נוסף iterable עם כל אפשרויות ההצבעה לסקר.
כל אפשרות הצבעה בסקר מיוצגת על ידי מחרוזת.
המחלקה תכיל את הפעולות הבאות:
במקרה של תיקו, החזירו מ־get_winner את אחת האפשרויות המובילות.
החזירו מהפעולות vote, add_option ו־remove_option את הערך True אם הפעולה עבדה כמצופה.
במקרה של הצבעה לאפשרות שאינה קיימת, מחיקת אפשרות שאינה קיימת או הוספת אפשרות שכבר קיימת, החזירו False.
ודאו שהקוד הבא מדפיס רק True עבור התוכנית שכתבתם:
In [ ]:
def cast_multiple_votes(poll, votes):
for vote in votes:
poll.vote(vote)
bridge_question = Poll('What is your favourite colour?', ['Blue', 'Yellow'])
cast_multiple_votes(bridge_question, ['Blue', 'Blue', 'Yellow'])
print(bridge_question.get_winner() == 'Blue')
cast_multiple_votes(bridge_question, ['Yellow', 'Yellow'])
print(bridge_question.get_winner() == 'Yellow')
print(bridge_question.get_votes() == [('Yellow', 3), ('Blue', 2)])
bridge_question.remove_option('Yellow')
print(bridge_question.get_winner() == 'Blue')
print(bridge_question.get_votes() == [('Blue', 2)])
bridge_question.add_option('Yellow')
print(bridge_question.get_votes() == [('Blue', 2), ('Yellow', 0)])
print(not bridge_question.add_option('Blue'))
print(bridge_question.get_votes() == [('Blue', 2), ('Yellow', 0)])
קטניס אוורדין הלכה לאיבוד באיזו זירה מעצבנת, ועכשיו היא מחפשת את הסניף הקרוב של אבו־חסן למנה משולשת ראויה.
צורת הזירה היא משולש שקודקודיו (0, 0), (2, 2) ו־(4, 0).
קטניס מתחילה מאחד הקודקודים ומחליטה על הצעד הבא שלה כך:
כתבו פעולה בשם plot_walks, שמקבלת כפרמטר את מספר הצעדים של קטניס.
הפעולה תצייר מפת נקודות בגודל 4 על 4, שכל נקודה בה מציינת מקום שקטניס סימנה במפה שלה.
השתמשו במנועי חיפוש כדי לקרוא על פעולות קסם שיכולות לעזור לכם, ועל מודולים לשרטוט גרפים.
שימו לב שנקודות יכולות להיות ממוקמות על x ו־y עשרוניים.