NumPy: الشروع في العمل مع NumPy
NumPy هو أساس الحوسبة العلمية في بايثون. سواء كنت تعمل في التعلم الآلي، تحليل البيانات، أو المحاكاة الهندسية، فإن كل مكتبة رقمية تقريبًا تُبنى على NumPy. يبدأ هذا الفصل من نقاط الضعف في أداء قوائم بايثون ويكشف كيف يحقق NumPy تسريعًا بمقدار 160 ضعفًا — خطوتك الأولى في الحوسبة الرقمية.
1. ما ستتعلمه
- ❶ لماذا تعاني قوائم بايثون مع الحوسبة الرقمية
- ❷ المزايا الأساسية لـ NumPy (السرعة / الذاكرة / البث)
- ❸ الاختلافات الرئيسية بين ndarray وقائمة بايثون
- ❹ نظرة عامة على النظام البيئي لـ NumPy
- ❺ تثبيت NumPy وتشغيل أول برنامج لك
2. رحلة التسريع مع بيانات بملايين السجلات
(1) المشكلة: حلقة Alice مع القائمة
Alice محللة بيانات، تحتاج إلى تربيع مليون رقم عشوائي. تكتب كود بايثون الأكثر "طبيعية" — حلقة for تعالج عناصر القائمة واحدًا تلو الآخر.
▶ مثال
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة بايثون المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
: تربيع قائمة (الصعوبة ⭐)
import time
import random
size = 1_000_000 # 1 million numbers
data = [random.random() for _ in range(size)]
start = time.time()
result = [x * x for x in data]
elapsed = time.time() - start
print(f"List comprehension: {elapsed:.4f} seconds")
# Typical output: List comprehension: 0.8000 seconds
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة بايثون المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
كود Alice يستغرق حوالي 0.8 ثانية. لا يبدو ذلك سيئًا — حتى تحتاج إلى إجراء نفس العملية على مئات الملايين من نقاط البيانات، حيث يصبح الانتظار لا يطاق.
(2) الحل: التوجيه المتجهي لـ Bob مع NumPy
Bob يغير سطرين فقط: استبدال القائمة بـ np.array والحلقة بعملية ضرب متجهية واحدة.
▶ مثال
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة بايثون المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
: تربيع NumPy (الصعوبة ⭐⭐)
import time
import numpy as np
size = 1_000_000 # 1 million numbers
data = np.random.random(size)
start = time.time()
result = data * data # vectorized operation
elapsed = time.time() - start
print(f"NumPy vectorized: {elapsed:.4f} seconds")
---
## 3. Typical output: NumPy vectorized: 0.0050 seconds
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة بايثون المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
نفس المليون رقم تستغرق فقط حوالي 5 ملي ثانية مع NumPy — أسرع بحوالي 160 ضعفًا.
(3) النتيجة: ما الذي يجعلها أسرع 160 ضعفًا
سرعة NumPy ليست بسبب "بايثون أصبح أسرع" — بل بسبب تحويل العمليات الحسابية إلى لغة C واستخدام الذاكرة المتجاورة:
- الطبقة الأساسية هي C: العمليات الأساسية لـ NumPy منفذة بلغة C/Fortran، متجاوزةً حمل المفسر الزائد لكل سطر في بايثون
- ذاكرة متجاورة: عناصر ndarray مرتبة بإحكام في الذاكرة، مما يحقق معدلات إصابة عالية لذاكرة التخزين المؤقت لوحدة المعالجة المركزية
- التوجيه المتجهي: تعليمة واحدة تعالج المصفوفة بأكملها مرة واحدة، دون الحاجة إلى حلقة على مستوى بايثون
هذه هي فلسفة NumPy الأساسية — سهولة بايثون مع سرعة C.
4. لماذا تعاني قوائم بايثون مع الأرقام
(1) الحمل الزائد للحلقة: التنفيذ المفسر
بايثون لغة ديناميكية الأنماط. كل تكرار للحلقة يمر بـ: التحقق من النوع ← البحث عن الدالة → تنفيذ العملية → تغليف النتيجة. بالنسبة للعمل الرقمي، هذه الخطوات الإضافية هي هدر محض.
---
## 5. Each iteration: type check + method lookup + boxing
result = []
for x in data: # Python loop overhead per iteration
result.append(x * x) # type check, method dispatch, result boxing
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة بايثون المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
مقارنة بكود C المترجم، يمكن أن يكون الحمل الزائد لتكرار حلقة بايثون الواحدة عشرات المرات أكثر تكلفة من عملية الضرب الفعلية.
(2) الحمل الزائد للنوع: كل عنصر هو كائن كامل
قائمة بايثون لا تخزن أرقامًا أولية — إنها تخزن مؤشرات PyObject. العدد الصحيح 42 يأخذ 28 بايت في بايثون، ولكن 4 بايت فقط في C.
6. المزايا الأساسية لـ NumPy
(1) ما هو ndarray
ndarray (مصفوفة متعددة الأبعاد) هي بنية البيانات الأساسية لـ NumPy. إنها:
- متجانسة: جميع العناصر تشترك في نفس النوع (مثل كلها
float64) - ثابتة الحجم: حجم العنصر ثابت، لا حاجة للتغليف
- متجاورة: العناصر مرتبة بإحكام في الذاكرة، بدون مؤشرات غير مباشرة
- متعددة الأبعاد: تدعم بيانات أحادية وثنائية وحتى N-الأبعاد
(2) ndarray مقابل قائمة بايثون
| الخاصية | قائمة بايثون | ndarray في NumPy |
|---|---|---|
| نوع العنصر | أي خليط | متجانس (نمط dtype واحد) |
| تخطيط الذاكرة | مصفوفة مؤشرات، عناصر متناثرة | كتلة ذاكرة متجاورة |
| ذاكرة عدد صحيح واحد | 28 بايت (PyObject) | 8 بايت (int64) |
| العمليات الجماعية | فهم القائمة / حلقة for | متجهية، تعليمة واحدة |
| البث | غير مدعوم | توسيع الأبعاد تلقائيًا |
| عرض الشريحة | يعيد نسخة | يعيد عرضًا افتراضيًا (بدون نسخ) |
| متعدد الأبعاد | قوائم متداخلة (غير منتظمة) | أبعاد N أصلية، خاصية shape |
| تحت الغطاء | مفسر CPython | كود C / Fortran مُجمّع |
▶ مثال
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة بايثون المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
: مقارنة الذاكرة (الصعوبة ⭐)
import sys
import numpy as np
size = 1_000_000 # 1 million integers
---
## 7. Python list
py_list = list(range(size))
list_bytes = sys.getsizeof(py_list) + sum(sys.getsizeof(x) for x in py_list[:1000]) * size // 1000
print(f"Python list: ~{list_bytes / 1024 / 1024:.1f} MB")
---
## 8. NumPy ndarray
np_array = np.arange(size, dtype=np.int64)
array_bytes = np_array.nbytes
print(f"NumPy ndarray: {array_bytes / 1024 / 1024:.1f} MB")
print(f"Ratio: {list_bytes / array_bytes:.1f}x more memory for list")
---
## 9. Typical output:
---
## 10. Python list: ~44.7 MB
---
## 11. NumPy ndarray: 7.6 MB
---
## 12. Ratio: 5.9x more memory for list
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة بايثون المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
مصفوفة ndarray بسيطة من النوع int64 تستخدم ذاكرة أقل بحوالي 6 مرات من قائمة بايثون بنفس الحجم — لأن ndarray تخزن القيم الأولية، وليس كائنات بايثون كاملة.
(3) نظرة أولى على البث (Broadcasting)
"البث" يسمح للمصفوفات ذات الأشكال المختلفة بالعمل معًا مباشرة، دون الحاجة إلى توسيع الأبعاد يدويًا.
▶ مثال
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة بايثون المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
: عمليات المصفوفة مقابل فهم القائمة (الصعوبة ⭐⭐)
import numpy as np
---
## 13. Task: add 100 to every element and multiply by 2
data = [1, 2, 3, 4, 5]
---
## 14. --- Python list ---
result_list = [(x + 100) * 2 for x in data]
print(result_list)
---
## 15. [202, 204, 206, 208, 210]
---
## 16. --- NumPy ndarray ---
arr = np.array(data)
result_np = (arr + 100) * 2 # broadcasting + vectorization
print(result_np)
---
## 17. [202 204 206 208 210]
---
## 18. With a 2D array and 1D array
matrix = np.array([[1, 2, 3],
[4, 5, 6]])
row = np.array([10, 20, 30])
print(matrix + row)
---
## 19. [[11 22 33]
---
## 20. [14 25 36]]
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة بايثون المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبتًا مسبقًا — قم بتثبيته محليًا (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
فهم القائمة يحتاج إلى كتابة منطق الحلقة، بينما NumPy يحتاج فقط إلى تعبير رياضي واحد — الكود هو الصيغة.
❓ أسئلة شائعة
array المضمنة في بايثون تدعم فقط مصفوفات أحادية البعد متجانسة بدون بث أو جبر خطي أو تحويل فورييه. ndarray في NumPy يدعم بيانات متعددة الأبعاد، البث، ومجموعة غنية من الدوال الرياضية — هما في مستويين مختلفين تمامًا.📖 ملخص
- قوائم بايثون تعاني من نقطتي ضعف للحوسبة الرقمية: حمل زائد عالٍ لتفسير الحلقات وذاكرة كبيرة لكل عنصر (كائنات كاملة)
- يحقق NumPy تسريعًا بحوالي 160 ضعفًا على مليون نقطة بيانات من خلال تنفيذ C + ذاكرة متجاورة + توجيه متجهي
- ndarray هي مصفوفة متجانسة وثابتة الحجم ومتجاورة متعددة الأبعاد، تستخدم ذاكرة أقل بحوالي 6 مرات من قوائم بايثون
- البث يسمح للمصفوفات ذات الأشكال المختلفة بالعمل معًا مباشرة — الكود هو الصيغة
- NumPy هو أساس النظام البيئي للحوسبة العلمية في بايثون: Pandas، Scikit-learn، SciPy، TensorFlow، وغيرها الكثير تعتمد عليه
- التثبيت بـ
pip install numpy، إنشاء المصفوفات بـnp.array()، التحقق من الإعدادات بـnp.show_config()
📝 تمارين
-
مبتدئ (الصعوبة ⭐): قم بتثبيت NumPy وشغّل
import numpy as np; print(np.__version__). تأكد من أن الإصدار ≥ 1.24. سجل المخرجات. -
متوسط (الصعوبة ⭐⭐): أنشئ قائمة ومصفوفة ndarray، كل منهما يحتوي على 5 ملايين رقم عشوائي. استخدم فهم القائمة وتوجيه NumPy المتجهي لحساب مربع كل رقم زائد 10. قم بقياس الوقت لكل منهما باستخدام
time.time()وقارن. سجل نسبة السرعة. -
متقدم (الصعوبة ⭐⭐⭐): شغّل
np.show_config()وحدد خلفية BLAS التي يستخدمها NumPy الخاص بك (OpenBLAS / MKL / غير ذلك). ابحث كيف تؤثر هذه الخلفية على أداء عمليات المصفوفات واكتب شرحًا موجزًا.