OOP חלק א׳ — Classes ו-Encapsulation
OOP חלק א׳ — Classes, Objects ו-Encapsulation
שיעור זה הוא הכי קריטי לסדנה. OOP אוגד נתונים ופעולות לתוך ישות אחת — Class. במקום 3000 משתנים לניהול 1000 סטודנטים, כותבים class אחד.
- Class ו-Object — blueprint vs instance, type(), isinstance()
- __init__ Constructor — אתחול attributes, ברירות מחדל
- Class Attributes vs Instance Attributes — משותפים vs ייחודיים
- Encapsulation — public/protected/private, Name Mangling
- Properties — @property, @setter, computed read-only properties
- Method types — instance, @classmethod, @staticmethod
- Magic Methods (Dunder) — __str__, __repr__, __len__, __eq__, __add__
- UML Class Diagram — קריאה וכתיבה, סמלים +/-/#
- Python vs C# — טבלת השוואה מקיפה
Class ו-Object: הרעיון הבסיסי
Class = תבנית (blueprint). Object = המופע (instance) הספציפי. class Dog מגדיר מבנה; rex = Dog("Rex") יוצר object.
__init__: ה-Constructor
__init__ נקראת אוטומטית בעת יצירת Object. self = ה-object הנוכחי (כמו this ב-C#).
class Student:
def __init__(self, name, age, grades=None):
self.name = name # instance attribute
self.age = age # instance attribute
self.grades = grades if grades is not None else []
def get_average(self):
if not self.grades:
return 0
return sum(self.grades) / len(self.grades)
# יצירת instances
alice = Student("Alice", 20, [90, 85, 92])
bob = Student("Bob", 22) # grades=[] כברירת מחדל
print(alice.name) # Alice
print(alice.get_average()) # 89.0
print(bob.get_average()) # 0
print(type(alice)) # <class '__main__.Student'>
print(isinstance(alice, Student)) # True
print(alice is bob) # False — שני objects שונים!alice.get_average() = Python מעבירה Student.get_average(alice). self = alice.
class Dog:
species = "Canis"
def __init__(self, name):
self.name = name
d = Dog("Rex")
print(d.species, d.name)מה הפלט?Class Attributes vs Instance Attributes
Class Attributes — משותפים לכל ה-objects. Instance Attributes — ייחודיים לכל object (נכתבים ב-__init__ עם self).
class BankAccount:
# Class Attributes — משותף לכל instances
bank_name = "בנק גאמא"
interest_rate = 0.03
total_accounts = 0
def __init__(self, owner, balance=0):
# Instance Attributes — ייחודי לכל instance
self.owner = owner
self.balance = balance
self.account_id = BankAccount.total_accounts + 1
BankAccount.total_accounts += 1 # עדכון class attribute
def get_info(self):
return f"[{self.account_id}] {self.owner}: ₪{self.balance}"
acc1 = BankAccount("Alice", 1000)
acc2 = BankAccount("Bob", 500)
print(acc1.get_info()) # [1] Alice: ₪1000
print(acc2.get_info()) # [2] Bob: ₪500
print(BankAccount.total_accounts) # 2
print(acc1.bank_name) # בנק גאמא! הפתעה: שינוי class attribute דרך instance ›
acc1.bank_name = "בנק פרטי" # ← יוצר INSTANCE attribute חדש!
print(acc1.bank_name) # בנק פרטי (instance attribute)
print(acc2.bank_name) # בנק גאמא (class attribute — לא השתנה!)
# acc1 עכשיו יש לו instance attribute bank_name שמסתיר את ה-class attribute
# שינוי class attribute נכון: תמיד דרך ClassName.attribute = ...class Counter:
count = 0
def __init__(self):
Counter.count += 1
c1 = Counter()
c2 = Counter()
print(Counter.count)מה הפלט?class A:
x = []
a = A()
b = A()
a.x.append(1)
print(b.x)מה הפלט?class A:
x = 5
a = A()
a.x = 10
print(A.x, a.x)מה הפלט?Encapsulation: הגנה על הנתונים
Encapsulation = מנגנון לשליטה על גישה לנתונים. ה-class חושף רק מה שצריך, ומסתיר את הפרטים הפנימיים. זה אחד מ-4 עמודות OOP.
| רמה | Python | C# | גישה |
|---|---|---|---|
| Public | name | public | כולם יכולים לגשת |
| Protected | _name | protected | ה-class עצמו + ירושה |
| Private | __name | private | ה-class בלבד |
class SecureAccount:
def __init__(self, owner, pin):
self.owner = owner # public
self._balance = 0 # protected — _: "אל תיגע"
self.__pin = pin # private — __ name mangling
def deposit(self, amount):
if amount <= 0:
raise ValueError("סכום חייב להיות חיובי")
self._balance += amount
def get_balance(self, pin):
if not self.__verify_pin(pin):
raise PermissionError("PIN שגוי!")
return self._balance
def __verify_pin(self, pin): # private method
return self.__pin == pin
acc = SecureAccount("Alice", "1234")
acc.deposit(1000)
print(acc.get_balance("1234")) # 1000
# ניסיון גישה לא נכון:
# print(acc.__pin) # AttributeError!
# print(acc._SecureAccount__pin) # עובד — Name Mangling, אבל רע!@property: getters ו-setters עם ולידציה
class Temperature:
def __init__(self, celsius=0):
self._celsius = None
self.celsius = celsius # ← קורא ל-setter מיד
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError(f"{value}°C מתחת לאפס מוחלט!")
self._celsius = value
@property
def fahrenheit(self): # read-only! אין setter
return self._celsius * 9/5 + 32
t = Temperature(100)
print(t.celsius) # 100
print(t.fahrenheit) # 212.0
# t.fahrenheit = 50 # AttributeError! — read-only
try:
t.celsius = -300
except ValueError as e:
print(e) # -300°C מתחת לאפס מוחלט!Methods: סוגי מתודות
יש 3 סוגי methods ב-Python. ההבדל: מי הפרמטר הראשון שלהם ומה הם יכולים לגשת.
| סוג | Decorator | פרמטר ראשון | גישה ל | שימוש |
|---|---|---|---|---|
| Instance Method | (ללא) | self | instance + class | הרוב הגדול של methods |
| Class Method | @classmethod | cls | class בלבד | factory methods, מונים |
| Static Method | @staticmethod | (ללא) | לא לinstance ולא לclass | utility functions |
class Date:
month_names = {1:"ינואר",2:"פברואר",3:"מרץ",4:"אפריל",
5:"מאי",6:"יוני",7:"יולי",8:"אוגוסט",
9:"ספטמבר",10:"אוקטובר",11:"נובמבר",12:"דצמבר"}
def __init__(self, day, month, year):
self.day, self.month, self.year = day, month, year
# Instance Method — עובד עם self
def to_string(self):
name = Date.month_names.get(self.month, "?")
return f"{self.day} ב{name} {self.year}"
# Class Method — factory: יוצר instance בדרך חלופית
@classmethod
def from_string(cls, date_str):
"""יוצר Date מ-string בפורמט DD/MM/YYYY"""
parts = date_str.split("/")
d, m, y = int(parts[0]), int(parts[1]), int(parts[2])
return cls(d, m, y) # cls = Date (או תת-מחלקה!)
# Static Method — לא צריך self או cls
@staticmethod
def is_valid_date(day, month, year):
"""בדיקת תקינות תאריך"""
if year < 1 or month < 1 or month > 12 or day < 1:
return False
days_in_month = [0,31,28,31,30,31,30,31,31,30,31,30,31]
return day <= days_in_month[month]
# שימוש:
d1 = Date(15, 3, 2024)
print(d1.to_string()) # 15 במרץ 2024
d2 = Date.from_string("20/06/2024") # class method
print(d2.to_string()) # 20 ביוני 2024
print(Date.is_valid_date(31, 2, 2024)) # False
print(Date.is_valid_date(29, 3, 2024)) # TrueMagic Methods (Dunder Methods)
Magic Methods הן מתודות מיוחדות עם שמות כמו __str__, __len__. Python קוראת להן אוטומטית בתגובה לפעולות מסוימות.
| Magic Method | מתי נקראת | דוגמה |
|---|---|---|
| __init__ | יצירת object | obj = MyClass() |
| __str__ | print(obj), str(obj) | ייצוג קריא למשתמש |
| __repr__ | repr(obj), ב-REPL | ייצוג debug מדויק |
| __len__ | len(obj) | מחזיר "אורך" |
| __eq__ | obj1 == obj2 | שוויון מותאם |
| __lt__ | obj1 < obj2 | השוואה — מאפשר sorted() |
| __add__ | obj1 + obj2 | חיבור מותאם |
| __contains__ | item in obj | בדיקת in |
| __getitem__ | obj[key] | indexing |
| __iter__ | for x in obj | איטרציה |
class Vector:
"""וקטור 2D — מדגים Magic Methods"""
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector(x={self.x}, y={self.y})"
def __len__(self):
return int((self.x**2 + self.y**2) ** 0.5)
def __eq__(self, other):
if not isinstance(other, Vector): return NotImplemented
return self.x == other.x and self.y == other.y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
def __lt__(self, other):
return len(self) < len(other) # מאפשר sorted()!
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1) # Vector(3, 4)
print(repr(v1)) # Vector(x=3, y=4)
print(len(v1)) # 5 (sqrt(9+16)=5)
print(v1 == v2) # False
print(v1 == Vector(3,4)) # True
print(v1 + v2) # Vector(4, 6)
print(v1 * 3) # Vector(9, 12)
vectors = [Vector(3,4), Vector(1,1), Vector(5,12)]
print(sorted(vectors)) # מסודר לפי magnitude!class Vec:
def __init__(self, x): self.x = x
def __add__(self, other): return Vec(self.x + other.x)
def __str__(self): return str(self.x)
v = Vec(3) + Vec(4)
print(v)מה הפלט?UML Class Diagram ו-Python vs C#
UML מתאר מבנה מחלקות בצורה גרפית. בסדנה ובראיון, לפעמים מציגים UML ומצפים שתבין — או מבקשים לצייר.
| סמל UML | משמעות | Python | C# |
|---|---|---|---|
| + | Public | name | public |
| - | Private | __name | private |
| # | Protected | _name | protected |
| «static» | Static | @staticmethod | static |
| {readonly} | Read-only property | @property (ללא setter) | { get; } |
Python vs C# — טבלת השוואה מקיפה
| תכונה | Python | C# |
|---|---|---|
| Class הגדרה | class MyClass: | public class MyClass { } |
| Constructor | def __init__(self, ...): | public MyClass(...) { } |
| this/self | self (מפורש) | this (משתמע) |
| Public | name (ברירת מחדל) | public type name; |
| Private | __name | private type _name; |
| Property | @property | public Type Name { get; set; } |
| Static | @staticmethod | static |
| Class Method | @classmethod | static (אין מקבילה מדויקת) |
| Inheritance | class Child(Parent): | class Child : Parent |
| Type hints | x: int (optional) | int x (חובה) |
אתגר מעשי — OOP: מחלקות ו-Magic Methods
שיא השיעור: לבנות, לתקן ולנתח — לא לזהות תשובה. פתרו את משימת החובה ואז את האתגר שמעליה; ההגשה נבדקת אוטומטית מול מקרי-קצה נסתרים (וחלקן אוכפות יעילות דרך גודל-קלט).
סיכום השיעור
- Class ו-Object: blueprint vs instance, self, type(), isinstance()
- __init__ Constructor: אתחול attributes, ברירות מחדל
- Class Attributes vs Instance Attributes: משותפים vs ייחודיים
- Encapsulation: public/protected/private, Name Mangling, Python vs C#
- Properties: @property, @setter, computed read-only properties
- Method types: instance, @classmethod, @staticmethod
- Magic Methods: __str__, __repr__, __len__, __eq__, __add__, __iter__
- UML Class Diagram: קריאה וכתיבה, סמלים +/-/#
- Python vs C#: טבלת השוואה, Properties, Static methods
לפני שיעור 8 — ודא שאתה יכול לכתוב Employee class מהראש ב-5 דקות. Encapsulation + Properties + Magic Methods — ידועים בעל-פה.
class Robot:
total = 0
def __init__(self, name, power):
Robot.total += 1
self.name = name
self.power = power
self.id = Robot.total
def __str__(self):
return f"Robot#{self.id}:{self.name}(p={self.power})"
def upgrade(self, boost):
self.power += boost
return self
r1 = Robot("Alpha", 10)
r2 = Robot("Beta", 20)
r1.upgrade(5).upgrade(5)
print(Robot.total)
print(r1)מה הפלט?class Box:
def __init__(self, w, h, d):
self._w, self._h, self._d = w, h, d
@property
def volume(self):
return self._w * self._h * self._d
def __eq__(self, other):
return self.volume == other.volume
def __lt__(self, other):
return self.volume < other.volume
b1 = Box(2, 3, 4)
b2 = Box(4, 3, 2)
b3 = Box(1, 1, 1)
print(b1 == b2)
print(b3 < b1)מה הפלט?שאלות תרגול ותשובות (5)
שאלה 1 · מה ההבדל בין Class לבין Object?
- א. Class הוא התבנית, Object הוא מופע קונקרטי שנוצר ממנה
- ב. Object הוא התבנית, Class הוא המופע
- ג. שניהם אותו דבר בפייתון
- ד. Class קיים רק ב-C# ולא בפייתון
Class = שרטוט (תבנית); Object/instance = הבית שנבנה לפי השרטוט ומחזיק ערכים משלו.
שאלה 2 · מה יודפס?
class Counter:
count = 0
def __init__(self):
Counter.count += 1
c1 = Counter()
c2 = Counter()
print(Counter.count)- א. 0
- ב. 1
- ג. 2
- ד. שגיאה
count הוא class attribute משותף, וכל קריאה ל-__init__ מקדמת אותו — אחרי שני מופעים הערך 2.
שאלה 3 · מה יודפס?
class A:
x = []
a = A()
b = A()
a.x.append(1)
print(b.x)- א. []
- ב. [1]
- ג. None
- ד. שגיאה
הרשימה היא class attribute אחת המשותפת לכל המופעים — שינוי דרך a נראה גם ב-b.
שאלה 4 · מהו הבנאי האמיתי בפייתון, זה שיוצר את האובייקט בזיכרון?
- א. __init__
- ב. __new__
- ג. __call__
- ד. __create__
__new__ יוצר ומחזיר את האובייקט, ורק אחריו __init__ מאתחל אותו ומחזיר None.
שאלה 5 · מהי המשמעות של private ב-C# מבחינת אבטחה?
- א. השדה מוצפן בזיכרון
- ב. אי אפשר לגשת אליו בשום דרך
- ג. הוא מונע טעויות מתכנת, אך ניתן לעקיפה ב-Runtime באמצעות Reflection
- ד. הוא נאכף רק בפייתון
private נאכף בקומפילציה בלבד; Reflection מאפשר לקרוא ולשנות שדות פרטיים, ולכן אינו מנגנון אבטחה.