תחביר מחלקה בפייתון#
טקסט המופיע למטה בסגול מציין קטעים המופיעים בסרטון
ניתן להיעזר בו כדי לחזור על התכנים או לעיין בהם שוב.
בפייתון מחלקה מוגדרת באמצעות מילת המפתח class עם נקודותיים והזחה.
כל מה שמוזח ימינה משויך להגדרה של המחלקה.
בתוך מחלקה נגדיר את המתודות שונות. בד”כ נתחיל ממתודת הבנאי. לבנאי בפייתון יש שם מיוחד: __init__.
שדות המחלקה הם בעצם משתנים שנשמרים בתוך האובייקט. כדי לגשת לשדה מתוך מתודות המחלקה, משתמשים במילה השמורה self. המילה self מייצגת את האובייקט הנוכחי – כלומר את המופע שעליו מופעלת המתודה.
כאשר כותבים self.name או self.grade, למעשה מוסיפים או ניגשים לשדה בשם name או grade.
לכן, גם המילה self תמיד תהיה הפרמטר הראשון בקלט של מתודות - אחרת, לא ניתן היה לגשת לשדות המחלקה בתוך המתודות.
class ClassName:
"""documentation string"""
def __init__(self):
self.field1 = 0
self.field2 = "value"
def method_name1(self, arg1, arg2):
# method code
def method_name2(self, arg1):
# method code
Cell In[2], line 11
def method_name2(self, arg1):
^
IndentationError: expected an indented block after function definition on line 8
חשוב לדעת: עוד על self
בפייתון, המילה self היא הדרך של המחלקה להתייחס אל האובייקט עצמו – כלומר אל המופע שעליו המתודה מופעלת. כאשר אנו מגדירים מחלקה, כל מתודה מקבלת כפרמטר ראשון את self. זהו פרמטר שמועבר באופן אוטומטי בכל קריאה למתודה, ובאמצעותו ניתן לגשת בגוף המתודה לשדות ולמתודות של אותו אובייקט ספציפי.
הופעתו של self בתחילת כל מתודה איננה סתם מוסכמה, אלא מנגנון שמאפשר לנו להבדיל בין השדות וההתנהגויות של מופעים שונים שנוצרו מאותה מחלקה. כך למשל, אם ניצור שני אובייקטים ממחלקה “סטודנט”, לכל אחד מהם יהיו ערכים שונים לשדות כמו name או grade. השימוש ב־self.name או self.grade מבטיח שכל אובייקט ישמור על המידע הייחודי שלו, גם אם שניהם חולקים את אותה מתודה.
דוגמא#
כעת נממש מחלקה פשוטה המייצגת נקודה דו-מימדית:
כדי לייצג את היישות “נקודה דו מימדית”, נבנה טיפוס נתונים חדש, או מחלקה חדשה, בשם Point.
מקובל שבפייתון שמות של מחלקות מתחילים באות גדולה.
class Point:
"""represents a point in a 2D space.
Attributes: x, y """
כעת נאתחל את שדות המחלקה.
זכרו כי השדות מאותחלים בד”כ בבנאי (__init__):
class ClassName:
"""documentation string"""
def __init__(self):
self.field1 = 0 # <-- A new field initialization
self.field2 = "value" # <-- A new field initialization
שימו לב!
מתודת הבנאי לא מכילה את הפקודה return. היא אוטומטית מאחורי הקלעים יוצרת ומחזירה את האובייקט, ללא הפקודה.
אילו שדות נצטרך לאובייקט הנקודה הדו-מימדית שלנו?
נקודה על מרחב דו מימדי יכולה להיות מוגדרת על ידי מיקומה בצירים x ו-y.
לכן, נאתחל בהתאמה שני שדות x וy בבנאי.
class Point:
"""represents a point in a 2D space.
Attributes: x, y """
def __init__(self, x, y):
self.x = x
self.y = y
שימו לב
בפייתון, כל בנאי חייב להיקרא בשם __init__. המתודה הזו מופעלת אוטומטית בכל פעם שנוצר מופע חדש של מחלקה, ותפקידה לאתחל את השדות של אותו מופע. הפרמטר הראשון שלה הוא תמיד self, המייצג את האובייקט שנוצר.
המתודה __init__ מחזירה באופן מרומז הפניה לאובייקט החדש שנוצר ללא צורך בreturn בסופה. כלומר, לאחר הקריאה לבנאי, יוחזר מופע של המחלקה, שבו כבר אותחלו כל השדות שהוגדרו.
אם לא מגדירים מפורשות בנאי במחלקה, פייתון מספקת בנאי ברירת מחדל ריק, שאינו מקבל פרמטרים ואינו מאתחל שדות. במקרה כזה האובייקט נוצר, אך ללא שדות יחודיים.
חשוב לשים לב שבזמן הקריאה לבנאי או לכל מתודה אחרת במחלקה מתודות המחלקה, המשתמש אינו שולח את self – פייתון יוצרת אותו מאחורי הקלעים ומעבירה אותו כארגומנט ראשון.
לאחר שהגדרנו את המחלקה ואת הבנאי שלה, ניתן ליצור אובייקט חדש מסוג Point.
נעשה זאת על ידי קריאה לשם המחלקה, והכנסת הקלטים לבנאי בסוגריים.
p1 = Point(3.0, 4.0)
print(p1.x)
print(p1.y)
3.0
4.0
אז מה קרה כאן בעצם?
יצרנו אובייקט מהסוג Point, אשר הוזנו לו ערכי השדות במהלך הקריאה פונקציית הבנאי.
לאחר שהסתיימה הקריאה, הצבנו את האובייקט במשתנה p1. כאשר אנחנו כותבים p1., אנחנו ניגשים לשדות ולמתודות של האובייקט p1. לכן, כשניגשנו לp1.x ביקשנו את השדה x השייך לאובייקט p1.
שימו לב
בקטע הקוד לעיל קיים הבדל מהותי בין המשתנים שמועברים כפרמטרים לבנאי (x, y) לבין השדות self.x ו-self.y. הפרמטרים הפורמליים x ו-y קיימים רק בזמן הקריאה לבנאי – הם לוקליים לבנאי, ולא ניתן לגשת אליהם מחוץ לו, אפילו במתודות אחרות של האובייקט. לעומת זאת, כאשר אנו כותבים self.x = x ו-self.y = y, אנחנו יוצרים שדות קבועים בתוך האובייקט הנוכחי. השדות האלה ילוו את האובייקט לכל אורך חייו, ויהיו נגישים בכל מתודה אחרת שנקרא על אותו מופע דרך self.
p1 = Point(3.0, 4.0)
x = p1.y
print(x)
print(p1.x)
4.0
3.0
מתודות#
כעת נתמקד בהגדרה ושימוש במתודות חדשות במחלקה.
ניתן להגדיר מתודה חדשה באמצעות המילה def בתוך ההזחה של המחלקה.
class ClassName:
"""documentation string"""
def __init__(self):
self.field1 = 0
self.field2 = "value"
def method_name1(self, arg1, arg2): # <-- Definition of a new method
# method code
def method_name2(self, arg1):
# method code
תזכורת
בכל מתודה שמוגדרת בתוך מחלקה, הארגומנט הראשון הוא תמיד self. למרות שהוא מופיע בהגדרה, כאשר אנו מפעילים את המתודה בפועל איננו מעבירים אותו – פייתון עושה זאת מאחורי הקלעים. למעשה, self מייצג את האובייקט שהפעיל את המתודה.
באמצעות self ניתן לגשת לשדות האובייקט בתוך המחלקה, וגם להפעיל מתודות אחרות מאותה מחלקה. רק כך אפשר להבטיח שהמתודה תעבוד על שדות המחלקה של אותו המופע.
זכרו כי ניתן להשתמש בself רק בתוך המחלקה, גישה לשדה או מתודה של אובייקט מחוץ למחלקה נעשית ישירות מתוך האובייקט (המופע של המחלקה).
כעת, נוסיף מתודה המחזירה מחרוזת המייצגת את האובייקט שלנו בפורמט הבא: <x,y>.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def get_str_point(self): # New method
return f'<{self.x},{self.y}>'
p1 = Point(3.0, 4.0)
print(p1.get_str_point())
<3.0,4.0>
נסו בעצמכם#
ממשו את המתודה distance של המחלקה Point. אשר מחזירה את המרחק בין הנקודה ממנה הופעלה המתודה לבין נקודה נוספת המועברת בקלט המתודה (other).
| Calculate the distance between between one point to another |
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def get_str_point(self):
return f'<{self.x},{self.y}>'
def distance(self, other):
return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5
p1 = Point(2,-3)
p2 = Point(4,5)
print(p1.distance(p2)) # Should print 8.246..
לחצו כאן כדי לצפות בפתרון
def distance(self, other):
return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5
הערה
היה ניתן לממש מתודה זאת גם בפונקציה רגילה, שאינה מתודה.
ניצור כעת פונקציה חדשה (לא מתודה) המזיזה נקודה point (מסוג Point) בdx לאורך ציר ה-x וב-dy לאורך ציר הy:
def move_point(point, dx, dy):
point.x += dx
point.y += dy
p1 = Point(3.0, 4.0)
print(p1.get_str_point())
move_point(p1, 2, -1)
print(p1.get_str_point())
הפונקציה הזו איננה מתודה, כלומר לא משויכת לאובייקט ואינה חלק מהמחלקה. לכן, במקום להשתמש בself, נעביר את האובייקט עצמו לפונקציה (point), וניגש ממנו לשדות x ו-y.
שימו לב
השימוש במילה self נעשה אך ורק בתוך מחלקה, כאשר פונים אל האובייקט שאותו יוצרים (בבנאי) או האובייקט עליו מופעלת המתודה (במתודות אחרות).
מחוץ למחלקה לעולם לא נשתמש בself, אלא נפנה לאובייקט הקונקרטי ישירות (דרך המשתנה המכיל אותו).
נסו בעצמכם#
כתבו את הפונקציה middle_point(p1,p2) המקבלת שני אובייקטים מסוג Point ומחזירה את הנקודה האמצעית בין שתי נקודות - גם היא מסוג Point.
def middle_point(p1, p2):
# Write you code here
pass
p1 = Point(1,1)
p2 = Point(3,5)
p3 = middle_point(p1,p2)
print(p3.get_str_point()) # printed value be <2.0,3.0>
לחצו כאן כדי לצפות בפתרון.
def middle_point(p1, p2):
# Calculate new x and y
new_x = (p1.x + p2.x) / 2
new_y = (p1.y + p2.y) / 2
return Point(new_x, new_y) # Create a Point object with these values
דוגמא מסכמת: ייצוג מעגל בפייתון#
טקסט המופיע למטה בסגול מציין קטעים המופיעים בסרטון
ניתן להיעזר בו כדי לחזור על התכנים או לעיין בהם שוב.
נראה כעת דוגמא מסכמת למימוש מחלקה בה נייצג מעגל באמצעות תכנות מונחה עצמים, ואחריה תוכלו לתרגל זאת בעצמכם.
מתמטית, ניתן לייצג מעגל באמצעות נקודה ורדיוס.
כדי לייצג מעגל כמחלקה בפייתון, נגדיר מחלקה חדשה שנקרא לה Circle - אשר תכיל שני שדות (שנקבל בבנאי):
center- מטיפוס המחלקהPointשיצרנו למעלה.radius- מטיפוסfloat.
נגדיר כעת את המתודות שנרצה עבור מחלקת Circle.
שם מתודה |
תיאור |
קלט |
ערך החזר |
|---|---|---|---|
|
הבנאי |
center ( |
- |
|
הדפסת ייצוג המעגל (מרכז המעגל והרדיוס) למסך |
- |
- |
|
בדיקה האם הנקודה נופלת בתוך המעגל |
אובייקט מטיפוס |
True / False |
רשימת דרישות כזו נקראת גם מפרט (specification) – היא מתארת מה המחלקה אמורה לעשות, באילו פעולות היא צריכה לתמוך, ומה הפלט המצופה מכל פעולה. שימו לב שמפרט לא קובע איך בדיוק זה יתבצע בקוד.
מימוש המחלקה Circle בפייתון:#
כשלב ראשון - הגדרנו את המחלקה ואת שתי המתודות הראשונות במפרט.
הגדרנו את הבנאי, אשר מקבל ערכים ומציב אותם בשדות המתאימים.
בנוסף, הגדרנו את המתודה print_circle אשר מדפיסה את ערכי מרכז המעגל והרדיוס למסך. שימו לב שעבור מתודה זו, ניגשנו לשדה x של האובייקט self.center - שהוא מסוג Point.
class Circle:
""" represents a circle shape
attributes: center, radius"""
def __init__(self, center, radius):
self.center = center
self.radius = radius
def print_circle(self):
print('center:', self.center.x, self.center.y, \
', radius:', self.radius)
כעת ניתן ליצור אובייקט מעגל חדש, ולהשתמש במתודה print_circle כדי להדפיס את הייצוג שלו.
c = Circle(Point(1,2), 1)
c.print_circle()
center: 1 2 , radius: 1
כעת, נוסיף את המתודה in_circle, המקבלת אובייקט מסוג נקודה, ומחזירה אם הנקודה נופלת בתוך אובייקט המעגל.
לשם כך ניעזר במתודה distance של המחלקה Point.
class Circle:
def __init__(self, center, radius):
self.center = center
self.radius = radius
def print_circle(self):
print('center:', self.center.x, self.center.y, \
', radius:', self.radius)
### new code here ###
def in_circle(self, p):
return self.center.distance(p) < self.radius
#####################
c = Circle(Point(1,1), 2)
print(c.in_circle(Point(0,0)))
print(c.in_circle(Point(2,3)))
תרגול: בניית מלבן#
כעת אתם תבנו בעצמכם מחלקה המייצגת מלבן לפי המפרט הבא:
המחלקה תיקרא Rectangle ותייצג מלבן במישור דו־ממדי. למחלקה יהיו השדות הבאים:
width- מספר שלם או עשרוני (intאוfloat) המתאר את רוחב המלבן.height-מספר שלם או עשרוני (intאוfloat) המתאר את גובה המלבן.corner- אובייקט מטיפוסPointהמייצג את הפינה השמאלית־עליונה של המלבן.
הניחו שהמלבן מקביל לציר ה-x, כלומר צלעותיו מונחות במקביל לצירים של מערכת הצירים.
בנוסף, למחלקה יהיו את המתודות הבאות:
__init__(self, width, height, corner)- בנאי שמקבל את רוחב המלבן, גובהו ואת הפינה השמאלית־עליונה (אובייקט מסוג Point) ומאתחל את השדות המתאימים.get_str(self)- מתודה המחזירה מחרוזת המכילה את פרטי המלבן (רוחב, גובה ומיקום הפינה השמאלית־עליונה).get_center(self)- מתודה שמחזירה אובייקט מטיפוסPointהמייצג את מרכז המלבן.רמז: כדי לחשב את מרכז המלבן, יש להשתמש בקואורדינטות של הפינה השמאלית-עליונה, ולהוסיף חצי מהרוחב לכיוון ה-x, ולהחסיר חצי מהגובה לכיוון ה-y.
class Rectangle:
"""represents a rectangle in a 2D space.
attributes: width, height, lower-left corner"""
def __init__(self, width, height, corner):
# Write you code here
pass
def get_str(self):
# Write you code here
pass
def get_center(self):
# Write you code here
pass
rect = Rectangle(100, 200, Point(0,0))
print(rect.get_str())
print(rect.get_center())
rect = Rectangle(100, 200, Point(0,0))
center = rect.get_center()
print(center.get_str_point())
לחצו פה כדי לצפות בפתרון
class Rectangle:
"""represents a rectangle in a 2D space.
attributes: width, height, lower-left corner"""
def __init__(self, width, height, corner):
# Write you code here
self.width = width
self.height = height
self.corner = corner
def get_str(self):
# Write you code here
return f"point:{self.corner.x} {self.corner.y}, size: {self.width}x{self.height}"
def get_center(self):
# Write you code here
center_x = self.corner.x + self.width / 2
center_y = self.corner.y - self.height / 2
return Point(center_x, center_y)