6.2. المصفوفة ndarray

ndarray هي النوع الذي يحمل البيانات العددية في numpy. وهي شيئان في واحد: كتلة بيانات واحدة محزومة، ووصّاف صغير أمام تلك الكتلة يصف كيفية قراءتها.

6.2.1. داخل الصندوق

تحمل كتلة البيانات كل عنصر من عناصر المصفوفة طرفًا إلى طرف، دون أي شيء إضافي بينها. ويشغل كل عنصر نفس العدد من البايتات -- بايت واحد لمصفوفة من قيم uint8، وبايتان لـ uint16، وأربعة لـ float. فمصفوفة uint8 بـ 256 عنصرًا هي 256 بايتًا من البيانات بالضبط؛ بينما تأخذ نفس الأرقام الـ 256 في list في Python كيلوبايتًا -- خانة واحدة بطول 32 بت لكل عنصر بغض النظر عن قلة البتات التي تحتاجها القيمة فعليًا.

يسجل الوصّاف ما تعنيه الكتلة. وتكفي خمس قيم لوصف أي مصفوفة مستطيلة، مهما بلغ عدد أبعادها:

  • dtype -- نوع العنصر. ويقرر عدد البايتات التي يأخذها كل عنصر ونطاق القيم التي يمكن أن يحملها (يُغطّى في أنواع البيانات (Dtypes)).

  • itemsize -- عرض البايتات لعنصر واحد، مشتقًا من نوع البيانات.

  • ndim -- عدد الأبعاد (1 لمتجه، 2 لمصفوفة، 3 لحجم، حتى 4).

  • shape -- الحجم على طول كل بُعد بصيغة صف.

  • strides -- كيفية التنقل عبر كتلة البيانات للسير على كل محور. يُغطّى في الشكل والخطوات (strides).

هذا كل شيء. فكل مسار سريع في numpy -- الحساب، والاختزالات، والبثّ، والتقطيع -- يعمل مباشرة من تلك القيم الخمس بالإضافة إلى مؤشر البيانات، دون أي عبء Python لكل عنصر.

6.2.2. ما يحققه التصميم

تنبثق ثلاث خصائص من "الكتلة المحزومة + الوصّاف الصغير" وتحدّد كيفية تصرّف بقية هذا القسم.

الحساب على مستوى العناصر يجري كاستدعاء واحد. فـ a + b بين مصفوفتين متطابقتي الشكل يجمع المخزنين المؤقتين ويكتب ثالثًا، كل ذلك داخل استدعاء مكتبة واحد. وتفعل np.sin(a) الشيء نفسه لجيب كل عنصر. وتعمل عوامل الحساب والمقارنة وعوامل البتات جميعها بهذه الطريقة.

النظر إلى نفس البيانات بطريقة مختلفة مجاني. فطلب منطقة فرعية من مصفوفة، أو نفس البيانات مرتبة بشكل مختلف، لا يحرّك أي بايتات. وتعيد العملية وصّافًا جديدًا يشير إلى نفس كتلة البيانات. وتُسمّى النتيجة عرضًا (view) -- نافذة ثانية على نفس المخزن المؤقت الأساسي. والكتابة عبر العرض تكتب إلى المصدر.

الأشكال المختلطة لا تزال تعمل. فمصفوفة أقصر مقابل أطول، أو صف مقابل مصفوفة، أو عمود مقابل صف -- يصطفّها numpy عبر البثّ (broadcasting)، وهو مجموعة صغيرة من القواعد التي تقرر أي محور قصير يتمدد ليطابق المحور الطويل. والتمدد افتراضي؛ ولا تُضاعَف أي بيانات.

6.2.3. ما يكلفه التصميم

ينتج قيدان من نفس التصميم.

كل عنصر له نفس النوع. فيمكن للقائمة أن تحمل int بجوار str بجوار قائمة من ثلاث قيم int أخرى؛ بينما لا تستطيع ndarray ذلك. فنوع البيانات ثابت عند وقت الإنشاء. وتغطي صفحة أنواع البيانات (Dtypes) المجموعة الصغيرة من الأنواع التي يدعمها numpy والقواعد التي تنتج عن تثبيت واحد.

تنمية المصفوفة ليست مجانية. فالقائمة تحتفظ بخانات احتياطية في النهاية وتدعم .append بثمن زهيد. أما ndarray فهي بالضبط الحجم الذي تحتاجه؛ والإلحاق سيعني تخصيص مخزن مؤقت جديد أكبر ونسخ المحتويات القديمة إليه. ولا توجد طريقة append()، عن قصد. والنمط الصحيح على الكاميرا هو تخصيص الوجهة مسبقًا بحجمها النهائي وملؤها؛ وتغطي الأداء هذه التقنية.

بوجود مخزن مؤقت محزوم ومُنمّط للبيانات، ووصّاف صغير للبيانات الوصفية، وثلاث ضمانات سلوكية (حساب سريع على مستوى العناصر، وعروض بديلة لنفس البيانات دون نسخ، وأشكال تُبَثّ)، تكون ndarray هي الأساس الذي تستند إليه بقية الفصل. وكيفية نشوء المصفوفة فعليًا -- من قيمة حرفية، أو من تخصيص مملوء مسبقًا، أو من مخزن مؤقت لطرفية -- هي السؤال العملي التالي.