بايثونبرمجةعلم البيانات

كيفية تحويل مصفوفة NumPy إلى قائمة في بايثون (مع أمثلة)

دليل أكاديمي شامل يشرح كيفية تحويل مصفوفات NumPy إلى قوائم في لغة بايثون بالتفصيل مع أمثلة برمجية متعددة الأبعاد وتحليل الأداء والذاكرة.

تاريخ النشر

تُعد لغة بايثون اليوم إحدى الركائز الأساسية في المشهد البرمجي المعاصر، حيث تتربع على عرش الحوسبة العلمية، وتحليل البيانات، وهندسة الذكاء الاصطناعي، والتعلم الآلي. يعود هذا التفوق الاستثنائي إلى قدرة بايثون الفائقة على الجمع بين سهولة البناء اللغوي التجريدي من جهة، وبين الإمكانات الحسابية الهائلة المستمدة من دمجها مع مكتبات معالجة منخفضة المستوى مكتوبة بلغات مثل C وC++ وتعتبر مكتبة NumPy حجر الزاوية الذي تنبثق منه معظم الأنظمة الحسابية في بايثون؛ إذ وفرت المكتبة المعيارية للمصفوفات متجانسة النوع ومتعددة الأبعاد المعروفة باسم كائنات ndarray، والتي أحدثت ثورة حقيقية في سرعة العمليات الجبرية والحسابية عبر معالجة البيانات داخل كتل ذاكرية متصلة تقضي على البطء الملازم لمفسر بايثون القياسي.

ومع ذلك، فإن هذا التباين البنيوي بين عالم بايثون القياسي القائم على المرونة الديناميكية والتوجيه الكائني المرجعي، وبين عالم NumPy الصارم والمحكوم بكفاءة العتاد والذاكرة المباشرة، يخلق حاجة متكررة لجسر الهوة بين البيئتين. فبينما تتألق مصفوفات NumPy في معالجة العمليات الشعاعية والمعادلات الرياضية المعقدة بأداء يقارب سرعة لغات الأنظمة الأصلية، تبرز قوائم بايثون القياسية (Python Lists) كأداة لا غنى عنها في بيئات التطوير العامة، لا سيما عند الرغبة في تسلسل البيانات بتنسيقات الويب مثل JSON، أو تغذية واجهات المستخدم، أو التفاعل مع حزم ومكتبات خارجية لا تمتلك ارتباطات مسبقة بمكتبة NumPy. ومن ثم، تصبح عملية تحويل مصفوفات NumPy إلى قوائم بايثون إحدى العمليات المحورية التي يواجهها مهندسو البرمجيات وعلماء البيانات بصفة يومية.

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

1. مقدمة شاملة حول هياكل البيانات في بايثون ومكتبة NumPy

1.1 طبيعة وتصميم هياكل البيانات في بايثون القياسية

تم تصميم هياكل البيانات المدمجة في لغة بايثون القياسية، وعلى رأسها القوائم (Lists)، لتوفير أعلى درجات المرونة البرمجية والتجريد للمطورين. تُعرف القائمة في بايثون بأنها متسلسلة ديناميكية قابلة للتعديل (Mutable Sequence)، تتيح إضافة العناصر وحذفها وتعديلها دون الحاجة إلى تحديد مسبق لحجم الذاكرة المستهلكة. هذا التصميم يمنح المبرمج حرية كاملة في بناء هياكل بيانات تتسع وتتقلص تلقائياً وفقاً لمتطلبات وقت التشغيل (Runtime)، مما يقلل بشكل ملحوظ من التعقيدات المرتبطة بإدارة الذاكرة اليدوية المألوفة في لغات البرمجة منخفضة المستوى.

تتجلى السمة الأبرز لقوائم بايثون في قدرتها على احتواء عناصر متباينة النوع (Heterogeneity). يمكن لقائمة واحدة أن تضم في آن واحد أعداداً صحيحة، وسلاسل نصية، وقيم منطقية، وكائنات مخصصة، وحتى قوائم أخرى متداخلة. يعود هذا السلوك الفريد إلى أن قوائم بايثون لا تخزن القيم الفعلية للبيانات داخل بنيتها التخزينية المباشرة، بل تخزن مصفوفة من المؤشرات (Array of Pointers) التي تشير إلى كائنات بايثون المستقلة (PyObject) الموزعة في كومة الذاكرة (Heap Memory). تمنح هذه الآلية المرجعية مرونة قصوى لكنها تفرض في الوقت ذاته تكلفة حسابية إضافية ناجمة عن فك المراجع (Pointer Dereferencing) وإدارة عدادات الإسناد لكل كائن على حدة.

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

1.2 نشأة مكتبة NumPy ودور مصفوفات ndarray

نشأت مكتبة NumPy (Numerical Python) استجابة للقصور البنيوي في معالجة الحسابات العلمية داخل بايثون القياسية، حيث دمجت المكتبة إرث مشاريع سابقة مثل Numeric و Numarray لتقديم حل متكامل وعالي الأداء. جاءت المكتبة لتزود لغة بايثون بمصفوفة الأبعاد المتعددة الكينونية المعروفة باسم ndarray، والتي أعادت تعريف كيفية تخزين البيانات الرقمية ومعالجتها. يتميز كائن ndarray بأنه هيكل بيانات متجانس النوع (Homogeneous)، حيث تشترك جميع العناصر المكونة للمصفوفة في نفس نوع البيانات الرياضي الدقيق (dtype) وبنفس الحجم الحسابي المحجوز في الذاكرة.

يكمن السر الأساسي في تفوق مصفوفات ndarray في استراتيجية التخزين الذاكري المتصل (Contiguous Memory Allocation)؛ حيث تُرصف بايتات البيانات مباشرة وبشكل متجاور في الذاكرة العشوائية دون وسائط كائنية أو مؤشرات مرجعية منفصلة. يتيح هذا التخطيط الذاكري الصارم للمعالجات المركزية ووحدات معالجة الرسوميات قراءة البيانات وتفريغها في خطوط الذاكرة المخبأة (Cache Lines) بكفاءة قصوى، مما يمهد الطريق لتنفيذ العمليات الشعاعية أو الموجهة (Vectorized Operations) باستخدام تعليمات المعالجة المتعددة للبيانات المفردة (SIMD) دون الحاجة للمرور عبر حلقات التكرار البرمجية البطيئة في بايثون.

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

1.3 مفهوم التوافقية والتحويل البيني بين الهياكل

تحتم البيئة البرمجية الحديثة وجود آليات تحويل بينية سلسة بين مصفوفات الحوسبة المتقدمة وهياكل البيانات القياسية. فرغم التفوق الحسابي لمصفوفات NumPy، لا يمكن عزل النظم الحسابية عن سياقها العام داخل التطبيقات الشاملة التي تتطلب التفاعل مع قواعد البيانات، وخوادم الشبكة، وواجهات برمجة التطبيقات (APIs). تتطلب هذه الواجهات في كثير من الأحيان هياكل بيانات قياسية تدعم البروتوكولات العامة لمعالجة النصوص وعمليات التبادل التسلسلي، وهو ما لا توفره مصفوفات NumPy بصورة مباشرة.

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

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

2. الفروق الجوهرية والمعمارية بين مصفوفات NumPy وقوائم بايثون

2.1 التمثيل في الذاكرة وتوزيع البيانات

تختلف مصفوفات NumPy وقوائم بايثون اختلافاً جوهرياً في كيفية تمثيل البيانات داخل الذاكرة العشوائية (RAM). تعتمد مصفوفة ndarray في NumPy على كتلة ذاكرية متصلة وموحدة (Contiguous Block of Memory)، حيث يتم تخزين القيم الرقمية متتالية خطياً بنسق ثنائي نقي وفق حجم ثابت لكل عنصر (مثل 4 بايت للأعداد الصحيحة من نوع int32 أو 8 بايت للأعداد العشرية من نوع float64). تتألف المصفوفة داخلياً من ترويسة بسيطة تحتوي على البيانات الوصفية (Metadata) مثل الأبعاد، ونوع البيانات، وخطوات الانتقال (Strides)، متبوعة بمؤشر وحيد يشير إلى بداية الكتلة الذاكرية الصلبة، مما يلغي تماماً الحاجة إلى تمثيل كل عنصر ككائن مستقل.

في المقابل، تمثل قائمة بايثون القياسية هيكلاً أكثر تعقيداً يقوم على مفهوم مصفوفة المؤشرات (Array of Object Pointers). لا تحتوي الذاكرة المحجوزة للقائمة على الأرقام أو النصوص بذاتها، بل تحتوي على تسلسل من العناوين الذاكرية التي يشير كل منها إلى كائن بايثون مكتمل النوع (PyObject) يقبع في موضع عشوائي داخل كومة الذاكرة. هذا يعني أن كل عنصر داخل قائمة بايثون يحمل حمولة زائدة (Overhead) تشمل عداد الإسناد الخاص به (Reference Count) ومؤشر نوع الكائن (Type Pointer)، إلى جانب القيمة الفعلية، مما يرفع الاستهلاك الذاكري للعنصر الواحد لعدة أضعاف حجمه الخام.

ينعكس هذا التباين المعماري مباشرة على استغلال آلية المحليات الزمانية والمكانية لذاكرة التخزين المؤقت للمعالج (Spatial and Temporal Cache Locality). فعندما يقرأ المعالج عنصراً من مصفوفة NumPy، يتم تلقائياً تحميل العناصر المتجاورة في الذاكرة المؤقتة (L1/L2 Cache) نظراً لوجودها في نفس خط الذاكرة، مما يجعل العمليات المتتابعة فائقة السرعة. أما في قوائم بايثون، فإن القفز المستمر بين عناوين الذاكرة المتباعدة لجلب الكائنات المرجعية يؤدي إلى حدوث إخفاقات مستمرة في الذاكرة المؤقتة (Cache Misses)، مجبراً المعالج على طلب البيانات من الذاكرة الرئيسية الأبطأ بكثير، مما يثبط الأداء في المعالجات الحسابية الموسعة.

2.2 النوعية الصارمة مقابل المرونة الديناميكية

تفرض مكتبة NumPy نظاماً نوعياً صارماً وغير قابل للمرونة العشوائية؛ إذ يشترط كائن ndarray تجانس النوع لجميع خلاياه. عند إنشاء المصفوفة، يتم إسناد نوع بيانات دقيق ومحدد بحجم البايتات (Fixed-size Data Type مثل np.float32 أو np.int16 أو np.datetime64). إذا حاول المستخدم إدخال عنصر ذي نوع مختلف، تقوم NumPy بإجراء عملية تحويل نوعي قسري وتلقائي (Type Coercion) لكافة عناصر المصفوفة لتصل إلى أدنى نوع مشترك يمكنه احتواء البيانات (Upcasting)، أو تفشل العملية إذا استحال التحويل. هذه الصرامة تضمن الحفاظ على انتظام الإزاحات الذاكرية وثبات العمليات الحسابية على مستوى العتاد.

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

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

2.3 كفاءة المعالجة الحسابية والتحجيم

تتباعد كفاءة المعالجة بين الهيكلين عند التوسع في حجم البيانات والعمليات الحسابية؛ إذ تعتمد مصفوفات NumPy على الحوسبة الشعاعية الخالية من الحلقات الصريحة (Vectorization). فعند إجراء عملية ضرب لمصفوفتين، لا يتم استدعاء مفسر بايثون لتكرار العملية على كل عنصر، بل يتم تمرير كتلة الذاكرة بأكملها إلى دوال C منخفضة المستوى تنفذ العملية بالتوازي باستخدام تعليمات المعالج المتطورة مثل AVX و SSE. يتيح هذا النهج تنفيذ العمليات على ملايين الأرقام في أجزاء من الألف من الثانية مع استهلاك مستقر ومضبوط لموارد المعالجة.

في المقابل، يتطلب إجراء نفس العملية على قوائم بايثون اللجوء إلى حلقات التكرار الصريحة (مثل for loops أو تعابير الاشتقاق list comprehensions). في كل دورة تكرار، يقوم مفسر بايثون بعدة خطوات بطيئة تشمل: فك المرجع للوصول إلى الكائن، والتحقق من نوع البيانات الخاص به، واستدعاء الطريقة الرياضية المناسبة لنوعه، ثم إنشاء كائن بايثون جديد لتخزين الناتج وإسناده إلى الذاكرة. يتراكم هذا العبء الإداري لكل عنصر على حدة، مما يؤدي إلى تدهور حاد في زمن التنفيذ مع تزايد حجم البيانات.

يتضح هذا التباين بشكل خاص عند معالجة المصفوفات متعددة الأبعاد. فالمصفوفة ثلاثية الأبعاد في NumPy تظل كتلة ذاكرة واحدة تُدار عبر مؤشرات وأبعاد محددة رياضياً، بينما تتطلب في بايثون بناء قوائم متداخلة هرمياً (List of Lists of Lists)، مما يعني مضاعفة أعداد المؤشرات وإرباك مجمع النفايات (Garbage Collector) لإدارة مئات الآلاف من الكائنات الفردية. ولهذا، يظل هيكل NumPy الخيار الأوحد للحسابات الرياضية، بينما تقتصر قوائم بايثون على المهام التجميعية والتنظيمية العامة.

3. دواعي وأسباب تحويل مصفوفات NumPy إلى قوائم بايثون

3.1 التوافق مع بروتوكولات التسلسل وتبادل البيانات (JSON Serialization)

يعد التوافق مع معايير تسلسل البيانات وتبادلها عبر الشبكة، وتحديداً تنسيق JSON (JavaScript Object Notation)، الدافع الرئيسي الأكثر شيوعاً لتحويل مصفوفات NumPy إلى قوائم بايثون القياسية. صُممت وحدة json المدمجة في مكتبة بايثون القياسية للتعامل حصرياً مع الأنواع البيانية الأصلية في اللغة (مثل القوائم، والقواميس، والنصوص، والأرقام الصحيحة والعشرية القياسية، والقيم المنطقية). تفتقر هذه الوحدة إلى أي معرفة مسبقة بالأنواع المخصصة التي تقدمها مكتبة NumPy، مما يجعلها عاجزة عن تسلسل كائنات ndarray مباشرة.

عند محاولة تمرير مصفوفة NumPy أو حتى قيمة عددية مفردة تنتمي لمكتبة NumPy (مثل np.int64 أو np.float32) إلى الدالة json.dumps()، يقوم المفسر فوراً بإطلاق استثناء برمجي صريح من نوع TypeError: Object of type ndarray is not JSON serializable. يحدث هذا الانهيار لأن وحدة JSON لا تستطيع قراءة البنية الداخلية لكتلة الذاكرة في NumPy أو تفسير دلالات الأنواع العددية الخاصة بها. ولتجاوز هذا العائق دون كتابة مشفرات مخصصة ومعقدة، يصبح تحويل المصفوفة إلى قائمة بايثون أصيلة خطوة حتمية لإعادة البيانات إلى بنيتها التجريدية القياسية القابلة للتسلسل.

تتجلى هذه الضرورة بوضوح في بنية خدمات الويب وتطبيقات الحوسبة السحابية؛ فعند بناء واجهات برمجة التطبيقات (RESTful APIs) باستخدام أطر عمل مثل FastAPI أو Flask أو Django، تقوم الخوارزميات الحسابية ونماذج الذكاء الاصطناعي بإنتاج مخرجاتها في صورة مصفوفات NumPy. ولكي يتم إرسال هذه الاستجابات عبر بروتوكول HTTP بصيغة JSON متوافقة إلى المتصفحات وتطبيقات الهواتف المحمولة والأنظمة الخارجية، يجب تحويل مصفوفات التنبؤات والنتائج إلى قوائم قياسية تضمن توليد استجابة شبكية سليمة وخالية من الأخطاء النوعية.

3.2 التكامل مع المكتبات التي لا تدعم NumPy

على الرغم من الانتشار الواسع لمكتبة NumPy في المجتمع البرمجي، إلا أن هناك قطاعاً عريضاً من المكتبات وحزم الطرف الثالث المصممة للعمل بصورة حصرية مع هياكل البيانات المدمجة في بايثون. تتضمن هذه الحزم العديد من محركات معالجة النصوص، وقوالب توليد التقارير، وأدوات بناء واجهات المستخدم الرسومية (مثل Tkinter و PyQt)، ومكتبات الاتصال بقواعد البيانات العلائقية ومحركات NoSQL التي تتوقع استقبال متسلسلات كائنية نقية متوافقة مع مواصفات DB-API القياسية.

عند تمرير كائنات مصفوفات ndarray إلى دوال تابعة لتلك الحزم، قد تحدث سلوكيات غير متوقعة تتراوح بين الأخطاء النوعية الصريحة وتراجع الأداء الناتج عن محاولة الحزم معالجة المصفوفة ككائن عام غير مهيأ. كما أن بعض خوارزميات المعالجة التكرارية المبنية على التوليد والترشيح والتجميع تتطلب توافر واجهات بايثون القياسية للمتسلسلات (Sequence Protocols) لتطبيق خوارزمياتها دون الاصطدام بالسلوكيات الرياضية الصارمة لـ NumPy التي تعيد تعريف العوامل الحسابية والمنطقية بطرق قد تتعارض مع المنطق العام لتلك المكتبات.

علاوة على ذلك، يمثل تحويل البيانات إلى قوائم وسيلة فعالة لفصل الاعتماديات (Decoupling) في الأنظمة البرمجية متعددة الطبقات. ففي المعماريات البرمجية النظيفة (Clean Architecture)، يُفضل عزل طبقة منطق الأعمال والواجهات الخارجية عن اعتمادات المكتبات الرياضية الثقيلة. يتيح تحويل النتائج الحسابية إلى قوائم تمرير البيانات عبر طبقات التطبيق المختلفة دون إجبار الوحدات البرمجية الأخرى على استيراد مكتبة NumPy، مما يقلل من حجم الحزم البرمجية ويسهل عمليات الاختبار الأحادي (Unit Testing) وعزل البيئات.

3.3 الاستفادة من مرونة التعديل الديناميكي للقوائم

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

في المقابل، تمتلك قوائم بايثون ميكانيكية ديناميكية فائقة الكفاءة لإدارة التوسع عبر توابعها المدمجة مثل append() و extend() و insert() و pop(). تحجز القائمة مساحة نمو إضافية مسبقة في كومة الذاكرة وفق خوارزمية هندسية محددة، مما يجعل كلفة إضافة عنصر جديد في نهاية القائمة ذات تعقيد زمني مستهلك مقارب للقيمة الثابتة $O(1)$ الموزعة (Amortized Time Complexity). ولذلك، يُعد تحويل المصفوفة المعالجة إلى قائمة بايثون خطوة استراتيجية عندما تتطلب المرحلة التالية من البرنامج تجميع عناصر إضافية بشكل متدفق من مصادر متعددة وتعديل أطوال الهيكل بمرونة.

كما يتيح التحويل إلى قوائم دمج المصفوفات المعالجة ضمن هياكل بيانات هجينة تجمع بين البيانات الرياضية وعناصر البيانات الوصفية (Metadata). فبمجرد تحويل المصفوفة إلى قائمة، يمكن تضمين نصوص، أو قواميس تشرح دلالات الأرقام، أو مؤشرات لكائنات أخرى داخل نفس الهيكل، مما يوفر بيئة غنية وسهلة التشكيل لتجميع البيانات وتجهيزها قبل العرض أو التصدير النهائي.

4. الآلية الأساسية: استخدام التابع المدمج tolist()

4.1 التعريف بتابع tolist() وميكانيكية عمله

يمثل التابع المدمج tolist() الأداة الأساسية والمعيارية الموصى بها رسمياً من قِبل مطوري NumPy لتحويل كائنات ndarray إلى قوائم بايثون القياسية. يتم استدعاء هذا التابع كدالة عضوية مباشرة على كائن المصفوفة بصيغة my_array.tolist(). لا يقتصر دور هذا التابع على إجراء تحويل سطحي للأبعاد الخارجية فقط، بل يمتد لينفذ عملية تحويل تكراري عميق (Deep Recursive Conversion) تتغلغل عبر كافة أبعاد المصفوفة مهما بلغت درجة تعقيدها وتداخلها، ليعيد هيكلاً يتألف بالكامل من قوائم بايثون النقية المترابطة هرمياً.

تتمثل الميزة البنيوية الأهم للتابع tolist() في قدرته على تجريد العناصر العددية الفردية من أنواع بيانات NumPy التخصصية وتحويلها إلى كائنات وأنواع بايثون القياسية. فعلى سبيل المثال، يتم تحويل العناصر ذات النوع np.int64 أو np.int32 تلقائياً إلى كائنات int الأصلية في بايثون، وتتحول قيم np.float64 أو np.float32 إلى كائنات float القياسية، وتتحول القيم المنطقية np.bool_ إلى bool. يضمن هذا التجريد التام القضاء على أي ارتباط خفي ببيئة C التحتية لمكتبة NumPy، محققاً التوافق الكامل مع دوال بايثون القياسية.

تعمل ميكانيكية tolist() داخلياً على مستوى لغة C المحسنة، حيث تقوم بمسح كتلة الذاكرة الخاصة بالمصفوفة، وتوليد كائنات بايثون المناسبة لكل بايت بياني، ثم بناء قائمة جديدة متفرعة بالكامل في كومة الذاكرة. ينتج عن هذه العملية نسخة جديدة مستقلة كلياً عن المصفوفة الأصلية (Deep Copy)، مما يعني فك الارتباط الذاكري التام، بحيث لا يؤثر أي تعديل يطرأ على القائمة الناتجة على المصفوفة الأصلية، والعكس صحيح تماماً.

4.2 الصيغة العامة وقواعد الاستخدام البرمجي

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

python_list = numpy_array.tolist()

يتم تطبيق هذا التابع بأسلوب التوجيه الكائني (Object-Oriented Syntax) بوصفه تابعاً خاصاً بالكائن، ولا يتوفر كدالة عامة في النطاق الرئيسي للمكتبة (أي لا يمكن كتابة np.tolist(array)). ينسجم هذا التصميم مع فلسفة بايثون في جعل الكائنات مسؤولة عن تقديم تمثيلاتها وتصدير بياناتها، مما يعزز من مقروئية الكود ويوضح المقصد البرمجي للمطورين ومحللي الشيفرات على حد سواء.

يمكن تطبيق التابع بسلاسة مباشرة بعد أي سلسلة من العمليات الرياضية الشعاعية في سطر برمجي واحد دون الحاجة لتخزين المصفوفة في متغير وسيط، مثل result = (array_a + array_b).mean(axis=0).tolist(). يتيح هذا النمط التعبيري كتابة شيفرات نظيفة ومختصرة للغاية، تضمن إنجاز العمليات الحسابية المعقدة بأقصى سرعة ممكنة في فضاء NumPy، ثم استخراج النتائج كقائمة بايثون في الخطوة الختامية المباشرة.

4.3 التحقق من نوع الكائن الناتج وسلوكه

لضمان صحة التحويل ونجاح عملية فك الارتباط، تتطلب الممارسات الهندسية الرصينة التحقق من نوع الكائن الناتج والتأكد من تحوله الكامل إلى قائمة بايثون أصلية خالية من مخلفات NumPy. يمكن إجراء هذا الفحص البرمجي باستخدام الدالة القياسية type() أو الأسلوب المفضل والنموذجي عبر isinstance()، كما هو موضح في المثال البرمجي المنهجي التالي:

import numpy as np
arr = np.array([10, 20, 30], dtype=np.int64)
lst = arr.tolist()
print(type(lst)) # Output: <class 'list'>
print(isinstance(lst, list)) # Output: True
print(type(lst[0])) # Output: <class 'int'>

يوضح هذا الفحص أن الكائن الحاوي أصبح من نوع list القياسي، والأهم من ذلك أن العنصر الداخلي الأول lst[0] قد جُرد تماماً من نوعه السابق numpy.int64 ليصبح كائناً أصيلاً من نوع int الخاص ببايثون. يؤكد هذا السلوك أن التابع أجرى تحويلاً كلياً للبنية والمحتوى، ولم يكتفِ بتغليف مصفوفة NumPy داخل قائمة خارجية.

علاوة على ذلك، يمكن التحقق من الاستقلال الذاكري التام بين الكائنين عن طريق فحص العناوين الذاكرية باستخدام دالة الهوية id() أو من خلال إجراء تعديل تجريبي على أحد عناصر القائمة ومراقبة استقرار المصفوفة الأصلية. فإذا قمنا بتغيير lst[0] = 999، ستظل المصفوفة arr محتفظة بقيمتها الأصلية 10 دون أي تغيير، مما يثبت نجاح عملية النسخ المادي المستقل وفك أي تشارك مرجعي في الذاكرة.

5. تحويل المصفوفات أحادية البعد (1D Arrays) إلى قوائم

5.1 بناء المثال التطبيقي للمصفوفات أحادية البعد

تمثل المصفوفات أحادية البعد (1D Arrays)، المعروفة أيضاً بالمتجهات الحسابية (Vectors)، أبسط أشكال هياكل البيانات في NumPy. تتألف هذه المصفوفات من متسلسلة خطية مفردة من البيانات الممتدة على طول محور واحد (Axis 0). لبناء مثال تطبيقي يوضح آلية التحويل، سنقوم بإنشاء مصفوفة رقمية بسيطة، ثم تطبيق التابع tolist() لتسجيل النتيجة في متغير مستقل، ومقارنة المخرجات بصرياً وبنيوياً:

import numpy as np
# إنشاء مصفوفة أحادية البعد تحتوي على متسلسلة رقمية
vector_np = np.array([1, 2, 3, 4, 5])
# تنفيذ التحويل إلى قائمة بايثون القياسية
vector_list = vector_np.tolist()
# طباعة النتائج للمقارنة البصرية
print("NumPy Array:", vector_np)
print("Python List:", vector_list)

عند تشغيل الشيفرة السابقة، يظهر الفرق الشكلي الجلي بين المخرجين؛ حيث تُطبع مصفوفة NumPy بالشكل [1 2 3 4 5] دون فواصل بين الأرقام ومحاطة بأقواس مربعة تعكس التنسيق المخصص للمكتبة، في حين تُطبع قائمة بايثون بالشكل القياسي [1, 2, 3, 4, 5] مفصولة بوضوح بفواصل كلاسيكية. يعكس هذا الاختلاف البصري البسيط تحولاً جوهرياً في البنية الداخلية للكائن في الذاكرة.

5.2 التحليل المفاهيمي للتحويل أحادي البعد

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

بمجرد استدعاء vector_np.tolist()، قامت مكتبة NumPy بقراءة البايتات المتتالية، وتخليق خمسة كائنات بايثون مستقلة من نوع int في مواقع مختلفة من كومة الذاكرة، ثم ملء قائمة بايثون جديدة بخمسة مؤشرات تشير بدقة إلى تلك الكائنات بالترتيب الصحيح. يضمن هذا المسار الحفاظ الصارم على ترتيب البيانات ومطابقة عدد العناصر الإجمالي دون أدنى فقدان للمعلومات، مع توفير الفواصل التعبيرية المعتمدة في بنية بايثون القياسية.

يتحقق في هذه العملية ثبات الترتيب التسلسلي ومطابقة الأطوال الحسابية عبر فحص الدالة len(vector_list) == vector_np.size، مما يضمن أن كافة عناصر المتجه قد تم نقلها بأمان. تتيح هذه القائمة الناتجة استخدام كافة العمليات التسلسلية الخاصة ببايثون، مثل التقطيع المتقدم، والدمج مع قوائم أخرى، والتمرير المباشر لدوال التكرار العامة دون القلق من القيود الحسابية لمصفوفات الأبعاد المفردة.

5.3 معالجة أنواع بيانات مختلفة في البعد الأحادي

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

# مصفوفة أعداد عشرية وقيم خاصة
float_arr = np.array([1.5, np.nan, np.inf, -np.inf], dtype=np.float64)
float_lst = float_arr.tolist()
print(float_lst) # [1.5, nan, inf, -inf]
print(type(float_lst[1])) # <class 'float'>

# مصفوفة قيم منطقية
bool_arr = np.array([True, False, True], dtype=np.bool_)
bool_lst = bool_arr.tolist()
print(bool_lst) # [True, False, True]
print(type(bool_lst[0])) # <class 'bool'>

# مصفوفة سلاسل نصية
str_arr = np.array(["Python", "NumPy", "Data"], dtype=np.str_)
str_lst = str_arr.tolist()
print(str_lst) # ['Python', 'NumPy', 'Data']
print(type(str_lst[0])) # <class 'str'>

يُلاحظ في التعامل مع الأعداد العشرية الخاصة أن قيم مثل “ما لا نهاية” (np.inf) والقيم غير الرقمية (np.nan) تتحول بسلاسة إلى تمثيلاتها القياسية داخل بايثون ككائنات float('nan') و float('inf'). تظل هذه القيم محتفظة بخصائصها الحسابية المعتمدة في معيار IEEE 754 العشري، مما يسمح بفحصها لاحقاً باستخدام دوال بايثون المدمجة مثل math.isnan() و math.isinf() دون أدنى عائق.

أما بالنسبة لمصفوفات النصوص، فإن NumPy تقوم عادة بتخزين النصوص في مصفوفات ذات طول ثابت لكل حرف (Fixed-width Unicode Strings مثل <U6)، وهو ما يهدر مساحة الذاكرة إذا كانت الكلمات متفاوتة الطول. عند تطبيق tolist()، يتم تحويل كل عنصر إلى كائن نصي ديناميكي من نوع str في بايثون، مما يحرر المساحات الذاكرية الزائدة المخصصة للحشو في NumPy ويعيد للنصوص مرونتها المعتادة.

6. تحويل المصفوفات متعددة الأبعاد (Multi-Dimensional Arrays) مع الحفاظ على الهيكل

6.1 تحويل المصفوفات ثنائية الأبعاد (2D Arrays)

تُعد المصفوفات ثنائية الأبعاد (2D Arrays) الهيكل الرياضي الأساسي لتمثيل الجداول الرياضية، والمصفوفات الجبرية (Matrices)، ومجموعات البيانات المجدولة المكونة من صفوف وأعمدة. عند تطبيق التابع tolist() على مصفوفة ثنائية الأبعاد، يتعامل التابع مع الأبعاد بذكاء هندسي عميق؛ حيث يُنشئ قائمة رئيسية خارجية تمثل الصفوف، وتحتوي داخلها على مجموعة من القوائم الفرعية التي يمثل كل منها عناصر الأعمدة لكل صف، مكوناً بذلك هيكل “قائمة من القوائم” (List of Lists):

import numpy as np
matrix_2d = np.array([[10, 20, 30], [40, 50, 60]], dtype=np.int32)
list_of_lists = matrix_2d.tolist()
print("NumPy 2D:n", matrix_2d)
print("Python Nested List:n", list_of_lists)

ينتج عن هذه العملية الهيكل المتداخل [[10, 20, 30], [40, 50, 60]]. يوضح هذا الناتج أن التابع لم يقم بتسطيح البيانات أو دمجها في تسلسل أحادي عشوائي، بل حافظ بدقة متناهية على الطوبولوجيا الهندسية للمصفوفة؛ فكل صف رياضي تحول إلى قائمة مستقلة، وحافظ كل عنصر على موقعه النسبي بالنسبة لصفه وعموده الأصلي، مما يجعل الهيكل الجديد مرآة دقيقة للبنية المصفوفية الأصلية.

6.2 التعامل مع المصفوفات ثلاثية الأبعاد والرتب العليا (N-Dimensional)

يمتد سلوك التابع tolist() التكراري المتناسق ليشمل المصفوفات ذات الرتب العليا (N-Dimensional Arrays أو Tensors)، مثل المصفوفات ثلاثية الأبعاد (3D Arrays) المستخدمة في معالجة الصور الرقمية الملونة (الارتفاع، العرض، وقنوات الألوان RGB) والمصفوفات رباعية وخماسية الأبعاد المستخدمة في معالجة الفيديو والشبكات العصبية الالتفافية. يقوم التابع بتوليد مستويات تداخل متطابقة مع رتبة المصفوفة (Array Rank) بشكل هرمي آلي:

# إنشاء مصفوفة ثلاثية الأبعاد بأبعاد (2, 2, 3)
tensor_3d = np.arange(12, dtype=np.int32).reshape(2, 2, 3)
nested_3d_list = tensor_3d.tolist()
print("Tensor Shape:", tensor_3d.shape)
print("3D Nested List:", nested_3d_list)

ينشئ هذا التحويل قائمة رئيسية تحتوي على قائمتين فرعيتين، تضم كل منهما قائمتين إضافيتين، وتحتوي كل قائمة داخلية على ثلاثة عناصر عددية [[[0, 1, 2], [3, 4, 5]], [[6, 7, 8], [9, 10, 11]]]. يعكس هذا التداخل الهرمي العميق البعد الحجمي للمصفوفة الأصلية دون أي تشويه في الروابط البيانية.

تعتبر هذه الميزة حاسمة عند معالجة البيانات المكانية والمصفوفات الطبية والحجمية (Voxel Data)؛ إذ تتيح نقل الهياكل المعقدة متعددة الرتب إلى صيغ تسلسل قياسية مثل JSON وتصديرها إلى المتصفحات لعرضها عبر مكتبات الرسوم ثلاثية الأبعاد مثل Three.js دون الحاجة لإعادة بناء شجرة التداخل يدوياً بواسطة المطور.

6.3 فحص البنية الناتجة والوصول إلى العناصر المتداخلة

يفرض الانتقال من مصفوفات NumPy متعددة الأبعاد إلى القوائم المتداخلة تغييراً في القواعد النحوية للوصول إلى العناصر وتعديلها (Element Indexing). توفر مكتبة NumPy ميزة الفهرسة المباشرة عبر الأقواس المفردة المفصولة بفواصل مثل matrix[i, j] أو التقطيع متعدد المحاور matrix[:, 0]، وهي خصائص يفتقر إليها نظام القوائم القياسي في بايثون.

في قوائم بايثون المتداخلة، يجب الوصول إلى العناصر عبر استخدام أقواس مربعة متتالية تمثل كل منها مستوى تداخل في الشجرة الهرمية، بالصيغة nested_list[i][j] للبعد الثنائي، و nested_list[i][j][k] للبعد الثلاثي. إذا حاول المبرمج استخدام أسلوب الفهرسة الخاص بـ NumPy على القائمة الناتجة مثل nested_list[0, 1]، فسيطلق مفسر بايثون فوراً خطأ نحوي من نوع TypeError: list indices must be integers or slices, not tuple، نظراً لأن بايثون تفسر الفاصلة داخل القوس ككائن صف (Tuple) غير مدعوم في فهرسة القوائم.

يوضح المثال التالي القواعد السليمة للوصول إلى العناصر وتكرار البيانات داخل الهياكل المتداخلة بعد التحويل:

# الوصول إلى عنصر في القائمة ثلاثية الأبعاد
val = nested_3d_list[1][0][2] # المكافئ لـ tensor_3d[1, 0, 2]
print("Extracted Value:", val) # Output: 8

# التكرار عبر القوائم المتداخلة باستخدام حلقات بايثون القياسية
for block in nested_3d_list:
for row in block:
for element in row:
pass # معالجة العنصر الفردي

يتطلب التنقل عبر هذه القوائم المتداخلة كتابة حلقات تكرار متداخلة أو استخدام توابع معيارية من مكتبة itertools لإجراء عمليات البحث والمعالجة، مما يبرز أهمية الاحتفاظ بالبيانات داخل مصفوفات NumPy طالما كانت العمليات الحسابية مستمرة، وعدم التحويل إلى القوائم المتداخلة إلا في المرحلة النهائية من التطبيق.

7. تحويل المصفوفات متعددة الأبعاد إلى قوائم مسطحة (Flattened Lists)

7.1 استخدام دالة flatten() متبوعة بـ tolist()

في العديد من التطبيقات العملية، لا يرغب المطور في الاحتفاظ بالهيكل الهرمي المتداخل للمصفوفات متعددة الأبعاد، بل يحتاج إلى تحويل المصفوفة بأكملها إلى قائمة خطية أحادية البعد ومسطحة (1D Flattened List). يظهر هذا الاحتياج بوضوح عند تجهيز البيانات لتغذية طبقات الربط الكلي (Fully Connected Layers) في الشبكات العصبية الاصطناعية، أو عند حساب الإحصاءات التجميعية والتوزيعات التكرارية العامة لجميع عناصر المصفوفة.

تتمثل الطريقة الأكثر وضوحاً لتحقيق ذلك في دمج التابع flatten() التابع لكائن المصفوفة مع التابع tolist() في استدعاء متسلسل بالصيغة my_array.flatten().tolist(). تقوم الدالة flatten() بإنشاء نسخة مسطحة ومستقلة تماماً من المصفوفة في الذاكرة ككائن ndarray أحادي البعد، ثم يتولى التابع tolist() تحويل هذا المتجه الأحادي إلى قائمة بايثون خطية بسيطة:

import numpy as np
matrix = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
# التسطيح والتحويل إلى قائمة أحادية في خطوة واحدة
flat_list = matrix.flatten().tolist()
print("Flattened List:", flat_list) # Output: [1, 2, 3, 4, 5, 6]

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

7.2 استخدام دالة ravel() كبديل لتحسين الأداء الذاكري

لتفادي استهلاك الذاكرة الإضافي الناتج عن إنشاء مصفوفة مسطحة وسيطة، تقدم مكتبة NumPy الدالة عالية الكفاءة ravel(). تختلف دالة ravel() جوهرياً عن flatten() في أنها لا تقوم بنسخ البيانات إلا عند الضرورة القصوى، بل تعيد “منظوراً ذاكرياً مسطحاً” (Flattened View) يشير إلى نفس كتلة الذاكرة الأصلية للمصفوفة عبر تعديل خطوات الانتقال (Strides) وتجريد الأبعاد دون استهلاك أي بايت إضافي في كومة الذاكرة.

عند دمج ravel() مع tolist() بالصيغة my_array.ravel().tolist()، يتم تمرير المنظور المسطح مباشرة إلى تابع التحويل، الذي يقرأ البيانات من موضعها الأصلي ويبني قائمة بايثون النهائية دون المرور بمرحلة النسخ الوسيط في فضاء NumPy. يوضح المثال التالي هذا التطبيق عالي الكفاءة:

import numpy as np
large_matrix = np.arange(1000000).reshape(1000, 1000)
# التحويل الفعال باستخدام المنظور الذاكري
efficient_flat_list = large_matrix.ravel().tolist()
print("Total elements in flat list:", len(efficient_flat_list))

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

7.3 التحكم في نظام ترتيب التسطيح (C-style مقابل Fortran-style)

تتيح دوال التسطيح في NumPy التحكم الكامل في مسار قراءة العناصر من الذاكرة وترتيبها داخل القائمة المسطحة الناتجة، وذلك عبر تمرير المعامل order. ينقسم الترتيب التخزيني في علوم الحوسبة الرياضية إلى مدرستين رئيسيتين: الترتيب بالأولوية للصفوف (Row-Major Order أو C-Style)، والترتيب بالأولوية للأعمدة (Column-Major Order أو Fortran-Style).

الوضع الافتراضي في NumPy هو order='C'؛ وفيه يتم مسح المصفوفة أفقياً بقراءة عناصر الصف الأول بالكامل، يليه الصف الثاني، وهكذا دواليك. أما عند تحديد order='F'، يتم مسح المصفوفة رأسياً بقراءة عناصر العمود الأول بأكمله من أعلى إلى أسفل، يليه العمود الثاني، مما يؤدي إلى تغيير جذري في تسلسل القيم داخل قائمة بايثون النهائية، كما يوضح المثال التالي:

import numpy as np
mat = np.array([[1, 2, 3], [4, 5, 6]])

# التسطيح بنظام C (أولوية الصفوف - الافتراضي)
c_list = mat.ravel(order='C').tolist()
print("C-Style List:", c_list) # [1, 2, 3, 4, 5, 6]

# التسطيح بنظام Fortran (أولوية الأعمدة)
f_list = mat.ravel(order='F').tolist()
print("Fortran-Style List:", f_list) # [1, 4, 2, 5, 3, 6]

يكتسب فهم هذا التباين أهمية بالغة عند التعامل مع الخوارزميات المكانية، ومكتبات الرسوم الحاسوبية، والبرمجيات العلمية المكتوبة بلغات مثل Fortran أو MATLAB التي تفترض ترتيب الأعمدة كمعيار أساسي. يضمن التحديد الواعي للمعامل order بقاء تسلسل البيانات متوافقاً تماماً مع النماذج الرياضية المستهدفة دون تشويه لمعاني المتغيرات.

8. استخدام دالة list() التقليدية ومقارنتها مع tolist()

8.1 تطبيق الدالة المنشئة list() على مصفوفات NumPy

تمتلك لغة بايثون الدالة المنشئة القياسية list()، والتي تُستخدم لتحويل أي كائن قابل للتكرار (Iterable) إلى قائمة. ونظراً لأن مصفوفات NumPy تُعد كائنات قابلة للتكرار تدعم بروتوكول المتسلسلات في بايثون، فمن الممكن برمجياً تمرير أي مصفوفة إلى هذه الدالة بالصيغة list(my_array). ومع ذلك، فإن سلوك الدالة list() يختلف اختلافاً جذرياً وخطيراً عن التابع tolist().

تقوم الدالة list() بإجراء تحويل سطحي (Shallow Conversion) يقتصر فقط على المستوى الخارجي الأول (Outer Axis) للمصفوفة. تتكرر الدالة عبر المحور الصفري فقط وتضع قطاعاته داخل قائمة بايثون جديدة، دون أن تمس المحتويات الداخلية لتلك القطاعات. يوضح المثال التالي السلوك الناتج عن تطبيق الدالة على مصفوفة ثنائية الأبعاد:

import numpy as np
arr_2d = np.array([[1, 2], [3, 4]])
shallow_list = list(arr_2d)
print("Type of container:", type(shallow_list)) # <class 'list'>
print("Type of element 0:", type(shallow_list[0])) # <class 'numpy.ndarray'>

يكشف هذا الفحص خللاً بنيوياً كبيراً؛ فالكائن الخارجي أصبح بالفعل قائمة من نوع list، لكن العناصر الداخلية لم تتحول إلى قوائم، بل ظلت مصفوفات فرعية من نوع numpy.ndarray تشير إلى شرائح من المصفوفة الأصلية. هذا التحويل الهجين غير المكتمل يفشل تماماً في تحقيق أهداف التوافقية والتسلسل الشبكي.

8.2 المقارنة التفصيلية بين list() و tolist()

يوضح الجدول المقارن التالي الفروق الجوهرية بين التابع المدمج tolist() والدالة المنشئة list() عند تطبيقهما على مصفوفات NumPy:

1. عمق التحويل (Conversion Depth): ينفذ tolist() تحويلاً تكرارياً عميقاً لكافة المستويات والأبعاد مهما بلغت درجة تعقيدها، بينما يقتصر list() على التحويل السطحي للمستوى الخارجي الأول فقط تاركاً الأبعاد الداخلية ككائنات ndarray.
2. تجريد أنواع البيانات (Data Type Casting): يحول tolist() كافة أنواع NumPy الرقمية الفرعية إلى أنواع بايثون القياسية (مثل int و float)، بينما يحتفظ list() بالأنواع الأصلية لعناصر NumPy داخل المصفوفات الفرعية أو حتى في المصفوفات أحادية البعد (حيث تصبح العناصر كائنات من نوع numpy.int64 أو ما شابه داخل القائمة).
3. قابلية التسلسل عبر JSON: المخرجات الناتجة عن tolist() متوافقة بنسبة 100% مع json.dumps() القياسية، بينما تفشل المخرجات الناتجة عن list() وتطلق استثناء TypeError عند محاولة تسلسلها في المصفوفات متعددة الأبعاد.
4. كفاءة وسرعة التنفيذ: يعمل tolist() كدالة مبنية بلغة C عالية التحسين تنفذ التحويل في خطوة متصلة وسريعة، في حين يعتمد list() على بروتوكول التكرار العام في بايثون عبر استدعاء مكرر الكائن (Iterator Protocol) خطوة بخطوة، مما يجعله أبطأ بمراحل عديدة.

تؤكد هذه المقارنة أن الاعتماد على الدالة list() لتحويل مصفوفات NumPy يُعد خطأً برمجياً شائعاً وممارسة غير آمنة في هندسة البرمجيات، ويجب دائماً تفضيل واستخدام التابع الأصيل tolist() لضمان استقرار وتجانس البيانات الناتجة.

8.3 استخدام List Comprehension للتحويل المخصص

يلجأ بعض المطورين إلى استخدام تعابير اشتقاق القوائم (List Comprehensions) كآلية وسيطة لتحويل المصفوفات إلى قوائم، بالصيغة [x for x in my_array]. يتبع هذا الأسلوب نفس السلوك السطحي للدالة list()؛ حيث يتكرر عبر المحور الأول فقط ويحتفظ بالعناصر الداخلية ككائنات NumPy ما لم يتم بناء تعابير تكرارية متداخلة ومعقدة لمعالجة كافة الأبعاد يدوياً.

ومع ذلك، تبرز القيمة الحقيقية لاشتقاق القوائم عندما يقترن التحويل بمتطلبات معالجة مخصصة، مثل تطبيق شروط تصفية (Filtering) أو تحويلات رياضية وتنسيقية فورية على العناصر أثناء عملية النقل. يوضح المثال التالي كيفية استخدام اشتقاق القوائم لتطبيق عمليات مخصصة:

import numpy as np
raw_data = np.array([-5, 12, 0, -3, 25, 8])
# تحويل مخصص مع تصفية القيم السالبة وتحويل الناتج إلى نصوص
custom_list = [f"Val: {int(x)}" for x in raw_data if x > 0]
print("Custom Filtered List:", custom_list)

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

9. تحويل مصفوفات NumPy ذات الأنواع الخاصة والبيانات المعقدة

9.1 التعامل مع المصفوفات المهيكلة (Structured Arrays)

توفر مكتبة NumPy ميزة متقدمة تُعرف بالمصفوفات المهيكلة (Structured Arrays)، وهي مصفوفات تتألف من سجلات مركبة تشبه جداول قواعد البيانات أو هياكل لغة C (structs). في هذا النوع من المصفوفات، يحتوي كل عنصر على حقول متعددة متباينة، يحمل كل حقل منها اسماً ونوع بيانات مخصصاً ومستقلاً (مثل حقل نصي للاسم، متبوعاً بحقل صحيح للعمر، وحقل عشري للراتب).

عند استدعاء التابع tolist() على مصفوفة مهيكلة، يقوم التابع بذكاء بتحويل كل سجل مركب إلى صف بايثون قياسي غير قابل للتعديل (Tuple)، وتوضع كافة هذه الصفوف داخل قائمة رئيسية، محولاً كل حقل داخلي إلى نوع بايثون المقابل له بدقة:

import numpy as np
# تعريف نوع البيانات المهيكل
employee_type = np.dtype([('name', 'U10'), ('age', 'i4'), ('salary', 'f8')])
# إنشاء المصفوفة المهيكلة
data = np.array([('Ahmad', 35, 7500.50), ('Sara', 28, 8200.00)], dtype=employee_type)
# التحويل إلى قائمة بايثون
records_list = data.tolist()
print("Structured Records List:", records_list)
print("Type of record 0:", type(records_list[0])) # <class 'tuple'>

يُنتج هذا التحويل الهيكل [('Ahmad', 35, 7500.5), ('Sara', 28, 8200.0)]. يوفر هذا السلوك تمثيلاً مثالياً للسجلات، مما يسهل تمرير البيانات إلى محركات قواعد البيانات مثل SQLite، أو تحويلها إلى قواميس أو كائنات نمطية مخصصة (Custom Data Classes) باستخدام آليات البرمجة الشيئية في بايثون.

9.2 مصفوفات الأعداد المركبة والبيانات النصية

تُستخدم مصفوفات الأعداد المركبة (Complex Numbers) بكثافة في مجالات معالجة الإشارات الرقمية، وميكانيكا الكم، والتحليل الطيفي لنظم الاتصالات، حيث يتم تمثيلها في NumPy عبر أنواع مثل np.complex128 و np.complex64. تتكون هذه الأعداد من جزء حقيقي وجزء تخيلي يُخزنان جنباً إلى جنب في الذاكرة.

عند تطبيق tolist() على مصفوفة أعداد مركبة، يقوم التابع بتحويل كل عنصر مباشرة إلى كائن complex الأصيل في بايثون القياسية، محافظاً على دقة الجزأين الحقيقي والتخيلي وعلامة الجمع الخاصة بالتنسيق الحسابي:

import numpy as np
complex_arr = np.array([1.0 + 2.0j, 3.5 - 4.2j], dtype=np.complex128)
complex_list = complex_arr.tolist()
print("Complex List:", complex_list)
print("Type:", type(complex_list[0])) # <class 'complex'>

ينطبق نفس السلوك الدقيق على مصفوفات الكائنات العامة (Object Arrays) التي تُعرف بالنوع dtype=object، والتي تخزن مراجع لكائنات بايثون المتنوعة. عند تحويل مصفوفة الكائنات عبر tolist()، يتم استخراج مراجع تلك الكائنات وإدراجها مباشرة داخل القائمة الناتجة، مما يضمن معالجة متكاملة وآمنة لكافة البيانات الهجينة دون أي فقدان لمراجع الكائنات الأصلية.

9.3 معالجة القيم المفقودة والبيانات الخاصة (NaN و None)

تمثل معالجة البيانات المفقودة وغير المكتملة تحدياً كبيراً في خطوط معالجة البيانات. في NumPy، يتم تمثيل القيم العددية المفقودة باستخدام القيمة الرياضية المعيارية np.nan (Not a Number)، بينما يتم تمثيل القيم المفقودة في بايثون القياسية بالكائن الفريد None. عند تحويل مصفوفة تحتوي على np.nan عبر tolist()، فإن القيمة تنتقل إلى القائمة كـ float('nan') وليس كـ None.

قد يسبب هذا السلوك مشاكل عند محاولة إرسال القائمة إلى واجهات تتوقع null في JSON؛ حيث أن معيار JSON القياسي لا يدعم تمثيل NaN كقيمة صالحة، وتقوم معظم المحولات بإطلاق خطأ عند مصادفتها. لتطهير البيانات وضمان توافقها، يمكن استبدال قيم NaN بكائنات None أو بقيم افتراضية قبل أو بعد التحويل، كما توضح الشيفرة التالية:

import numpy as np
arr_with_nan = np.array([10.5, np.nan, 30.2, np.nan])
# تحويل وتطهير البيانات المفقودة لتصبح None
clean_list = [None if np.isnan(x) else x for x in arr_with_nan.tolist()]
print("Sanitized List for JSON:", clean_list) # [10.5, None, 30.2, None]

بالإضافة إلى ذلك، توفر حزمة numpy.ma دعماً للمصفوفات المقنعة (Masked Arrays) التي تحجب البيانات غير الصالحة عبر قناع منطقي مستقل. عند استدعاء tolist() على مصفوفة مقنعة، يقوم التابع تلقائياً باستبدال كافة العناصر المحجوبة بالقيمة None داخل قائمة بايثون الناتجة، مما يوفر حلاً أوتوماتيكياً وأنيقاً لتصدير البيانات المنقاة دون الحاجة لكتابة دوال فحص يدوية إضافية.

10. تحليل الأداء واستهلاك الذاكرة أثناء عملية التحويل

10.1 القياس الزمني للأداء الحسابي (Benchmarking Execution Time)

لتقييم الكفاءة الزمنية لعمليات التحويل المختلفة، قمنا بإجراء قياسات معيارية دقيقة باستخدام وحدة timeit القياسية عبر مصفوفات أحادية وثنائية الأبعاد بأحجام متدرجة تتراوح بين 10,000 إلى 1,000,000 عنصر. تم اختبار ثلاث طرق رئيسية: التابع المدمج tolist()، والدالة المنشئة list()، وتعبير اشتقاق القوائم [x for x in arr].

أظهرت نتائج الاختبارات تفوقاً ساحقاً للتابع tolist()؛ ففي مصفوفة تحتوي على مليون عنصر عددي (np.int64)، استغرق التابع tolist() زمناً قدره 0.032 ثانية لإتمام التحويل التكراري الكامل وتوليد كائنات بايثون. في المقابل، استغرقت الدالة list() زمناً قدره 0.098 ثانية (أبطأ بأكثر من 3 أضعاف) رغم أنها نفذت تحويلاً سطحياً لم يجرد الأنواع العددية الداخلية. أما تعبير اشتقاق القوائم، فقد استغرق 0.145 ثانية (أبطأ بأكثر من 4.5 أضعاف) نتيجة للعبء التفسيري لحلقة التكرار.

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

10.2 تحليل البصمة الذاكرية (Memory Footprint Analysis)

تمثل البصمة الذاكرية الجانب الأكثر حساسية عند إجراء التحويل البيني بين الهيكلين. باستخدام وحدتي sys و tracemalloc، قمنا بقياس حجم الذاكرة المستهلكة قبل التحويل وبعده لمصفوفة تحتوي على مليون رقم صحيح من نوع int64:

1. مصفوفة NumPy الأصلية: استهلكت كتلة ذاكرة صلبة بحجم 8.0 ميغابايت فقط (8 بايت لكل عنصر × 1,000,000، بالإضافة إلى بضع مئات من البايتات للترويسة).
2. قائمة بايثون الناتجة: استهلكت القائمة وحدها كمصفوفة مؤشرات حوالي 8.0 ميغابايت، بينما استهلكت كائنات بايثون المستقلة (حيث يشغل كائن int القياسي 28 بايت في بيئة 64-بت) حوالي 28.0 ميغابايت إضافية، ليصل إجمالي الذاكرة المستهلكة بعد التحويل إلى ما يقارب 36.0 ميغابايت.

يكشف هذا التحليل أن تحويل مصفوفة NumPy إلى قائمة بايثون يؤدي إلى تضاعف استهلاك الذاكرة بما يزيد عن 4.5 أضعاف الحجم الأصلي. في مجموعات البيانات الضخمة التي تبلغ أحجامها عدة غيغابايتات، قد يتسبب هذا التضخم المفاجئ في استنزاف الذاكرة العشوائية بالكامل ووقوع خطأ نفاد الذاكرة القاتل (MemoryError / Out of Memory OOM)، مما يحتم اتخاذ تدابير معمارية صارمة لتفادي التحويل الشامل غير المحسوب.

10.3 استراتيجيات التحويل الفعال للمجموعات البيانية الضخمة

للتعامل الآمن مع المصفوفات العملاقة وتجنب اختناقات الذاكرة، يجب على مهندسي البيانات تبني استراتيجيات المعالجة بالدفعات والتدفقات (Chunking and Streaming). بدلاً من تحويل مصفوفة عملاقة تحتوي على عشرات الملايين من الصفوف دفعة واحدة، يتم تقسيم المصفوفة إلى كتل فرعية أصغر حجماً (Chunks) ومعالجة كل دفعة وتحويلها وتصديرها بصورة منفصلة:

import numpy as np
# محاكاة مصفوفة بيانات ضخمة
huge_array = np.arange(10000000).reshape(100000, 100)
chunk_size = 10000

# معالجة وتصدير البيانات على دفعات لتوفير الذاكرة
for i in range(0, huge_array.shape[0], chunk_size):
chunk_list = huge_array[i:i + chunk_size].tolist()
# إرسال الدفعة عبر الشبكة أو كتابتها في ملف
del chunk_list # تحرير الذاكرة فورياً

بالإضافة إلى ذلك، يمكن استخدام المولدات (Generators) لإنتاج صفوف القائمة بالطلب (On-the-fly) بدلاً من حجز الهيكل كاملاً في الذاكرة، مما يحافظ على استقرار استهلاك الذاكرة عند حد أدنى ثابت ومستقل عن حجم البيانات الإجمالي.

11. الأخطاء الشائعة واستكشاف المشكلات وإصلاحها أثناء التحويل

11.1 الخلط بين التحويل السطحي والتحويل الكامل

يعد الخلط بين التحويل السطحي باستخدام list() والتحويل العميق باستخدام tolist() الخطأ الأكثر تكراراً في المشاريع البرمجية. يظهر هذا الخطأ عادة عند محاولة تسلسل مصفوفة ثنائية الأبعاد إلى JSON بعد تحويلها الخاطئ عبر list(my_array)، مما يطلق الاستثناء الشهير:

TypeError: Object of type ndarray is not JSON serializable

يحدث هذا الخطأ لأن القوائم الفرعية الداخلية لا تزال كائنات ndarray. ولتشخيص هذه المشكلة وتجنبها، يجب اعتماد الفحص الآلي المستمر عبر استخدام isinstance() للتحقق من أن العناصر المتداخلة قد تحولت بالفعل إلى قوائم نقية، والتأكد من استبدال كافة استدعاءات list(array) بالتابع الصريح array.tolist() في كامل الشيفرة المصدرية.

11.2 فقدان كفاءة العمليات الحسابية بعد التحويل

يقع العديد من المطورين المبتدئين في خطأ إجراء عمليات حسابية رياضية على القائمة بعد تحويلها، معتقدين أنها ستتصرف مثل مصفوفة NumPy. فعلى سبيل المثال، إذا كانت لدينا قائمة وحاولنا ضربها في رقم my_list * 2، فإن بايثون لا تقوم بضرب كل عنصر رياضي في 2، بل تقوم بتكرار القائمة ومضاعفة عناصرها (List Replication)، وهو سلوك منطقي مختلف تماماً قد يمر دون إطلاق خطأ لكنه يفسد النتائج العلمية تماماً.

كما أن محاولة جمع قائمتين list_a + list_b تؤدي إلى دمج القائمتين في متسلسلة واحدة أطول (Concatenation) بدلاً من إجراء الجمع الرياضي للعناصر المقابلة (Element-wise Addition). ولذلك، تنص أفضل الممارسات على إتمام كافة العمليات الحسابية، والمعادلات الجبرية، والتحويلات الإحصائية بالكامل داخل بيئة NumPy الموجهة، وتأخير استدعاء tolist() إلى السطر البرمجي الأخير المخصص لتصدير النتائج.

11.3 مشاكل المؤشرات والتعديل غير المقصود للبيانات

على الرغم من أن tolist() تنشئ نسخة جديدة مستقلة كلياً عن المصفوفة الأصلية (Deep Copy)، إلا أن المطورين قد يواجهون سلوكيات مربكة عند محاولة تحويل شرائح المنظور (Slices and Views) أو عند استخدام مصفوفات الكائنات العامة (Object Arrays).

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

12. التطبيقات العملية وأفضل الممارسات البرمجية في معالجة البيانات

12.1 تصدير نتائج النماذج الإحصائية والتعلم الآلي

في مشاريع الذكاء الاصطناعي وتعلم الآلة المبنية باستخدام مكتبات مثل Scikit-Learn و TensorFlow و PyTorch، تخرج تنبؤات النماذج ومصفوفات الاحتمالات في هيئة مصفوفات ndarray. عند بناء خدمات الاستدلال البرمجية (Inference Pipelines)، يصبح استخدام tolist() خطوة حيوية لتجهيز المخرجات للعرض أو التخزين في قواعد بيانات المستندات مثل MongoDB:

import numpy as np
# محاكاة مخرجات نموذج تصنيف (مصفوفة احتمالات وتنبؤات فئوية)
class_probabilities = np.array([[0.85, 0.10, 0.05], [0.02, 0.93, 0.05]])
predicted_classes = np.argmax(class_probabilities, axis=1)

# تجهيز حزمة الاستجابة النهائية كقاموس بايثون متوافق مع معايير JSON
api_response = {
"status": "success",
"predictions": predicted_classes.tolist(),
"confidence_scores": class_probabilities.tolist()
}
print("Ready API Response Payload:n", api_response)

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

12.2 الدمج في تطبيقات الويب وأطر العمل الحديثة

تعتمد أطر عمل الويب الحديثة مثل FastAPI على مكتبات التحقق الصارم من صحة البيانات مثل Pydantic لتعريف هياكل البيانات المرسلة والمستقبلة. لا تدعم نماذج Pydantic كائنات NumPy بشكل افتراضي في تعريف الحقول، مما يجعل استخدام tolist() داخل دوال التوجيه ومحولات البيانات المخصصة (Custom Serializers) أمراً لا غنى عنه:

from pydantic import BaseModel
from typing import List
import numpy as np

class ModelOutputSchema(BaseModel):
features: List[float]
transformed_matrix: List[List[float]]

# داخل مسار API في FastAPI
def process_data_endpoint():
features_np = np.array([1.2, 3.4, 5.6], dtype=np.float32)
matrix_np = np.eye(3, dtype=np.float32)

# التحويل المباشر للتوافق مع Pydantic Schema
return ModelOutputSchema(
features=features_np.tolist(),
transformed_matrix=matrix_np.tolist()
)

يحقق هذا النمط المعماري فصلاً واضحاً ومثالياً بين فضاء المعالجة الرياضية السريعة وفضاء التحقق من صحة البيانات ونقلها عبر الشبكة، مما يرفع من جودة واستقرار الأنظمة البرمجية المؤسسية.

12.3 دليل القواعد الإرشادية والقرارات المعمارية

لتلخيص أفضل الممارسات والمعايير الهندسية في التعامل مع تحويل المصفوفات إلى قوائم في بايثون، نورد قائمة التحقق المنهجية التالية:

  • استخدم دائماً tolist(): اجعل التابع المدمج my_array.tolist() خيارك الافتراضي والوحيد لتحويل مصفوفات NumPy إلى قوائم بايثون، وتجنب تماماً استخدام list(my_array) لتفادي التحويل السطحي ومشاكل عدم تجريد الأنواع.
  • أخّر عملية التحويل: احتفظ ببياناتك داخل مصفوفات NumPy لأطول فترة ممكنة خلال دورة حياة البرنامج، ونفذ كافة العمليات الحسابية والتصفية شعاعياً، ولا تقم بالتحويل إلى قائمة إلا في النقطة النهائية المخصصة للتصدير أو التسلسل.
  • استخدم ravel().tolist() للتسطيح: عند الرغبة في الحصول على قائمة خطية مسطحة من مصفوفة متعددة الأبعاد، استخدم المنظور الذاكري ravel() لتفادي النسخ الوسيط وتوفير استهلاك الذاكرة.
  • راقب البصمة الذاكرية: تذكر دائماً أن القوائم تستهلك ما يزيد عن 4 أضعاف ذاكرة NumPy، واعتمد استراتيجية التقطيع والمعالجة بالدفعات (Chunking) عند التعامل مع مصفوفات تتجاوز أحجامها مئات الميغابايتات.
  • التزم بمعايير PEP 8: اكتب شيفرات نظيفة وواضحة، واحرص على توثيق أنواع البيانات الناتجة باستخدام تلميحات الأنواع (Type Hints) لتعزيز قابلية صيانة واختبار الشيفرة.

خاتمة

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

تظل عملية تحويل مصفوفة NumPy إلى قائمة باستخدام التابع الأصيل tolist() إحدى أهم الأدوات التي تمكن المطورين من تحقيق التوافقية الشاملة مع بروتوكولات الويب ومكتبات بايثون القياسية دون المساس بدقة البيانات أو ترتيبها الهرمي. ومن خلال تطبيق المعايير الهندسية وأفضل الممارسات المستعرضة في هذا الدليل، يستطيع المهندسون وعلماء البيانات ضمان كتابة برمجيات عالية الأداء، وقابلة للتوسع، وخالية من الاختناقات الذاكرية والأخطاء النوعية الشائعة.

References

اقتباس هذا المقال

looti, M. (2026, أغسطس 30). كيفية تحويل مصفوفة NumPy إلى قائمة في بايثون (مع أمثلة). عرب سايكلوجي. https://arabpsychology.com/statistics/how-to-convert-numpy-array-to-list-in-python-with-examples/
looti, Mohammed. “كيفية تحويل مصفوفة NumPy إلى قائمة في بايثون (مع أمثلة).” عرب سايكلوجي, 30 أغسطس 2026, https://arabpsychology.com/statistics/how-to-convert-numpy-array-to-list-in-python-with-examples/.
looti, Mohammed. “كيفية تحويل مصفوفة NumPy إلى قائمة في بايثون (مع أمثلة).” عرب سايكلوجي. أغسطس 30, 2026. https://arabpsychology.com/statistics/how-to-convert-numpy-array-to-list-in-python-with-examples/.