July 16, 2021

פייתון 18 - מחלקות חלק ב


פרק קודם:

פייתון 17 - מחלקות חלק א

מה נלמד

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

מחלקות בפייתון

1
2
3
4
5
6
7
8
class MyClass:
Variable = ''

def __init__(self, variable):
self.Variable = variable

def Function(self):
pass

מחלקה

כדי להגדיר את המחלקה כל מה שצריך זה לכתוב את המילה class:

1
2
class MyClass:
pass

*זוכרים pass? זה סתם שומר מקום ששמים אם אין שום דבר לשים אחרי נקודותיים.

ניתן להגדיר מחלקה ריקה שבעצם אין לה מידע ואין לה התנהגות.

בנאים - Constructors

מחלקות הן רק התרשים של המחלקה לאובייקט/עצם.
לעצם קוראים “Instance”.

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

1
2
3
4
5
6
7
8
9
10
11
class MyClass:
def __init__(self, variable):
print("Hello from Ctor")
self.Variable = variable

# \/
instance = MyClass(3)
# ^
# יוצר את העצם
# וקורא לבנאי
print(instance.Variable)

חתימת הבנאי

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

1
def __init(self, variable):

הבנאי היא פונקצייה בשם _init_ והיא תמיד נקראית כך!
הפרמטר הראשון self הוא מילה שמורה בפייתון ומגדיר את “עצמו” בתור משתנה.
הפרמטר השני variable הוא משתנה רגיל שמעבירים לפונקצית הבנאי.

גוף פונקציית הבנאי

1
self.Variable = variable

המשתנה self מגדיר את העצם שאותו יוצרים.
כל גישה למשתנה self ניגשת לעצם החדש שיווצר.
לכן השורה הזו יוצרת משתנה Variable בכל עצם שנוצר.

ניתן לגשת למשתנה משום שמשתנה כזה הוא ציבורי.

1
print(instance.Variable)

עוד מעט נראה איך מגדירים משתנה פרטי שאי אפשר לגשת אליו מבחוץ!

משתני מחלקות

ניתן להגדיר משתנה גם במחלקה במקום בגוף הבנאי:

1
2
class MyClass:
ClassVariable = 3

ההבל בין משתנה מחלקה למשתנה עצם הוא שמשתנה מחלקה נוצר גם בלי יצירה של עצם מהמחלקה.

1
2
3
4
class MyClass:
ClassVariable = 3

print(MyClass.ClassVariable)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyClass:
ClassVariable = 3

# הדפסה ראשונה
print(MyClass.ClassVariable)

# משנים את הערך ל-4
MyClass.ClassVariable = 4

print(MyClass.ClassVariable)

# יצירה של עצם חדש
instance = MyClass()
# הדפסה, מה יודפס?
print(instance.ClassVariable)

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

1
print(MyClass.ClassVariable)

פונקציות

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

1
2
3
4
5
6
class MyClass:
def Func(self):
print("Hello World")

a = MyClass()
a.Func()

תרגיל

1.

  • מה ההבדל בין מחלקה לעצם?
  • מהו בנאי?
  • מה ההבדל בין משתנה סטטי למשתנה עצם?
  1. תיצרו מחלקה למחשבון עם 4 הפעולות הבסיסיות:
  • חיבור
  • חיסור
  • כפל
  • חילוק
  1. כתבו מחלקה שמתארת חיה.
    לחיה יש שם,
    חיה יכולה “לדבר” - היא אומרת שלום עם השם שלה.



עקרונות תכנות מונחה עצמים בפייתון

ארבעת העקרונות המנחים אותנו הם:

  • כימוס
  • ירושה
  • הפשטה
  • פולימורפיזם

כימוס - משתנים פרטיים

עקרון הכימוס מגדיר שיש לשמור על מידע והמצב של המחלקה בתוך המחלקה ושלא תהיה גישה מבחוץ,
למשל הדוגמא הזו לא שומרת על עקרון הכימוס:

1
2
3
4
5
6
7
class Bird:
def __init__(self):
self.Saying = 'Chirp Chirp'

bird = Bird()

print(bird.Saying) # <--- ניתן לגשת למשתנה!

כדי לשמור על עקרון הכימוס ניתן להוסיף שני קווים תחתוניים:

1
2
3
4
5
6
7
class Bird:
def __init__(self):
self.__Saying = 'Chirp Chirp'

bird = Bird()

print(bird.__Saying) # שגיאה!!!!

מאחורי הקלעים

ניתן לראות שפייתון מייצר שם שונה למשתנה כאשר מוסיפים קווים תחתוניים:

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

ירושה

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Person:
def __init__(self):
self.Name = ''
self.Age = 0

def SayHello(self):
print(f'{self.Name} says Hello!')


class Worker(Person):
def __init__(self):
Person.__init__(self)
self.Title = ''
self.Salary = 0

def SayHello(self):
if self.Title == 'Manager':
print(f'{self.Title} {self.Name} says Hello!')
else:
Person.SayHello(self)

def Work(self):
print(f'Work work work...')


worker = Worker()
worker.Title = 'Manager'
worker.Name = 'Bob'

worker.SayHello()
worker.Work()

הגדרה ירושה

הירושה מתבצעת בשורה הזו:

1
class Worker(Person):

פונקציות “יורשות”

הגדרנו את SayHello שכותב שלום עם השם במחלקת הבסיס.

1
2
def SayHello(self):
print(f'{self.Name} says Hello!')

במחלקה הנגזרת ירשנו את הפונקציה הזו ומימשנו פונקציה זהה בחתימה.

1
2
3
4
5
def SayHello(self):
if self.Title == 'Manager':
print(f'{self.Title} {self.Name} says Hello!')
else:
Person.SayHello(self)

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

1
2
else:
Person.SayHello(self)

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

1
2
3
4
5
6
    def __init__(self):
# \/
Person.__init__(self)
# ^
self.Title = ''
self.Salary = 0

דרך שנייה שניתן לגשת למחלקת הבסיס במחלקה הנגזרת היא בעזרת super.

1
2
3
4
5
6
    def __init__(self):
# \/
super().__init__()
# ^
self.Title = ''
self.Salary = 0

הפשטה

יש שני דרכים לבצע הפשטה:

  1. להגדיר מחלקה וליצור ממנה עצם עם מידע ספציפי.
1
2
3
4
5
6
7
8
9
10
11
12
class Animal:
def __init__(self, name, type, size):
self.Name = name
self.Type = type
self.Size = size

def GetDetails(self):
return f'{self.Name} is a {self.Type}, its size is {self.Size}'


dog = Animal('Buddy', 'Dog', 'Small')
print(dog.GetDetails())
  1. להגדיר ירושה ממחלקה אבסטרקטית וליצור את העצם שלה
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Animal:
def __init__(self, name, type, size):
self.Name = name
self.Type = type
self.Size = size

def GetDetails(self):
return f'{self.Name} is a {self.Type}, its size is {self.Size}'


class Dog(Animal):
def __init__(self, name, size):
Animal.__init__(self, name, 'Dog', size)


dog = Dog('Buddy', 'Small')
print(dog.GetDetails())

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

1
2
3
4
5
6
class Animal:
# Type is the name of the animal's type, ex: Dog, Cat, Dolphin, etc...
def __init__(self, name, type, size):
self.Name = name
self.Type = type
self.Size = size

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

פולימורפיזם - פייתון כשפה דינאמית

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

1
2
3
4
5
def MyFunc(number):
return number * 3

MyFunc(4)
MyFunc('Hello World') # עובד אבל אנחנו מקבלים פלט לא צפוי!

התוצאה:

לפייתון יש אפשרות לומר מה הם המשתנים שאנחנו מעבירים ומחזירים מפונקציות:

1
2
3
4
5
def MyFunc(number: int) -> int:
return number * 3

MyFunc(4)
MyFunc('Hello World') # עובד אבל אנחנו מקבלים פלט לא צפוי!
  • בכחול - הגדרנו רמז לפייתון שאנחנו רוצים סוג משתנה מסוג מספר. כשאנחנו רוצים להגדיר סוג משתנה לפרמטר אנחנו מוסיפים נקודותיים : ושם המשתנה, למשל: int, str, list וכדו'...
  • באדום - הגדרנו שהפונקציה הזו מחזירה מספר גם כן.

השימוש של הרמזים האלו לא מכריח את פייתון להשתמש במשתנה מסוג מספר - כי פייתון היא שפה דינאמית.
היא יכולה לקבל כל משתנה וליצור משתנים בזמן ריצה!
תריצו את הקוד הבא ותראו מה קורה!

1
2
3
4
5
6
7
class Person:
pass

person = Person()
person.Name = 'Bob'

print(person.Name)

מה שזה מאפשר לעשות זה פולימורפיזם עם אובייקטים נגזרים מאותה מחלקת בסיס:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Animal:
def Say(self):
pass


class Dog(Animal):
def Say(self):
print("Bark bark")


class Penguin(Animal):
def Say(self):
print("Wack")


def IntroduceAnimal(animal: Animal):
animal.Say()


dog = Dog()
penguin = Penguin()

IntroduceAnimal(dog)
IntroduceAnimal(penguin)

תרגילים

  1. על כל קטע קוד כתבו איזה עקרון ממומש ותסבירו איך הוא ממומש.

א.

1
2
3
4
5
6
7
8
class DBAccess:
def __init__(self):
self.__realAccess = DBAccess('https://localhost:6980', 'user', 'pass')

def ReadList(self, typeName):
query = self.__readAccess.CreateQuery('SELECT * FROM $param')
query.AddParameter('$param', typeName)
return self.__readlAccess.ReadList(query)

ב.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Spell:
def __init__(self, caster):
self.Caster = caster

def Cast(self):
pass


class FireBallSpell(Spell):
def __init__(self, caster, defender):
Spell.__init__(self, caster)
self.Defender = defender

def Cast(self):
wisdomDifference = self.Caster.Wisdom - self.Defender.Wisdom
if wisdomDifference > 0:
damage = self.Caster.Wisdom / self.Defender.MagicDefense
print(f'{self.Caster.Name} casted a fireball for {damage} damage!')
self.Defender.ReduceMagicDamage(damage)


class SmallHealingSpell(Spell):
def __init__(self, caster):
Spell.__init__(self, caster)

def Cast(self):
healingHp = 20
print(f'{self.Caster.Name} healed for {healingHp} hp')
self.Caster.AddHealth(healingHp)


# איפשהו במקום אחר בקוד המשחק שלנו:
spell = PickSpell()
spell.Cast()

ג.

1
2
3
4
5
6
7
8
9
10
11
class Furniture:
def __init__(self, widthCm, heightCm, depthCm):
self.__widthCm = widthCm
self.__heightCm = heightCm
self.__depthCm = depthCm


class SmallChair(Furniture):
def __init__(self):
super().__init__(self, 30, 50, 30)

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

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

מהלך המשחק:

  1. השלב הראשון יהיה לקבוע את סדר השחקנים ולהציג את זה לשחקנים למסך.
  2. כל שחקן משחק בתורו.
  3. המשחק ייתן אופציה לשחקן לבחור קטגוריה ותינתן שאלה ע”פ רמת קושי רנדומלית.
    ככל שרמת הקושי עולה הנקודות ששחקן מקבל על תשובה נכונה עולות.
  4. לשחקן תוצג השאלה עם כל התשובות ועליו לבחור תשובה.
  5. אם השחקן ענה על השאלה נכון השאלה לא תוצג שנית לאף שחקן אחר.
  6. המשחק נגמר כאשר שחקן הגיע לכמות מסוימת של נקודות או אזלו השאלות.
  7. השחקן שיש לו הכי הרבה נקודות ניצח.
  8. אחרי הניצחון המשחק מתרסט וכל השאלות חוזרות להיות פעילות וכמות השחקנים נמחקת.

כדי לייצר מספר רנדומלי ניתן להשתמש בקוד הבא:

1
2
3
import random

print(random.randint(3, 9))

random.randint(min,max) מייצר מספר בין min ל-max.

להלן גם קובץ JSON שמכיל שאלות לדוגמא:



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

פייתון 19 - מחלקות חלק ג

על הפוסט

הפוסט נכתב על ידי Ilya, רישיון על ידי CC BY-NC-ND 4.0.

שתפו את הפוסט

Email Facebook Linkedin Print

קנו לי קפה

#Software#Python