2025, Oct 31 09:32
NumPy में इंडेक्सिंग: view बनाम copy, चेनिंग और इन-प्लेस अपडेट
NumPy इंडेक्सिंग में बेसिक स्लाइसिंग व एडवांस्ड इंडेक्सिंग पर view और copy कैसे बनते हैं, इन-प्लेस अपडेट क्यों चूकते हैं, और чेनिंग से बचने का सही तरीका जानें.
NumPy में इंडेक्सिंग दिखने में आसान लगती है, लेकिन जैसे ही आप चेनिंग के जरिए बेसिक स्लाइसिंग को एडवांस्ड इंडेक्सिंग के साथ मिलाते हैं, चीजें उलझ सकती हैं। ऑपरेशनों के क्रम में ज़रा-सा बदलाव भी इन-प्लेस अपडेट से खिसककर किसी अस्थायी कॉपी में चुपचाप लिखने का कारण बन सकता है। नीचे एक छोटा-सा उदाहरण है जो इस जाल और उससे बचाव दोनों को दिखाता है।
दोहराने योग्य उदाहरण
import numpy as np
idx = np.arange(2, 4)
arr_left = np.arange(5)
arr_left[:][idx] = 0  # arr_left को संशोधित करता है
print(arr_left)
arr_right = np.arange(5)
arr_right[idx][:] = 0  # arr_right को संशोधित नहीं करता
print(arr_right)
पहले असाइनमेंट में ऐरे अपडेट हो जाता है। दूसरे में मूल डेटा पर कुछ भी असर नहीं पड़ता। फर्क असाइनमेंट की परिभाषा का नहीं, बल्कि इस बात का है कि आप कब view पर काम कर रहे हैं और कब आप पहले ही कॉपी पर पहुँच चुके हैं।
वास्तव में क्या होता है
स्लाइस arr_left[:] उसी मेमोरी का view है जो arr_left की है। यानी इस view के ज़रिए किया गया कोई भी इन-प्लेस बदलाव मूल डेटा को बदल सकता है। इसके उलट, arr_right[idx] एडवांस्ड इंडेक्सिंग का उपयोग करता है, जो एक नई, स्वतंत्र ऐरे कॉपी बनाता है। एक बार वह कॉपी बन गई तो उस पर [:] लागू करना सिर्फ कॉपी पर असर करेगा, arr_right पर नहीं।
इसे base एट्रिब्यूट से साफ़ देखा जा सकता है। अगर दो ऐरे एक ही आधारभूत मेमोरी साझा करते हैं, तो view का base मूल की ओर इशारा करता है; जबकि स्वतंत्र कॉपी में base None होता है।
probe_view = arr_left[:]
print(probe_view.base is arr_left)  # True: मेमोरी साझा करता है
probe_copy = arr_right[idx]
print(probe_copy.base)               # None: स्वतंत्र कॉपी
भले ही arr_left और arr_left[:] अलग Python ऑब्जेक्ट हों और उनकी पहचानें अलग हों, वे एक ही NumPy डेटा बफर साझा करते हैं। इसी वजह से view के जरिए लिखने पर मूल ऐरे बदल जाता है।
क्रम क्यों मायने रखता है
असाइनमेंट के लक्ष्य बाएँ से दाएँ मूल्यांकित होते हैं। arr_left[:][idx] = 0 में बायाँ हिस्सा arr_left[:] एक view है, और फिर [idx] के साथ असाइनमेंट उसी view पर होता है, जो arr_left की मेमोरी में लिख देता है। arr_right[idx][:] = 0 में बायाँ हिस्सा arr_right[idx] पहले ही एक कॉपी बना देता है; फिर [:] उसी कॉपी पर लागू होता है, और असाइनमेंट केवल कॉपी को बदलता है।
सरल समाधान
इस परिस्थिति में [:][idx] या [idx][:] जैसी चेनिंग की कोई ज़रूरत नहीं। सीधे उसी इंडेक्स से लक्ष्य को संबोधित करें जिसकी आपको ज़रूरत है। अगर उद्देश्य एडवांस्ड इंडेक्सिंग के जरिए मूल ऐरे में लिखना है, तो एडवांस्ड इंडेक्स को असाइनमेंट के बाएँ हिस्से में रखें।
import numpy as np
idx = np.arange(2, 4)
arr = np.arange(5)
arr[idx] = 0  # सीधे arr को बदलता है
print(arr)
NumPy कब view बनाता है और कब copy?
NumPy की अपनी भाषा यह नियम संक्षेप में बताती है:
जब मूल ऐरे में तत्वों को ऑफ़सेट और स्ट्राइड्स के साथ संबोधित किया जा सकता है, तब views बनते हैं। इसलिए, बेसिक इंडेक्सिंग हमेशा views बनाती है। [...] दूसरी ओर, एडवांस्ड इंडेक्सिंग हमेशा कॉपी बनाती है।
buf = np.arange(5)
buf[:]        # व्यू (बेसिक इंडेक्सिंग)
buf[1:3]      # व्यू (बेसिक इंडेक्सिंग)
buf[1:3][:]   # व्यू (बेसिक इंडेक्सिंग)
buf[[2, 3]]   # कॉपी (एडवांस्ड इंडेक्सिंग)
चेनिंग इसलिए मायने रखती है कि जैसे ही आप एडवांस्ड इंडेक्सिंग में शिफ्ट होकर कॉपी ले लेते हैं, उसके बाद की कोई भी स्लाइसिंग उसी कॉपी पर लागू होगी, मूल पर नहीं। असाइनमेंट के बाएँ हिस्से में सीधे एडवांस्ड इंडेक्स रखने से आपकी लिखाई मूल ऐरे को ही निशाना बनाती है।
बहु-आयामी इंडेक्सिंग पर नोट्स
कई आयामों में, अलग-अलग अक्षों को इंडेक्स करते समय a[x][y] की बजाय a[x, y] को तरजीह दें। पहला तरीका क्रम में दो अलग-अलग इंडेक्सिंग ऑपरेशन करता है, जिससे semantics आसानी से बदल सकते हैं या अनावश्यक कॉपी बन सकती है। जब x और y ऐरे हों, तो a[x, y] तत्व-वार जोड़े निकालता है — [a[xv, yv] for (xv, yv) in zip(x, y)] — न कि x और y के सभी संयोजन। सभी संयोजन पाने के लिए a[x][:, y] जैसा तरीका चयन के व्यवहार को बदलता है, लेकिन यह view नहीं, कॉपी बनाता है।
यह क्यों मायने रखता है
प्रदर्शन-संवेदी या मेमोरी-सचेत कोड में, अनजाने में कॉपी पर काम करना समय भी बर्बाद करता है और नतीजे भी चौंका सकता है। कौन-सी क्रियाएँ views बनाती हैं और कौन-सी copies, यह जानने से आकस्मिक बेअसर असाइनमेंट से बचाव होता है और मेमोरी ट्रैफ़िक पर नियंत्रण बना रहता है।
मुख्य बातें
यदि आपको डेटा को इन-प्लेस बदलना है, तो असाइनमेंट के बाएँ हिस्से में सीधे एडवांस्ड इंडेक्स रखें, और [:][idx] या [idx][:] जैसी चेनिंग से बचें। याद रखें: बेसिक स्लाइसिंग views लौटाती है, जबकि एडवांस्ड इंडेक्सिंग copies देती है। यही मानसिक मॉडल काफ़ी है यह भाँपने के लिए कि आपकी लिखाई मूल ऐरे में जाएगी या किसी अस्थायी कॉपी में खो जाएगी।
यह लेख StackOverflow पर एक प्रश्न (लेखक: Daniel Reese) और mozway के उत्तर पर आधारित है।