Why OOP? — ليه بنستخدم الـ OOP؟
تنظيم الداتا بشكل يعكس الواقع الحقيقي
البرامج الصغيرة ممكن نكتبها بشوية متغيرات ودوال (Variables & Functions) متبعترة. لكن لما البرنامج يكبر ويبقى سيستم كامل لجامعة مثلاً، هيبقى عندنا آلاف الطلاب والكورسات.
الـ OOP بيخلينا نبرمج الحاجات دي كأنها "كيانات" حقيقية (Entities) زي Student و Course. وده بيسهل قراءة الكود وتعديله وصيانته.
الـ Object-Oriented Programming هو طريقة نبني بيها البرامج باستخدام Classes و Objects.
📐 الـ Class (البلوبرنت / التصميم)
- هو الرسمة أو "الأسطمبة" — القالب الفاضي
- بيعرّف إيه البيانات الموجودة وإيه الأفعال الممكنة
- مفيش فيه داتا حقيقية لطالب معين
- زي فكرة "فورم تقديم فاضي"
👤 الـ Object (النسخة / الـ Instance)
- هو الحاجة الحقيقية اللي اتعملت من القالب
- "الطالب علي" أو "الطالبة منى"
- كل Object شايل بيانات مختلفة
- زي "فورم اتملى ببيانات طالب حقيقي"
فكر في أي سيستم حواليك — كل كيان فيه ممكن يبقى Class:
🎓 جامعة
Student— الطالبCourse— المادةInstructor— الأستاذ
🛒 متجر إلكتروني
Product— المنتجOrder— الطلبUser— المستخدم
📚 مكتبة
Book— الكتابMember— العضوLoan— الإعارة
Class, Object, Attribute & Method
الكلاس بيتكون من إيه من جوه؟
كل Class بيتكون من نوعين من الحاجات:
- Attributes: هي "البيانات" اللي جوه الـ Object — الكائن ده بيعرف إيه؟ (زي اسمه، مستواه، قسمه)
- Methods: هي "الدوال" المكتوبة جوه الـ Class — الكائن ده بيعرف يعمل إيه؟ (زي إنه يعرض بياناته أو يرفع مستواه)
عشان نكتب Class في Python بنستخدم كلمة class، والاسم بيبدأ بحرف كابيتال (PascalCase). لو الكلاس فاضي بنحط pass.
# 1. تعريف الـ Class class Student: pass # فاضي حالياً — هنضيف حاجات بعدين # 2. إنشاء Objects (Instantiation) — بننادي الكلاس زي الدالة student1 = Student() student2 = Student() # print(student1) هيطبع عنوان الـ Object في الذاكرة — ده مش مهم دلوقتي # المهم إن student1 و student2 هما objects مختلفة!
ممكن نضيف بيانات للـ Object بعد ما اتعمل مباشرة، بس دي طريقة ضعيفة ومش مفضلة!
class Student: pass student1 = Student() student1.name = "Mona" student1.level = 2 student2 = Student() # نسيت تضيف level لـ student2! print(student1.name) # Mona print(student2.level) # AttributeError — خطأ!
Constructor & Self — دالة البناء
ازاي ننظم ولادة الـ Object بشكل صحيح؟
__init__ — الـ Constructorدي دالة بتشتغل أوتوماتيك أول ما تعمل Object جديد. وظيفتها إنها تضمن إن الـ Object ده اتخلق وهو معاه كل الداتا الأساسية بتاعته. الـ __init__ دي بتبدأ وتخلص بـ double underscore ما قبلها وما بعدها.
class Student: # الـ Constructor — بيتنادى أوتوماتيك عند إنشاء Object def __init__(self, name, level, department="General"): self.name = name # self.name = الـ attribute بتاع الـ Object self.level = level self.department = department # Python بتنادي __init__ لوحدها أول ما تعمل Object student1 = Student("Mona", 2, "IS") student2 = Student("Ali", 2) # هياخد "General" كـ default print(student1.name) # Mona print(student2.department) # General
self؟كلمة self بترمز للـ Object الحالي اللي بيتعمله إنشاء أو اللي بينادي الدالة. لما نقول self.name = name كأننا بنقول "يا Python، خد القيمة اللي جيالك وحطها في الـ Object اللي في إيدينا دلوقتي".
class Student: def __init__(self, name, level): self.name = name # self هنا = الـ Object اللي بيتعمل دلوقتي self.level = level def display_info(self): # لما student1.display_info() تتنادى، self هنا = student1 print(self.name, self.level) student1 = Student("Mona", 2) student1.display_info() # Mona 2 student2 = Student("Ali", 3) student2.display_info() # Ali 3 — كل object بياناته مختلفة!
self كأول parameter، Python هيرمي Error أول ما تيجي تنادي الدالة دي!Naming Conventions & Methods — الأسماء والأساليب
print vs return وأسلوب كتابة الكود الصح
في Python في اتفاقيات مهمة للتسمية لازم تتبعها:
| نوع الاسم | النمط المستخدم | مثال |
|---|---|---|
| Class Names (أسماء الكلاسات) | PascalCase | Student, LibraryBook, UserProfile |
| Attributes & Methods | snake_case | student_name, display_info, get_grade |
| Private (Convention) | _underscore | _internal_data, _password |
promote() بدل f().في الـ Backend، الدوال في الغالب بترجع قيمة (return) مش بس بتطبعها (print). عارف الفرق؟
class Course: def __init__(self, title, hours): self.title = title self.hours = hours def description(self): print(self.title, self.hours) c = Course("Python", 3) c.description() # بتطبع على الشاشة — مش بترجع قيمة!
class Course: def __init__(self, title, hours): self.title = title self.hours = hours def description(self): return f"{self.title} — {self.hours} hours" c = Course("Python", 3) info = c.description() print(info) # Python — 3 hours
print() بتعرض على الشاشة. return بيبعت القيمة لمن نادى الدالة عشان يستخدمها. في الـ Backend دايماً تقريباً بنستخدم return!الـ Methods مش بس بتعرض البيانات — ممكن تعدّلها كمان. وده أحسن من تعديلها من بره الكلاس مباشرة!
class Student: def __init__(self, name, level): self.name = name self.level = level def promote(self): self.level += 1 # بتزود الـ level بواحد def display_info(self): return f"Student: {self.name}, Level: {self.level}" student1 = Student("Mona", 2) print(student1.display_info()) # Student: Mona, Level: 2 student1.promote() print(student1.display_info()) # Student: Mona, Level: 3
Inheritance & Overriding — الوراثة والتعديل
إعادة استخدام الكود بذكاء
لو عندنا كلاسين بيشاركوا في بيانات مشتركة (زي الطالب والمدرس — الاتنين أشخاص ليهم اسم وـ ID)، بدل ما نكرر الكود، بنعمل كلاس كبير (Parent Class) اسمه Person، ونخلي الكلاسات الصغيرة (Child Classes) تورث منه.
class Student(Person): — ومفيش كلمة اسمها inherits في Python! (خليها تفرق عن Java اللي بتستخدم extends)class Person: def __init__(self, name): self.name = name def introduce(self): print("I am a person") # Student "is a" Person — وراثة بالأقواس (مش كلمة inherits!) class Student(Person): def __init__(self, name, level): # super() بتنادي Constructor الـ Parent super().__init__(name) self.level = level # Method Overriding: بنعيد كتابة الدالة عشان نغير سلوكها def introduce(self): print(f"I am a student named {self.name}, level {self.level}") p = Person("Ahmed") s = Student("Mona", 2) p.introduce() # I am a person s.introduce() # I am a student named Mona, level 2 print(s.name) # Mona — ورّثها من Person!
super()؟دالة super() بتديلك صلاحية توصل لدوال الكلاس الأب (Parent) وتشغلها جوه الكلاس الابن (Child).
- بتشغّل الـ
__init__بتاعة الأب فمش بنكتب نفس كود الداتا المشتركة مرتين. - الابن بيورث كل الـ Attributes والـ Methods من الأب.
super()مش بتعطي الكلاس "عدد لا نهائي من الـ Attributes" — دي معلومة غلط!
super() مش بتضيف attributes غير محدودة — ده كلام غلط. هي بس بتديك أداة توصل لدوال الـ Parent Class.Python مافيهاش حاجة اسمها Private بالمعنى الحرفي زي Java! لكن في convention (اتفاقية) بين المبرمجين:
✅ Public (عام)
- أي attribute أو method عادي
- مثل:
self.name - يقدر أي كود من بره الكلاس يوصله
⚠️ "Private" by Convention
- بتبدأ بـ underscore
_name - مثل:
self._password - مش قانون — Convention بس
- بيقول "متعدلوش من بره!"
Composition vs Inheritance — "has a" vs "is a"
التفكير الصح في تصميم الكلاسات
قبل ما تستخدم الـ Inheritance، اسأل نفسك: هل العلاقة بين الكلاسين هي "is a" ولا "has a"؟
🧬 Inheritance — "is a"
StudentIS APerson✅CarIS AVehicle✅- استخدم Inheritance هنا
- الابن بيورث كل حاجة من الأب
🔧 Composition — "has a"
CourseHAS ANInstructor✅CarHAS ANEngine✅- استخدم Composition هنا
- Object جوه Object
class Instructor: def __init__(self, name): self.name = name class Course: def __init__(self, title, instructor_name): self.title = title # Course "has an" Instructor — مش Course "is" Instructor self.instructor = Instructor(instructor_name) def show(self): print(f"{self.title} taught by {self.instructor.name}") course1 = Course("Python", "Dr. Mohamed") course1.show() # Python taught by Dr. Mohamed
File Handling — التعامل مع الملفات
حفظ الداتا بره الـ RAM
المتغيرات بتتمسح أول ما البرنامج يقفل. لو عايز تحفظ Logs (سجلات) أو Settings أو تصدّر بيانات أو تقرأ ملفات تهيئة — بنستخدم الملفات.
بنفتح الملف بالدالة open(filename, mode) وبعدين نستخدمه.
📝 Reading Methods — طرق القراءة
read()— بيقرأ الملف كله كـ String واحدreadline()— بيقرأ سطر واحد بسreadlines()— بيقرأ كل السطور ويرجعها في List
✍️ Writing Methods — طرق الكتابة
write(text)— بيكتب نص في الملفwritelines(list)— بيكتب list من الأسطر- الـ
\nبيعمل سطر جديد في الملف
كل mode بيفتح الملف بطريقة مختلفة. لازم تعرف الفرق كويس:
FileNotFoundError!"a" مش "w".with open(...)لو استخدمت open() العادية، لازم تقفل الملف في الآخر بـ file.close(). الأفضل نستخدم with open() لأنها بتقفل الملف أوتوماتيك أول ما البلوك يخلص، حتى لو حصل Error في النص.
# كتابة — "w" بيمسح القديم ويكتب جديد with open("students.txt", "w") as file: file.write("Mona\n") # \n = سطر جديد file.write("Ali\n") # الملف اتقفل لوحده هنا! # إضافة — "a" بيحتفظ بالقديم ويضيف with open("students.txt", "a") as file: file.write("Nour\n") # هيتضاف في الآخر
# read() — كل الملف في String واحد with open("students.txt", "r") as file: content = file.read() print(content) # readlines() — كل سطر عنصر في List with open("students.txt", "r") as file: lines = file.readlines() for line in lines: print(line.strip()) # strip يشيل \n
JSON & File Errors — الجيسون وأخطاء الملفات
ازاي بنبعت ونستقبل داتا في الويب؟
FileNotFoundError: بتظهر لو استخدمت"r"على ملف مش موجود أو مساره غلط. الحل:try/except.PermissionError: لو البرنامج مش واخد صلاحية يدخل على الملف أو يكتب فيه.
try: with open("data.txt", "r") as file: content = file.read() print(content) except FileNotFoundError: print("الملف مش موجود — File not found!") except PermissionError: print("مفيش صلاحية للوصول للملف ده!")
الـ JSON (JavaScript Object Notation) هي طريقة بنرتب بيها البيانات عشان ننقلها بسهولة بين الـ Backend والـ Frontend. شكلها في Python شبه الـ Dictionary بالظبط.
لما يكون عندك Object، مينفعش ترميه زي ما هو في ملف JSON! لازم الأول تحوله لـ Dictionary (العملية دي اسمها Serialization).
import json student_data = {"name": "Mona", "level": 2, "dept": "IS"} # json.dump(): بتحفظ الـ dict في ملف JSON with open("student.json", "w") as f: json.dump(student_data, f, indent=4) # indent بتخلي شكله منسق # json.load(): بتقرأ من ملف JSON وترجعه dict with open("student.json", "r") as f: data = json.load(f) print(data["name"]) # Mona # json.dumps(): بتحوّل dict لـ String JSON (مش لملف) json_string = json.dumps(student_data) # json.loads(): بتحوّل String JSON لـ dict back_to_dict = json.loads(json_string)
| الدالة | الاستخدام | اتجاه التحويل |
|---|---|---|
json.dump() | كتابة dict في ملف JSON | dict → file |
json.load() | قراءة من ملف JSON | file → dict |
json.dumps() | تحويل dict لـ String | dict → string |
json.loads() | تحويل String لـ dict | string → dict |
ربط الـ OOP بالـ File Handling: بنحفظ بيانات الـ Objects في ملف JSON!
import json class Student: def __init__(self, name, level, dept): self.name = name self.level = level self.dept = dept # بنحول الـ Object لـ dict عشان نحفظه في JSON (Serialization) def to_dict(self): return { "name": self.name, "level": self.level, "dept": self.dept } # إنشاء Objects s1 = Student("Mona", 2, "IS") s2 = Student("Ali", 3, "CS") # حفظ في JSON file students_list = [s1.to_dict(), s2.to_dict()] with open("students.json", "w") as f: json.dump(students_list, f, indent=4) print("✅ تم حفظ بيانات الطلاب في students.json")
أسئلة امتحانات OOP & File Handling
كل أسئلة المحاضرة دي من امتحانات 2021 + 2025 — مرتبة حسب ترتيب الشرح 🇪🇬
class Name:
def __init__(self, name):
self.name = name
name1 = Name('ABC')
name2 = name1لما بتعمل
name2 = name1 إنت مش بتعمل نسخة جديدة من الـ Object! إنت بس بتعمل "إشارة" جديدة (Reference) لنفس المكان في الذاكرة. فالاتنين بيكون ليهم نفس الـ id() بالظبط. Python's id() بيرجع عنوان الـ Object في الذاكرة — وطالما الاتنين بيشاوروا على نفس المكان، الـ id هيبقى واحد.
inherits keyword.Python مفيهاش كلمة اسمها
inherits! عشان نورث بنستخدم الأقواس العادية: class Child(Parent):. الفخ ده عشان يلخبطك مع Java اللي بتستخدم extends أو C# اللي بتستخدم :. في Python: الأقواس = الوراثة.
super() function, the child class will NOT inherit the methods and properties from the parent class.العكس تماماً! دالة
super() هي اللي بنستخدمها عشان ننادي دوال الأب (زي الـ __init__) ونضمن إن الابن ورث واستفاد من خصائص الأب بالكامل. super() بتمكّن الوراثة مش بتمنعها!
super() function in Python enables a class to have unlimited number of attributes.دالة
super() ملهاش أي علاقة بعدد الـ Attributes! هي مجرد أداة عشان توصل لدوال الـ Parent Class. إنت أصلاً تقدر تعمل عدد لا نهائي من الـ Attributes في أي كلاس حتى بدون ما تستخدم super().
constructor method in a class?الـ
constructor في JavaScript هو نفس الـ __init__ في Python — بيتنادى أوتوماتيك لما تعمل Object جديد ووظيفته يهيّأ الـ properties (الخصائص) بتاعة الـ Object. المفهوم واحد في اللغتين، بس الاسم مختلف.
super keyword do?كلمة
super في JavaScript زي دالة super() في Python تماماً — بتنادي الـ Constructor بتاع الـ Parent Class. وده بيضمن إن الـ Child Class ورث الـ properties بتاعت الأب وهيّأها صح. مفهوم متشابه في اللغتين!
lst = []
nums = [15, 6]
lst.append(nums)
nums = [10, 30, 20]
lst.append(nums)
lst.sort()
print(lst)lst.append(nums) بيحط الـ list كـ عنصر واحد جوه الـ lst. لما nums اتغيرت لـ [10,30,20]، الـ reference القديمة اللي في lst لسه شايلة [15,6] لأن nums اتعينت من جديد مش اتعدلت. lst.sort() بيرتب الـ lists بالمقارنة: [10,...] < [15,...] فـ[10,30,20] بتيجي أول.
x = {"name": "Ahmed", "id": "1", "mobile": 123456789, "mobile": 987654321}
print(x)في Python الـ Dictionary مينفعش يبقى فيها نفس الـ key أكتر من مرة. لو حصل كده، القيمة الأخيرة بتغلب وبتبقى هي اللي محفوظة. فـ
mobile الأخيرة (987654321) هي اللي هتتحفظ وهتتطبع. مفيش Error.
def f(**person):
print("Person's name is " + person["name"])
f(name="Ahmed", age=10)**person بيخلي الدالة تقبل أي عدد من الـ keyword arguments وتحطهم في dict اسمه person. فـperson هيبقى {"name": "Ahmed", "age": 10}. بعدين person["name"] بيرجع "Ahmed". الدالة بتطبع "Person's name is Ahmed" بس — مش الـ age.
y = lambda x: x / 20
print(y(100))الـ
lambda في Python هي دالة مجهولة بسيطة (Anonymous Function). y = lambda x: x / 20 بيعمل دالة بتاخد x وبترجع x/20. لما بنقول y(100) — بترجع 100/20 = 5.0. ممكن نستخدم lambda مع map وfilter وreduce.
myNumbers = [1, 2, 3] then x = myNumbers.pop() — The removed item will be 1.دالة
pop() من غير arguments بتشيل آخر عنصر في الـ list. فـ x = myNumbers.pop() هتشيل 3 (مش 1) وتحطه في x. الـ list هتبقى [1, 2]. لو عايز تشيل عنصر معين بالـ index، بتكتب pop(0) مثلاً.
update() method.للـ List: بنستخدم
extend() عشان نضيف عناصر من list تانية.للـ Set: بنستخدم
update() عشان نضيف عناصر من set تانية.update() هي method خاصة بالـ Set والـ Dictionary — مش الـ List!
my-var is an incorrect variable name in Python.في Python، أسماء المتغيرات مينفعش تحتوي على
- (hyphen/minus). Python بيفسّر my-var كإنها عملية طرح (my minus var)! الصح بنستخدم underscore: my_var. قواعد التسمية الصح: حروف، أرقام، underscore — وتبدأ بحرف أو underscore.