פרק קודם:
פייתון 17 - מחלקות חלק אמה נלמד
- איך לבנות מחלקה בפייתון
- בנאים
- משתני מחלקה
- פונקציות
- ירושה
- פולימורפיזם
- פייתון כשפה דינאמית
מחלקות בפייתון
1 | class MyClass: |
מחלקה
כדי להגדיר את המחלקה כל מה שצריך זה לכתוב את המילה class
:
1 | class MyClass: |
*זוכרים pass
? זה סתם שומר מקום ששמים אם אין שום דבר לשים אחרי נקודותיים.
ניתן להגדיר מחלקה ריקה שבעצם אין לה מידע ואין לה התנהגות.
בנאים - Constructors
מחלקות הן רק התרשים של המחלקה לאובייקט/עצם.
לעצם קוראים “Instance”.
כדי ליצור עצם קוראים למחלקה כמו פונקציה, וזה בעצם קורא לבנאי.
בנאי הוא פונקציה שמופעלת כאשר העצם נוצר.
1 | class MyClass: |
חתימת הבנאי
חתימה של פונקציה מגדירה את השם שלה, פרמטרים ומה היא מחזירה
1 | def __init(self, variable): |
הבנאי היא פונקצייה בשם _init_
והיא תמיד נקראית כך!
הפרמטר הראשון self
הוא מילה שמורה בפייתון ומגדיר את “עצמו” בתור משתנה.
הפרמטר השני variable
הוא משתנה רגיל שמעבירים לפונקצית הבנאי.
גוף פונקציית הבנאי
1 | self.Variable = variable |
המשתנה self
מגדיר את העצם שאותו יוצרים.
כל גישה למשתנה self
ניגשת לעצם החדש שיווצר.
לכן השורה הזו יוצרת משתנה Variable בכל עצם שנוצר.
ניתן לגשת למשתנה משום שמשתנה כזה הוא ציבורי.
1 | print(instance.Variable) |
עוד מעט נראה איך מגדירים משתנה פרטי שאי אפשר לגשת אליו מבחוץ!
משתני מחלקות
ניתן להגדיר משתנה גם במחלקה במקום בגוף הבנאי:
1 | class MyClass: |
ההבל בין משתנה מחלקה למשתנה עצם הוא שמשתנה מחלקה נוצר גם בלי יצירה של עצם מהמחלקה.
1 | class MyClass: |
אפילו יותר מזה, אם משנים את המשתנה הזה ויוצרים עצם חדש, אז העצם ייקבל את הערך הזה גם כן.
1 | class MyClass: |
משתנה כזה לעיתים נקרא סטטי - Static.
משתנים ופונקציות סטטיות מוגדרות ברמת המחלקה ולא ברמת העצם.
זה למה ניתן לבצע הדפסה כזו:
1 | print(MyClass.ClassVariable) |
פונקציות
פונקציות מוגדרות מתחת למחלקה כמו פונקציות רגילות.
הגישה אליהן בעזרת הנקודה .
1 | class MyClass: |
תרגיל
1.
- מה ההבדל בין מחלקה לעצם?
- מהו בנאי?
- מה ההבדל בין משתנה סטטי למשתנה עצם?
- תיצרו מחלקה למחשבון עם 4 הפעולות הבסיסיות:
- חיבור
- חיסור
- כפל
- חילוק
- כתבו מחלקה שמתארת חיה.
לחיה יש שם,
חיה יכולה “לדבר” - היא אומרת שלום עם השם שלה.
מה ההבדל בין מחלקה לעצם?
מחלקה היא כמו תבנית ועצם הוא הבנייה שלה.מהו בנאי?
פונקציה שנקראת כדי לאתחל את העצם של המחלקה.מה ההבדל בין משתנה סטטי למשתנה עצם?
משתנה עצם הוא שונה לכל עצם כאשר משתנה סטטי הוא למחלקה.
לכל עצם של המחלקה יש משתנה סטטי שונה אך המשתנה הסטטי של המחלקה נשאר זהה.
משתנה מחלקה:
1 | class MyClass: |
משתנה עצם:
1 | class MyClass: |
1 | class Calculator: |
1 | class Animal: |
עקרונות תכנות מונחה עצמים בפייתון
ארבעת העקרונות המנחים אותנו הם:
- כימוס
- ירושה
- הפשטה
- פולימורפיזם
כימוס - משתנים פרטיים
עקרון הכימוס מגדיר שיש לשמור על מידע והמצב של המחלקה בתוך המחלקה ושלא תהיה גישה מבחוץ,
למשל הדוגמא הזו לא שומרת על עקרון הכימוס:
1 | class Bird: |
כדי לשמור על עקרון הכימוס ניתן להוסיף שני קווים תחתוניים:
1 | class Bird: |
מאחורי הקלעים
ניתן לראות שפייתון מייצר שם שונה למשתנה כאשר מוסיפים קווים תחתוניים:
שם המשתנה מאחורי הקלעים הוא בעצם _Bird__Saying.
אבל עצם השימוש בקווים תחתוניים אומר למתכנת שזה משתנה פרטי!
ירושה
כפי שהגדרנו בחלק הראשון - ירושה זה לקבל מידע ופונקציות ממחלקת הבסיס.
על מנת לרשת מחלקה משתמשים בסוגריים מעוגלים אחרי שם המחלקה עם מחלקת הבסיס:
1 | class Person: |
הגדרה ירושה
הירושה מתבצעת בשורה הזו:
1 | class Worker(Person): |
פונקציות “יורשות”
הגדרנו את SayHello שכותב שלום עם השם במחלקת הבסיס.
1 | def SayHello(self): |
במחלקה הנגזרת ירשנו את הפונקציה הזו ומימשנו פונקציה זהה בחתימה.
1 | def SayHello(self): |
כדי לבצע את זה צריך להגדיר פונקציה עם אותה השם ועם אותם הפרמטרים אחרת זו תהיה פונקציה חדשה.
אך הפונקציה הזו רק מסתירה את הפונקציה מאחורי הקלעים, עדיין ניתן לקרוא לפונקציה המקורייה וזה מה שעשינו בעצם בהגדרת ה-else:
1 | else: |
בעזרת השם של מחלקת הבסיס אנחנו יכולים לקרוא לפונקציה מתוך הפונקציה החדשה שהגדרנו במחלקה הנגזרת.
אם שמתם לב גם לבנאי קראנו בצורה הזו!
1 | def __init__(self): |
דרך שנייה שניתן לגשת למחלקת הבסיס במחלקה הנגזרת היא בעזרת super
.
1 | def __init__(self): |
הפשטה
יש שני דרכים לבצע הפשטה:
- להגדיר מחלקה וליצור ממנה עצם עם מידע ספציפי.
1 | class Animal: |
- להגדיר ירושה ממחלקה אבסטרקטית וליצור את העצם שלה
1 | class Animal: |
אחד ההבדלים המובהקים הם שאנחנו יוצרים מחלקה נוספת שמסתירה עקרונות מסוימים כמו סוג החיה.
הפשטה כזו היא לעיתים מה שאנחנו רוצים כדי ליצור קוד מובן יותר למתכנתים.
זאת על מנת להסביר את הקוד יותר טוב מבלי לכתוב הערות מיותרות, אחרת היינו צריכים להסביר את מהות המשתנה Type
.
1 | class Animal: |
במושגי תכנות מונחה עצמים זה לא מה שאנחנו רוצים כי יש לנו את הכלים לבצע את זה בצורה יותר מסודרת ויותר מובנת.
פולימורפיזם - פייתון כשפה דינאמית
פייתון היא שפה דינאמית - זאת אומרת שאנחנו יכולים לעשות כל מיני טריקים כמו להעביר משתנים שלא ציפינו להם:
1 | def MyFunc(number): |
התוצאה:
לפייתון יש אפשרות לומר מה הם המשתנים שאנחנו מעבירים ומחזירים מפונקציות:
1 | def MyFunc(number: int) -> int: |
- בכחול - הגדרנו רמז לפייתון שאנחנו רוצים סוג משתנה מסוג מספר. כשאנחנו רוצים להגדיר סוג משתנה לפרמטר אנחנו מוסיפים נקודותיים : ושם המשתנה, למשל: int, str, list וכדו'...
- באדום - הגדרנו שהפונקציה הזו מחזירה מספר גם כן.
השימוש של הרמזים האלו לא מכריח את פייתון להשתמש במשתנה מסוג מספר - כי פייתון היא שפה דינאמית.
היא יכולה לקבל כל משתנה וליצור משתנים בזמן ריצה!
תריצו את הקוד הבא ותראו מה קורה!
1 | class Person: |
מה שזה מאפשר לעשות זה פולימורפיזם עם אובייקטים נגזרים מאותה מחלקת בסיס:
1 | class Animal: |
תרגילים
- על כל קטע קוד כתבו איזה עקרון ממומש ותסבירו איך הוא ממומש.
א.
1 | class DBAccess: |
ב.
1 | class Spell: |
ג.
1 | class Furniture: |
2.
בעזרת תכנות מונחה עצמים נממש משחק טריויה!
למשחק יהיה תפריט ראשי שניתן לבחור כמות שחקנים.
אחרי שבוחרים כמות שחקנים כל שחקן ייקבל שם רנדומלי (צורת המימוש יכולה להיות שלכם).
מאיפה התכנית מקבלת שאלות:
תשתמשו באבסטרקציה ופולימורפיזם כדי לכתוב קוד שלוקח את השאלות שלו ממקור כלשהו.
כמו כן עליכם לקבוע כיצד שאלה נראית, כמה תשובות יש לה.
יש לשייך שאלה לקטגוריה מסוימת.
לכל שאלה יש רמת קושי.
מהלך המשחק:
- השלב הראשון יהיה לקבוע את סדר השחקנים ולהציג את זה לשחקנים למסך.
- כל שחקן משחק בתורו.
- המשחק ייתן אופציה לשחקן לבחור קטגוריה ותינתן שאלה ע”פ רמת קושי רנדומלית.
ככל שרמת הקושי עולה הנקודות ששחקן מקבל על תשובה נכונה עולות. - לשחקן תוצג השאלה עם כל התשובות ועליו לבחור תשובה.
- אם השחקן ענה על השאלה נכון השאלה לא תוצג שנית לאף שחקן אחר.
- המשחק נגמר כאשר שחקן הגיע לכמות מסוימת של נקודות או אזלו השאלות.
- השחקן שיש לו הכי הרבה נקודות ניצח.
- אחרי הניצחון המשחק מתרסט וכל השאלות חוזרות להיות פעילות וכמות השחקנים נמחקת.
כדי לייצר מספר רנדומלי ניתן להשתמש בקוד הבא:
1 | import random |
random.randint(min,max)
מייצר מספר בין min ל-max.
להלן גם קובץ JSON שמכיל שאלות לדוגמא:
א.
הקוד מממש את עקרון הכימוס.
מאחורי הקלעים הוא שומר משתנה פרטי בשם __realAccess
ולא מאפשר לקוד מבחוץ לגשת אליו בקלות.
ב.
העקרון הממומש הוא ירושה ופולימורפיזם,
הסיבה לכך שההתנהגות שלנו משתנה בין קסם לקסם שממומש בעזרת מחלקות,
השימוש בפולימורפיזם מתבטא בכך שאנחנו בוחרים בקסם כלשהו ולא ידוע לנו איזה קסם נבחר,
ואנחנו משתמשים במתודת Cast
שכל מחלקת קסם מממשת.
ג.
העקרון המומש הוא עקרון ההפשטה.
ההפשטה היא עצם קיום מחלקת Furniture
אשר מעניקה יכולת אבסטרקטית לתיאור רהיטים.
מכיוון ש”רהיט” זה רעיון מופשט - יכול להיות כיסא, יכול להיות שולחן וכדו’…
עיצוב
קודם נעצב את המחלקות וניתן מבט ראשון התוכנה שלנו תיראה.
המחלקות הראשונות שלנו יהיו מחלקות מידע (מודלים!)
מודלים
שאלה
מחלקת שאלה היא מחלקה שמחזיקה:
- תוכן השאלה
- תשובות
- תשובה נכונה
- קושי
- מזהה שאלה - מספר כלשהו שמזהה את השאלה כמשהו ייחודי
- האם ענינו על השאלה
קטגוריה
- שאלות (רשימה של שאלות)
- שם קטגוריה
- כמה שאלות נענו מהקטגוריה
שחקן
- שם
- כמות נקודות
מחלקות
יוצר טריויה
טוען את רשימת השאלות ומחזיר רשימה של קטגוריות
- GetCategories()
יוצר שם שחקן
מחזיר שם של שחקן ייחודי
- GetName()
מחלקת ניהול שחקנים
המחלקה תחזיק את רשימת השקחנים ותיתן לנו את השחקן הבא שיישחק.
- ResetPlayers()
- GetNextPlayer()
- GetPlayesr()
משחק
מחלקת המשחק הראשית מבצעת את מהלך המשחק
- רשימת שחקנים
- רשימת קטגוריות
- השחקן הנוכחי שמשחק
- פונקציית Run() - מבצעת את מהלך המשחק
תפריט ראשי
יהיו שני אופציות בתפריט הזה - שחק ולצאת
לאחר מכן הוא יריץ את המשחק
קוד פתרון
העיצוב הבסיסי נתן לנו כיוון והמימוש הוא כבר חלק מהקוד.
את הקוד לפתרון ניתן למצוא כאן:
Github
יותר מדי מחלקות קוד ולכן לא יכולתי לעלות את הכל במקטע אחד.
כדי להריץ יש להריץ את Main.py.
1 | python Main.py |
הפתרון מכיל מחלקות בסיסיות לטיפול בקוד, שימו לב שהרוב נופל על מחלקת המשחק,
היינו יכולים לפצל את המחלקה הזו עוד יותר אך פונקציות היו מספיק מועילות.
מילת המפתח כאן היא - מספיק.
כמובן שניתן לפצל יותר ויותר, לכתוב מחלקות נוספות ולסבך את הקוד עוד יותר.
אך אנחנו צריכים למצוא את האיזון הדק בין מורכבות לפשטות.
בפרק הבא נעסוק בעיצוב מחלקות, כיצד לגשת לחשיבה נכונה ונלמד עקרונות עיצוב בסיסיים:
פייתון 19 - מחלקות חלק ג