بناء آلة حاسبة بلغة Python — مشروع كامل خطوة بخطوة
دليل عملي كامل لبناء آلة حاسبة احترافية بلغة Python — من التخطيط إلى التنفيذ، مع واجهة رسومية بـ Tkinter
في المقال السابق، بنينا 5 مشاريع Python صغيرة. الآن سنبني مشروعاً كاملاً واحترافياً — آلة حاسبة بواجهة رسومية حقيقية.
هذا المشروع سيعلمك:
- التخطيط للمشاريع قبل البرمجة
- البرمجة الكائنية (OOP)
- بناء واجهات رسومية بـ Tkinter
- معالجة الأخطاء بشكل احترافي
- تنظيم الكود في ملفات متعددة
بنهاية هذا المقال، سيكون لديك آلة حاسبة كاملة يمكنك استخدامها فعلياً على جهازك.
1. التخطيط قبل البرمجة
لماذا التخطيط مهم؟
الكثير من المبتدئين يبدأون الكتابة مباشرة، ثم يجدون أنفسهم في فوضى. التخطيط يوفر 50% من الوقت.
تحديد المتطلبات
قبل كتابة أي كود، اسأل:
| السؤال | الجواب |
|---|---|
| ماذا يفعل التطبيق؟ | آلة حاسبة بواجهة رسومية |
| ما العمليات المطلوبة؟ | جمع، طرح، ضرب، قسمة، نسبة مئوية، جذر |
| ما التقنيات؟ | Python + Tkinter (مدمج في Python) |
| من المستخدم؟ | أي شخص يحتاج حسابات سريعة |
| ما الميزات الإضافية؟ | حفظ السجل، دعم لوحة المفاتيح |
تصميم الواجهة
قبل البرمجة، تخيل شكل الآلة الحاسبة النهائي:
| C | ± | % | ÷ |
| 7 | 8 | 9 | × |
| 4 | 5 | 6 | − |
| 1 | 2 | 3 | + |
| √ | 0 | . | = |
هذا التصميم مطابق تماماً للكود الذي سنبنيه.
2. بناء النسخة الأولى — بدون واجهة رسومية
قبل بناء الواجهة، سنبني المنطق الأساسي كنسخة نصية. هذا يساعدنا على:
- اختبار المنطق قبل الواجهة
- فصل الاهتمامات (Logic vs UI)
- سهولة اكتشاف الأخطاء
الكود الكامل — نسخة نصية
# calculator_logic.py
# المنطق الأساسي للآلة الحاسبة
def add(a, b):
"""جمع رقمين"""
return a + b
def subtract(a, b):
"""طرح رقمين"""
return a - b
def multiply(a, b):
"""ضرب رقمين"""
return a * b
def divide(a, b):
"""قسمة رقمين"""
if b == 0:
raise ValueError("لا يمكن القسمة على صفر")
return a / b
def percentage(a, b):
"""حساب النسبة المئوية"""
if b == 0:
raise ValueError("لا يمكن القسمة على صفر")
return (a * b) / 100
def power(a, b):
"""رفع رقم لقوة"""
return a ** b
def square_root(a):
"""الجذر التربيعي"""
if a < 0:
raise ValueError("لا يمكن جذر رقم سالب")
return a ** 0.5
def calculate(a, operator, b):
"""تنفيذ العملية بناءً على المشغل"""
operations = {
"+": add,
"-": subtract,
"*": multiply,
"/": divide,
"%": percentage,
"**": power,
}
if operator not in operations:
raise ValueError(f"عملية غير مدعومة: {operator}")
return operations[operator](a, b)
def main():
"""اختبار المنطق في الطرفية"""
print("=" * 40)
print("🧮 آلة حاسبة - نسخة نصية")
print("=" * 40)
print("أدخل العملية بالصيغة: الرقم عملية الرقم")
print("مثال: 5 + 3")
print("اكتب 'خروج' للإنهاء")
print("=" * 40)
while True:
try:
user_input = input("\n> ").strip()
if user_input == "خروج":
print("👋 وداعاً!")
break
parts = user_input.split()
if len(parts) != 3:
print("❌ صيغة خاطئة. استخدم: الرقم عملية الرقم")
continue
a = float(parts[0])
operator = parts[1]
b = float(parts[2])
result = calculate(a, operator, b)
print(f"النتيجة: {result}")
except ValueError as e:
print(f"❌ خطأ: {e}")
except Exception as e:
print(f"❌ خطأ غير متوقع: {e}")
if __name__ == "__main__":
main()
اختبار النسخة النصية
عند تشغيلها:
========================================
🧮 آلة حاسبة - نسخة نصية
========================================
أدخل العملية بالصيغة: الرقم عملية الرقم
مثال: 5 + 3
اكتب 'خروج' للإنهاء
========================================
> 5 + 3
النتيجة: 8.0
> 10 / 0
❌ خطأ: لا يمكن القسمة على صفر
> 4 ** 2
النتيجة: 16.0
> خروج
👋 وداعاً!
3. بناء الواجهة الرسومية بـ Tkinter
ما هو Tkinter؟
Tkinter هي مكتبة Python المدمجة لبناء الواجهات الرسومية. لا تحتاج تثبيت — موجودة مع Python.
المكونات الأساسية في Tkinter
| المكون | الوظيفة |
|---|---|
Tk() | النافذة الرئيسية |
Label() | نص ثابت |
Button() | زر قابل للنقر |
Entry() | حقل إدخال |
Frame() | إطار لتنظيم المكونات |
الكود الكامل — النسخة الرسومية
هذا هو المشروع الكامل. احفظه في ملف calculator.py:
"""
🧮 آلة حاسبة احترافية بلغة Python
====================================
مشروع كامل مع واجهة رسومية
"""
import tkinter as tk
from tkinter import messagebox
from datetime import datetime
class Calculator:
"""آلة حاسبة بواجهة رسومية"""
def __init__(self, root):
"""تهيئة الآلة الحاسبة"""
self.root = root
self.root.title("🧮 آلة حاسبة | NahdaAI")
self.root.geometry("400x600")
self.root.resizable(False, False)
self.root.configure(bg="#1a1a1a")
# المتغيرات
self.current_input = "0"
self.previous_value = None
self.operation = None
self.should_reset = False
self.history = []
# بناء الواجهة
self.create_display()
self.create_buttons()
self.bind_keyboard()
def create_display(self):
"""إنشاء شاشة العرض"""
# إطار الشاشة
display_frame = tk.Frame(
self.root,
bg="#1a1a1a",
height=150
)
display_frame.pack(fill="x", padx=20, pady=20)
display_frame.pack_propagate(False)
# التاريخ
self.date_label = tk.Label(
display_frame,
text=datetime.now().strftime("%Y-%m-%d"),
font=("Arial", 10),
bg="#1a1a1a",
fg="#666"
)
self.date_label.pack(anchor="e")
# الشاشة الرئيسية
self.display = tk.Label(
display_frame,
text="0",
font=("Arial", 48, "bold"),
bg="#1a1a1a",
fg="white",
anchor="e"
)
self.display.pack(fill="x", expand=True)
# سجل العملية
self.history_label = tk.Label(
display_frame,
text="",
font=("Arial", 12),
bg="#1a1a1a",
fg="#888",
anchor="e"
)
self.history_label.pack(anchor="e")
def create_buttons(self):
"""إنشاء الأزرار"""
# إطار الأزرار
buttons_frame = tk.Frame(self.root, bg="#1a1a1a")
buttons_frame.pack(fill="both", expand=True, padx=20, pady=10)
# تصميم الأزرار (النص، اللون، العرض)
buttons = [
("C", "#ff4444", 1),
("±", "#555555", 1),
("%", "#555555", 1),
("÷", "#ff9500", 1),
("7", "#333333", 1),
("8", "#333333", 1),
("9", "#333333", 1),
("×", "#ff9500", 1),
("4", "#333333", 1),
("5", "#333333", 1),
("6", "#333333", 1),
("-", "#ff9500", 1),
("1", "#333333", 1),
("2", "#333333", 1),
("3", "#333333", 1),
("+", "#ff9500", 1),
("√", "#555555", 1),
("0", "#333333", 1),
(".", "#333333", 1),
("=", "#ff9500", 1),
]
# إنشاء الأزرار
row = 0
col = 0
for text, color, width in buttons:
btn = tk.Button(
buttons_frame,
text=text,
font=("Arial", 24, "bold"),
bg=color,
fg="white",
activebackground="#666666",
activeforeground="white",
border=0,
width=4,
height=2,
cursor="hand2",
command=lambda t=text: self.on_button_click(t)
)
btn.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
col += 1
if col > 3:
col = 0
row += 1
# تكوين أوزان الشبكة
for i in range(4):
buttons_frame.grid_columnconfigure(i, weight=1)
for i in range(5):
buttons_frame.grid_rowconfigure(i, weight=1)
def bind_keyboard(self):
"""ربط لوحة المفاتيح"""
self.root.bind("<Key>", self.on_key_press)
self.root.bind("<Return>", lambda e: self.on_button_click("="))
self.root.bind("<Escape>", lambda e: self.on_button_click("C"))
self.root.bind("<BackSpace>", self.on_backspace)
def on_key_press(self, event):
"""معالجة ضغطات المفاتيح"""
key = event.char
if key.isdigit():
self.on_button_click(key)
elif key == ".":
self.on_button_click(".")
elif key == "+":
self.on_button_click("+")
elif key == "-":
self.on_button_click("-")
elif key == "*":
self.on_button_click("×")
elif key == "/":
self.on_button_click("÷")
elif key == "=":
self.on_button_click("=")
def on_backspace(self, event):
"""حذف آخر رقم"""
if len(self.current_input) > 1:
self.current_input = self.current_input[:-1]
else:
self.current_input = "0"
self.update_display()
def on_button_click(self, value):
"""معالجة نقر الأزرار"""
if value.isdigit():
self.handle_number(value)
elif value == ".":
self.handle_decimal()
elif value == "C":
self.handle_clear()
elif value == "±":
self.handle_sign_toggle()
elif value == "%":
self.handle_operation("%")
elif value == "√":
self.handle_square_root()
elif value in ["+", "-", "×", "÷"]:
self.handle_operation(value)
elif value == "=":
self.handle_equals()
def handle_number(self, num):
"""إضافة رقم"""
if self.should_reset:
self.current_input = num
self.should_reset = False
elif self.current_input == "0":
self.current_input = num
else:
self.current_input += num
self.update_display()
def handle_decimal(self):
"""إضافة فاصلة عشرية"""
if self.should_reset:
self.current_input = "0."
self.should_reset = False
elif "." not in self.current_input:
self.current_input += "."
self.update_display()
def handle_clear(self):
"""مسح الكل"""
self.current_input = "0"
self.previous_value = None
self.operation = None
self.should_reset = False
self.history_label.config(text="")
self.update_display()
def handle_sign_toggle(self):
"""تبديل الإشارة"""
if self.current_input != "0":
if self.current_input.startswith("-"):
self.current_input = self.current_input[1:]
else:
self.current_input = "-" + self.current_input
self.update_display()
def handle_operation(self, op):
"""معالجة العمليات"""
try:
current = float(self.current_input)
if self.previous_value is not None and self.operation and not self.should_reset:
result = self.calculate(self.previous_value, self.operation, current)
self.current_input = str(result)
self.previous_value = result
else:
self.previous_value = current
self.operation = op
self.should_reset = True
# تحديث السجل
symbol_map = {"×": "×", "÷": "÷", "+": "+", "-": "-", "%": "%"}
self.history_label.config(
text=f"{self.previous_value} {symbol_map.get(op, op)}"
)
self.update_display()
except Exception as e:
self.show_error(str(e))
def handle_square_root(self):
"""حساب الجذر التربيعي"""
try:
current = float(self.current_input)
if current < 0:
raise ValueError("لا يمكن جذر رقم سالب")
result = current ** 0.5
self.history_label.config(text=f"√{current} =")
self.current_input = str(result)
self.should_reset = True
self.update_display()
except Exception as e:
self.show_error(str(e))
def handle_equals(self):
"""تنفيذ الحساب"""
try:
if self.operation and self.previous_value is not None:
current = float(self.current_input)
result = self.calculate(self.previous_value, self.operation, current)
# إضافة للسجل
history_entry = f"{self.previous_value} {self.operation} {current} = {result}"
self.history.append(history_entry)
self.current_input = str(result)
self.history_label.config(text=history_entry)
self.previous_value = None
self.operation = None
self.should_reset = True
self.update_display()
except Exception as e:
self.show_error(str(e))
def calculate(self, a, operator, b):
"""تنفيذ العملية الحسابية"""
operations = {
"+": lambda x, y: x + y,
"-": lambda x, y: x - y,
"×": lambda x, y: x * y,
"÷": lambda x, y: self.safe_divide(x, y),
"%": lambda x, y: (x * y) / 100,
}
if operator not in operations:
raise ValueError(f"عملية غير مدعومة: {operator}")
return operations[operator](a, b)
def safe_divide(self, a, b):
"""قسمة آمنة"""
if b == 0:
raise ValueError("لا يمكن القسمة على صفر")
return a / b
def update_display(self):
"""تحديث الشاشة"""
# تنسيق الرقم
try:
value = float(self.current_input)
if value.is_integer():
display_text = f"{int(value):,}"
else:
display_text = f"{value:,.10g}"
except ValueError:
display_text = self.current_input
# تعديل حجم الخط حسب طول النص
font_size = 48
if len(display_text) > 10:
font_size = 32
if len(display_text) > 14:
font_size = 24
self.display.config(
text=display_text,
font=("Arial", font_size, "bold")
)
def show_error(self, message):
"""عرض رسالة خطأ"""
self.display.config(text="خطأ", fg="#ff4444")
self.history_label.config(text=message)
# إعادة اللون بعد ثانية
self.root.after(1500, lambda: self.display.config(fg="white"))
def main():
"""تشغيل التطبيق"""
root = tk.Tk()
calculator = Calculator(root)
root.mainloop()
if __name__ == "__main__":
main()
4. شرح الكود بالتفصيل
1. البرمجة الكائنية (OOP)
بدلاً من كتابة دوال متفرقة، جمعنا كل شيء في class Calculator:
| المكون | الشرح |
|---|---|
__init__() | تهيئة الكائن — تُنفذ عند الإنشاء |
self.root | النافذة الرئيسية |
self.current_input | الرقم الحالي على الشاشة |
self.previous_value | الرقم السابق للعملية |
self.operation | العملية المعلقة |
2. إدارة الحالة (State Management)
الآلة الحاسبة تحفظ حالتها في متغيرات:
self.current_input = "0" # ما يظهر على الشاشة
self.previous_value = None # الرقم قبل العملية
self.operation = None # العملية الحالية
self.should_reset = False # هل نبدأ رقماً جديداً؟
self.history = [] # سجل العمليات
3. دعم لوحة المفاتيح
self.root.bind("<Key>", self.on_key_press)
self.root.bind("<Return>", lambda e: self.on_button_click("="))
self.root.bind("<Escape>", lambda e: self.on_button_click("C"))
هذا يسمح للمستخدم بكتابة الأرقام مباشرة بدلاً من النقر.
4. معالجة الأخطاء
try:
result = self.calculate(a, op, b)
except ValueError as e:
self.show_error(str(e))
كل عملية محاطة بـ try/except لمنع انهيار التطبيق.
5. التشغيل والاستخدام
تشغيل التطبيق
python calculator.py
اختبار الوظائف
| العملية | الخطوات | النتيجة |
|---|---|---|
| الجمع | 5 + 3 = | 8 |
| الطرح | 10 - 4 = | 6 |
| الضرب | 6 × 7 = | 42 |
| القسمة | 20 ÷ 4 = | 5 |
| النسبة | 200 % 15 = | 30 |
| الجذر | √16 | 4 |
| خطأ | 5 ÷ 0 = | لا يمكن القسمة على صفر |
6. التوسعات المقترحة
1. إضافة سجل العمليات (History)
def show_history(self):
"""عرض سجل العمليات"""
history_window = tk.Toplevel(self.root)
history_window.title("📋 سجل العمليات")
history_window.geometry("400x500")
if not self.history:
tk.Label(
history_window,
text="لا توجد عمليات محفوظة",
font=("Arial", 14)
).pack(pady=50)
return
for entry in reversed(self.history[-20:]):
tk.Label(
history_window,
text=entry,
font=("Arial", 12),
anchor="e"
).pack(fill="x", padx=20, pady=5)
2. حفظ السجل في ملف
import json
def save_history(self):
"""حفظ السجل في ملف"""
with open("calculator_history.json", "w", encoding="utf-8") as f:
json.dump(self.history, f, ensure_ascii=False, indent=2)
3. الوضع الليلي/النهاري
def toggle_theme(self):
"""تبديل الوضع الليلي"""
if self.current_theme == "dark":
self.current_theme = "light"
self.root.configure(bg="#ffffff")
# تغيير باقي الألوان...
else:
self.current_theme = "dark"
self.root.configure(bg="#1a1a1a")
# تغيير باقي الألوان...
4. اختصارات لوحة المفاتيح المتقدمة
# عمليات سريعة
self.root.bind("<Control-c>", lambda e: self.handle_clear())
self.root.bind("<Control-h>", lambda e: self.show_history())
7. هيكل المشروع الاحترافي
عندما يكبر المشروع، نظّمه في ملفات:
calculator/
├── main.py # نقطة البداية
├── calculator.py # المكونات
├── logic.py # المنطق الحسابي
├── history.py # إدارة السجل
├── config.py # الإعدادات
└── requirements.txt # المتطلبات
مثال — فصل المنطق
# logic.py
class CalculatorLogic:
@staticmethod
def add(a, b):
return a + b
@staticmethod
def subtract(a, b):
return a - b
# main.py
from logic import CalculatorLogic
class CalculatorApp:
def __init__(self):
self.logic = CalculatorLogic()
الفائدة: يمكن اختبار المنطق بدون الواجهة.
8. تحويل المشروع إلى تطبيق تنفيذي (.exe)
لتوزيع آلتك الحاسبة على أصدقائك:
1. تثبيت PyInstaller
pip install pyinstaller
2. إنشاء الملف التنفيذي
pyinstaller --onefile --windowed calculator.py
3. النتيجة
ستجد ملف calculator.exe في مجلد dist/ — يمكنك تشغيله بنقرة مزدوجة على أي جهاز Windows!
9. أخطاء شائعة
الخطأ 1: نسيان self
# خطأ
def create_display(self):
display = Label(root, text="0") # root غير معروف
# صحيح
def create_display(self):
display = Label(self.root, text="0")
الخطأ 2: عدم استخدام lambda في command
# خطأ — كل الأزرار ستستخدم آخر قيمة
for text in ["1", "2", "3"]:
btn = Button(command=lambda: print(text))
# صحيح
for text in ["1", "2", "3"]:
btn = Button(command=lambda t=text: print(t))
الخطأ 3: عدم معالجة الأخطاء
# خطأ — سينهار عند القسمة على صفر
result = float(a) / float(b)
# صحيح
try:
result = float(a) / float(b)
except ZeroDivisionError:
result = "خطأ"
قائمة تحقق — هل بنيت الآلة الحاسبة؟
| المهارة | الحالة |
|---|---|
| التخطيط قبل البرمجة | ⬜ |
| بناء النسخة النصية | ⬜ |
| فهم Tkinter الأساسي | ⬜ |
| البرمجة الكائنية (OOP) | ⬜ |
| إدارة الحالة | ⬜ |
| معالجة الأخطاء | ⬜ |
| دعم لوحة المفاتيح | ⬜ |
| تحويل المشروع إلى .exe | ⬜ |
الخلاصة
في هذا المقال، بنيت آلة حاسبة احترافية كاملة:
- ✅ تخطيط المشروع — قبل كتابة أي كود
- ✅ النسخة النصية — لاختبار المنطق
- ✅ الواجهة الرسومية — بـ Tkinter
- ✅ البرمجة الكائنية — كود منظم
- ✅ دعم لوحة المفاتيح — تجربة أفضل
- ✅ معالجة الأخطاء — تطبيق مستقر
- ✅ تحويلها لتطبيق .exe — للمشاركة
هذا المشروع يمكنك إضافته لمعرض أعمالك!
في المقال القادم: سنبني تطبيق مهام متكامل مع قاعدة بيانات — مشروع احترافي آخر!
ما التوسعة التي أضفتها لآلتك الحاسبة؟ شاركنا في التعليقات!
فريق نهضة
فريق عربي متخصص في التقنية والذكاء الاصطناعي وتطوير الذات
📚 مقالات ذات صلة
بناء تطبيق مهام كامل بلغة Python — مع قاعدة بيانات
دليل عملي لبناء تطبيق إدارة مهام احترافي بلغة Python — مع قاعدة بيانات SQLite وواجهة رسومية Tkinter
5 مشاريع Python للمبتدئين — ابنِ أول أعمالك
5 مشاريع Python عملية للمبتدئين مع الكود الكامل والشرح خطوة بخطوة — ابنِ معرض أعمالك الأول وابدأ رحلتك البرمجية
الدوال (Functions) في Python — نظّم كودك باحتراف
دليل شامل لتعلم الدوال في Python — تعريف الدوال، المعاملات، القيم المُرجعة، الدوال المجهولة، مع مشاريع تطبيقية خطوة بخطوة