بايثون المتقدم: شرح الأداء ببساطة (مع رسوم توضيحية وكود حقيقي)
أداء بايثون المتقدم: الأساسيات في مقال واحد — كود حقيقي، مخططات وخطوات ملموسة، مقتطفات من دورة تتكون من 35 درسًا.
دليل مباشر وموجز: Python Avancé Performance مفكك مع مخططات وأمثلة عملية وأوامر مجربة. كل شيء مستمد من دورة منظمة تضم 11 فصلاً — إليك أفضل ما فيها.
- المقدمة والتثبيت
- توصيف الكود
- Comprehensions iterators generators
- Multithreading vs Multiprocessing
- asyncio et coroutines
تقديم الدورة
الأهداف التعليمية
المشكلة العملية
هل سبق أن مررت بهذا المشهد؟ تطلق سكريبت Python في الساعة 17:00 لحساب تقرير، ثم تذهب لتحضر قهوة، وعندما تعود بعد 30 دقيقة لا يزال السكريبت يعمل بعد. والأسوأ: زميلك الذي يفعل الشيء نفسه بلغة R أو Julia انتهى في 3 دقائق.
الأعراض النموذجية لبرنامج Python بطيء
ما ستتعلم القيام به
المحاور الثلاثة الكبرى للتحسين
| المحور | السؤال المطروح | الكسب النموذجي |
|---|---|---|
| 1. الخوارزمية | هل كودي في O(n²) بينما يمكن أن يكون في O(n log n)؟ | 10× إلى 10,000× |
| 2. بنية البيانات | هل أستخدم list بينما set يكفي؟ |
10× إلى 1000× |
| 3. التزامن / التوازي | هل يمكنني تنفيذ هذه المهام الثماني في الوقت نفسه؟ | 2× إلى 16× (حسب CPU/IO) |
قاعدة 80/20 (باريتو)
في 80٪ من البرامج، يُقضى 80٪ من وقت التنفيذ في 20٪ من الكود. غالباً ما تكون النسبة 90/10 أو 95/5.
دونالد كنوث، أسطورة الحوسبة، لخص الأمر عام 1974 كالتالي:
(التحسين المبكر هو جذر كل الشرور.)
الترجمة العملية: اكتب أولاً كوداً واضحاً وصحيحاً. استمتع به. إذا كان بطيئاً جداً، حسّن فقط الأجزاء الساخنة. وإلا ستضيع وقتاً في جعل كود غير مؤثر غير قابل للقراءة.
قبل وبعد يتحدثان
إليك مثالاً حقيقياً: جمع مربعات الأعداد من 0 إلى 10 ملايين.
# النسخة البسيطة: حلقة Python total = 0 for i in range(10_000_000): total += i * i # الوقت: حوالي 1.2 ثانية على حاسوب محمول حديث # النسخة الموجهة باستخدام NumPy import numpy as np arr = np.arange(10_000_000) total = (arr * arr).sum() # الوقت: حوالي 0.04 ثانية -> أسرع بـ30 ضعفاً # النسخة المترجمة باستخدام Numba @jit from numba import jit @jit(nopython=True) def somme_carres(n): total = 0 for i in range(n): total += i * i return total # الوقت: حوالي 0.01 ثانية -> أسرع بـ120 ضعفاً
نفس المشكلة، نفس اللغة، نفس النتيجة الرياضية — لكن أسرع بـ120 مرة. هذا بالضبط ما ستتعلم القيام به بشكل منهجي على كودك الخاص.
ما ستبنيه
المرحلة 1: القياس (الفصلان 0-1)
تثبيت الأدوات، إجراء أول توصيف، تعلم قراءة تقرير cProfile. ستتمكن من تحديد عنق الزجاجة في أقل من 5 دقائق.
المرحلة 2: التحسين (الفصول 2-7)
المولدات، threading، multiprocessing، asyncio، NumPy، Numba، Cython، التخزين المؤقت. كل ترسانة مطور Python الحديث.
المولدات باستخدام yield
yield، الكلمة المفتاحية التي تحول الدالة إلى مولد، وتعلم بناء أنابيب معالجة بيانات كسولة قادرة على معالجة ملفات بحجم عدة غيغابايت ببضع ميغابايت من الذاكرة.الأهداف التعليمية
مولد أول
def compter(max): n = 0 while n < max: yield n # توقف الدالة وأعد n n += 1 # الاستدعاء: لا يفعل شيئاً، نحصل على مولد g = compter(5) print(type(g)) # <class 'generator'> # الاستهلاك for n in g: print(n) # 0 1 2 3 4
yield، يحولها Python إلى مصنع مولدات. الاستدعاء لا يشغل الكود: بل يعيد كائن مولد. لا يُنفذ الكود إلا عند كل استدعاء next().yield مقابل return
| return | yield |
|---|---|
| تنهي الدالة | تعلق الدالة |
| تفقد الحالة | تحتفظ بالحالة |
| تعيد قيمة (مرة واحدة) | يمكن استدعاؤها عدة مرات |
| تعيد كل شيء دفعة واحدة | تعيد عنصراً في كل مرة |
حالة استخدام حقيقية: قراءة ملف سجل كبير
def lire_log(chemin): """مولد يعيد كل سطر دون تحميل الكل.""" with open(chemin, encoding="utf-8") as f: for ligne in f: yield ligne.rstrip("\n") def filtrer_erreurs(lignes): """مولد يحتفظ فقط بالأسطر التي تحتوي ERROR.""" for ligne in lignes: if "ERROR" in ligne: yield ligne def extraire_codes(lignes): """مولد يعيد رمز HTTP لكل سطر.""" for ligne in lignes: try: code = int(ligne.split()[-1]) yield code except (ValueError, IndexError): continue # الأنبوب: لا تستهلك أي خطوة ذاكرة، حتى لـ50 غيغابايت lignes = lire_log("acces.log") erreurs = filtrer_erreurs(lignes) codes = extraire_codes(erreurs) # الاستهلاك النهائي from collections import Counter print(Counter(codes).most_common(5)) # [(500, 1284), (502, 412), (503, 309), ...]
cat file | grep ERROR | awk '{print $NF}' | sort | uniq -c. قابل للقراءة، معياري، وذاكرة ثابتة.yield from: التفويض إلى مولد آخر
def sous_compter(a, b): for i in range(a, b): yield i def compter_tout(): yield from sous_compter(0, 3) # 0,1,2 yield from sous_compter(10, 13) # 10,11,12 yield 99 print(list(compter_tout())) # [0, 1, 2, 10, 11, 12, 99]
yield from يتجنب الحلقة for x in autre: yield x ويتعامل أيضاً بشكل صحيح مع الاستثناءات والقيم المرسلة.
send(): مولدات ثنائية الاتجاه
يمكن إرسال قيم إلى مولد (نادر الاستخدام لكنه قوي).
def echo(): while True: recu = yield print("تم الاستلام:", recu) g = echo() next(g) # تشغيل المولد g.send("hello") g.send("world") # يطبع: Reçu : hello / Reçu : world
هذه الآلية هي أصل asyncio قبل Python 3.5. اليوم نفضل async/await.
الفخ رقم 1: المولد لا يُجتاز إلا مرة واحدة
g = (i*i for i in range(5)) print(list(g)) # [0, 1, 4, 9, 16] print(list(g)) # [] -- انتبه، g استُنفد
الحل: إعادة إنشاء المولد، أو تحويله إلى قائمة إذا احتجته عدة مرات:
data = [i*i for i in range(5)] # قائمة، قابلة لإعادة الاستخدام
التوصيف وإيجاد الاختناقات
الأهداف التعليمية
1. cProfile العام
python -m cProfile -o pipeline.prof pipeline_v0.py
للاستكشاف التفاعلي باستخدام pstats:
python -m pstats pipeline.prof % sort cumulative % stats 15
42_847_310 function calls in 1083.42 seconds
ncalls tottime cumtime filename:lineno(function)
1 0.000 1083.42 pipeline_v0.py:1(<module>)
1 0.005 1083.41 pipeline_v0.py:42(main)
1 654.21 750.18 pipeline_v0.py:11(traiter_transactions)
5000001 34.20 34.20 <built-in method strip>
5000001 28.45 28.45 <built-in method upper>
3750000 21.89 45.30 pipeline_v0.py:18(traiter_transactions/dict.get)
5000001 18.95 18.95 <built-in method float>
3750000 180.45 180.45 list.append (resultats)
1 268.32 268.32 pipeline_v0.py:32(agreger)
1 65.10 65.10 pipeline_v0.py:39(sauver)traiter_transactions = 70٪ من الوقت. داخله: strip/upper (60 ث)، append (180 ث)، float() (19 ث). agreger = 25٪. sauver = 6٪. إذن الأولوية الأولى = traiter_transactions.2. التصور باستخدام snakeviz
snakeviz pipeline.prof
يفتح متصفح. عرض «sunburst»: دائرة مركزية تمثل البرنامج كاملاً، مقسمة إلى أرباع تتناسب مع الوقت. انقر للتكبير.
في ملفنا الشخصي نرى فوراً:
3. التكبير باستخدام line_profiler
نزين الدالة الحرجة:
@profile
def traiter_transactions(produits):
...kernprof -l -v pipeline_v0.py
يغطي هذا المقال المقتطفات الأكثر فائدة — الدورة الكاملة Python Avancé Performance (11 فصلاً، 35 درساً، تمارين مصححة ومشروع نهائي) تأخذك إلى النهاية.
./acceder-au-cours-complet cours gratuit : Maîtriser Claude Codeالأسئلة الشائعة
كم من الوقت يستغرق تعلم Python Avancé Performance؟
هل هناك متطلبات مسبقة؟
من أين نبدأ عملياً؟
📬 هل تريد تلقي هذا النوع من الأدلة كل أسبوع؟ اشترك مجاناً — كود حقيقي، بدون ثرثرة.