एक फ़ंक्शन कॉलर द्वारा कथित कुछ तर्कों को संशोधित कर सकता है, लेकिन अन्य नहीं?


182

मैं चर दायरे के लिए पायथन के दृष्टिकोण को समझने की कोशिश कर रहा हूं। इस उदाहरण में, f()मान को बदलने में सक्षम क्यों है x, जैसा कि भीतर माना जाता है main(), लेकिन मूल्य नहीं है n?

def f(n, x):
    n = 2
    x.append(4)
    print('In f():', n, x)

def main():
    n = 1
    x = [0,1,2,3]
    print('Before:', n, x)
    f(n, x)
    print('After: ', n, x)

main()

आउटपुट:

Before: 1 [0, 1, 2, 3]
In f(): 2 [0, 1, 2, 3, 4]
After:  1 [0, 1, 2, 3, 4]

7
अच्छी तरह से यहाँ समझाया nedbatchelder.com/text/names.html
रौशन

जवाबों:


212

कुछ उत्तरों में फ़ंक्शन कॉल के संदर्भ में "कॉपी" शब्द होता है। मुझे यह भ्रामक लगता है।

पायथन कभी एक फ़ंक्शन कॉल के दौरान आपके द्वारा पास की गई वस्तुओं की प्रतिलिपि नहीं बनाता है ।

फ़ंक्शन पैरामीटर नाम हैं । जब आप किसी फ़ंक्शन को कॉल करते हैं तो पायथन इन मापदंडों को उन वस्तुओं से बांधता है जो आप पास करते हैं (एक कॉलर स्कोप में नामों के माध्यम से)।

वस्तुएँ परस्पर (जैसे सूचियाँ) या अपरिवर्तनीय (जैसे पूर्णांक, पायथन में तार) हो सकती हैं। परिवर्तनशील वस्तु जिसे आप बदल सकते हैं। आप एक नाम नहीं बदल सकते हैं, आप इसे किसी अन्य वस्तु से बांध सकते हैं।

आपका उदाहरण स्कोप या नेमस्पेस के बारे में नहीं है , यह पायथन में किसी ऑब्जेक्ट के नामकरण और बंधन और परिवर्तनशीलता के बारे में है।

def f(n, x): # these `n`, `x` have nothing to do with `n` and `x` from main()
    n = 2    # put `n` label on `2` balloon
    x.append(4) # call `append` method of whatever object `x` is referring to.
    print('In f():', n, x)
    x = []   # put `x` label on `[]` ballon
    # x = [] has no effect on the original list that is passed into the function

यहाँ पायथन में अन्य भाषाओं और नामों में चर के अंतर पर अच्छी तस्वीरें हैं ।


3
इस लेख ने मुझे समस्या को बेहतर ढंग से समझने में मदद की और यह एक वर्कअराउंड और कुछ उन्नत उपयोगों का सुझाव देता है: पायथन में डिफ़ॉल्ट पैरामीटर मान
Gfy

@ ठीक है, मैंने पहले भी इसी तरह के उदाहरण देखे हैं लेकिन मेरे लिए यह वास्तविक दुनिया की स्थिति का वर्णन नहीं करता है। यदि आप ऐसा कुछ संशोधित कर रहे हैं जो उसमें पारित हो गया है तो इसे डिफ़ॉल्ट रूप देने का कोई मतलब नहीं है।
मार्क रैनसम

@MarkRansom, मुझे लगता है कि अगर आप में ही वैकल्पिक उत्पादन गंतव्य प्रदान करना चाहते हैं यह मेकअप भावना करता है: def foo(x, l=None): l=l or []; l.append(x**2); return l[-1]
Janusz Lenar

सेबस्टियन के कोड की अंतिम पंक्ति के लिए, इसने कहा "मूल सूची पर उपरोक्त का कोई प्रभाव नहीं है"। लेकिन मेरी राय में, इसका केवल "n" पर कोई प्रभाव नहीं है, लेकिन मुख्य () फ़ंक्शन में "x" को बदल दिया है। क्या मैं सही हूँ?
user17670

1
@ user17670: x = []में f()सूची पर कोई प्रभाव नहीं है xमुख्य कार्य में। मैंने टिप्पणी को अपडेट किया है, इसे और अधिक विशिष्ट बनाने के लिए।
jfs

15

आपको पहले ही कई उत्तर मिल चुके हैं, और मैं मोटे तौर पर JF सेबेस्टियन से सहमत हूं, लेकिन आप इसे शॉर्टकट के रूप में उपयोगी पा सकते हैं:

जब भी आप देखते हैं varname =, आप फ़ंक्शन के दायरे में एक नया नाम बाइंडिंग बना रहे हैं । इस दायरे मेंvarname आने से पहले जो कुछ भी बाध्य था वह खो गया है ।

किसी भी समय आप देख varname.foo()रहे हैं कि आप किस पद्धति पर कॉल कर रहे हैं varname। विधि varname (उदा list.append) को बदल सकती है । varname(या, बल्कि, वह वस्तु जिसका varnameनाम है) एक से अधिक दायरे में मौजूद हो सकता है, और चूंकि यह एक ही वस्तु है, इसलिए सभी स्कोप में कोई भी परिवर्तन दिखाई देगा।

[ध्यान दें कि globalकीवर्ड पहले मामले के लिए एक अपवाद बनाता है]


13

fवास्तव में x(जो किसी सूची के उदाहरण के लिए हमेशा एक ही संदर्भ है) के मूल्य में परिवर्तन नहीं करता है । बल्कि, यह सामग्री को बदल देता है इस सूची को ।

दोनों मामलों में, एक संदर्भ की एक प्रति फ़ंक्शन को दी जाती है। फ़ंक्शन के अंदर,

  • nएक नया मान असाइन किया गया है। केवल फ़ंक्शन के अंदर का संदर्भ संशोधित किया गया है, न कि इसके बाहर का।
  • xएक नया मान निर्दिष्ट नहीं किया जाता है: न तो फ़ंक्शन के अंदर और न ही संदर्भ को संशोधित किया जाता है। इसके बजाय, xका मान संशोधित किया गया है।

चूंकि xफ़ंक्शन के अंदर और बाहर दोनों समान मूल्य को संदर्भित करते हैं, दोनों संशोधन देखते हैं। इसके विपरीत, nफ़ंक्शन के अंदर और बाहर यह फ़ंक्शन के अंदर पुन: असाइन किए जाने के बाद विभिन्न मानों को संदर्भित nकरता है।


8
"कॉपी" भ्रामक है। पायथन में सी जैसे चर नहीं हैं। पायथन में सभी नाम संदर्भ हैं। आप नाम को संशोधित नहीं कर सकते, आप बस इसे किसी अन्य वस्तु से बांध सकते हैं, बस। यह केवल पायथन में उत्परिवर्तनीय और अपरिवर्तनीय वस्तु के बारे में बात करने के लिए समझ में आता है न कि वे नाम हैं।
jfs

1
@ जेएफ सेबस्टियन: आपका बयान सबसे अच्छा भ्रामक है। संदर्भ के रूप में संख्याओं के बारे में सोचना उपयोगी नहीं है।
पित्रौ

9
@dysfunctor: संख्या अपरिवर्तनीय वस्तुओं के संदर्भ हैं। यदि आप उनके बारे में कुछ और तरीके से सोचते हैं, तो आपके पास समझाने के लिए अजीब विशेष मामलों का एक समूह है। यदि आप उन्हें अपरिवर्तनीय मानते हैं, तो कोई विशेष मामले नहीं हैं।
S.Lott

@ एस.लॉट: भले ही हुड के नीचे क्या हो रहा है, गुइडो वैन रोसुम ने पायथन को डिजाइन करने में बहुत प्रयास किया ताकि प्रोग्रामर संख्याओं को सिर्फ ... संख्याओं के रूप में बता सके।
पितरौ

1
@JF, संदर्भ की प्रतिलिपि बनाई गई है।
18

7

मैं भ्रम को कम करने के लिए चर का नाम बदल दूंगा। n -> एनएफ या Nmainx -> xf या xmain :

def f(nf, xf):
    nf = 2
    xf.append(4)
    print 'In f():', nf, xf

def main():
    nmain = 1
    xmain = [0,1,2,3]
    print 'Before:', nmain, xmain
    f(nmain, xmain)
    print 'After: ', nmain, xmain

main()

जब आप फ़ंक्शन को f कहते हैं , तो पायथन रनटाइम xmain की एक प्रति बनाता है और इसे xf को असाइन करता है , और इसी तरह nmain की एक कॉपी nf को असाइन करता है ।

N के मामले में , कॉपी किया गया मान 1 है।

के मामले में एक्स मूल्य की नकल की जाती है नहीं शाब्दिक सूची [0, 1, 2, 3] । यह उस सूची का संदर्भ है। xf और xmain एक ही सूची में इंगित कर रहे हैं, इसलिए जब आप xf को संशोधित करते हैं तो आप xmain को संशोधित भी कर रहे हैं ।

यदि, हालांकि, आप कुछ लिखना चाहते थे:

    xf = ["foo", "bar"]
    xf.append(4)

आप पाएंगे कि xmain नहीं बदला है। ऐसा इसलिए है, क्योंकि लाइन में xf = ["foo", "bar"] आपके पास एक नई सूची को इंगित करने के लिए xf है । इस नई सूची में आपके द्वारा किए गए किसी भी बदलाव का उस सूची पर कोई प्रभाव नहीं पड़ेगा जो अभी भी xmain को इंगित करता है।

उम्मीद है की वो मदद करदे। :-)


2
"N के मामले में, जो मान कॉपी किया गया है ..." - यह गलत है, यहां कोई नकल नहीं की जाती है (जब तक कि आप संदर्भों की गिनती नहीं करते हैं)। इसके बजाय, अजगर 'नाम' का उपयोग करता है जो वास्तविक वस्तुओं की ओर इशारा करता है। nf और xf nf = 2का नाम nmain और xmain तक है , जब तक कि नाम nfको इंगित करने के लिए परिवर्तित नहीं किया जाता है 2। संख्याएँ अपरिवर्तनीय हैं, सूचियाँ परस्पर हैं।
केसी कुबैल

2

यह एक सूची है क्योंकि एक परिवर्तनशील वस्तु है। आप [0,1,2,3] के मूल्य पर x सेट नहीं कर रहे हैं, आप ऑब्जेक्ट पर एक लेबल को परिभाषित कर रहे हैं [0,1,2,3]।

आपको अपने फंक्शन की घोषणा इस तरह से करनी चाहिए:

def f(n, x=None):
    if x is None:
        x = []
    ...

3
इसका उत्परिवर्तन से कोई लेना-देना नहीं है। यदि आप x = x + [4]इसके बजाय करते हैं x.append(4), तो आपको कॉल करने वाले में कोई परिवर्तन नहीं दिखाई देगा, हालांकि एक सूची परिवर्तनशील है। यह वास्तव में उत्परिवर्तित है, तो इसके साथ क्या करना है।
glglgl

1
OTOH, यदि आप करते हैं x += [4]तो xउत्परिवर्तित होता है, ठीक उसी तरह जैसे कि क्या होता है x.append(4), इसलिए कॉल करने वाले को परिवर्तन दिखाई देगा।
PM 2Ring

2

n एक इंट (अपरिवर्तनीय) है, और एक कॉपी फंक्शन में जाती है, इसलिए फंक्शन में आप कॉपी को बदल रहे हैं।

X एक सूची (म्यूटेबल) है, और पॉइंटर की एक प्रति को पास कर दिया जाता है ताकि फ़ंक्शन x.append (4) सूची की सामग्री को बदल दे। हालाँकि, आपने अपने कार्य में x = [0,1,2,3,4] कहा था, आप x की सामग्री को मुख्य () में नहीं बदलेंगे।


3
"सूचक की प्रति" को देखें। दोनों स्थानों पर वस्तुओं के संदर्भ मिलते हैं। n एक अपरिवर्तनीय वस्तु का संदर्भ है; x एक उत्परिवर्तित वस्तु का संदर्भ है।
S.Lott

2

यदि फ़ंक्शन पूरी तरह से अलग-अलग चर के साथ फिर से लिखे गए हैं और हम उन पर आईडी कहते हैं, तो यह बिंदु को अच्छी तरह से दिखाता है। मुझे यह पहली बार नहीं मिला और महान विवरण के साथ jfs की पोस्ट पढ़ी , इसलिए मैंने खुद को समझने / समझाने की कोशिश की:

def f(y, z):
    y = 2
    z.append(4)
    print ('In f():             ', id(y), id(z))

def main():
    n = 1
    x = [0,1,2,3]
    print ('Before in main:', n, x,id(n),id(x))
    f(n, x)
    print ('After in main:', n, x,id(n),id(x))

main()
Before in main: 1 [0, 1, 2, 3]   94635800628352 139808499830024
In f():                          94635800628384 139808499830024
After in main: 1 [0, 1, 2, 3, 4] 94635800628352 139808499830024

z और x में एक ही id है। जैसा कि लेख कहता है, उसी अंतर्निहित संरचना के लिए बस अलग-अलग टैग।


0

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

इन दो कार्यों के विपरीत

def foo(x):
    x[0] = 5

def goo(x):
    x = []

अब, जब आप शेल में टाइप करेंगे

>>> cow = [3,4,5]
>>> foo(cow)
>>> cow
[5,4,5]

इसकी तुलना गू से करें।

>>> cow = [3,4,5]
>>> goo(cow)
>>> goo
[3,4,5]

पहले मामले में, हम गाय के पते की नकल करने के लिए एक प्रति पास करते हैं ताकि वहां रहने वाली वस्तु की स्थिति को संशोधित किया जा सके। वस्तु संशोधित हो जाती है।

दूसरे मामले में आप गाय के पते की एक प्रति गुओ को देते हैं। तब goo उस प्रति को बदलने के लिए आगे बढ़ता है। प्रभाव: कोई नहीं।

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

स्पष्टीकरण बहुत सारे भ्रम को समाप्त करता है। पायथन मूल्य द्वारा पतों के वैरिएबल स्टोर को पास करता है।


पॉइंटर वैल्यू से एक शुद्ध पास संदर्भ द्वारा पास से बहुत अलग नहीं है यदि आप इसके बारे में सही तरीके से सोचते हैं ...
गैलीनेट

गू को देखो। क्या आप संदर्भ से शुद्ध पास हैं, तो इसने अपना तर्क बदल दिया होगा। नहीं, पायथन एक शुद्ध पास-बाय-संदर्भ भाषा नहीं है। यह मान द्वारा संदर्भों को पास करता है।
ncmathsadist

0

पायथन को संदर्भ के मूल्य से कॉपी किया जाता है। एक ऑब्जेक्ट मेमोरी में एक फ़ील्ड पर कब्जा कर लेता है, और उस ऑब्जेक्ट के साथ एक संदर्भ जुड़ा होता है, लेकिन खुद मेमोरी में एक फ़ील्ड पर कब्जा कर लेता है। और नाम / मान एक संदर्भ के साथ जुड़ा हुआ है। अजगर फ़ंक्शन में, यह हमेशा संदर्भ के मूल्य की प्रतिलिपि बनाता है, इसलिए आपके कोड में, n को एक नया नाम होने के लिए कॉपी किया जाता है, जब आप इसे असाइन करते हैं, तो कॉलर स्टैक में एक नया स्थान होता है। लेकिन सूची के लिए, नाम भी कॉपी किया गया था, लेकिन यह उसी मेमोरी का उल्लेख करता है (चूंकि आप सूची को कभी भी नया मान नहीं देते हैं)। यह अजगर में एक जादू है!


0

मेरी सामान्य समझ यह है कि किसी भी वस्तु चर (जैसे कि एक सूची या एक तानाशाह, दूसरों के बीच) को उसके कार्यों के माध्यम से संशोधित किया जा सकता है। मेरा मानना ​​है कि आप ऐसा करने में सक्षम नहीं हैं - पैरामीटर को फिर से असाइन करें - यानी, इसे कॉल करने योग्य फ़ंक्शन के भीतर संदर्भ द्वारा असाइन करें।

यह कई अन्य भाषाओं के अनुरूप है।

यह कैसे काम करता है यह देखने के लिए निम्न स्क्रिप्ट चलाएँ:

def func1(x, l1):
    x = 5
    l1.append("nonsense")

y = 10
list1 = ["meaning"]
func1(y, list1)
print(y)
print(list1)

-3

मैंने अपने उत्तर टन को संशोधित किया था और महसूस किया कि मुझे कुछ भी कहने की ज़रूरत नहीं है, अजगर ने खुद को पहले ही समझाया था।

a = 'string'
a.replace('t', '_')
print(a)
>>> 'string'

a = a.replace('t', '_')
print(a)
>>> 's_ring'

b = 100
b + 1
print(b)
>>> 100

b = b + 1
print(b)
>>> 101

def test_id(arg):
    c = id(arg)
    arg = 123
    d = id(arg)
    return

a = 'test ids'
b = id(a)
test_id(a)
e = id(a)

# b = c  = e != d
# this function do change original value
del change_like_mutable(arg):
    arg.append(1)
    arg.insert(0, 9)
    arg.remove(2)
    return

test_1 = [1, 2, 3]
change_like_mutable(test_1)



# this function doesn't 
def wont_change_like_str(arg):
     arg = [1, 2, 3]
     return


test_2 = [1, 1, 1]
wont_change_like_str(test_2)
print("Doesn't change like a imutable", test_2)

यह शैतान संदर्भ / मूल्य / उत्परिवर्तनीय या नहीं / उदाहरण, नाम स्थान या चर / सूची या str नहीं है, IT SYNTAX, EQUAL SIGN है।

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