NumPy: النسخ والعروض
آخر تحديث: 2026-08-26
عدّل تشارلي مصفوفة فرعية واكتشف أن المصفوفة الأصلية تغيرت أيضاً — أمضى ساعتين في التصحيح. تقول أليس: "تحقق من الخاصية .base. إذا كان base is هو المصفوفة الأصلية، فهو عرض. عندما تشك، استخدم .copy()."
تسلط هذه القصة الضوء على مشكلة جوهرية: ليست كل عمليات NumPy تُرجع مصفوفات مستقلة. فهم الفرق بين العروض والنسخ أمر أساسي لتجنب أخطاء تعديل البيانات الخفية.
1. آلية العرض
(1) ما هو العرض
العرض هو "نافذة" أخرى إلى المصفوفة الأصلية — تشارك نفس الذاكرة. تعديل العرض يُعدّل المصفوفة الأصلية مباشرة.
import numpy as np
a = np.array([1, 2, 3, 4, 5])
b = a[1:4] # الشريحة تُرجع عرضاً
b[0] = 999
print(a) # [ 1 999 3 4 5] تغيرت الأصلية!
print(b) # [999 3 4]
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة Python المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبت مسبقاً — قم بتثبيته محلياً (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
ميزة العروض هي عدم النسخ — لا ذاكرة إضافية، سريعة جداً.
(2) علاقة الذاكرة
a = np.array([10, 20, 30])
b = a[:] # عرض
print(a.__array_interface__['data'][0]) # عنوان بيانات الأصلية
print(b.__array_interface__['data'][0]) # عنوان بيانات العرض (نفس العنوان!)
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة Python المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبت مسبقاً — قم بتثبيته محلياً (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
كلاهما يشير إلى نفس عنوان بداية الذاكرة — فقط البيانات الوصفية (الشكل، الخطوات) تختلف.
(3) الخاصية .base
a = np.arange(12).reshape(3, 4)
b = a[0:2, 1:3] # شريحة — عرض
c = a[[0, 2]] # فهرسة متقدمة — نسخة
print(b.base is a) # True — عرض
print(c.base is a) # False — نسخة
2. أي العمليات تُرجع عروضاً مقابل نسخ
(1) العروض (ذاكرة مشتركة، بدون نسخ)
| العملية | مثال |
|---|---|
| التقطيع الأساسي | a[1:5], a[1:5:2], a[:, 2:4] |
.view() |
a.view(np.float32) |
reshape |
a.reshape(3, 4) |
transpose / .T |
a.T, a.transpose(1, 0) |
ravel (عادةً) |
a.ravel() |
squeeze |
a.squeeze() |
newaxis |
a[:, np.newaxis] |
(2) النسخ (ذاكرة جديدة، مستقلة)
| العملية | مثال |
|---|---|
.copy() |
a.copy() |
| الفهرسة المتقدمة | a[[0, 2, 4]] |
| الفهرسة المنطقية | a[a > 0] |
astype |
a.astype(np.float32) |
flatten |
a.flatten() |
np.where |
np.where(a > 0, a, 0) |
▶ مثال
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة Python المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبت مسبقاً — قم بتثبيته محلياً (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
: التحقق من العرض مقابل النسخة باستخدام .base (الصعوبة ⭐)
import numpy as np
a = np.arange(12).reshape(3, 4)
# شريحة أساسية — عرض
b = a[0:2]
print(f"slice base is a: {b.base is a}") # True
---
## 3. إعادة تشكيل — عرض
c = a.reshape(6, 2)
print(f"reshape base is a: {c.base is a}") # True
---
## 4. فهرسة متقدمة — نسخة
d = a[[0, 2]]
print(f"fancy base is a: {d.base is a}") # False
---
## 5. فهرسة منطقية — نسخة
e = a[a > 5]
print(f"boolean base is a: {e.base is a}") # False
> **المخرجات:** قم بتشغيل NumPy 2.x في بيئة Python المحلية لديك لرؤية مخرجات ndarray. خادم Piston لا يحتوي على NumPy مثبت مسبقاً — قم بتثبيته محلياً (`pip install numpy`) وقارن. قد تختلف القيم الفعلية حسب إصدار NumPy والبذرة العشوائية.
6. جداول المقارنة
(1) شجرة قرار العرض مقابل النسخة
هل هو تقطيع أساسي (start:stop:step)؟
├── نعم → عرض (ذاكرة مشتركة)
└── لا → هل هي فهرسة متقدمة/منطقية؟
├── نعم → نسخة (ذاكرة جديدة)
└── لا → تحقق من الخاصية .base
(2) تأثير الأداء
| العملية | الزمن (مليون عنصر) | الذاكرة |
|---|---|---|
| عرض (شريحة) | O(1) | مشتركة |
| نسخة (متقدمة) | O(n) | تخصيص جديد |
| .copy() | O(n) | تخصيص جديد |
| astype | O(n) | تخصيص جديد |
▶ مثال: إعادة التشكيل والتحويل يُرجعان عروضاً (الصعوبة ⭐⭐)
import numpy as np
a = np.arange(12).reshape(3, 4)
---
## 7. إعادة التشكيل تُرجع عرضاً
b = a.reshape(6, 2)
print(f"reshape base is a: {b.base is a}")
---
## 8. التحويل يُرجع عرضاً
c = a.T
print(f"transpose base is a: {c.base is a}")
---
## 9. تعديل العرض يُغيّر الأصلية
c[0, 0] = 999
print(f"a[0, 0] after transpose mod: {a[0, 0]}")
المخرجات:
# تم التنفيذ بنجاح
المخرجات:
TEXT 📖 للعرض فقطreshape base is a: True transpose base is a: True a[0, 0] after transpose mod: 999
▶ مثال: flatten مقابل ravel — نسخة مقابل عرض (الصعوبة ⭐⭐)
import numpy as np
a = np.arange(12).reshape(3, 4)
---
## 10. ravel عادةً يُرجع عرضاً
r = a.ravel()
print(f"ravel base is a: {r.base is a}")
---
## 11. flatten دائماً يُرجع نسخة
f = a.flatten()
print(f"flatten base is a: {f.base is a}")
---
## 12. بعد التحويل، قد يحتاج ravel إلى نسخ
t = a.T
r2 = t.ravel()
print(f"ravel of transposed base is a: {r2.base is a}")
المخرجات:
# تم التنفيذ بنجاح
المخرجات:
TEXT 📖 للعرض فقطravel base is a: True flatten base is a: False ravel of transposed base is a: False
❓ أسئلة شائعة
arr.base is original. إذا كان True، فهو عرض. إذا كان arr.base هو None، فهي تملك بياناتها الخاصة.a.view(np.float32) يعيد تفسير نفس البايتات كنوع مختلف. هذا خطير ونادراً ما يكون مطلوباً. استخدم astype للحصول على نسخة آمنة مع تحويل..copy()؟📖 ملخص
- العروض تشارك الذاكرة مع المصفوفة الأصلية؛ تعديلها يُعدّل الأصلية
arr.base is original— True يعني عرض، False يعني مستقل- التقطيع الأساسي، إعادة التشكيل، التحويل، ravel: تُرجع عروضاً
- الفهرسة المتقدمة، الفهرسة المنطقية، flatten، astype: تُرجع نسخاً
- العروض سريعة (O(1)) لكنها خطيرة؛ النسخ آمنة لكنها تكلف ذاكرة
- عندما تشك، استخدم
.copy()صراحة
📝 تمارين
-
مبتدئ (الصعوبة ⭐): أنشئ مصفوفة أحادية البعد من 10 عناصر. أنشئ شريحة، وفهرسة متقدمة، واختيار قناع منطقي. لكل منها، تحقق مما إذا كان تعديل النتيجة يُغيّر الأصلية.
-
متوسط (الصعوبة ⭐⭐): أنشئ مصفوفة 3x4، خذ شريحة 2x2، وعدّلها. لاحظ تغير الأصلية. الآن استخدم
.copy()وكرر — تأكد من بقاء الأصلية دون تغيير. -
متقدم (الصعوبة ⭐⭐⭐): أنشئ مصفوفة ثلاثية الأبعاد، حولها، ثم حاول إعادة تشكيل النتيجة. تحقق مما إذا كانت إعادة التشكيل تُرجع عرضاً (استخدم
.base). اشرح لماذا أو لم لا.