بايثون المتقدم: شرح الأداء ببساطة (مع رسوم توضيحية وكود حقيقي)

أداء بايثون المتقدم: الأساسيات في مقال واحد — كود حقيقي، مخططات وخطوات ملموسة، مقتطفات من دورة تتكون من 35 درسًا.

بايثون المتقدم: شرح الأداء ببساطة (مع رسوم توضيحية وكود حقيقي)

دليل مباشر وموجز: Python Avancé Performance مفكك مع مخططات وأمثلة عملية وأوامر مجربة. كل شيء مستمد من دورة منظمة تضم 11 فصلاً — إليك أفضل ما فيها.

tl;dr
  • المقدمة والتثبيت
  • توصيف الكود
  • Comprehensions iterators generators
  • Multithreading vs Multiprocessing
  • asyncio et coroutines
~$ cat ./parcours.md # Python Avancé Performance — 10 فصول
01
مقدمة وتثبيت
→ عرض الدورة→ تثبيت أدوات التنميط+ 1 دروس أخرى
02
تنميط الكود
→ cProfile et pstats→ timeit لقياسات دقيقة+ 1 دروس أخرى
03
تعبيرات التضمين والمكررات والمولدات
→ تعبيرات القوائم والمجموعات والقواميس→ بروتوكول المكرر+ 1 دروس أخرى
04
تعدد الخيوط مقابل تعدد المعالجات
→ Threading، GIL وحدوده→ Multiprocessing، التوازي الحقيقي+ 1 دروس أخرى
05
asyncio والـ coroutines
→ مقدمة إلى asyncio→ async / await في التطبيق العملي+ 1 دروس أخرى
06
Cython و Numba والتوجيه
→ التوجيه باستخدام NumPy→ Numba JIT+ 1 دروس أخرى
07
إدارة الذاكرة
→ جامع القمامة في Python→ المراجع الضعيفة باستخدام weakref+ 1 دروس أخرى
08
التخزين المؤقت والميمويزيشن
→ functools.lru_cache→ التخزين المؤقت على القرص باستخدام joblib و diskcache+ 1 دروس أخرى
🏁
المشروع النهائي (+ 2 فصول في الطريق)
→ تعود بمشروع ملموس وقابل للعرض

تقديم الدورة

NOTEالهدف — فهم معنى «تحسين كود Python»، ومتى يجب القيام بذلك (ومتى يجب تجنبه)، والحصول على رؤية واضحة للمسار الذي ينتظرك في هذه الدورة.

الأهداف التعليمية

TIPعند نهاية هذه الوحدة — ستتمكن من شرح سبب شهرة Python بالبطء (ولماذا هذا أسطورة جزئياً)، وستعرف المحاور الثلاثة الكبرى للتحسين، وستفهم القاعدة الذهبية: قِس قبل أن تحسّن.

المشكلة العملية

هل سبق أن مررت بهذا المشهد؟ تطلق سكريبت Python في الساعة 17:00 لحساب تقرير، ثم تذهب لتحضر قهوة، وعندما تعود بعد 30 دقيقة لا يزال السكريبت يعمل بعد. والأسوأ: زميلك الذي يفعل الشيء نفسه بلغة R أو Julia انتهى في 3 دقائق.

الأعراض النموذجية لبرنامج Python بطيء

ما ستتعلم القيام به

NOTEهل Python بطيئة حقاً؟ — Python النقي (CPython) قد يكون أبطأ من C بـ50 إلى 100 ضعف في الحلقات العددية. لكن، معظم المكتبات العلمية (NumPy، Pandas، scikit-learn) مكتوبة بلغة C أو Fortran. عند استخدامه جيداً، يصل Python إلى 80-95٪ من أداء C. المشكلة ليست «Python بطيئة» بل «كودي مكتوب بشكل سيئ».

المحاور الثلاثة الكبرى للتحسين

المحور السؤال المطروح الكسب النموذجي
1. الخوارزمية هل كودي في O(n²) بينما يمكن أن يكون في O(n log n)؟ 10× إلى 10,000×
2. بنية البيانات هل أستخدم list بينما set يكفي؟ 10× إلى 1000×
3. التزامن / التوازي هل يمكنني تنفيذ هذه المهام الثماني في الوقت نفسه؟ 2× إلى 16× (حسب CPU/IO)
WARNINGترتيب الهجوم — دائماً بهذا الترتيب: الخوارزمية > البنية > التوازي. توازي خوارزمية سيئة يشبه وضع 8 أشخاص لحفر نفق بملعقة صغيرة بينما كانت الحفارة كافية.

قاعدة 80/20 (باريتو)

في 80٪ من البرامج، يُقضى 80٪ من وقت التنفيذ في 20٪ من الكود. غالباً ما تكون النسبة 90/10 أو 95/5.

TIPالنتيجة — لا فائدة من تحسين كل الكود. ابحث عن الأسطر الخمسة التي تكلف 95٪ من الوقت، حسّنها بقوة، واترك الباقي.

دونالد كنوث، أسطورة الحوسبة، لخص الأمر عام 1974 كالتالي:

NOTE«Premature optimization is the root of all evil.»
(التحسين المبكر هو جذر كل الشرور.)

الترجمة العملية: اكتب أولاً كوداً واضحاً وصحيحاً. استمتع به. إذا كان بطيئاً جداً، حسّن فقط الأجزاء الساخنة. وإلا ستضيع وقتاً في جعل كود غير مؤثر غير قابل للقراءة.

قبل وبعد يتحدثان

إليك مثالاً حقيقياً: جمع مربعات الأعداد من 0 إلى 10 ملايين.

output
# النسخة البسيطة: حلقة 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

NOTEالهدف — اكتشاف yield، الكلمة المفتاحية التي تحول الدالة إلى مولد، وتعلم بناء أنابيب معالجة بيانات كسولة قادرة على معالجة ملفات بحجم عدة غيغابايت ببضع ميغابايت من الذاكرة.

الأهداف التعليمية

TIPعند نهاية هذه الوحدة — ستتمكن من كتابة مولد، استخدامه في أنبوب معالجة، والاختيار بذكاء بين قائمة ومولد ومجموعة مادية.

مولد أول

output
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
NOTEالسحر — بمجرد أن تحتوي الدالة على yield، يحولها Python إلى مصنع مولدات. الاستدعاء لا يشغل الكود: بل يعيد كائن مولد. لا يُنفذ الكود إلا عند كل استدعاء next().

yield مقابل return

returnyield
تنهي الدالةتعلق الدالة
تفقد الحالةتحتفظ بالحالة
تعيد قيمة (مرة واحدة)يمكن استدعاؤها عدة مرات
تعيد كل شيء دفعة واحدةتعيد عنصراً في كل مرة

حالة استخدام حقيقية: قراءة ملف سجل كبير

output
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), ...]
TIPفن الأنبوب — كل مولد يقوم بشيء واحد ويمرر النتيجة إلى التالي. هذا نموذج أدوات يونكس: cat file | grep ERROR | awk '{print $NF}' | sort | uniq -c. قابل للقراءة، معياري، وذاكرة ثابتة.

yield from: التفويض إلى مولد آخر

output
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(): مولدات ثنائية الاتجاه

يمكن إرسال قيم إلى مولد (نادر الاستخدام لكنه قوي).

output
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: المولد لا يُجتاز إلا مرة واحدة

output
g = (i*i for i in range(5))
print(list(g))   # [0, 1, 4, 9, 16]
print(list(g))   # []  -- انتبه، g استُنفد

الحل: إعادة إنشاء المولد، أو تحويله إلى قائمة إذا احتجته عدة مرات:

output
data = [i*i for i in range(5)]   # قائمة، قابلة لإعادة الاستخدام

التوصيف وإيجاد الاختناقات

NOTEالهدف — تطبيق طريقة الفصل 1 على أنبوب ETL الخاص بنا: استخدام cProfile لرؤية أين ذهب الوقت، وsnakeviz للتصور، وline_profiler للتكبير على الدالة الحرجة.

الأهداف التعليمية

TIPعند نهاية هذه الوحدة — ستتمكن من توصيف سكريبت إنتاج كامل، قراءة التقرير، تحديد الأسطر الثلاثة أو الأربعة التي تستهلك 90٪ من الوقت، وكتابة «تقرير تشخيصي» واضح لفريقك.

1. cProfile العام

output
python -m cProfile -o pipeline.prof pipeline_v0.py

للاستكشاف التفاعلي باستخدام pstats:

output
python -m pstats pipeline.prof
% sort cumulative
% stats 15
output
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)
NOTEالقراءةtraiter_transactions = 70٪ من الوقت. داخله: strip/upper (60 ث)، append (180 ث)، float() (19 ث). agreger = 25٪. sauver = 6٪. إذن الأولوية الأولى = traiter_transactions.

2. التصور باستخدام snakeviz

output
snakeviz pipeline.prof

يفتح متصفح. عرض «sunburst»: دائرة مركزية تمثل البرنامج كاملاً، مقسمة إلى أرباع تتناسب مع الوقت. انقر للتكبير.

في ملفنا الشخصي نرى فوراً:

3. التكبير باستخدام line_profiler

نزين الدالة الحرجة:

output
@profile
def traiter_transactions(produits):
    ...
output
kernprof -l -v pipeline_v0.py
va-plus-loin

يغطي هذا المقال المقتطفات الأكثر فائدة — الدورة الكاملة Python Avancé Performance (11 فصلاً، 35 درساً، تمارين مصححة ومشروع نهائي) تأخذك إلى النهاية.

./acceder-au-cours-complet cours gratuit : Maîtriser Claude Code

الأسئلة الشائعة

كم من الوقت يستغرق تعلم Python Avancé Performance؟
مع تقدم منظم (11 فصلاً، 35 درساً قصيراً وعملياً)، يمكن الوصول إلى مستوى تشغيلي في بضعة أسابيع بمعدل 30 إلى 60 دقيقة يومياً. المهم هو تطبيق كل مفهوم فوراً.
هل هناك متطلبات مسبقة؟
من الأفضل أن تكون مرتاحاً مع أساسيات المجال: هذا المحتوى يتعمق، مع حالات حقيقية.
من أين نبدأ عملياً؟
أعد إنتاج أوامر هذا المقال، ثم تابع الدورة الكاملة Python Avancé Performance: تربط الـ35 درساً بالترتيب، مع تمارين ومشروع نهائي.

📬 هل تريد تلقي هذا النوع من الأدلة كل أسبوع؟ اشترك مجاناً — كود حقيقي، بدون ثرثرة.