2025, Oct 18 15:31
Python सूची से NumPy dtype=object एरे: copy=False और zero-copy का व्यवहार
Python सूची से NumPy dtype=object एरे बनाते समय copy=False क्यों विफल होता है, zero-copy की सीमाएं क्या हैं, और शैलो बनाम deep copy के सही उपाय जानें.
Python सूची से डेटा को दोबारा बनाए बिना NumPy ndarray बनाना सरल लगता है — जब तक कि dtype=object सामने न आ जाए। dtype=object वाले एरे कच्चे, सतत मानों की बजाय Python ऑब्जेक्ट्स के रेफरेंस संभालते हैं। इसी वजह से zero-copy कन्वर्ज़न की उम्मीदें उलझ सकती हैं, खासकर जब np.asarray(..., copy=False) आज़माएं और सख्त असफलता मिले। आइए समझते हैं कि वास्तव में क्या होता है और किस पर भरोसा किया जा सकता है।
समस्या
उद्देश्य है: एक Python सूची को dtype=object वाले NumPy एरे में बदला जाए, और निर्माण के समय कॉपी न बने। copy=False के जरिए मजबूर करने पर त्रुटि आती है:
import numpy as np
items = ['spam', 'eggs']
obj_view = np.asarray(items, dtype='object', copy=False)  # ValueError
ऐसा तब भी होता है जब dtype=object एरे रेफरेंस रखते हैं, और ऐसे एरे पर tobytes() वस्तुओं की सामग्री नहीं बल्कि पॉइंटर बाइट्स देता है। सवाल यह है कि क्या ndarray सीधे सूची की मौजूदा स्टोरेज साझा कर सकता है।
NumPy सूचियों को object dtype के साथ और बिना कैसे हैंडल करता है
एक साधारण सीक्वेंस लें और दो बार बदलें: एक बार NumPy को dtype चुनने दें, और दूसरी बार dtype=object तय करके। बिना स्पष्ट dtype के, NumPy एक फिक्स्ड-विड्थ स्ट्रिंग एरे बनाता है, स्ट्रिंग डेटा को कॉम्पैक्ट रिप्रेज़ेंटेशन में कॉपी करके:
import numpy as np
words = ['one', 'two', 'three']
fixed_str = np.asarray(words)  # dtype कुछ इस तरह बनता है '<U5'
यह एरे स्ट्रिंग्स को फिक्स्ड-लंबाई Unicode के रूप में रखता है, Python ऑब्जेक्ट्स के रूप में नहीं। यह स्ट्रिंग डेटा की कॉपी है। इस उदाहरण में आकार 3*5*4=60 बाइट्स बैठता है।
dtype=object के साथ, NumPy मूल Python ऑब्जेक्ट्स के रेफरेंस रखता है। इससे कंटेनर का शैलो कॉपी बनता है (एरे अपने पॉइंटर ब्लॉक का मालिक होता है), लेकिन तत्व वही Python ऑब्जेक्ट्स रहते हैं:
obj_arr = np.asarray(words, dtype=object)
# तीसरे तत्व की ऑब्जेक्ट पहचान समान है
id(words[2])
id(obj_arr[2])
पहचानें मिलती हैं, जो दिखाती हैं कि एरे मूल स्ट्रिंग्स के रेफरेंस पकड़े हुए है।
अब एक mutable तत्व जोड़ें तो व्यवहार और साफ दिखता है। साझा ऑब्जेक्ट को किसी भी कंटेनर से बदलने पर दोनों में असर दिखता है:
mixed = ['one', 'two', 'three', ['a', 'b']]
obj_box = np.array(mixed, object)
# एरे रेफरेंस के जरिए नेस्टेड सूची में बदलाव करें
obj_box[3].append('c')
# बदलाव दोनों कंटेनरों में दिखेगा क्योंकि दोनों उसी नेस्टेड सूची को संदर्भित करते हैं
mixed
obj_box
लेकिन, मूल सूची में किसी शीर्ष-स्तरीय तत्व को बदलने से ऑब्जेक्ट एरे में रखा मान नहीं बदलता, क्योंकि एरे का पॉइंटर ब्लॉक अलग है:
mixed[1] = 12.3
mixed
obj_box
यहां copy=False क्यों विफल होता है
NumPy को पॉइंटर ब्लॉक की वह शैलो कॉपी भी न करने के लिए मजबूर करना स्पष्ट त्रुटि देता है। copy=False का आशय है कि कोई कॉपी हर हालत में न हो; जब NumPy ऐसा नहीं कर पाता, तो वह मना कर देता है:
ValueError: Unable to avoid copy while creating an array as requested. If using np.array(obj, copy=False) replace it with np.asarray(obj) to allow a copy when needed (no behavior change in NumPy 1.x). For more details, see the migration guide.
व्यावहारिक तौर पर, copy=None (डिफ़ॉल्ट) यहां उतना ही उपयोगी है। जरूरत पड़ने पर ही कॉपी होगी। copy=True कंटेनर की कॉपी को मजबूर करता है, पर dtype=object के लिए यह भी शैलो ही रहता है; Python ऑब्जेक्ट्स की प्रतिकृतियां नहीं बनतीं। असल deep copy के लिए, यानी संदर्भित Python ऑब्जेक्ट्स की प्रतिलिपि बनाने के लिए, copy.deepcopy जैसा टूल चाहिए।
क्या ndarray सूची की आंतरिक स्टोरेज साझा कर सकता है?
Python सूची के आंतरिक पॉइंटर बफर को सीधे NumPy ऑब्जेक्ट एरे के रूप में इस्तेमाल करना इंटरप्रेटर की आंतरिक संरचना पर निर्भर करता है। व्यवहार में, Python सूची और NumPy ऑब्जेक्ट एरे दोनों तत्वों के लिए संलग्न पॉइंटर्स रखते हैं, लेकिन सूची के बफर पर सीधे ndarray बिठाना इम्प्लीमेंटेशन-डिपेंडेंट और असुरक्षित है। list प्रकार अपनी आंतरिक बफर को एक्सपोज़ नहीं करता, इसलिए ऐसा करने के लिए हैकी उपायों की जरूरत पड़ेगी जो पॉइंटर आकार, एंडियननेस और CPython ऑब्जेक्ट्स के मेमोरी लेआउट जैसी बारीकियों पर टिके होंगे, और रिसाइज़ या रनटाइम बदलावों पर आसानी से टूट सकते हैं।
कारगर तरीका
समर्थित तरीका यही है कि ऐसा ऑब्जेक्ट एरे बनाएँ जो मूल Python ऑब्जेक्ट्स को संदर्भित करे, यह मानते हुए कि एरे का पॉइंटर ब्लॉक उसका अपना होगा। इससे वही हासिल होता है जो ज़्यादातर लोग चाहते हैं: ऑब्जेक्ट्स की कोई डुप्लिकेशन नहीं, भले ही कंटेनर अलग हो।
import numpy as np
data_src = ['one', 'two', 'three', ['a', 'b']]
ref_array = np.asarray(data_src, dtype=object)  # डिफ़ॉल्ट रूप से copy=None; रेफरेंस का शैलो कॉपी
# नेस्टेड सूची बदलकर रेफरेंशियल शेयरिंग साबित करें
ref_array[3].append('c')
# data_src और ref_array अब वही बदली हुई नेस्टेड सूची दिखाते हैं
# सूची में किसी शीर्ष-स्तरीय तत्व को बदलने से एरे की उस जगह पर कोई असर नहीं पड़ता
data_src[1] = 12.3
यह क्यों मायने रखता है
ऑब्जेक्ट एरे और Python सूचियाँ एक जैसी लग सकती हैं क्योंकि दोनों Python ऑब्जेक्ट्स के रेफरेंस रखती हैं, मगर उनका व्यवहार अलग है। सूचियाँ append कर सकती हैं; एरे reshape कर सकते हैं। dtype=object के साथ आमतौर पर गति नहीं मिलती; बस कुछ ऑपरेशन सिंटैक्स के लिहाज से आसान हो सकते हैं। जब वेक्टराइज़्ड परफॉर्मेंस चाहिए, तो मूल numeric/string dtypes बेहतर रहते हैं। और जब Python-स्तर की परिवर्तनीयता और विषमता चाहिए, तो सूची अक्सर ज्यादा उपयुक्त होती है। यह जानना कि ऑब्जेक्ट एरे तत्वों के संदर्भ में शैलो होते हैं, साझा-स्थिति से जुड़े अनजाने बग्स से बचाता है और स्पष्ट करता है कि copy फ्लैग्स क्या सुनिश्चित कर सकते हैं और क्या नहीं।
निष्कर्ष
यदि आपको ऐसा NumPy एरे चाहिए जो मौजूदा Python ऑब्जेक्ट्स को संदर्भित करे, तो उसे dtype=object के साथ बनाएँ और डिफ़ॉल्ट copy व्यवहार पर भरोसा करें। कंटेनर का शैलो कॉपी अपेक्षित रखें और तत्वों के रेफरेंस साझा होंगे। copy=False थोपने पर, जब NumPy अपना पॉइंटर ब्लॉक आवंटित किए बिना नहीं चल सकता, तो त्रुटि उठेगी। वास्तविक Python ऑब्जेक्ट्स की deep copy के लिए copy.deepcopy जैसा साधन अपनाएँ। और यदि आप Python सूची की स्टोरेज पर सीधे सच्चा zero-copy view चाहते थे, तो वह न तो समर्थित है और न ही भरोसेमंद।
यह लेख StackOverflow पर पूछे गए प्रश्न (लेखक: Dryden) और hpaulj के उत्तर पर आधारित है।