مشروع: نظام إدارة المكتبة
هذا هو المشروع الختامي للمرحلة 4. يربط كل شيء من الدروس القليلة الماضية — الفئات، الوراثة، التغليف، الطرق السحرية، وعمليات الملفات — لبناء نظام إدارة مكتبة قابل للاستخدام حقاً. إكمال هذا المشروع يعني أنك أتقنت حقاً البرمجة كائنية التوجه في بايثون.
متطلبات المشروع
1. إدارة الكتب (CRUD)
├── إضافة كتاب (العنوان، المؤلف، ISBN، سنة النشر)
├── حذف كتاب (بواسطة ISBN)
├── تعديل معلومات الكتاب
└── البحث عن كتب (بحث ضبابي بالعنوان / بحث عن المؤلف)
2. إدارة الاستعارة
├── استعارة كتاب (تسجيل المستعير وتاريخ الاستعارة)
├── إرجاع كتاب (التحقق من التأخير)
└── عرض سجلات الاستعارة
3. إحصائيات البيانات
├── إجمالي الكتب
├── عدد الكتب المتاحة/المستعارة
└── العد حسب المؤلف
4. استمرارية البيانات (ملف CSV، تحميل تلقائي عند بدء التشغيل)
1. الكود الكامل
مثال: نظام إدارة المكتبة
import csv
import os
from datetime import datetime, timedelta
from dataclasses import dataclass
# ====== نموذج البيانات ======
@dataclass
class Book:
"""فئة بيانات الكتاب"""
title: str
author: str
isbn: str
year: int
is_borrowed: bool = False
borrower: str = ""
borrow_date: str = ""
def to_csv_row(self):
"""حوّل إلى صف CSV"""
return [self.title, self.author, self.isbn, str(self.year),
str(self.is_borrowed), self.borrower, self.borrow_date]
@staticmethod
def from_csv_row(row):
"""أنشئ كائن Book من صف CSV"""
return Book(
title=row[0], author=row[1], isbn=row[2],
year=int(row[3]),
is_borrowed=row[4] == "True",
borrower=row[5], borrow_date=row[6]
)
# ====== طبقة منطق الأعمال ======
class Library:
"""نظام إدارة المكتبة"""
DATA_FILE = "library_data.csv"
def __init__(self):
self.books = []
self.load_data()
# ---- الاستمرارية ----
def load_data(self):
"""حمّل البيانات من CSV"""
if not os.path.exists(self.DATA_FILE):
return
with open(self.DATA_FILE, "r", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader, None)
for row in reader:
if row:
self.books.append(Book.from_csv_row(row))
print(f"Loaded {len(self.books)} books.")
def save_data(self):
"""احفظ البيانات إلى CSV"""
with open(self.DATA_FILE, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Title", "Author", "ISBN", "Year",
"Borrowed", "Borrower", "Borrow Date"])
for book in self.books:
writer.writerow(book.to_csv_row())
# ---- إدارة الكتب ----
def add_book(self, title, author, isbn, year):
"""أضف كتاباً"""
for book in self.books:
if book.isbn == isbn:
return False, "ISBN already exists!"
self.books.append(Book(title, author, isbn, year))
self.save_data()
return True, f"Added '{title}'"
def delete_book(self, isbn):
"""احذف كتاباً"""
for i, book in enumerate(self.books):
if book.isbn == isbn:
removed = self.books.pop(i)
self.save_data()
return True, f"Deleted '{removed.title}'"
return False, "Book with this ISBN not found."
def search_by_title(self, keyword):
"""بحث ضبابي بالعنوان"""
return [b for b in self.books if keyword.lower() in b.title.lower()]
def search_by_author(self, author):
"""بحث عن المؤلف"""
return [b for b in self.books if author.lower() in b.author.lower()]
def get_book_by_isbn(self, isbn):
"""ابحث عن كتاب بواسطة ISBN"""
for book in self.books:
if book.isbn == isbn:
return book
return None
# ---- إدارة الاستعارة ----
def borrow_book(self, isbn, borrower):
"""استعِر كتاباً"""
book = self.get_book_by_isbn(isbn)
if not book:
return False, "Book not found."
if book.is_borrowed:
return False, f"'{book.title}' is already borrowed by {book.borrower}."
book.is_borrowed = True
book.borrower = borrower
book.borrow_date = datetime.now().strftime("%Y-%m-%d")
self.save_data()
return True, f"'{book.title}' has been borrowed by {borrower}."
def return_book(self, isbn):
"""أرجِع كتاباً"""
book = self.get_book_by_isbn(isbn)
if not book:
return False, "Book not found."
if not book.is_borrowed:
return False, f"'{book.title}' is not currently borrowed."
borrow_date = datetime.strptime(book.borrow_date, "%Y-%m-%d")
days_borrowed = (datetime.now() - borrow_date).days
is_overdue = days_borrowed > 30
book.is_borrowed = False
book.borrower = ""
book.borrow_date = ""
self.save_data()
if is_overdue:
return True, f"'{book.title}' returned. Overdue by {days_borrowed - 30} days."
return True, f"'{book.title}' returned. Borrowed for {days_borrowed} days."
# ---- الإحصائيات ----
def get_stats(self):
"""احصل على الإحصائيات"""
total = len(self.books)
borrowed = sum(1 for b in self.books if b.is_borrowed)
available = total - borrowed
author_count = {}
for book in self.books:
author_count[book.author] = author_count.get(book.author, 0) + 1
return {
"total": total,
"available": available,
"borrowed": borrowed,
"by_author": dict(sorted(author_count.items(),
key=lambda x: x[1], reverse=True))
}
# ====== طبقة العرض ======
def show_menu():
print("\n" + "=" * 40)
print("Library Management System")
print("=" * 40)
print("1. Add Book")
print("2. Delete Book")
print("3. Search Books")
print("4. Display All Books")
print("5. Borrow Book")
print("6. Return Book")
print("7. Statistics")
print("8. Exit")
print("=" * 40)
def display_books(books, title="Search Results"):
"""اعرض قائمة من الكتب"""
if not books:
print(f"\n{title}: No records.")
return
print(f"\n--- {title} ---")
print(f"{'Title':<20}{'Author':<12}{'ISBN':<15}{'Status':<8}")
print("-" * 55)
for b in books:
status = "Borrowed" if b.is_borrowed else "Available"
print(f"{b.title:<20}{b.author:<12}{b.isbn:<15}{status:<8}")
def main():
library = Library()
while True:
show_menu()
choice = input("Select option (1-8): ").strip()
if choice == "1":
title = input("Title: ").strip()
author = input("Author: ").strip()
isbn = input("ISBN: ").strip()
year = input("Year: ").strip()
success, msg = library.add_book(title, author, isbn, int(year))
print(f" {'OK' if success else 'Error'}: {msg}")
elif choice == "2":
isbn = input("Enter ISBN to delete: ").strip()
success, msg = library.delete_book(isbn)
print(f" {'OK' if success else 'Error'}: {msg}")
elif choice == "3":
print(" 1. Search by Title")
print(" 2. Search by Author")
opt = input("Choose: ").strip()
if opt == "1":
keyword = input("Enter title keyword: ").strip()
results = library.search_by_title(keyword)
elif opt == "2":
author = input("Enter author name: ").strip()
results = library.search_by_author(author)
else:
print("Invalid choice")
continue
display_books(results)
elif choice == "4":
display_books(library.books, "All Books")
elif choice == "5":
isbn = input("Enter ISBN to borrow: ").strip()
borrower = input("Borrower name: ").strip()
success, msg = library.borrow_book(isbn, borrower)
print(f" {'OK' if success else 'Error'}: {msg}")
elif choice == "6":
isbn = input("Enter ISBN to return: ").strip()
success, msg = library.return_book(isbn)
print(f" {'OK' if success else 'Error'}: {msg}")
elif choice == "7":
stats = library.get_stats()
print(f"\n--- Statistics ---")
print(f"Total books: {stats['total']}")
print(f"Available: {stats['available']} Borrowed: {stats['borrowed']}")
print(f"\nBy Author:")
for author, count in stats['by_author'].items():
print(f" {author}: {count} books")
elif choice == "8":
print("Thank you for using! Goodbye!")
break
else:
print("Invalid choice. Please enter 1-8.")
if __name__ == "__main__":
main()
أبرز ملامح التصميم
العمارة متعددة الطبقات: نموذج البيانات (Book)، منطق الأعمال (Library)، وطبقة العرض (main) منفصلة، بمسؤوليات واضحة لكل طبقة.
استمرارية البيانات: تخزين في ملف CSV مع تحميل تلقائي عند بدء التشغيل — البيانات تبقى بعد إعادة تشغيل البرنامج.
منطق الاستعارة: يحسب تلقائياً مدة الاستعارة؛ يُحذّر من التأخير إذا تجاوز 30 يوماً.
تبسيط @dataclass: فئة Book تستخدم مُزيّن @dataclass، مما يلغي الكثير من الكود الأساسي.
❓ أسئلة شائعة
@dataclass وكتابة __init__ يدوياً؟@dataclass يُنشئ تلقائياً __init__ و __repr__ و __eq__ وطرقاً أخرى، مما يوفر عليك كتابة الكود الأساسي. وظيفياً متطابق، فقط أكثر إيجازاً. عندما تخزّن فئة بشكل أساسي بيانات بدون منطق معقد، فضّل @dataclass.
س:لماذا استخدام CSV بدلاً من JSON لتخزين البيانات؟ ج:بيانات الكتب "جدولية" (صف واحد لكل كتاب، حقول ثابتة) — CSV يناسب هذا الهيكل جيداً ويمكن فتحه مباشرة في Excel. JSON أفضل للهياكل المتداخلة (مثل درجات الطلاب مع قواميس فرعية). كلاهما يعمل؛ اختر بناءً على هيكل البيانات.
س:هل استخدام سلاسل نصية مثل "مستعار"/"متاح" للحالة قد يسبب مشاكل؟ ج:كافٍ للمشاريع الصغيرة. نهج أكثر دقة هو استخدام Enum لتعريف الحالات، لتجنب الأخطاء الإملائية في السلاسل النصية. لكن التعدادات موضوع متقدم؛ السلاسل النصية تعمل بشكل جيد هنا.📖 ملخص
- نظام إدارة المكتبة يستخدم عمارة ثلاثية الطبقات: نموذج بيانات ← منطق أعمال ← طبقة عرض
@dataclassيُبسّط تعريفات فئات البيانات؛to_csv_row()/from_csv_row()تنفذ التسلسل- ملفات CSV توفر استمرارية البيانات؛
load_data()تقرأ عند التشغيل،save_data()تحفظ بعد كل تعديل - وحدة
datetimeتحسب مدة الاستعارة وتكتشف التأخير تلقائياً - شاملات القوائم وطريقة
get()في القاموس تُستخدم للإحصائيات
📝 تمارين
-
مبتدئ (الصعوبة ⭐): أضف حقل
categoryلفئةBook(رواية، علوم، تاريخ، إلخ.) واسمح للمستخدم باختيار فئة عند إضافة كتاب. -
متوسط (الصعوبة ⭐⭐): أضف ميزة "سجل الاستعارة" — في كل مرة يُرجَع فيها كتاب، ألحق سجل الاستعارة بملف
history.csv. تنسيق السجل: عنوان الكتاب، المستعير، تاريخ الاستعارة، تاريخ الإرجاع، حالة التأخير. -
متقدم (الصعوبة ⭐⭐⭐): بدون الرجوع إلى الكود أعلاه، ابنِ "نظام إدارة العضويات" من الصفر. تشمل الميزات: إضافة عضو (الاسم، الهاتف، النقاط)، استبدال النقاط، عرض قائمة الأعضاء، الترتيب حسب النقاط، حفظ البيانات إلى CSV. نظّم كودك باستخدام الفئات.



