सूची को पुनरावृत्त करते समय पायथन केवल व्यक्तिगत तत्व की प्रतिलिपि क्यों बनाता है?


31

मुझे एहसास हुआ कि पायथन में, अगर कोई लिखता है

for i in a:
    i += 1

मूल सूची के तत्व aवास्तव में बिल्कुल भी प्रभावित नहीं होंगे, क्योंकि चर iकेवल मूल तत्व की एक प्रति बन जाता है a

मूल तत्व को संशोधित करने के लिए,

for index, i in enumerate(a):
    a[index] += 1

जरूरत होगी।

मैं इस व्यवहार से वास्तव में हैरान था। यह बहुत ही स्पष्ट प्रतीत होता है, अन्य भाषाओं से अलग प्रतीत होता है और मेरे कोड में त्रुटियों के परिणामस्वरूप मुझे आज लंबे समय तक डिबग करना पड़ा।

मैंने पहले पायथन ट्यूटोरियल पढ़ा है। बस सुनिश्चित करने के लिए, मैंने अभी-अभी पुस्तक को फिर से चेक किया, और यह इस व्यवहार का उल्लेख भी नहीं करता है।

इस डिजाइन के पीछे तर्क क्या है? क्या यह बहुत सारी भाषाओं में एक मानक अभ्यास होने की उम्मीद है ताकि ट्यूटोरियल का मानना ​​है कि पाठकों को स्वाभाविक रूप से इसे प्राप्त करना चाहिए? वर्तमान में पुनरावृत्ति पर समान व्यवहार किन अन्य भाषाओं में है, कि मुझे भविष्य में ध्यान देना चाहिए?


19
यह तभी सही है जब iआप अपरिवर्तनीय हैं या आप एक नॉन-म्यूटिंग ऑपरेशन कर रहे हैं। एक नेस्टेड सूची के साथ for i in a: a.append(1)अलग व्यवहार होगा; पायथन नेस्टेड सूचियों की नकल नहीं करता है। हालाँकि पूर्णांक अपरिवर्तनीय हैं और इसके अलावा एक नई वस्तु मिलती है, यह पुराने को नहीं बदलता है।
jonrsharpe

10
यह बिल्कुल भी आश्चर्य की बात नहीं है। मैं ऐसी भाषा के बारे में नहीं सोच सकता जो पूर्णांक जैसे बुनियादी प्रकारों की एक सरणी के लिए समान नहीं है। उदाहरण के लिए, जावास्क्रिप्ट में प्रयास करें a=[1,2,3];a.forEach(i => i+=1);alert(a)। सी # में
edc65

7
क्या आप i = i + 1प्रभावित होने की उम्मीद करेंगे a?
डेल्टाब

7
ध्यान दें कि यह व्यवहार अन्य भाषाओं में भिन्न नहीं है। सी, जावास्क्रिप्ट, जावा आदि इस तरह से व्यवहार करते हैं।
स्लीवेटमैन

1
@jonrsharpe सूचियों के लिए "+ =" पुरानी सूची को बदल देता है, जबकि "+" एक नया बनाता है
Vasily Alexeev

जवाबों:


68

मैंने पहले ही एक समान प्रश्न का उत्तर दिया था और यह समझना बहुत महत्वपूर्ण है कि +=इसके अलग-अलग अर्थ हो सकते हैं:

  • अगर डेटा टाइप इन-प्लेस जोड़ (यानी सही ढंग से काम करने वाला __iadd__फ़ंक्शन) लागू करता है, तो iसंदर्भित डेटा को अपडेट किया जाता है (यदि यह सूची में या कहीं और है तो कोई फर्क नहीं पड़ता)।

  • यदि डेटा प्रकार किसी __iadd__विधि को कार्यान्वित नहीं करता i += xहै i = i + x, तो कथन के लिए सिंटैक्टिक चीनी है , इसलिए एक नया मान बनाया जाता है और चर नाम को सौंपा जाता है i

  • यदि डेटा प्रकार लागू __iadd__करता है, लेकिन यह कुछ अजीब करता है। यह संभव हो सकता है कि इसे अपडेट किया जाए ... या नहीं - यह इस बात पर निर्भर करता है कि वहां क्या लागू किया गया है।

पायथन पूर्णांक, फ्लोट, स्ट्रिंग्स लागू नहीं होते हैं __iadd__इसलिए इन्हें इन-प्लेस में अपडेट नहीं किया जाएगा। हालाँकि अन्य डेटा प्रकार इसे पसंद करते हैं numpy.arrayया listकार्यान्वित करते हैं और जैसा आप अपेक्षित करेंगे वैसा ही व्यवहार करेंगे। तो यह कॉपी या नो-कॉपी का मामला नहीं है जब iterating (आम तौर पर यह lists और tuples के लिए प्रतियां नहीं करता है - लेकिन यह भी कंटेनर __iter__और __getitem__विधि के कार्यान्वयन पर निर्भर करता है !) - यह डेटा प्रकार की बात है! आप अपने में संग्रहीत किए गए a


2
यह प्रश्न में वर्णित व्यवहार के लिए सही स्पष्टीकरण है।
पाबौक

19

स्पष्टीकरण - शब्दावली

पायथन संदर्भ और सूचक की अवधारणाओं के बीच अंतर नहीं करता है । वे आमतौर पर केवल संदर्भ शब्द का उपयोग करते हैं , लेकिन अगर आप C ++ जैसी भाषाओं के साथ तुलना करते हैं, जिसमें वह अंतर है - यह एक पॉइंटर के बहुत करीब है ।

चूँकि प्रश्नकर्ता स्पष्ट रूप से C ++ पृष्ठभूमि से आता है, और चूँकि वह भेद - जो स्पष्टीकरण के लिए आवश्यक है - पायथन में मौजूद नहीं है , मैंने C ++ की शब्दावली का उपयोग करने के लिए चुना है, जो है:

  • मान : वास्तविक डेटा जो मेमोरी में बैठता है। void foo(int x);एक फ़ंक्शन का एक हस्ताक्षर है जो मूल्य द्वारा पूर्णांक प्राप्त करता है ।
  • सूचक : एक स्मृति पते को मान के रूप में माना जाता है। इसकी ओर इंगित की गई मेमोरी तक पहुंचने के लिए इसे स्थगित किया जा सकता है। void foo(int* x);एक फ़ंक्शन का एक हस्ताक्षर है जो सूचक द्वारा पूर्णांक प्राप्त करता है ।
  • संदर्भ : संकेत के आसपास चीनी। पर्दे के पीछे एक संकेतक है, लेकिन आप केवल आस्थगित मूल्य तक पहुंच सकते हैं और उस पते को नहीं बदल सकते जो इसे इंगित करता है। void foo(int& x);एक फ़ंक्शन का एक हस्ताक्षर है जो संदर्भ द्वारा पूर्णांक प्राप्त करता है ।

आपका क्या मतलब है "अन्य भाषाओं से अलग"? अधिकांश भाषाएं जो मुझे पता है कि प्रत्येक लूप के लिए समर्थन तत्व की प्रतिलिपि बना रहा है जब तक कि विशेष रूप से अन्यथा निर्देश न दिया गया हो।

विशेष रूप से पायथन के लिए (हालांकि इनमें से कई कारण समान वास्तु या दार्शनिक अवधारणाओं के साथ अन्य भाषाओं पर लागू हो सकते हैं):

  1. यह व्यवहार उन लोगों के लिए बग पैदा कर सकता है जो इसके बारे में अनजान हैं, लेकिन वैकल्पिक व्यवहार उन लोगों के लिए भी बग पैदा कर सकता है जो इसके बारे में जानते हैं । जब आप एक चर ( i) असाइन करते हैं तो आप आमतौर पर बंद नहीं होते हैं और अन्य सभी चर पर विचार करते हैं जो इसके कारण बदल जाएंगे ( a)। जिस दायरे पर आप काम कर रहे हैं उसे सीमित करना स्पेगेटी कोड को रोकने का एक प्रमुख कारक है, और इसलिए प्रतिलिपि द्वारा पुनरावृत्ति आमतौर पर उन भाषाओं में भी डिफ़ॉल्ट है जो संदर्भ द्वारा पुनरावृत्ति का समर्थन करते हैं।

  2. पायथन चर हमेशा एक एकल पॉइंटर होते हैं, इसलिए यह कॉपी द्वारा पुनरावृति के लिए सस्ता है - संदर्भ द्वारा पुनरावृत्ति की तुलना में सस्ता है, जो आपको मूल्य का उपयोग करने पर हर बार एक अतिरिक्त डिफ्रेंसिंग की आवश्यकता होगी।

  3. पायथन में संदर्भ चर की अवधारणा नहीं है जैसे - उदाहरण के लिए - C ++। यही है, पायथन में सभी चर वास्तव में संदर्भ हैं, लेकिन इस अर्थ में कि वे संकेत हैं - सी + + type& nameतर्कों की तरह पीछे-पीछे दृश्यों का संदर्भ नहीं । चूंकि यह अवधारणा पायथन में मौजूद नहीं है, इसलिए संदर्भ द्वारा पुनरावृत्ति को लागू करना - अकेले इसे डिफ़ॉल्ट बनाने दें! - बाइटकोड को और अधिक जटिलता जोड़ने की आवश्यकता होगी।

  4. पायथन का forबयान न केवल सरणियों पर काम करता है, बल्कि जनरेटर की अधिक सामान्य अवधारणा पर भी काम करता है। दृश्यों के पीछे, पायथन iterआपके सरणियों पर एक वस्तु प्राप्त करने के लिए कहता है - जब आप nextउस पर कॉल करते हैं - या तो अगला तत्व या raiseसा लौटाता है StopIteration। पायथन में जनरेटर को लागू करने के कई तरीके हैं, और इसे पुनरावृत्ति-दर-संदर्भ के लिए लागू करना बहुत कठिन होता।


जवाब के लिए धन्यवाद। लगता है कि पुनरावृत्तियों पर मेरी समझ अभी भी पर्याप्त ठोस नहीं है। डिफ़ॉल्ट रूप से C ++ संदर्भ में पुनरावृत्तियों नहीं हैं? यदि आप पुनरावृत्ति को रोकते हैं तो आप हमेशा मूल कंटेनर के तत्व का मूल्य बदल सकते हैं?
xji

4
पायथन संदर्भ द्वारा पुनरावृति करता है (ठीक है, मूल्य से, लेकिन मूल्य एक संदर्भ है)। उत्परिवर्तित वस्तुओं की सूची के साथ यह कोशिश करना जल्दी से प्रदर्शित करेगा कि कोई भी नकल नहीं होती है।
jonrsharpe

C ++ में Iterators वास्तव में ऑब्जेक्ट हैं जो एरे में मूल्य तक पहुंचने के लिए आस्थगित हो सकते हैं। मूल तत्व को संशोधित करने के लिए, आप उपयोग करते हैं *it = ...- लेकिन इस प्रकार का सिंटैक्स पहले ही इंगित करता है कि आप कहीं और कुछ संशोधित कर रहे हैं - जिससे समस्या # 1 कम हो जाती है। कारण # 2 और # 3 भी लागू नहीं होते हैं, क्योंकि C ++ में प्रतिलिपि महंगी है और संदर्भ चर की अवधारणा मौजूद है। कारण # 4 के लिए - एक संदर्भ को वापस करने की क्षमता सभी मामलों के लिए सरल कार्यान्वयन की अनुमति देती है।
इदं आर्ये

1
@jonrsharpe हाँ, इसे संदर्भ द्वारा कहा जाता है, लेकिन किसी भी भाषा में जिसमें संकेत और संदर्भ के बीच अंतर होता है, इस प्रकार का पुनरावृत्ति सूचक द्वारा एक पुनरावृत्ति होगा (और क्योंकि सूचक मान हैं - मान द्वारा पुनरावृति)। मैं एक स्पष्टीकरण जोड़ दूँगा।
इदं आर्ये

20
आपका पहला पैराग्राफ बताता है कि पायथन, उन अन्य भाषाओं की तरह, एक लूप में तत्व को कॉपी करता है। यह नहीं है यह उस तत्व में आपके द्वारा किए गए परिवर्तनों की गुंजाइश को सीमित नहीं करता है। ओपी केवल इस व्यवहार को देखता है क्योंकि उनके तत्व अपरिवर्तनीय हैं; इस बात का उल्लेख किए बिना कि आपका उत्तर सबसे अधूरा है और सबसे भ्रामक है।
जोंशरशीप

11

यहां कोई भी उत्तर आपको वास्तव में यह बताने के लिए कोई कोड नहीं देता है कि पायथन भूमि में ऐसा क्यों होता है। और यह एक और अधिक गहरी दृष्टिकोण में देखने के लिए मजेदार है इसलिए यहां जाता है।

प्राथमिक कारण यह है कि आप अपेक्षा के अनुरूप काम नहीं करते हैं क्योंकि आप लिखते समय पायथन में हैं:

i += 1

यह वह नहीं कर रहा है जो आपको लगता है कि यह कर रहा है। पूर्णांक अपरिवर्तनीय हैं। यह तब देखा जा सकता है जब आप देखते हैं कि वास्तव में पायथन में क्या वस्तु है:

a = 0
print('ID of the first integer:', id(a))
a += 1
print('ID of the first integer +=1:', id(a))

आईडी फ़ंक्शन जीवनकाल में किसी वस्तु के लिए एक अद्वितीय और निरंतर मूल्य का प्रतिनिधित्व करता है। वैचारिक रूप से, यह C / C ++ में एक मेमोरी एड्रेस पर शिथिल रूप से मैप करता है। उपरोक्त कोड चलाना:

ID of the first integer: 140444342529056
ID of the first integer +=1: 140444342529088

इसका मतलब है कि पहला aअब दूसरे के समान नहीं है a, क्योंकि उनकी आईडी अलग हैं। प्रभावी रूप से वे स्मृति में विभिन्न स्थानों पर हैं।

एक वस्तु के साथ, हालांकि, चीजें अलग तरह से काम करती हैं। मैंने +=यहाँ ऑपरेटर को अधिलेखित कर दिया है:

class CustomInt:
  def __iadd__(self, other):
    # Override += 1 for this class
    self.value = self.value + other.value
    return self

  def __init__(self, v):
    self.value = v

ints = []
for i in range(5):
  int = CustomInt(i)
  print('ID={}, value={}'.format(id(int), i))
  ints.append(int)


for i in ints:
  i += CustomInt(i.value)

print("######")
for i in ints:
  print('ID={}, value={}'.format(id(i), i.value))

निम्न आउटपुट में यह परिणाम चल रहा है:

ID=140444284275400, value=0
ID=140444284275120, value=1
ID=140444284275064, value=2
ID=140444284310752, value=3
ID=140444284310864, value=4
######
ID=140444284275400, value=0
ID=140444284275120, value=2
ID=140444284275064, value=4
ID=140444284310752, value=6
ID=140444284310864, value=8

ध्यान दें कि इस मामले में आईडी विशेषता वास्तव में दोनों पुनरावृत्तियों के लिए समान है , भले ही ऑब्जेक्ट का मूल्य अलग है (आप ऑब्जेक्ट को idरखने वाले इंट वैल का भी पता लगा सकते हैं , जो बदलते हुए के रूप में बदल रहा होगा - क्योंकि पूर्णांक अपरिवर्तनीय हैं)।

जब आप एक अपरिवर्तनीय वस्तु के साथ एक ही व्यायाम चलाते हैं तो इसकी तुलना करें:

ints_primitives = []
for i in range(5):
  int = i
  ints_primitives.append(int)
  print('ID={}, value={}'.format(id(int), i))

print("######")
for i in ints_primitives:
  i += 1
  print('ID={}, value={}'.format(id(int), i))


print("######")
for i in ints_primitives:
  print('ID={}, value={}'.format(id(i), i))

यह आउटपुट:

ID=140023258889248, value=0
ID=140023258889280, value=1
ID=140023258889312, value=2
ID=140023258889344, value=3
ID=140023258889376, value=4
######
ID=140023258889280, value=1
ID=140023258889312, value=2
ID=140023258889344, value=3
ID=140023258889376, value=4
ID=140023258889408, value=5
######
ID=140023258889248, value=0
ID=140023258889280, value=1
ID=140023258889312, value=2
ID=140023258889344, value=3
ID=140023258889376, value=4

ध्यान देने योग्य कुछ बातें। सबसे पहले, के साथ लूप में +=, आप अब मूल ऑब्जेक्ट में नहीं जोड़ रहे हैं। इस मामले में, क्योंकि चींटियाँ पायथन में अपरिवर्तनीय प्रकारों में से हैं , अजगर एक अलग आईडी का उपयोग करता है। यह भी दिलचस्प है कि पायथन एक idही अपरिवर्तनीय मूल्य के साथ कई चर के लिए अंतर्निहित का उपयोग करता है :

a = 1999
b = 1999
c = 1999

print('id a:', id(a))
print('id b:', id(b))
print('id c:', id(c))

id a: 139846953372048
id b: 139846953372048
id c: 139846953372048

tl; dr - पायथन में मुट्ठी भर अपरिवर्तनीय प्रकार होते हैं, जो आपके द्वारा देखे जाने वाले व्यवहार का कारण बनते हैं। सभी उत्परिवर्तनीय प्रकारों के लिए, आपकी अपेक्षा सही है।


6

@ इडान का जवाब यह समझाने का एक अच्छा काम करता है कि पायथन लूप वैरिएबल को एक पॉइंटर के रूप में क्यों नहीं मानता है जिस तरह से आप सी में हो सकते हैं, लेकिन यह अधिक गहराई से समझाने लायक है कि कोड स्निपेट्स अनपैक कैसे करता है, जैसा कि पायथन में बहुत सारे सरल-प्रतीत बिट्स हैं कोड ऑफ कोड वास्तव में कॉल टू बिल्ट इन मेथड होगा । अपना पहला उदाहरण लेने के लिए

for i in a:
    i += 1

अनपैक करने के लिए दो चीजें हैं: for _ in _:सिंटैक्स और _ += _सिंटैक्स। लूप के लिए पहले लेने के लिए, अन्य भाषाओं की तरह पायथन में एक for-eachलूप है जो अनिवार्य रूप से एक इटेरेटर पैटर्न के लिए चीनी है। पायथन में, एक एटरेटर एक ऐसी वस्तु है जो एक ऐसी .__next__(self)विधि को परिभाषित करता है जो अनुक्रम में वर्तमान तत्व को लौटाता है, अगले को आगे बढ़ाता है और StopIterationअनुक्रम में अधिक आइटम नहीं होने पर बढ़ाएगा । Iterable एक ऑब्जेक्ट है जो एक .__iter__(self)विधि को परिभाषित करता है जो एक पुनरावृत्तिकर्ता को लौटाता है।

(एनबी: ए Iteratorभी एक है Iterableऔर अपने .__iter__(self)तरीके से खुद को लौटाता है ।)

पायथन में आमतौर पर एक इनबिल्ट फ़ंक्शन होता है जो कस्टम डबल अंडरस्कोर विधि को दर्शाता है। तो यह iter(o)जो करने के लिए हल करता है o.__iter__()और next(o)जो करने के लिए हल करता है o.__next__()। ध्यान दें कि इनबिल्ट फ़ंक्शंस अक्सर एक उचित डिफ़ॉल्ट परिभाषा आज़माएंगे यदि वे जिस विधि को निर्दिष्ट करेंगे उसे परिभाषित नहीं किया गया है। उदाहरण के लिए, len(o)आम तौर पर हल होता है, o.__len__()लेकिन यदि वह विधि परिभाषित नहीं है तो यह कोशिश करेगा iter(o).__len__()

पाश के लिए एक अनिवार्य रूप से करने के मामले में परिभाषित किया गया है next(), iter()और अधिक बुनियादी नियंत्रण संरचनाओं। सामान्य रूप से कोड

for i in %EXPR%:
    %LOOP%

जैसे कुछ करने के लिए unpacked हो जाएगा

_a_iter = iter(%EXPR%)
while True:
    try:
        i = next(_a_iter)
    except StopIteration:
        break
    %LOOP%

तो इस मामले में

for i in a:
    i += 1

के लिए unpacked हो जाता है

_a_iter = iter(a) # = a.__iter__()
while True:
    try: 
        i = next(_a_iter) # = _a_iter.__next__()
    except StopIteration:
        break
    i += 1

इसका दूसरा हिस्सा है i += 1। आम तौर पर %ASSIGN% += %EXPR%करने के लिए unpacked हो जाता है %ASSIGN% = %ASSIGN%.__iadd__(%EXPR%)। इधर __iadd__(self, other)उधर इजाफा करता है और खुद लौट जाता है।

(एनबी यह एक और मामला है जहां मुख्य विधि परिभाषित नहीं होने पर पायथन एक विकल्प का चयन करेगा। यदि ऑब्जेक्ट लागू नहीं होता है, __iadd__तो वह वापस गिर जाएगा __add__। यह वास्तव में इस मामले में intऐसा करता है जैसा कि लागू नहीं होता है __iadd__- जो समझ में आता है क्योंकि वे अपरिवर्तनीय हैं और इसलिए इसे संशोधित नहीं किया जा सकता है।)

तो यहां आपका कोड दिखता है

_a_iter = iter(a)
while True:
    try:
        i = next(_a_iter)
    except StopIteration:
        break
    i = iadd(i,1)

हम कहां परिभाषित कर सकते हैं

def iadd(o, v):
    try:
        return o.__iadd__(v)
    except AttributeError:
        return o.__add__(v)

आपके दूसरे बिट कोड में कुछ और चल रहा है। हमें जिन दो नई चीजों की जानकारी होनी चाहिए, वे %ARG%[%KEY%] = %VALUE%अनपैक हो जाती हैं (%ARG%).__setitem__(%KEY%, %VALUE%)और %ARG%[%KEY%]अनपैक हो जाती हैं (%ARG%).__getitem__(%KEY%)। इस ज्ञान को एक साथ रखने से हम फिर से a[ix] += 1अनपैक हो जाते हैं a.__setitem__(ix, a.__getitem__(ix).__add__(1))( __add__बजाय : __iadd__क्योंकि __iadd__किलों द्वारा लागू नहीं किया जाता है)। हमारा अंतिम कोड इस तरह दिखता है:

_a_iter = iter(enumerate(a))
while True:
    try:
        index, i = next(_a_iter)
    except StopIteration:
        break
    a.__setitem__(index, iadd(a.__getitem__(index), 1))

वास्तव में पहले एक सूची संशोधित नहीं करता है क्यों है, जबकि दूसरा करता है, हमारी पहली स्निपेट हो रही है में करने के लिए के रूप में अपने प्रश्न का उत्तर देने iसे next(_a_iter), साधन है जिसके iएक हो जाएगा int। चूँकि intसंशोधित नहीं किया जा सकता, i += 1इसलिए सूची में कुछ भी नहीं है। हमारे दूसरे मामले में हम फिर से संशोधन नहीं कर रहे हैं intलेकिन कॉल करके सूची को संशोधित कर रहे हैं __setitem__

इस पूरे विस्तृत अभ्यास का कारण यह है कि मुझे लगता है कि यह पायथन के बारे में निम्न पाठ सिखाता है:

  1. अजगर की पठनीयता की कीमत यह है कि यह हर समय इन जादू डबल स्कोर विधियों को बुला रहा है।
  2. इसलिए, पायथन कोड के किसी भी टुकड़े को सही मायने में समझने का एक मौका पाने के लिए आपको इन अनुवादों को इसके बारे में समझना होगा।

डबल अंडरस्कोर विधियां शुरू होने पर एक बाधा हैं, लेकिन वे पायथन के "रनवेबल स्यूडोकोड" प्रतिष्ठा का समर्थन करने के लिए आवश्यक हैं। एक सभ्य पायथन प्रोग्रामर को इन तरीकों की पूरी समझ होगी और वे कैसे प्राप्त करेंगे और उन्हें परिभाषित करेंगे जहां भी ऐसा करने के लिए अच्छा समझ में आता है।

संपादित करें : @deltab ने "संग्रह" शब्द के मेरे मैला उपयोग को ठीक किया।


2
"पुनरावृत्तियां भी संग्रह हैं" बिल्कुल सही नहीं है: वे भी चलने योग्य हैं, लेकिन संग्रह भी हैं __len__और__contains__
डेल्टैब

2

+=वर्तमान मूल्य परस्पर या अपरिवर्तनीय है या नहीं, इसके आधार पर अलग-अलग तरीके से काम करता है । यह मुख्य कारण था कि इसे अजगर में लागू होने में लंबा समय लगता है, क्योंकि पायथन डेवलपर्स डरते थे कि यह भ्रामक होगा।

यदि iकोई int है, तो इसे बदला नहीं जा सकता क्योंकि ints अपरिवर्तनीय हैं, और इस प्रकार यदि iपरिवर्तन का मूल्य है तो यह आवश्यक रूप से किसी अन्य ऑब्जेक्ट को इंगित करना चाहिए:

>>> i=3
>>> id(i)
14336296
>>> i+=1
>>> id(i)
14336272   # Other object

हालाँकि, यदि बायाँ हाथ पक्ष परस्पर है , तो + = वास्तव में इसे बदल सकता है; जैसे अगर यह एक सूची है:

>>> i=[]
>>> id(i)
140257231883944
>>> i+=[1]
>>> id(i)
140257231883944  # Still the same object!

लूप के लिए, बदले में iप्रत्येक तत्व को संदर्भित करता है a। यदि वे पूर्णांक हैं, तो पहला मामला लागू होता है, और इसका परिणाम i += 1यह होना चाहिए कि यह किसी अन्य पूर्णांक ऑब्जेक्ट को संदर्भित करता है। aपाठ्यक्रम की सूची में अभी भी वही तत्व हैं जो हमेशा से थे।


मुझे परिवर्तनशील और अपरिवर्तनीय वस्तुओं के बीच यह अंतर समझ में नहीं आता है: यदि एक अपरिवर्तनीय पूर्णांक ऑब्जेक्ट पर i = 1सेट iहोता है, तो एक अपरिवर्तनीय सूची ऑब्जेक्ट पर i = []सेट होना चाहिए i। दूसरे शब्दों में, पूर्णांक वस्तुएं अपरिवर्तनीय क्यों हैं और वस्तुओं को पारस्परिक रूप से सूचीबद्ध करना? मुझे इसके पीछे कोई तर्क नजर नहीं आता।
जियोर्जियो

@ जियोर्जियो: वस्तुएं अलग-अलग वर्गों से होती हैं, ऐसे listतरीकों को लागू करती हैं जो इसकी सामग्री को बदलते हैं, intनहीं। [] है एक परिवर्तनशील सूची वस्तु, और i = []की सुविधा देता है iकि वस्तु को देखें।
रेमकोगर्लिच

@ जियोर्जियो पायथन में एक अपरिवर्तनीय सूची जैसी कोई चीज नहीं है। सूचियाँ परस्पर हैं। इंटेगर नहीं हैं। यदि आप एक सूची जैसा कुछ चाहते हैं लेकिन अपरिवर्तनीय हैं, तो एक टपल पर विचार करें। ऐसा क्यों है, यह स्पष्ट नहीं है कि आप उस स्तर पर क्या चाहते हैं, जिसका उत्तर दिया गया है।
जॉन्सर्शपे

@RemcoGerlich: मैं समझता हूं कि विभिन्न वर्ग अलग-अलग व्यवहार करते हैं, मुझे समझ में नहीं आता है कि उन्हें इस तरह क्यों लागू किया गया, यानी मुझे इस पसंद के पीछे के तर्क समझ में नहीं आते हैं। मैंने +=दोनों प्रकारों के लिए समान रूप से (कम से कम आश्चर्य का सिद्धांत) व्यवहार करने के लिए ऑपरेटर / विधि को लागू किया होगा : या तो मूल वस्तु को बदल दें या पूर्णांक और सूची दोनों के लिए एक संशोधित प्रतिलिपि लौटाएं।
जियोर्जियो

1
@ जियोर्जियो: यह बिल्कुल सच है कि +=पायथन में आश्चर्य की बात है, लेकिन यह महसूस किया गया था कि आपके द्वारा उल्लिखित अन्य विकल्प भी आश्चर्यजनक थे, या कम से कम कम व्यावहारिक (मूल वस्तु को बदलते हुए सबसे सामान्य प्रकार के मूल्य के साथ नहीं किया जा सकता है) आप ints के साथ + = का उपयोग करते हैं। और एक पूरी सूची की नकल करना इसे बदलने की तुलना में बहुत अधिक महंगा है, पायथन सूची और शब्दकोशों जैसी चीजों की नकल नहीं करता है जब तक कि स्पष्ट रूप से नहीं बताया जाता है)। यह एक बहुत बड़ी बहस थी।
रेमकोगर्लिच

1

यहाँ का पाश एक तरह का अप्रासंगिक है। फ़ंक्शन मापदंडों या तर्कों की तरह, लूप के लिए सेट करना जैसे कि अनिवार्य रूप से सिर्फ फैंसी-दिखने वाला असाइनमेंट है।

पूर्णांक अपरिवर्तनीय हैं। उन्हें संशोधित करने का एकमात्र तरीका एक नया पूर्णांक बनाना है, और इसे मूल नाम के समान नाम देना है।

असाइनमेंट मैप के लिए पायथन के शब्दार्थ सीधे C के (सीधे तौर पर दिए गए CPython के PyObject * पॉइंटर्स) पर हैं, केवल कैवियट के साथ कि सब कुछ एक पॉइंटर है, और आपको डबल पॉइंटर्स की अनुमति नहीं है। निम्नलिखित कोड पर विचार करें:

a = 1
b = a
b += 1
print(a)

क्या होता है? यह प्रिंट करता है 1। क्यूं कर? यह वास्तव में निम्नलिखित सी कोड के बराबर है:

i64* a = malloc(sizeof(i64));
*a = 1;
i64* b = a;
i64* tmp = malloc(sizeof(i64));
tmp = *b + 1;
b = tmp;
printf("%d\n", *a);

सी कोड में, यह स्पष्ट है कि मूल्य aपूरी तरह से अप्रभावित है।

जैसे कि सूचियाँ क्यों काम करती हैं, इसका उत्तर मूल रूप से सिर्फ यही है कि आप एक ही नाम पर काम कर रहे हैं। सूचियाँ परस्पर हैं। नाम की वस्तु की पहचान a[0]बदल जाएगी, लेकिन a[0]अभी भी एक वैध नाम है। आप इसे निम्न कोड से देख सकते हैं:

x = 1
a = [x]
print(a[0] is x)
a[0] += 1
print(a[0] is x)

लेकिन, यह सूचियों के लिए विशेष नहीं है। a[0]उस कोड को इसके साथ बदलें yऔर आपको ठीक वही परिणाम मिलता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.