"कम से कम विस्मय" और म्यूटेबल डिफ़ॉल्ट तर्क


2593

पायथन के साथ लंबे समय से छेड़छाड़ करने वाले किसी भी व्यक्ति को निम्नलिखित मुद्दे से काट दिया गया है (या टुकड़े टुकड़े कर दिया गया है):

def foo(a=[]):
    a.append(5)
    return a

पायथन नौसिखियों को इस फ़ंक्शन से हमेशा केवल एक तत्व के साथ एक सूची वापस करने की उम्मीद होगी [5]:। परिणाम इसके बजाय बहुत अलग है, और बहुत आश्चर्यजनक है (एक नौसिखिए के लिए):

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()

मेरे एक प्रबंधक ने एक बार इस विशेषता के साथ अपनी पहली मुठभेड़ की थी, और इसे भाषा का "नाटकीय डिजाइन दोष" कहा। मैंने जवाब दिया कि व्यवहार में एक अंतर्निहित स्पष्टीकरण था, और यह वास्तव में बहुत ही हैरान और अप्रत्याशित है यदि आप आंतरिक को नहीं समझते हैं। हालाँकि, मैं निम्नलिखित प्रश्न का उत्तर (स्वयं को) देने में सक्षम नहीं था: फ़ंक्शन परिभाषा में डिफ़ॉल्ट तर्क को बांधने का क्या कारण है, और फ़ंक्शन निष्पादन पर नहीं? मुझे संदेह है कि अनुभवी व्यवहार का एक व्यावहारिक उपयोग है (जो वास्तव में सी में स्थैतिक चर का उपयोग करते हैं, बग प्रजनन के बिना?)

संपादित करें :

बेज़ेक ने एक दिलचस्प उदाहरण बनाया। आपकी अधिकांश टिप्पणियों और साथ में विशेष रूप से उतल की, मैंने आगे विस्तार से बताया:

>>> def a():
...     print("a executed")
...     return []
... 
>>>            
>>> def b(x=a()):
...     x.append(5)
...     print(x)
... 
a executed
>>> b()
[5]
>>> b()
[5, 5]

मेरे लिए, ऐसा लगता है कि डिजाइन का निर्णय मानकों के दायरे के सापेक्ष था: फ़ंक्शन के अंदर या इसके साथ "एक साथ"?

फ़ंक्शन के अंदर बाइंडिंग करने का मतलब यह होगा कि xफ़ंक्शन को कॉल किए जाने पर निर्दिष्ट डिफ़ॉल्ट पर प्रभावी रूप से बाध्य है, परिभाषित नहीं है, ऐसा कुछ जो एक गहरी खामी पेश करेगा: defलाइन इस अर्थ में होगी कि बाध्यकारी का हिस्सा फ़ंक्शन ऑब्जेक्ट) परिभाषा, और फ़ंक्शन इनवोकेशन समय पर डिफ़ॉल्ट पैरामीटर (असाइनमेंट) का हिस्सा होगा।

वास्तविक व्यवहार अधिक सुसंगत है: उस रेखा के निष्पादित होने पर उस पंक्ति की हर चीज का मूल्यांकन किया जाता है, जिसका अर्थ फ़ंक्शन परिभाषा है।



4
मुझे संदेह नहीं है कि परस्पर तर्क एक औसत व्यक्ति के लिए कम से कम विस्मयकारी सिद्धांत का उल्लंघन करते हैं, और मैंने शुरुआती लोगों को वहां कदम रखते देखा है, फिर मेलिंग सूचियों को मेलिंग ट्यूपल्स के साथ बदल दिया है। फिर भी परस्पर तर्क अभी भी पायथन ज़ेन (पीईपी 20) के अनुरूप हैं और "डच के लिए स्पष्ट" (हार्ड कोर पायथन प्रोग्रामर द्वारा समझा / शोषित) खंड में गिर जाता है। डॉक्टर स्ट्रिंग के साथ अनुशंसित वर्कअराउंड सबसे अच्छा है, फिर भी डॉक्टर स्ट्रिंग और किसी भी (लिखित) डॉक्स के लिए प्रतिरोध इतना असामान्य नहीं है। व्यक्तिगत रूप से, मैं एक डेकोरेटर (@fixed_defaults) कहना पसंद करूंगा।
सर्ग

5
जब मैं इसके पार आता हूं तो मेरा तर्क है: "आपको एक ऐसा फ़ंक्शन बनाने की आवश्यकता क्यों है जो एक उत्परिवर्ती लौटाता है जो वैकल्पिक रूप से एक ऐसा परिवर्तनशील हो सकता है जिसे आप फ़ंक्शन में पास करेंगे? या तो यह एक परिवर्तनशील बनाता है या एक नया बनाता है। आपको इसकी आवश्यकता क्यों है? एक फ़ंक्शन के साथ दोनों करने के लिए? और दुभाषिया को आपको अपने कोड में तीन पंक्तियों को जोड़ने के बिना ऐसा करने की अनुमति देने के लिए फिर से क्यों लिखा जाना चाहिए? " क्योंकि हम जिस तरह से इंटरप्रेटर फ़ंक्शन की परिभाषा और निकासी को संभालते हैं, उसके बारे में बात कर रहे हैं। यह एक बहुत ही आवश्यक उपयोग के मामले के लिए बहुत कुछ है।
एलन लेथर्ड

12
"पायथन नौसिखियों को इस फ़ंक्शन से हमेशा केवल एक तत्व के साथ एक सूची वापस करने की उम्मीद होगी: [5]"। मैं एक अजगर नौसिखिया हूँ, और मैं यह उम्मीद नहीं foo([1])करूँगा , क्योंकि जाहिर है [1, 5], वापस आ जाएगा , नहीं [5]। आपके कहने का मतलब यह है कि एक नौसिखिए से यह अपेक्षा की जाती है कि बिना किसी पैरामीटर के बुलाए गए फ़ंक्शन हमेशा वापस आएंगे [5]
सहानुभूतिपूर्ण

2
यह प्रश्न पूछता है कि "ऐसा [गलत तरीके से] क्यों लागू किया गया?" यह नहीं पूछता कि "सही तरीका क्या है?" , जो [ क्यों arg = कोई नहीं का उपयोग करके कवर किया गया है) पायथन के परस्पर डिफ़ॉल्ट तर्क समस्या को ठीक नहीं करता है? ] * ( stackoverflow.com/questions/10676729/… )। नए उपयोगकर्ता लगभग हमेशा पूर्व में कम और बाद में बहुत अधिक रुचि रखते हैं, इसलिए यह कभी-कभी बहुत ही उपयोगी लिंक / डोप का हवाला देता है।
smci

जवाबों:


1612

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

जैसे ही आप इस तरह से सोचते हैं, तो यह पूरी तरह से समझ में आता है: एक फ़ंक्शन एक ऐसी वस्तु है जिसका मूल्यांकन इसकी परिभाषा पर किया जा रहा है; डिफ़ॉल्ट पैरामीटर "सदस्य डेटा" की तरह हैं और इसलिए उनका राज्य एक कॉल से दूसरे कॉल में बदल सकता है - बिल्कुल किसी अन्य ऑब्जेक्ट की तरह।

किसी भी स्थिति में, पायथन में डिफ़ॉल्ट पैरामीटर मानों में इस व्यवहार के कारणों का एक बहुत अच्छा विवरण है ।
मुझे यह बहुत स्पष्ट लगा, और मैं वास्तव में इसे एक बेहतर ज्ञान के लिए पढ़ने का सुझाव देता हूं कि फ़ंक्शन ऑब्जेक्ट कैसे काम करते हैं।


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

85
यहां तक ​​कि अगर यह प्रथम श्रेणी की वस्तु है, तब भी कोई एक डिजाइन की कल्पना कर सकता है जहां प्रत्येक डिफ़ॉल्ट मान के लिए कोड को ऑब्जेक्ट के साथ संग्रहित किया जाता है और प्रत्येक बार फ़ंक्शन को पुन: मूल्यांकन किया जाता है। मैं यह नहीं कह रहा हूं कि यह बेहतर होगा, बस यह कि फ़र्स्ट-क्लास ऑब्जेक्ट होने के कारण यह पूरी तरह से काम नहीं करता है।
गेरिट

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

192
डिजाइन दोष है या नहीं, आपका जवाब यह लगता है कि यह व्यवहार किसी तरह से आवश्यक है, स्वाभाविक और स्पष्ट है कि कार्य प्रथम श्रेणी की वस्तुएं हैं, और यह केवल मामला नहीं है। पाइथन के पास क्लोजर हैं। यदि आप फ़ंक्शन की पहली पंक्ति पर असाइनमेंट के साथ डिफ़ॉल्ट तर्क को प्रतिस्थापित करते हैं, तो यह प्रत्येक कॉल की अभिव्यक्ति का मूल्यांकन करता है (संभावित रूप से एक एन्कोडिंग स्कोप में घोषित नामों का उपयोग करके)। इस बात का कोई कारण नहीं है कि हर बार फ़ंक्शन को ठीक उसी तरह से कॉल करने के लिए डिफ़ॉल्ट तर्क का मूल्यांकन करना संभव या उचित नहीं होगा।
मार्क अमेरी

24
डिजाइन सीधे से पालन नहीं करता है functions are objects। आपके प्रतिमान में, प्रस्ताव गुण के बजाय गुणों के रूप में फ़ंक्शन के डिफ़ॉल्ट मानों को लागू करना होगा।
bukzor

273

मान लें कि आपके पास निम्नलिखित कोड है

fruits = ("apples", "bananas", "loganberries")

def eat(food=fruits):
    ...

जब मैं खाने की घोषणा देखता हूं, तो सबसे कम हैरान करने वाली बात यह है कि यदि पहला पैरामीटर नहीं दिया गया है, तो यह टपल के बराबर होगा ("apples", "bananas", "loganberries")

हालांकि, बाद में कोड में माना जाता है, मैं कुछ ऐसा करता हूं

def some_random_function():
    global fruits
    fruits = ("blueberries", "mangos")

तब यदि डिफॉल्ट डिक्लेरेशन के बजाए डिफॉल्ट पैरामीटर फंक्शन एक्जीक्यूशन में बंधे होते हैं तो मुझे आश्चर्य होगा (बहुत बुरे तरीके से) कि फलों को बदला गया था। यह पता लगाने की तुलना में अधिक आश्चर्यजनक IMO होगा कि fooऊपर आपका फ़ंक्शन सूची को म्यूट कर रहा था।

असली समस्या उत्परिवर्तनीय चर के साथ है, और सभी भाषाओं में कुछ हद तक यह समस्या है। यहाँ एक प्रश्न है: मान लीजिए जावा में मेरे पास निम्नलिखित कोड हैं:

StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) );  // does this work?

अब, क्या मेरा नक्शा StringBufferकुंजी के मूल्य का उपयोग करता है जब इसे मानचित्र में रखा गया था, या क्या यह संदर्भ द्वारा कुंजी को संग्रहीत करता है? किसी भी तरह, किसी को आश्चर्य होता है; या तो वह व्यक्ति जिसने किसी वस्तु को उस Mapमूल्य के उपयोग से बाहर निकालने की कोशिश की है, जिसके समान वे इसे अंदर रखते हैं, या वह व्यक्ति जो अपनी वस्तु को प्राप्त नहीं कर सकता है, भले ही वे जिस कुंजी का उपयोग कर रहे हों वह वस्तुतः एक ही वस्तु हो। इसका उपयोग इसे मानचित्र में डालने के लिए किया गया था (यह वास्तव में यही कारण है कि पायथन अपने परस्पर निर्मित डेटा प्रकारों को शब्दकोश कुंजियों के रूप में उपयोग करने की अनुमति नहीं देता है)।

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

मैं व्यक्तिगत रूप से पायथन के वर्तमान दृष्टिकोण को पसंद करता हूं: फ़ंक्शन को परिभाषित करते समय डिफ़ॉल्ट फ़ंक्शन तर्क का मूल्यांकन किया जाता है और वह ऑब्जेक्ट हमेशा डिफ़ॉल्ट होता है। मुझे लगता है कि वे एक खाली सूची का उपयोग करके विशेष-मामला कर सकते हैं, लेकिन उस तरह के विशेष आवरण के कारण और भी अधिक विस्मय होगा, जिसका उल्लेख पीछे की ओर असंगत नहीं होना चाहिए।


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

47
दरअसल, मुझे नहीं लगता कि मैं आपके पहले उदाहरण से सहमत हूं। मुझे यकीन नहीं है कि मुझे पहली बार में एक इनिशियलाइज़र को संशोधित करने का विचार पसंद है, लेकिन अगर मैंने किया, तो मुझे उम्मीद है कि आप इसका ठीक उसी तरह से व्यवहार करेंगे जैसा आप वर्णन करते हैं - डिफ़ॉल्ट मान को बदलना ("blueberries", "mangos")
बेन ब्लैंक

12
डिफ़ॉल्ट पैरामीटर है किसी भी अन्य मामले की तरह। यह अप्रत्याशित है कि पैरामीटर एक वैश्विक चर है, और स्थानीय नहीं है। जो बदले में है क्योंकि कोड फ़ंक्शन परिभाषा पर निष्पादित किया जाता है, कॉल नहीं। एक बार जब आप इसे प्राप्त कर लेते हैं, और यह समान कक्षाओं के लिए चला जाता है, तो यह पूरी तरह से स्पष्ट है।
लेनार्ट रेगेब्र

17
मैं शानदार के बजाय भ्रामक उदाहरण खोजता हूं। यदि इसे असाइन some_random_function()करने के fruitsबजाय जोड़ा जाता है, तो व्यवहार बदल eat() जाएगा । वर्तमान अद्भुत डिजाइन के लिए बहुत कुछ। यदि आप एक डिफ़ॉल्ट तर्क का उपयोग करते हैं जो कहीं और संदर्भित है और फिर फ़ंक्शन के बाहर से संदर्भ को संशोधित करें, तो आप परेशानी पूछ रहे हैं। वास्तविक डब्ल्यूटीएफ तब होता है जब लोग एक ताजा डिफ़ॉल्ट तर्क (एक सूची शाब्दिक या एक निर्माता के लिए एक कॉल) को परिभाषित करते हैं, और फिर भी थोड़ा सा प्राप्त करते हैं।
एलेक्सिस

13
आपने स्पष्ट रूप globalसे टपल की घोषणा की और आश्वस्त किया - अगर इसके eatबाद अलग तरीके से काम किया जाए तो इसमें कुछ भी आश्चर्य की बात नहीं है।
user3467349

241

प्रलेखन का प्रासंगिक हिस्सा :

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

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin

180
वाक्यांश "यह आम तौर पर वह नहीं होता जो इरादा था" और "इसके आसपास एक तरीका है" गंध है जैसे वे एक डिजाइन दोष का दस्तावेजीकरण कर रहे हैं।
bukzor

4
@ मैथ्यू: मैं अच्छी तरह से जानता हूं, लेकिन यह नुकसान के लायक नहीं है। आप आमतौर पर स्टाइल गाइड और लिंटर को बिना शर्त के फ्लैग करने योग्य डिफ़ॉल्ट मानों को इस कारण से गलत देखेंगे। एक ही काम करने का स्पष्ट तरीका फ़ंक्शन ( function.data = []) या अभी तक बेहतर पर एक विशेषता को सामान करना है, एक वस्तु बनाएं।
bukzor

6
@bukzor: नुकसान पर ध्यान दिया जाना चाहिए और प्रलेखित किया जाना चाहिए, यही वजह है कि यह सवाल अच्छा है और इसने बहुत सारे बदलाव प्राप्त किए हैं। उसी समय, जरूरी नहीं कि नुकसान को दूर किया जाए। कितने पायथन शुरुआती ने एक समारोह में एक सूची पारित की है जो इसे संशोधित करती है, और मूल चर में परिवर्तन दिखाने के लिए हैरान थे? फिर भी परिवर्तनशील वस्तु प्रकार अद्भुत हैं, जब आप समझते हैं कि उनका उपयोग कैसे किया जाए। मुझे लगता है कि यह सिर्फ इस विशेष नुकसान पर राय के लिए उबलता है।
मैथ्यू

33
वाक्यांश "यह आम तौर पर वह नहीं होता है जिसका उद्देश्य था" का अर्थ है "वह नहीं जो प्रोग्रामर वास्तव में होना चाहता था," न कि "पायथन को क्या करना चाहिए।"
होल्डनवेब

4
@holdenweb वाह, मैं पार्टी के लिए मेगा लेट हूं। संदर्भ को देखते हुए, bukzor पूरी तरह से सही है: वे व्यवहार / परिणाम का दस्तावेजीकरण कर रहे हैं जो "इरादा" नहीं था जब उन्होंने फैसला किया कि उन्हें भाषा की परिभाषा को निष्पादित करना चाहिए। चूंकि यह उनकी डिजाइन पसंद का एक अनपेक्षित परिणाम है, यह एक डिजाइन दोष है। यदि यह एक डिजाइन दोष नहीं था, तो "इस के आसपास एक रास्ता" की पेशकश करने की भी आवश्यकता नहीं होगी।
कोड_ड्रेड

118

मुझे पता है कि पायथन दुभाषिया आंतरिक कामकाज के बारे में कुछ भी नहीं है (और मैं कंपाइलर और दुभाषियों में विशेषज्ञ नहीं हूं) तो मुझे दोष न दें अगर मैं किसी भी तरह की असंगत या असंभव बात का प्रस्ताव देता हूं।

बशर्ते कि अजगर की वस्तुएं आपस में जुड़ी हों, मुझे लगता है कि डिफ़ॉल्ट तर्क सामान को डिजाइन करते समय इस पर ध्यान दिया जाना चाहिए। जब आप किसी सूची को त्वरित करते हैं:

a = []

आप द्वारा संदर्भित एक नई सूची प्राप्त करने की उम्मीद करते हैं a

क्यों चाहिए a=[]में

def x(a=[]):

फंक्शन डेफिनेशन पर नई लिस्ट को इंस्टाल करें और इनवोकेशन पर नहीं? यह वैसा ही है जैसे आप पूछ रहे हैं "यदि उपयोगकर्ता तर्क प्रदान नहीं करता है, तो एक नई सूची को तुरंत टाइप करें और इसका उपयोग करें जैसे कि यह कॉलर द्वारा निर्मित किया गया था"। मुझे लगता है कि इसके बजाय यह अस्पष्ट है:

def x(a=datetime.datetime.now()):

aजब आप परिभाषित या निष्पादित कर रहे होते हैं, तब क्या आप डेटटाइम के अनुरूप डिफ़ॉल्ट करना चाहते हैं x? इस मामले में, पिछले एक के रूप में, मैं उसी व्यवहार को रखूंगा जैसे कि डिफ़ॉल्ट तर्क "असाइनमेंट" फ़ंक्शन का पहला निर्देश था ( datetime.now()फ़ंक्शन इन्वोकेशन पर कहा जाता है)। दूसरी ओर, यदि उपयोगकर्ता परिभाषा-समय मानचित्रण चाहता है, तो वह लिख सकता है:

b = datetime.datetime.now()
def x(a=b):

मुझे पता है, मुझे पता है: यह एक बंद है। वैकल्पिक रूप से पायथन परिभाषा-बाध्यकारी समय को बाध्य करने के लिए एक कीवर्ड प्रदान कर सकता है:

def x(static a=b):

11
आप ऐसा कर सकते हैं: def x (a = कोई नहीं): और फिर, यदि कोई नहीं है, तो a = datetime.datetime.now ()
Anon

20
इसके लिए शुक्रिया। मैं वास्तव में अपनी उंगली नहीं डाल सकता था कि यह मुझे आखिर तक क्यों परेशान करता है। आपने इसे कम से कम फ़ज़ और भ्रम के साथ खूबसूरती से किया है। जैसा कि कोई व्यक्ति C ++ में सिस्टम प्रोग्रामिंग से आ रहा है और कभी-कभी भाषा की विशेषताओं का "अनुवाद" कर रहा है, इस झूठे दोस्त ने मुझे क्लास विशेषताओं की तरह, सिर के नरम समय में नरम तरीके से लात मारी। मैं समझता हूं कि चीजें इस तरह से क्यों हैं, लेकिन मैं इसमें मदद नहीं कर सकता लेकिन इसे नापसंद कर सकता हूं, चाहे कोई भी सकारात्मक हो। कम से कम यह मेरे अनुभव के विपरीत है, कि मैं शायद (उम्मीद है) इसे कभी नहीं
भूलूंगा

5
@ और लंबे समय तक पाइथन का उपयोग करने के बाद, आप यह देखना शुरू कर देते हैं कि पाइथन के लिए चीजों को व्याख्यायित करना कितना तर्कसंगत है क्योंकि क्लास इसकी विशेषता बताती है - यह केवल C ++ (और जावा, और) जैसी भाषाओं की विशेष सीमाओं और सीमाओं के कारण है। C # ...) कि यह class {}ब्लॉक की सामग्री के लिए किसी भी तरह से समझ में आता है उदाहरणों से संबंधित है :) लेकिन जब कक्षाएं प्रथम श्रेणी की वस्तुएं हैं, तो जाहिर है कि प्राकृतिक चीज उनकी सामग्री (स्मृति में) उनकी सामग्री को प्रतिबिंबित करने के लिए है (कोड में)।
कार्ल केनचेल

6
मेरी पुस्तक में सामान्य संरचना कोई विचित्रता या सीमा नहीं है। मुझे पता है कि यह अनाड़ी और बदसूरत हो सकता है, लेकिन आप इसे किसी चीज की "परिभाषा" कह सकते हैं। गतिशील भाषाएं मुझे अराजकतावादियों की तरह लगती हैं: निश्चित रूप से हर कोई स्वतंत्र है, लेकिन आपको किसी को कचरा खाली करने और सड़क को खाली करने के लिए संरचना की आवश्यकता है। मुझे लगता है कि मैं बूढ़ा हूँ ... :)
AndreasT

4
फ़ंक्शन परिभाषा को मॉड्यूल लोड समय पर निष्पादित किया जाता है। फ़ंक्शन बॉडी को फ़ंक्शन कॉल समय पर निष्पादित किया जाता है। डिफ़ॉल्ट तर्क फ़ंक्शन की परिभाषा का हिस्सा है, फ़ंक्शन बॉडी का नहीं। (यह नेस्टेड कार्यों के लिए अधिक जटिल हो जाता है।)
लूत्ज़ प्रीचेल

84

ठीक है, कारण काफी सरल है कि जब कोड निष्पादित किया जाता है, तो बाइंडिंग की जाती है, और फ़ंक्शन परिभाषा को निष्पादित किया जाता है, ठीक है ... जब फ़ंक्शन परिभाषित होता है।

इसकी तुलना करें:

class BananaBunch:
    bananas = []

    def addBanana(self, banana):
        self.bananas.append(banana)

यह कोड ठीक उसी अप्रत्याशित घटना से ग्रस्त है। केले एक वर्ग विशेषता है, और इसलिए, जब आप इसमें चीजें जोड़ते हैं, तो यह उस वर्ग के सभी उदाहरणों में जोड़ा जाता है। वजह बिल्कुल एक जैसी है।

यह सिर्फ "हाउ इट वर्क्स" है, और इसे फंक्शन के मामले में अलग तरीके से काम करना संभवत: जटिल होगा, और क्लास केस में असंभव होने की संभावना है, या कम से कम ऑब्जेक्ट इंस्टेंशन को बहुत धीमा कर देगा, क्योंकि आपको क्लास कोड को आसपास रखना होगा। और वस्तुओं का निर्माण होने पर इसे निष्पादित करें।

हां, यह अप्रत्याशित है। लेकिन एक बार पैसा डूबने के बाद, यह पूरी तरह से फिट बैठता है कि अजगर सामान्य रूप से कैसे काम करता है। वास्तव में, यह एक अच्छा शिक्षण सहायता है, और एक बार जब आप समझते हैं कि ऐसा क्यों होता है, तो आप अजगर को बेहतर तरीके से समझेंगे।

यह कहा कि यह किसी भी अच्छे पायथन ट्यूटोरियल में प्रमुखता से होना चाहिए। क्योंकि जैसा कि आप उल्लेख करते हैं, हर कोई इस समस्या में जल्द या बाद में भागता है।


आप एक वर्ग विशेषता को कैसे परिभाषित करते हैं जो एक वर्ग के प्रत्येक उदाहरण के लिए अलग है?
कियवेली

19
यदि यह प्रत्येक उदाहरण के लिए अलग है तो यह एक वर्गीय विशेषता नहीं है। क्लास की विशेषताएँ क्लास पर विशेषता हैं। इसलिए यह नाम। इसलिए वे सभी उदाहरणों के लिए समान हैं।
लेनार्ट रेगेब्र जूल

1
आप एक वर्ग में एक विशेषता को कैसे परिभाषित करते हैं जो एक वर्ग के प्रत्येक उदाहरण के लिए अलग है? (उन लोगों के लिए फिर से परिभाषित किया गया है जो यह निर्धारित नहीं कर सकते थे कि पायथन के नामकरण से परिचित नहीं एक व्यक्ति वर्ग के सामान्य सदस्य चर के बारे में पूछ सकता है)।
किवेली जू

@Kievieli: आप एक वर्ग के सामान्य सदस्य चर के बारे में बात कर रहे हैं। :-) आप किसी भी विधि में self.attribute = value कहकर उदाहरण विशेषताओं को परिभाषित करते हैं। उदाहरण के लिए __init __ ()।
लेन्आर्ट रेगेब्र जूल 16'09

@Kieveli: दो उत्तर: आप नहीं कर सकते, क्योंकि कोई भी चीज जिसे आप एक वर्ग स्तर पर परिभाषित करते हैं, वह एक वर्गीय विशेषता होगी, और कोई भी उदाहरण जो उस विशेषता तक पहुंचता है, उसी वर्ग के गुण को एक्सेस करेगा; आप, propertyएस का उपयोग करके / की तरह कर सकते हैं - जो वास्तव में वर्ग स्तर के कार्य हैं जो सामान्य विशेषताओं की तरह काम करते हैं लेकिन वर्ग के बजाय उदाहरण में विशेषता को बचाते हैं ( self.attribute = valueलेन्नर्ट के रूप में उपयोग करके )।
एतान फुरमान

66

आप आत्मनिरीक्षण क्यों नहीं करते?

मैं वास्तव में आश्चर्यचकित हूं कि किसी ने भी कॉलबल्स पर पायथन ( 2और 3लागू) द्वारा पेश किए गए आनंददायक आत्मनिरीक्षण का प्रदर्शन नहीं किया है ।

दिए गए एक साधारण छोटे कार्य को funcनिम्न प्रकार से दिया गया है:

>>> def func(a = []):
...    a.append(5)

जब पायथन इसका सामना करता है, तो पहली चीज जो यह करेगी वह codeइस फ़ंक्शन के लिए एक ऑब्जेक्ट बनाने के लिए इसे संकलित करता है। जब यह संकलन कदम किया जाता है, तो पायथन * का मूल्यांकन करता है और फिर फ़ंक्शन ऑब्जेक्ट में डिफ़ॉल्ट तर्क (यहां एक खाली सूची ) संग्रहीत करता []है । जैसा कि शीर्ष उत्तर में उल्लेख किया गया है: सूची aको अब फ़ंक्शन का सदस्य माना जा सकता है func

तो, चलो कुछ आत्मनिरीक्षण करते हैं, पहले और बाद में यह जांचने के लिए कि फ़ंक्शन ऑब्जेक्ट के अंदर सूची का विस्तार कैसे होता है। मैं इसके लिए उपयोग कर रहा हूँ Python 3.x, Python 2 के लिए समान लागू होता है (उपयोग __defaults__या func_defaultsPython 2 में; हाँ, एक ही चीज़ के लिए दो नाम)।

निष्पादन से पहले समारोह:

>>> def func(a = []):
...     a.append(5)
...     

पायथन इस परिभाषा को निष्पादित करने के बाद इसे निर्दिष्ट ( a = []यहां) कोई भी डिफ़ॉल्ट पैरामीटर लेगा और उन्हें __defaults__फ़ंक्शन ऑब्जेक्ट (प्रासंगिक अनुभाग: कॉलबल्स) के लिए विशेषता में क्रैम करेगा :

>>> func.__defaults__
([],)

ठीक है, तो एक खाली सूची में एकल प्रविष्टि के रूप में __defaults__, जैसा कि अपेक्षित था।

निष्पादन के बाद कार्य:

चलिए अब इस फंक्शन को अंजाम देते हैं:

>>> func()

अब, __defaults__फिर से देखते हैं :

>>> func.__defaults__
([5],)

आश्चर्यचकित? वस्तु के अंदर का मूल्य बदल जाता है! फ़ंक्शन के लिए लगातार कॉल अब बस उस एम्बेडेड listऑब्जेक्ट के लिए संलग्न होंगे :

>>> func(); func(); func()
>>> func.__defaults__
([5, 5, 5, 5],)

इसलिए, आपके पास यह है, यही कारण है कि यह 'दोष' होता है, क्योंकि डिफ़ॉल्ट तर्क फ़ंक्शन ऑब्जेक्ट का हिस्सा हैं। यहाँ कुछ भी अजीब नहीं चल रहा है, यह सब थोड़ा आश्चर्यजनक है।

इसका मुकाबला करने के लिए सामान्य समाधान Noneडिफ़ॉल्ट के रूप में उपयोग करना और फिर फ़ंक्शन बॉडी में इनिशियलाइज़ करना है:

def func(a = None):
    # or: a = [] if a is None else a
    if a is None:
        a = []

चूंकि फ़ंक्शन बॉडी को हर बार नए सिरे से निष्पादित किया जाता है, इसलिए आपको हमेशा एक नई नई खाली सूची मिलती है यदि कोई तर्क पारित नहीं किया गया था a


यह सत्यापित करने के लिए कि सूची में __defaults__वही है जो फ़ंक्शन में उपयोग किया गया है, funcआप फ़ंक्शन बॉडी के अंदर उपयोग idकी गई सूची को वापस करने के लिए अपने फ़ंक्शन को बदल सकते हैं a। फिर, इसे सूची में __defaults__(स्थिति [0]में __defaults__) से तुलना करें और आप देखेंगे कि ये वास्तव में एक ही सूची उदाहरण के लिए कैसे संदर्भित हैं:

>>> def func(a = []): 
...     a.append(5)
...     return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True

आत्मनिरीक्षण की शक्ति के साथ सभी!


* यह सत्यापित करने के लिए कि पायथन फ़ंक्शन के संकलन के दौरान डिफ़ॉल्ट तर्कों का मूल्यांकन करता है, निम्नलिखित को निष्पादित करने का प्रयास करें:

def bar(a=input('Did you just see me without calling the function?')): 
    pass  # use raw_input in Py2

जैसा कि आप देखेंगे, input()फ़ंक्शन के निर्माण की प्रक्रिया से पहले इसे बुलाया जाता है और इसे नाम barसे बांध दिया जाता है।


1
क्या id(...)उस अंतिम सत्यापन की आवश्यकता है, या isऑपरेटर उसी प्रश्न का उत्तर देगा?
दास-जी

1
@ डीएएस-जी isबस ठीक करेगा, मैंने सिर्फ id(val)इसलिए इस्तेमाल किया क्योंकि मुझे लगता है कि यह अधिक सहज हो सकता है।
दिमित्रिस फासरकिस हिलियार्ड

Noneडिफ़ॉल्ट के रूप में उपयोग करना गंभीर रूप से __defaults__आत्मनिरीक्षण की उपयोगिता को सीमित करता है, इसलिए मुझे नहीं लगता कि यह __defaults__काम करने के तरीके के रूप में अच्छी तरह से काम करता है। आलसी-मूल्यांकन दोनों पक्षों से कार्य चूक को उपयोगी रखने के लिए अधिक करेगा।
ब्रिलिएंड

58

मैं सोचता था कि रनटाइम पर ऑब्जेक्ट्स बनाना बेहतर दृष्टिकोण होगा। मैं अब कम निश्चित हूं, क्योंकि आप कुछ उपयोगी सुविधाओं को खो देते हैं, हालांकि यह नौसिखिया भ्रम को रोकने के लिए केवल इसके लायक हो सकता है। ऐसा करने के नुकसान हैं:

1. प्रदर्शन

def foo(arg=something_expensive_to_compute())):
    ...

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

2. बाध्य मापदंडों को मजबूर करना

एक उपयोगी चाल के लिए एक लैम्ब्डा का बाँध मानकों करने के लिए है वर्तमान में एक चर के बंधन जब लैम्ब्डा बनाई गई है। उदाहरण के लिए:

funcs = [ lambda i=i: i for i in range(10)]

यह क्रमशः 0,1,2,3 ... लौटने वाले कार्यों की एक सूची देता है। यदि व्यवहार को बदल दिया जाता है, तो वे इसके बजाय i iके कॉल-टाइम मान से बाध्य होंगे , इसलिए आपको उन कार्यों की एक सूची मिलेगी, जो सभी लौट आए 9

इसे लागू करने का एकमात्र तरीका यह होगा कि आई बाउंड के साथ एक और क्लोजर बनाया जाए:

def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]

3. आत्मनिरीक्षण

कोड पर विचार करें:

def foo(a='test', b=100, c=[]):
   print a,b,c

हम inspectमॉड्यूल का उपयोग करके तर्क और चूक के बारे में जानकारी प्राप्त कर सकते हैं , जो

>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))

डॉक्यूमेंट जनरेशन, मेटाप्रोग्रामिंग, डेकोरेटर आदि जैसी चीजों के लिए यह जानकारी बहुत उपयोगी है।

अब, मान लीजिए कि चूक के व्यवहार को बदला जा सकता है ताकि यह बराबर हो:

_undefined = object()  # sentinel value

def foo(a=_undefined, b=_undefined, c=_undefined)
    if a is _undefined: a='test'
    if b is _undefined: b=100
    if c is _undefined: c=[]

हालाँकि, हम आत्मनिरीक्षण करने की क्षमता खो चुके हैं, और देखते हैं कि डिफ़ॉल्ट तर्क क्या हैं । क्योंकि वस्तुओं का निर्माण नहीं किया गया है, हम वास्तव में फ़ंक्शन को कॉल किए बिना कभी भी उन्हें पकड़ नहीं सकते हैं। सबसे अच्छा हम कर सकते हैं कि स्रोत कोड को स्टोर करें और इसे एक स्ट्रिंग के रूप में लौटाएं।


1
यदि आप प्रत्येक के लिए एक मान के बजाय डिफ़ॉल्ट तर्क बनाने के लिए कोई फ़ंक्शन था, तो भी आप आत्मनिरीक्षण प्राप्त कर सकते हैं। निरीक्षण मॉड्यूल सिर्फ उस फ़ंक्शन को कॉल करेगा।
यारचू

@SilentGhost: मैं बात कर रहा हूँ कि क्या व्यवहार को फिर से बनाने के लिए बदल दिया गया है - इसे एक बार बनाने के बाद वर्तमान व्यवहार होता है, और क्यों परिवर्तनशील डिफ़ॉल्ट समस्या मौजूद है।
ब्रायन

1
@ यायरचू: मान लेता है कि निर्माण सुरक्षित है (इसलिए इसका कोई दुष्प्रभाव नहीं है)। आर्गन्स का परिचय कुछ भी नहीं करना चाहिए , लेकिन मनमाने कोड का मूल्यांकन करने से एक प्रभाव समाप्त हो सकता है।
ब्रायन

1
एक अलग भाषा डिजाइन अक्सर चीजों को अलग तरह से लिखने का मतलब है। आपका पहला उदाहरण आसानी से लिखा जा सकता है: सस्ता = महंगा (); def foo (arg = _ सस्ती), यदि आप विशेष रूप से इसे दोबारा नहीं चाहते हैं।
ग्लेन मेनार्ड

@ ग्लेन - यही मैं "बाह्य रूप से चर कैश" के साथ उल्लेख कर रहा था - यह थोड़ा अधिक क्रिया है, और आप अपने नामस्थान में अतिरिक्त चर के साथ समाप्त होते हैं।
ब्रायन

55

पाइथन की रक्षा में 5 अंक

  1. सादगी : व्यवहार निम्नलिखित अर्थों में सरल है: अधिकांश लोग इस जाल में केवल एक बार गिरते हैं, कई बार नहीं।

  2. संगति : पायथन हमेशा वस्तुओं को पारित करता है, नामों को नहीं। डिफ़ॉल्ट पैरामीटर, जाहिर है, फ़ंक्शन हेडिंग का हिस्सा है (फ़ंक्शन बॉडी नहीं)। इसलिए इसे मॉड्यूल लोड समय पर मूल्यांकन किया जाना चाहिए (और केवल मॉड्यूल लोड समय पर, जब तक कि नेस्टेड न हो), फ़ंक्शन कॉल समय पर नहीं।

  3. उपयोगिता : जैसा कि फ्रेडरिक लुंड "पायथन में डिफ़ॉल्ट पैरामीटर मान" की अपनी व्याख्या में बताते हैं , वर्तमान व्यवहार उन्नत प्रोग्रामिंग के लिए काफी उपयोगी हो सकता है। (किफायत से इस्तेमाल करो।)

  4. पर्याप्त दस्तावेज : सबसे बुनियादी पायथन प्रलेखन में, ट्यूटोरियल, इस मुद्दे को जोर से "महत्वपूर्ण कार्यों को परिभाषित करने वाले खंड " के पहले उपधारा में "महत्वपूर्ण चेतावनी" के रूप में घोषित किया गया है । चेतावनी यहां तक ​​कि बोल्डफेस का उपयोग करती है, जो शायद ही कभी शीर्षकों के बाहर लागू होती है। RTFM: ठीक मैनुअल पढ़ें।

  5. मेटा-लर्निंग : जाल में गिरना वास्तव में एक बहुत ही उपयोगी क्षण है (कम से कम यदि आप एक चिंतनशील शिक्षार्थी हैं), क्योंकि आप बाद में ऊपर दिए गए "कंसिस्टेंसी" को बेहतर ढंग से समझ पाएंगे और यह आपको पायथन के बारे में बहुत कुछ सिखाएगा।


18
इस व्यवहार को खोजने में मुझे एक वर्ष का समय लगा, उत्पादन पर अपने कोड को गड़बड़ कर रहा है, जब तक कि मैं इस डिजाइन दोष में संयोग से टकरा नहीं गया, एक पूर्ण सुविधा को हटा दिया। मैं Django का उपयोग कर रहा हूँ। चूंकि मंचन के वातावरण में कई अनुरोध नहीं थे, इसलिए इस बग का QA पर कोई प्रभाव नहीं पड़ा। जब हम जीवित हो गए और एक साथ कई अनुरोध प्राप्त हुए - कुछ उपयोगिता कार्यों ने एक दूसरे के मापदंडों को लिखना शुरू कर दिया! सुरक्षा छेद बनाना, बग्स और क्या नहीं।
ओरिदम

7
@oriadam, कोई अपराध नहीं है, लेकिन मुझे आश्चर्य है कि आपने इससे पहले भागे बिना पायथन को कैसे सीखा। मैं अब सिर्फ पायथन सीख रहा हूं और इस संभावित नुकसान का उल्लेख आधिकारिक पायथन ट्यूटोरियल में किया गया है, जिसमें डिफ़ॉल्ट नियमों का पहला उल्लेख है। (जैसा कि इस उत्तर के बिंदु 4 में उल्लेख किया गया है।) मुझे लगता है कि नैतिक है - बल्कि असंगत रूप से — भाषा का आधिकारिक डॉक्स पढ़ने के लिए जिसे आप उत्पादन सॉफ्टवेयर बनाने के लिए उपयोग करते हैं।
वाइल्डकार्ड

साथ ही, यह आश्चर्य की बात होगी कि (मेरे लिए) यदि फंक्शन कॉल मैं कर रहा हूं, तो इसके अलावा अज्ञात जटिलता के एक फ़ंक्शन को बुलाया गया था।
वेटिन

52

इस व्यवहार को आसान तरीके से समझाया गया है:

  1. फ़ंक्शन (वर्ग आदि) घोषणा केवल एक बार निष्पादित की जाती है, सभी डिफ़ॉल्ट मान ऑब्जेक्ट बनाते हैं
  2. सब कुछ संदर्भ द्वारा पारित किया गया है

इसलिए:

def x(a=0, b=[], c=[], d=0):
    a = a + 1
    b = b + [1]
    c.append(1)
    print a, b, c
  1. a बदलता नहीं है - हर असाइनमेंट कॉल नई int ऑब्जेक्ट बनाता है - नई ऑब्जेक्ट मुद्रित होता है
  2. b परिवर्तित नहीं होता है - नया सरणी डिफ़ॉल्ट मान और मुद्रित से बनाया गया है
  3. c परिवर्तन - ऑपरेशन एक ही वस्तु पर किया जाता है - और यह मुद्रित होता है

(वास्तव में, ऐड एक बुरा उदाहरण है, लेकिन पूर्णांक अपरिवर्तनीय है फिर भी मेरा मुख्य बिंदु है।)
एनॉन

यह देखने के लिए चेक करने के बाद कि यह मेरे चैरगिन के लिए है, बी सेट के साथ [], बी .__ __ ([1]) रिटर्न [1] जोड़ते हैं, लेकिन बी अभी भी छोड़ देते हैं [भले ही सूचियाँ परस्पर हैं। मेरी गलती।
आयन

@ शान: वहाँ है __iadd__, लेकिन यह int के साथ काम नहीं करता है। बेशक। :-)
वेकी '

35

आप यह क्यों पूछ रहे हैं:

def func(a=[], b = 2):
    pass

आंतरिक रूप से इसके बराबर नहीं है:

def func(a=None, b = None):
    a_default = lambda: []
    b_default = lambda: 2
    def actual_func(a=None, b=None):
        if a is None: a = a_default()
        if b is None: b = b_default()
    return actual_func
func = func()

स्पष्ट रूप से फ़ेक (कोई नहीं, कोई भी) कॉल करने के मामले को छोड़कर, जिसे हम अनदेखा करेंगे।

दूसरे शब्दों में, डिफ़ॉल्ट मापदंडों का मूल्यांकन करने के बजाय, उनमें से प्रत्येक को स्टोर क्यों नहीं किया जाता है, और फ़ंक्शन को कॉल करने पर उनका मूल्यांकन किया जाता है?

एक उत्तर शायद वहीं है - यह हर फ़ंक्शन को डिफ़ॉल्ट पैरामीटर के साथ प्रभावी रूप से बंद कर देगा। यहां तक ​​कि अगर यह सब दुभाषिया में छिपा हुआ है और पूर्ण-बंद नहीं है, तो डेटा कहीं जमा हो जाता है। यह धीमा होगा और अधिक मेमोरी का उपयोग करेगा।


6
इसे बंद करने की आवश्यकता नहीं होगी - इसके बारे में सोचने का एक बेहतर तरीका बस बायोटेक को कोड की पहली पंक्ति बनाने में चूक करना होगा - आखिरकार आप उस बिंदु पर शरीर को वैसे भी संकलित कर रहे हैं - कोड के बीच कोई वास्तविक अंतर नहीं है शरीर में तर्क और कोड में।
ब्रायन

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

लेनार्ट के साथ सहमत हुए। जैसा कि गुइडो कहने के शौकीन हैं, हर भाषा सुविधा या मानक पुस्तकालय के लिए, इसका उपयोग करने वाला कोई व्यक्ति है।
जेसन बेकर

6
इसे बदलना अब पागलपन होगा - हम सिर्फ यह खोज रहे हैं कि यह ऐसा क्यों है। यदि इसके साथ शुरू करने के लिए देर से डिफ़ॉल्ट मूल्यांकन किया गया, तो जरूरी नहीं कि यह आश्चर्यजनक हो। यह निश्चित रूप से सच है कि इस तरह के एक कोर के पार्सिंग में व्यापक अंतर होगा, और शायद कई अस्पष्ट, भाषा पर संपूर्ण प्रभाव।
ग्लेन मेनार्ड

35

1) "म्यूटेबल डिफॉल्ट आर्ग्यूमेंट" की तथाकथित समस्या सामान्य रूप से एक विशेष उदाहरण है, जिसमें यह दर्शाया गया है:
"इस समस्या वाले सभी कार्य वास्तविक पैरामीटर पर समान साइड इफेक्ट समस्या से ग्रस्त हैं ,"
जो कि कार्यात्मक प्रोग्रामिंग के नियमों के खिलाफ है, आमतौर पर अवांछनीय है और दोनों को एक साथ तय किया जाना चाहिए।

उदाहरण:

def foo(a=[]):                 # the same problematic function
    a.append(5)
    return a

>>> somevar = [1, 2]           # an example without a default parameter
>>> foo(somevar)
[1, 2, 5]
>>> somevar
[1, 2, 5]                      # usually expected [1, 2]

समाधान : एक प्रतिलिपि
एक पूरी तरह से सुरक्षित समाधान है copyया deepcopyपहले इनपुट ऑब्जेक्ट या फिर कॉपी के साथ जो कुछ भी करना है।

def foo(a=[]):
    a = a[:]     # a copy
    a.append(5)
    return a     # or everything safe by one line: "return a + [5]"

कई अंतर्निहित उत्परिवर्तनीय प्रकारों की एक प्रतिलिपि पद्धति होती है जैसे some_dict.copy()या some_set.copy()आसानी से somelist[:]या जैसे कॉपी की जा सकती है list(some_list)। हर वस्तु की नकल copy.copy(any_object)या उससे अधिक पूरी तरह से की जा सकती है copy.deepcopy()(बाद में उपयोगी वस्तु अगर उत्परिवर्तनीय वस्तुओं से बनी हो)। कुछ वस्तुएं मूल रूप से "फ़ाइल" ऑब्जेक्ट जैसे साइड इफेक्ट्स पर आधारित होती हैं और कॉपी द्वारा सार्थक रूप से पुन: प्रस्तुत नहीं की जा सकती हैं। नकल

एक समान SO प्रश्न के लिए उदाहरण समस्या

class Test(object):            # the original problematic class
  def __init__(self, var1=[]):
    self._var1 = var1

somevar = [1, 2]               # an example without a default parameter
t1 = Test(somevar)
t2 = Test(somevar)
t1._var1.append([1])
print somevar                  # [1, 2, [1]] but usually expected [1, 2]
print t2._var1                 # [1, 2, [1]] but usually expected [1, 2]

इस फ़ंक्शन द्वारा लौटाए गए उदाहरण के किसी भी सार्वजनिक विशेषता में इसे न तो सहेजा जाना चाहिए । (यह मानते हुए कि इस वर्ग के बाहर से निजी विशेषताओं को संशोधित नहीं किया जाना चाहिए या सम्मेलन द्वारा उपवर्गों को संशोधित किया जाना चाहिए। यानी _var1एक निजी वर्ग है)

निष्कर्ष:
इनपुट पैरामीटर्स ऑब्जेक्ट्स को स्थान (संशोधित) में संशोधित नहीं किया जाना चाहिए और न ही उन्हें फ़ंक्शन द्वारा लौटाए गए ऑब्जेक्ट में बाँधा जाना चाहिए। (यदि हम बिना साइड इफ़ेक्ट के प्रोग्रामिंग करते हैं, जिसकी पुरज़ोर सिफारिश की जाती है। विकी को "साइड इफ़ेक्ट" के बारे में देखें। (इस संदर्भ में पहले दो पैराग्राफ रीलेवेंट हैं।)।

2)
केवल अगर वास्तविक पैरामीटर पर साइड इफेक्ट की आवश्यकता होती है लेकिन डिफ़ॉल्ट पैरामीटर पर अवांछित है तो उपयोगी समाधान def ...(var1=None): if var1 is None: var1 = [] मोर है।

3) कुछ मामलों में डिफ़ॉल्ट मापदंडों का परस्पर व्यवहार उपयोगी है


5
मुझे आशा है कि आप जानते हैं कि अजगर है कर रहे हैं नहीं एक कार्यात्मक प्रोग्रामिंग भाषा।
वेखी

6
हां, पायथन एक बहु-प्रतिमान भाषा है जिसमें कुछ कार्यात्मक विशेषताएं हैं। ("हर समस्या को एक कील की तरह मत देखो क्योंकि आपके पास एक हथौड़ा है।") उनमें से कई पायथन सर्वोत्तम अभ्यासों में हैं। पायथन में एक दिलचस्प HOWTO कार्यात्मक प्रोग्रामिंग है अन्य विशेषताएं क्लोजर और करी हैं, यहां उल्लेख नहीं किया गया है।
हाइनेसर

1
मैं इस देर के चरण में यह भी जोड़ना चाहता हूं कि जहां आवश्यक हो, वहां डेटा कॉपी से बचने के लिए पायथन के असाइनमेंट शब्दार्थ को स्पष्ट रूप से डिजाइन किया गया है, इसलिए प्रतियों का निर्माण (और विशेष रूप से गहरी प्रतियों का) रन-टाइम और मेमोरी उपयोग दोनों को प्रतिकूल रूप से प्रभावित करेगा। इसलिए उनका उपयोग केवल आवश्यक होने पर ही किया जाना चाहिए, लेकिन नए लोगों को अक्सर समझने में कठिनाई होती है।
होल्डनवेब

1
@holdenweb मैं सहमत हूं। एक अस्थायी प्रति सबसे सामान्य तरीका है और कभी-कभी एकमात्र संभव तरीका है कि मूल उत्परिवर्तित डेटा को एक संभावित फ़ंक्शन से कैसे संरक्षित किया जाए जो आपको संभावित रूप से संशोधित करता है। सौभाग्य से एक फ़ंक्शन जो अनुचित रूप से डेटा को संशोधित करता है उसे बग माना जाता है और इसलिए असामान्य है।
हाइनेसर

मैं इस जवाब से सहमत हूं। और मुझे समझ में नहीं आता है कि def f( a = None )जब आप वास्तव में कुछ और मतलब रखते हैं तो निर्माण की सिफारिश क्यों की जाती है। नकल करना ठीक है, क्योंकि आपको तर्क-वितर्क नहीं करना चाहिए। और जब आप करते हैं if a is None: a = [1, 2, 3], तो आप वैसे भी सूची की प्रतिलिपि बनाते हैं।
कोदडो

30

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

>>> def foo(a):
    a.append(5)
    print a

>>> a  = [5]
>>> foo(a)
[5, 5]
>>> foo(a)
[5, 5, 5]
>>> foo(a)
[5, 5, 5, 5]
>>> foo(a)
[5, 5, 5, 5, 5]

इस कोड में दृष्टि में कोई डिफ़ॉल्ट मान नहीं है, लेकिन आपको ठीक यही समस्या मिलती है।

समस्या यह है कि कॉलर से पारित होने वाले एक परिवर्तनशील परिवर्तनशील fooको संशोधित कर रहा है, जब कॉलर को इसकी उम्मीद नहीं है। इस तरह कोड ठीक होगा यदि फ़ंक्शन को कुछ कहा जाता था append_5; तब कॉल करने वाला फ़ंक्शन को कॉल करेगा ताकि वे उस मूल्य को संशोधित कर सकें, और व्यवहार अपेक्षित होगा। लेकिन इस तरह के एक फ़ंक्शन को डिफ़ॉल्ट तर्क लेने की बहुत संभावना नहीं होगी, और शायद सूची वापस नहीं होगी (क्योंकि कॉलर के पास पहले से ही उस सूची का संदर्भ है, वह जिसे अभी पारित किया गया है)।

आपका मूल foo, डिफ़ॉल्ट तर्क के साथ, aयह संशोधित नहीं किया जाना चाहिए कि क्या यह स्पष्ट रूप से पारित किया गया था या डिफ़ॉल्ट मान प्राप्त हुआ था। जब तक यह संदर्भ / नाम / प्रलेखन से स्पष्ट नहीं हो जाता है, तब तक आपके कोड को परस्पर तर्क छोड़ देना चाहिए कि तर्क को संशोधित किया जाना चाहिए। स्थानीय टेंपरेरी के रूप में तर्कों के रूप में पारित किए जाने योग्य परस्पर मूल्यों का उपयोग करना एक बहुत बुरा विचार है, चाहे हम पाइथन में हैं या नहीं और इसमें डिफ़ॉल्ट तर्क शामिल हैं या नहीं।

यदि आपको किसी चीज़ की गणना करने के लिए स्थानीय अस्थायी रूप से अस्थायी रूप से हेरफेर करने की आवश्यकता है, और आपको तर्क मान से अपना हेरफेर शुरू करने की आवश्यकता है, तो आपको एक प्रतिलिपि बनाने की आवश्यकता है।


7
हालांकि संबंधित, मुझे लगता है कि यह अलग व्यवहार है (जैसा कि हम "इन-प्लेस" appendबदलने की उम्मीद करते हैं a)। कि प्रत्येक कॉल पर डिफ़ॉल्ट म्यूटेबल फिर से त्वरित नहीं है, कम से कम मेरे लिए "अप्रत्याशित" बिट ... है। :)
एंडी हेडन

2
@AndyHayden यदि फ़ंक्शन से तर्क को संशोधित करने की उम्मीद की जाती है, तो यह डिफ़ॉल्ट रूप से क्यों समझ में आएगा?
मार्क रंसोम

@MarkRansom एकमात्र ऐसा उदाहरण है जिसके बारे में मैं सोच सकता हूं cache={}। हालांकि, मुझे संदेह है कि यह "कम से कम विस्मय" तब सामने आता है जब आप उस फ़ंक्शन को उम्मीद नहीं करते (या चाहते हैं) कि आप तर्क को म्यूट करने के लिए कॉल कर रहे हैं।
एंडी हेडन

1
@AndyHayden मैंने उस भावना के विस्तार के साथ यहां अपना जवाब छोड़ दिया। आप क्या सोचते हैं मुझे बताओ। मैं cache={}पूर्णता के लिए इसमें आपका उदाहरण जोड़ सकता हूं ।
मार्क

1
@AndyHayden मेरे उत्तर की बात यह है कि यदि आप कभी किसी तर्क के डिफ़ॉल्ट मान को बदलकर चकित रह जाते हैं, तो आपके पास एक और बग है, जो यह है कि डिफ़ॉल्ट रूप से उपयोग किए जाने पर आपका कोड गलती से कॉलर के मूल्य को म्यूट कर सकता है। और ध्यान दें कि Noneअगर arg None उस समस्या को हल नहीं करता है तो वास्तविक डिफ़ॉल्ट का उपयोग करना और असाइन करना (मैं इसे उस कारण के लिए एक विरोधी पैटर्न मानता हूं)। यदि आप तर्क मानों को टालते हुए अन्य बग को ठीक करते हैं या नहीं, तो उनके पास चूक है या नहीं, तो आप इस "आश्चर्यजनक" व्यवहार के बारे में कभी ध्यान नहीं देंगे या देखभाल नहीं करेंगे।
बेन

27

पहले से ही व्यस्त विषय, लेकिन जो मैंने यहां पढ़ा, उससे निम्नलिखित ने मुझे यह महसूस करने में मदद की कि यह आंतरिक रूप से कैसे काम कर रहा है:

def bar(a=[]):
     print id(a)
     a = a + [1]
     print id(a)
     return a

>>> bar()
4484370232
4484524224
[1]
>>> bar()
4484370232
4484524152
[1]
>>> bar()
4484370232 # Never change, this is 'class property' of the function
4484523720 # Always a new object 
[1]
>>> id(bar.func_defaults[0])
4484370232

2
वास्तव में यह नए लोगों को a = a + [1]ओवरलोड के रूप में थोड़ा भ्रमित करने वाला हो सकता है a... इसे बदलने b = a + [1] ; print id(b)और लाइन जोड़ने पर विचार करें a.append(2)। इससे यह और स्पष्ट हो जाएगा कि +दो सूचियों पर हमेशा एक नई सूची (सौंपी गई b) बनाई जाती है , जबकि एक संशोधित aअभी भी एक ही हो सकती है id(a)
जॉर्न हीस

25

यह एक प्रदर्शन अनुकूलन है। इस कार्यक्षमता के परिणामस्वरूप, आपको लगता है कि इन दो फ़ंक्शन कॉल में से कौन सा तेज़ है?

def print_tuple(some_tuple=(1,2,3)):
    print some_tuple

print_tuple()        #1
print_tuple((1,2,3)) #2

मैं तुम्हें एक संकेत देता हूँ। यहां देखें डिसएफ़ड्स (देखें http://docs.python.org/library/dis.html ):

#1

0 LOAD_GLOBAL              0 (print_tuple)
3 CALL_FUNCTION            0
6 POP_TOP
7 LOAD_CONST               0 (None)
10 RETURN_VALUE

#2

 0 LOAD_GLOBAL              0 (print_tuple)
 3 LOAD_CONST               4 ((1, 2, 3))
 6 CALL_FUNCTION            1
 9 POP_TOP
10 LOAD_CONST               0 (None)
13 RETURN_VALUE

मुझे संदेह है कि अनुभवी व्यवहार का एक व्यावहारिक उपयोग है (जो वास्तव में सी में स्थैतिक चर का उपयोग करते हैं, बग प्रजनन के बिना?)

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


24

पायथन: द म्यूटेबल डिफॉल्ट आर्ग्युमेंट

डिफ़ॉल्ट तर्कों का मूल्यांकन उस समय होता है जब फ़ंक्शन को एक फ़ंक्शन ऑब्जेक्ट में संकलित किया जाता है। जब फ़ंक्शन द्वारा उपयोग किया जाता है, तो उस फ़ंक्शन द्वारा कई बार, वे एक ही वस्तु होते हैं।

जब वे उत्परिवर्तित होते हैं, जब उत्परिवर्तित होते हैं (उदाहरण के लिए, इसमें एक तत्व जोड़कर) वे लगातार कॉल पर उत्परिवर्तित रहते हैं।

वे उत्परिवर्तित रहते हैं क्योंकि वे हर बार एक ही वस्तु हैं।

समतुल्य कोड:

चूंकि सूची फ़ंक्शन के लिए बाध्य है जब फ़ंक्शन ऑब्जेक्ट संकलित और त्वरित किया जाता है, यह:

def foo(mutable_default_argument=[]): # make a list the default argument
    """function that uses a list"""

इसके लगभग बराबर है:

_a_list = [] # create a list in the globals

def foo(mutable_default_argument=_a_list): # make it the default argument
    """function that uses a list"""

del _a_list # remove globals name binding

प्रदर्शन

यहां एक प्रदर्शन है - आप सत्यापित कर सकते हैं कि वे हर बार उसी वस्तु के होते हैं, जिसके द्वारा उन्हें संदर्भित किया जाता है

  • यह देखने के बाद कि सूची किसी फ़ंक्शन ऑब्जेक्ट के संकलन से पहले पूरी हो गई है,
  • यह देखते हुए कि सूची के संदर्भित होने पर आईडी हर बार एक ही है,
  • यह देखते हुए कि सूची में परिवर्तन तब होता है जब इसका उपयोग करने वाले फ़ंक्शन को दूसरी बार कहा जाता है,
  • उस क्रम का अवलोकन करना जिसमें उत्पादन स्रोत से मुद्रित होता है (जिसे मैंने आपके लिए सुविधाजनक रूप से गिना है):

example.py

print('1. Global scope being evaluated')

def create_list():
    '''noisily create a list for usage as a kwarg'''
    l = []
    print('3. list being created and returned, id: ' + str(id(l)))
    return l

print('2. example_function about to be compiled to an object')

def example_function(default_kwarg1=create_list()):
    print('appending "a" in default default_kwarg1')
    default_kwarg1.append("a")
    print('list with id: ' + str(id(default_kwarg1)) + 
          ' - is now: ' + repr(default_kwarg1))

print('4. example_function compiled: ' + repr(example_function))


if __name__ == '__main__':
    print('5. calling example_function twice!:')
    example_function()
    example_function()

और इसके साथ चल रहा है python example.py:

1. Global scope being evaluated
2. example_function about to be compiled to an object
3. list being created and returned, id: 140502758808032
4. example_function compiled: <function example_function at 0x7fc9590905f0>
5. calling example_function twice!:
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a']
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a', 'a']

क्या यह "लिस्ट विस्मय" के सिद्धांत का उल्लंघन करता है?

निष्पादन का यह क्रम अक्सर पायथन के नए उपयोगकर्ताओं के लिए भ्रामक है। यदि आप पायथन निष्पादन मॉडल को समझते हैं, तो यह काफी अपेक्षित हो जाता है।

नए पायथन उपयोगकर्ताओं के लिए सामान्य निर्देश:

लेकिन यही कारण है कि नए उपयोगकर्ताओं के लिए सामान्य निर्देश इसके बजाय उनके डिफ़ॉल्ट तर्क बनाना है:

def example_function_2(default_kwarg=None):
    if default_kwarg is None:
        default_kwarg = []

यह फ़ंक्शन को यह बताने के लिए कि किसी भी व्यक्ति ने डिफॉल्ट के अलावा किसी अन्य तर्क को प्राप्त किया है या नहीं, एक संतरी वस्तु के रूप में कोई नहीं सिंगलटन का उपयोग करता है। यदि हमें कोई तर्क नहीं मिलता है, तो हम वास्तव []में डिफ़ॉल्ट के रूप में एक नई खाली सूची का उपयोग करना चाहते हैं ।

जैसा कि नियंत्रण प्रवाह पर ट्यूटोरियल अनुभाग कहता है:

यदि आप नहीं चाहते हैं कि डिफ़ॉल्ट को बाद की कॉल के बीच साझा किया जाए, तो आप इसके स्थान पर फ़ंक्शन लिख सकते हैं:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

24

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

def a(): return []

def b(x=a()):
    print x

उम्मीद है कि यह दिखाने के लिए पर्याप्त है कि defकथन के निष्पादन के समय डिफ़ॉल्ट तर्क अभिव्यक्तियों को निष्पादित करना आसान नहीं है या समझ में नहीं आता है, या दोनों।

जब आप डिफ़ॉल्ट कंस्ट्रक्टर का उपयोग करने का प्रयास करते हैं तो मैं मानता हूं कि यह एक गोच है।


20

कोई भी नहीं का उपयोग करके एक साधारण समाधान

>>> def bar(b, data=None):
...     data = data or []
...     data.append(b)
...     return data
... 
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3, [34])
[34, 3]
>>> bar(3, [34])
[34, 3]

19

यदि आप निम्नलिखित बातों को ध्यान में रखते हैं तो यह व्यवहार आश्चर्यजनक नहीं है:

  1. असाइनमेंट के प्रयासों पर रीड-ओनली क्लास विशेषताओं का व्यवहार, और वह
  2. कार्य ऑब्जेक्ट हैं (स्वीकृत उत्तर में अच्छी तरह से समझाया गया है)।

इस थ्रेड में (2) की भूमिका को बड़े पैमाने पर कवर किया गया है। (1) संभवतया विस्मय कारक है, क्योंकि अन्य भाषाओं से आने पर यह व्यवहार "सहज" नहीं है।

(1) कक्षाओं पर पायथन ट्यूटोरियल में वर्णित है । केवल-पढ़ने के लिए एक विशेषता वर्ग के लिए एक मान निर्दिष्ट करने के प्रयास में:

... अंतरतम दायरे के बाहर पाए जाने वाले सभी चर केवल पढ़ने के लिए होते हैं ( इस तरह के चर को लिखने का प्रयास सरलतम रूप से बाहरी चर को अपरिवर्तित छोड़ते हुए, अंतरतम दायरे में एक नया स्थानीय चर बनाएगा )।

मूल उदाहरण पर वापस देखें और उपरोक्त बिंदुओं पर विचार करें:

def foo(a=[]):
    a.append(5)
    return a

यहाँ fooएक वस्तु है और (पर उपलब्ध ) aका एक गुण है । चूँकि एक सूची है, परिवर्तनशील है और इस प्रकार यह एक पढ़ने-लिखने की विशेषता है । यह फ़ंक्शन द्वारा त्वरित होने पर हस्ताक्षर द्वारा निर्दिष्ट के रूप में खाली सूची में आरंभीकृत होता है, और जब तक फ़ंक्शन ऑब्जेक्ट मौजूद रहता है, तब तक पढ़ने और लिखने के लिए उपलब्ध होता है।foofoo.func_defs[0]aafoo

fooकिसी डिफ़ॉल्ट को ओवरराइड किए बिना कॉल करना उस डिफ़ॉल्ट के मान का उपयोग करता है foo.func_defs। इस स्थिति में, फ़ंक्शन ऑब्जेक्ट के कोड स्कोप के भीतर foo.func_defs[0]उपयोग किया जाता aहै। परिवर्तन करने के लिए aबदलने के foo.func_defs[0], जो का हिस्सा है fooवस्तु और में कोड के निष्पादन के बीच बनी रहती है foo

अब, इसे अन्य भाषाओं के डिफ़ॉल्ट तर्क व्यवहार के अनुकरण पर प्रलेखन से उदाहरण से तुलना करें , जैसे कि फ़ंक्शन हस्ताक्षर को हर बार फ़ंक्शन निष्पादित होने पर उपयोग किया जाता है:

def foo(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

खाते में (1) और (2) लेते हुए , कोई यह देख सकता है कि यह वांछित व्यवहार क्यों पूरा करता है:

  • जब fooफ़ंक्शन ऑब्जेक्ट को तुरंत foo.func_defs[0]किया जाता है None, तो एक अपरिवर्तनीय ऑब्जेक्ट पर सेट किया जाता है।
  • जब फ़ंक्शन को डिफॉल्ट्स के साथ निष्पादित किया जाता है ( Lफ़ंक्शन कॉल में निर्दिष्ट कोई पैरामीटर नहीं है ), foo.func_defs[0]( None) के रूप में स्थानीय दायरे में उपलब्ध है L
  • पर L = [], असाइनमेंट सफल नहीं हो सकता है foo.func_defs[0], क्योंकि यह विशेषता केवल-पढ़ने के लिए है।
  • प्रति (1) , नाम Lसे एक नया स्थानीय चर भी स्थानीय दायरे में बनाया गया है और फ़ंक्शन कॉल के शेष के लिए उपयोग किया जाता है। foo.func_defs[0]इस प्रकार भविष्य के आक्रमणों के लिए अपरिवर्तित रहता है foo

19

मैं फ़ंक्शन के लिए एक डिफ़ॉल्ट सूची मान पास करने के लिए एक वैकल्पिक संरचना प्रदर्शित करने जा रहा हूं (यह शब्दकोशों के साथ समान रूप से अच्छी तरह से काम करता है)।

जैसा कि दूसरों ने बड़े पैमाने पर टिप्पणी की है, सूची पैरामीटर फ़ंक्शन के लिए बाध्य है जब इसे निष्पादित होने पर विरोध के रूप में परिभाषित किया जाता है। चूँकि सूचियाँ और शब्दकोश परस्पर भिन्न हैं, इसलिए इस पैरामीटर में कोई भी परिवर्तन इस फ़ंक्शन के अन्य कॉल को प्रभावित करेगा। नतीजतन, फ़ंक्शन को बाद की कॉलें इस साझा सूची को प्राप्त करेंगी जो फ़ंक्शन के किसी भी अन्य कॉल द्वारा बदल दी गई हो सकती हैं। इससे भी बदतर, दो पैरामीटर एक ही समय में इस फ़ंक्शन के साझा पैरामीटर का उपयोग दूसरे द्वारा किए गए परिवर्तनों से अनजान हैं।

गलत तरीका (शायद ...) :

def foo(list_arg=[5]):
    return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
# The value of 6 appended to variable 'a' is now part of the list held by 'b'.
>>> b
[5, 6, 7]  

# Although 'a' is expecting to receive 6 (the last element it appended to the list),
# it actually receives the last element appended to the shared list.
# It thus receives the value 7 previously appended by 'b'.
>>> a.pop()             
7

आप सत्यापित कर सकते हैं कि वे एक और एक ही वस्तु का उपयोग करके हैं id:

>>> id(a)
5347866528

>>> id(b)
5347866528

ब्रेट स्लेटकिन के "प्रभावी पायथन: 59 बेहतर पायथन को लिखने के विशिष्ट तरीके", आइटम 20: Noneगतिशील डिफ़ॉल्ट तर्क को निर्दिष्ट करने के लिए उपयोग और डॉकस्ट्रिंग (पृष्ठ 48)।

पाइथन में वांछित परिणाम प्राप्त करने के लिए कन्वेंशन को डिफ़ॉल्ट मान प्रदान करना है Noneऔर डॉकस्ट्रिंग में वास्तविक व्यवहार का दस्तावेजीकरण करना है।

यह कार्यान्वयन सुनिश्चित करता है कि फ़ंक्शन के लिए प्रत्येक कॉल या तो डिफ़ॉल्ट सूची प्राप्त करता है या फिर फ़ंक्शन को दी गई सूची।

पसंदीदा तरीका :

def foo(list_arg=None):
   """
   :param list_arg:  A list of input values. 
                     If none provided, used a list with a default value of 5.
   """
   if not list_arg:
       list_arg = [5]
   return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
>>> b
[5, 7]

c = foo([10])
c.append(11)
>>> c
[10, 11]

'गलत पद्धति' के लिए वैध उपयोग के मामले हो सकते हैं जिससे प्रोग्रामर ने डिफ़ॉल्ट सूची पैरामीटर को साझा करने का इरादा किया है, लेकिन यह नियम की तुलना में अधिक संभावना है।


17

यहाँ समाधान हैं:

  1. Noneअपने डिफ़ॉल्ट मान (या एक गैर object) के रूप में उपयोग करें , और रनटाइम पर अपने मूल्यों को बनाने के लिए उस पर स्विच करें; या
  2. lambdaअपने डिफ़ॉल्ट पैरामीटर के रूप में उपयोग करें , और डिफ़ॉल्ट मान प्राप्त करने के लिए एक कोशिश ब्लॉक के भीतर इसे कॉल करें (यह इस तरह की चीज है कि लैम्ब्डा एब्सट्रैक्शन के लिए है)।

दूसरा विकल्प अच्छा है क्योंकि फ़ंक्शन के उपयोगकर्ता एक कॉल करने योग्य में गुजर सकते हैं, जो पहले से मौजूद हो सकता है (जैसे कि type)


16

जब हम ऐसा करते हैं:

def foo(a=[]):
    ...

... हम तर्क aको एक अनाम सूची में निर्दिष्ट करते हैं , यदि कॉलर कोई मान नहीं देता है।

इस चर्चा के लिए चीजों को सरल बनाने के लिए, आइए अस्थायी रूप से अनाम सूची को नाम दें। कैसे के बारे में pavlo?

def foo(a=pavlo):
   ...

किसी भी समय, यदि कॉलर हमें नहीं बताता है कि क्या aहै, तो हम पुन: उपयोग करते हैं pavlo

यदि pavloउत्परिवर्तनीय (परिवर्तनीय) है, और fooइसे संशोधित करना समाप्त होता है, तो अगली बार जब हम नोटिस करते हैं तो एक प्रभाव fooको निर्दिष्ट किए बिना कहा जाता है a

तो यह वही है जो आप देख रहे हैं (याद रखें, pavloयह आरंभिक है] []:

 >>> foo()
 [5]

अब, pavlo[5] है।

कॉलिंग foo()फिर से संशोधित करता है pavlo:

>>> foo()
[5, 5]

निर्दिष्ट aजब बुला foo()सुनिश्चित pavloछुआ नहीं है।

>>> ivan = [1, 2, 3, 4]
>>> foo(a=ivan)
[1, 2, 3, 4, 5]
>>> ivan
[1, 2, 3, 4, 5]

तो, pavloअभी भी है [5, 5]

>>> foo()
[5, 5, 5]

16

मैं कभी-कभी इस व्यवहार का निम्न पैटर्न के विकल्प के रूप में उपयोग करता हूं:

singleton = None

def use_singleton():
    global singleton

    if singleton is None:
        singleton = _make_singleton()

    return singleton.use_me()

यदि singletonकेवल द्वारा उपयोग किया जाता है use_singleton, तो मुझे प्रतिस्थापन के रूप में निम्न पैटर्न पसंद है:

# _make_singleton() is called only once when the def is executed
def use_singleton(singleton=_make_singleton()):
    return singleton.use_me()

मैंने इसका उपयोग क्लाइंट क्लासेस को इंस्टेंट करने के लिए किया है जो बाहरी संसाधनों को एक्सेस करता है, और मेमोइज़ेशन के लिए डाइट या लिस्ट बनाने के लिए भी।

चूंकि मुझे नहीं लगता कि यह पैटर्न अच्छी तरह से जाना जाता है, इसलिए मैं भविष्य की गलतफहमी के खिलाफ सुरक्षा के लिए एक छोटी टिप्पणी करता हूं।


2
मैं संस्मरण के लिए एक डेकोरेटर जोड़ना पसंद करता हूं, और फ़ंक्शन ऑब्जेक्ट पर ही संस्मरण कैश डाल देता हूं।
स्टेफानो बोरीनी

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

15

आप ऑब्जेक्ट के स्थान पर इसे गोल कर सकते हैं (और इसलिए गुंजाइश के साथ टाई):

def foo(a=[]):
    a = list(a)
    a.append(5)
    return a

बदसूरत, लेकिन यह काम करता है।


3
यह उन मामलों में एक अच्छा समाधान है जहां आप फ़ंक्शन द्वारा अपेक्षित तर्क के प्रकारों का दस्तावेजीकरण करने के लिए स्वचालित प्रलेखन पीढ़ी सॉफ्टवेयर का उपयोग कर रहे हैं। एक = कोई नहीं डालना और फिर एक सेट करना [अगर कोई नहीं है] एक पाठक को एक नज़र में समझने में मदद नहीं करता है कि क्या अपेक्षित है।
माइकल स्कॉट कथबर्ट

शांत विचार: नाम की गारंटी देने वाले इसे कभी भी संशोधित नहीं किया जा सकता है। वह सहीं मे मुझे पसन्द है।
होल्डनवेब

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

13

यह सच हो सकता है:

  1. कोई व्यक्ति हर भाषा / पुस्तकालय सुविधा का उपयोग कर रहा है, और
  2. यहां व्यवहार को बदलना बीमार हो जाएगा, लेकिन

यह पूरी तरह से ऊपर की दोनों विशेषताओं को पकड़ने के लिए सुसंगत है और फिर भी एक और बिंदु बनाता है:

  1. यह एक भ्रामक विशेषता है और यह पायथन में दुर्भाग्यपूर्ण है।

अन्य उत्तर, या उनमें से कम से कम कुछ या तो अंक 1 और 2 बनाते हैं, लेकिन 3 नहीं, या बिंदु 3 और नीचे बिंदु 1 और 2 बनाते हैं। लेकिन तीनों सत्य हैं।

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

मौजूदा व्यवहार पाइथोनिक नहीं है, और पाइथन सफल है क्योंकि भाषा के बारे में बहुत कम जानकारी आसपास के कम से कम विस्मय के सिद्धांत का उल्लंघन करती हैयह बुरी तरह से। यह एक वास्तविक समस्या है कि इसे उखाड़ फेकना बुद्धिमानी होगी या नहीं। यह एक डिजाइन दोष है। यदि आप व्यवहार का पता लगाने की कोशिश करके भाषा को बेहतर ढंग से समझते हैं, तो मैं कह सकता हूं कि C ++ यह और बहुत कुछ करता है; उदाहरण के लिए, सूक्ष्म सूचक त्रुटियों के लिए आप नेविगेट करके बहुत कुछ सीखते हैं। लेकिन यह पायथोनिक नहीं है: जो लोग इस व्यवहार के चेहरे पर दृढ़ता के लिए अजगर की परवाह करते हैं, वे लोग हैं जो भाषा के लिए तैयार हैं क्योंकि पायथन में अन्य भाषा की तुलना में बहुत कम आश्चर्य है। डाबब्लर्स और जिज्ञासु तब पाइथोनिस्टस बन जाते हैं जब उन्हें इस बात पर आश्चर्य होता है कि किसी काम को पूरा करने में कितना कम समय लगता है - न कि एक डिजाइन के कारण fl - मेरा मतलब है, छिपी हुई तर्क पहेली - जो प्रोग्रामर के अंतर्ज्ञान के खिलाफ कटौती करता है जो पाइथन के लिए खींचे जाते हैं क्योंकि यह सिर्फ काम करता है


6
-1 हालांकि एक रक्षात्मक परिप्रेक्ष्य, यह एक जवाब नहीं है, और मैं इससे असहमत हूं। बहुत सारे विशेष अपवाद अपने स्वयं के कोने के मामलों को भूल जाते हैं।
मार्सिन

3
तो, यह "आश्चर्यजनक रूप से अनभिज्ञ" है यह कहना कि पायथन में [] रहने के लिए एक डिफ़ॉल्ट तर्क के लिए अधिक समझ में आता है [] हर बार फ़ंक्शन को कहा जाता है?
क्रिस्टोस हेवर्ड

3
और यह दुर्भाग्यपूर्ण माना जाता है कि कोई भी किसी भी व्यक्ति के लिए डिफ़ॉल्ट तर्क सेट करने के लिए एक दुर्भाग्यपूर्ण मुहावरे के रूप में विचार करता है, और फिर फ़ंक्शन सेटिंग के शरीर में यदि तर्क == कोई नहीं: तर्क = []? क्या इस मुहावरे को दुर्भाग्यपूर्ण माना जाना दुर्भाग्यपूर्ण है क्योंकि अक्सर लोग चाहते हैं कि एक भोला-भाला नवागंतुक क्या उम्मीद करेगा, कि यदि आप f (तर्क = []) असाइन करते हैं, तो तर्क स्वतः ही [] के मान के लिए डिफ़ॉल्ट हो जाएगा?
क्रिस्टोस हेडवर्ड

3
लेकिन पायथन में, भाषा की भावना का हिस्सा यह है कि आपको बहुत अधिक गहरी गोता लगाने की ज़रूरत नहीं है; array.sort () काम करता है, और काम करता है, भले ही आप सॉर्टिंग, बिग-ओ और कॉन्स्टेंट के बारे में कितना कम समझते हैं। सरणी छँटाई तंत्र में अजगर की सुंदरता, अनगिनत उदाहरणों में से एक देने के लिए, यह है कि आपको आंतरिक में एक गहरी गोता लगाने की आवश्यकता नहीं है। और इसे अलग तरह से कहने के लिए, पायथन की सुंदरता यह है कि किसी को भी कुछ ऐसा करने के लिए कार्यान्वयन में गहरा गोता लगाने की आवश्यकता नहीं है, बस काम करता है। और एक वर्कअराउंड है ... (यदि तर्क == कोई नहीं: तर्क = []), विफल।
क्रिस्टोस हेडवर्ड

3
एक स्टैंडअलोन के रूप में, बयान का x=[]अर्थ है "एक खाली सूची ऑब्जेक्ट बनाएं, और इसे 'x' नाम से बांधें।" तो, def f(x=[])एक खाली सूची भी बनाई गई है। यह हमेशा x के लिए बाध्य नहीं होता है, इसलिए इसके बजाय यह डिफ़ॉल्ट सरोगेट के लिए बाध्य हो जाता है। बाद में जब f () कहा जाता है, तो डिफ़ॉल्ट को समाप्त कर दिया जाता है और x से बाउंड कर दिया जाता है। चूँकि यह खाली सूची ही थी जिसे दूर किया गया था, वही सूची केवल x को बाँधने के लिए उपलब्ध है, चाहे इसके अंदर कुछ भी अटक गया हो या नहीं। यह अन्यथा कैसे हो सकता है?
जेरी बी

10

यह एक डिजाइन दोष नहीं है । जो भी इस पर यात्रा करता है वह कुछ गलत कर रहा है।

आपके द्वारा इस समस्या में भाग लेने के 3 मामले हैं:

  1. आप फ़ंक्शन के साइड इफेक्ट के रूप में तर्क को संशोधित करने का इरादा रखते हैं। इस मामले में यह डिफ़ॉल्ट तर्क के लिए कभी भी समझ में नहीं आता है। एकमात्र अपवाद तब होता है जब आप फ़ंक्शन विशेषताओं का उपयोग करने के लिए तर्क सूची का दुरुपयोग कर रहे होते हैं, उदा cache={}, और आपको फ़ंक्शन को वास्तविक तर्क के साथ कॉल करने की उम्मीद नहीं होगी।
  2. आप तर्क को बिना शर्त छोड़ने का इरादा रखते हैं, लेकिन आपने गलती से इसे संशोधित कर दिया है। यह एक बग है, इसे ठीक करें।
  3. आप फ़ंक्शन के अंदर उपयोग के लिए तर्क को संशोधित करने का इरादा रखते हैं, लेकिन फ़ंक्शन के बाहर संशोधन देखने योग्य होने की उम्मीद नहीं करता है। उस मामले में आपको तर्क की एक प्रति बनाने की आवश्यकता है , चाहे वह डिफ़ॉल्ट थी या नहीं! पायथन एक कॉल-बाय-वैल्यू भाषा नहीं है, इसलिए यह आपके लिए कॉपी नहीं बनाता है, आपको इसके बारे में स्पष्ट होना चाहिए।

प्रश्न में उदाहरण श्रेणी 1 या 3 में गिर सकता है। यह विचित्र है कि यह पारित सूची को संशोधित करता है और इसे वापस लौटाता है; आपको एक या दूसरे को चुनना चाहिए।


"कुछ गलत करना" निदान है। उस ने कहा, मुझे लगता है कि ऐसे समय थे = कोई भी पैटर्न उपयोगी नहीं है, लेकिन आम तौर पर आप संशोधित नहीं करना चाहते हैं यदि उस मामले में एक परिवर्तनशील (2) पारित हो गया है। cache={}पैटर्न वास्तव में एक साक्षात्कार-एकमात्र समाधान, वास्तविक कोड में आप शायद चाहते है @lru_cache!
एंडी हेडन

9

इस "बग" ने मुझे बहुत सारे ओवरटाइम काम के घंटे दिए! लेकिन मुझे इसका एक संभावित उपयोग दिखाई देने लगा है (लेकिन मुझे यह पसंद आया होगा कि यह निष्पादन के समय, अभी भी है)

मैं आपको वह देने जा रहा हूं जो मुझे एक उपयोगी उदाहरण के रूप में दिखता है।

def example(errors=[]):
    # statements
    # Something went wrong
    mistake = True
    if mistake:
        tryToFixIt(errors)
        # Didn't work.. let's try again
        tryToFixItAnotherway(errors)
        # This time it worked
    return errors

def tryToFixIt(err):
    err.append('Attempt to fix it')

def tryToFixItAnotherway(err):
    err.append('Attempt to fix it by another way')

def main():
    for item in range(2):
        errors = example()
    print '\n'.join(errors)

main()

निम्नलिखित प्रिंट करता है

Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way

8

बस फ़ंक्शन को बदल दें:

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a

7

मुझे लगता है कि इस सवाल का उत्तर यह है कि अजगर कैसे डेटा को पैरामीटर (मान या संदर्भ द्वारा पास) में परिवर्तित करता है, न कि पारस्परिकता या कैसे अजगर "डीफ़" कथन को संभालता है।

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

उपरोक्त दो बिंदुओं को स्वीकार करते हुए, आइए बताते हैं कि अजगर कोड का क्या हुआ। यह केवल वस्तुओं के लिए संदर्भ से गुजरने के कारण है, लेकिन परस्पर / अपरिवर्तनीय, या यकीनन इस तथ्य से कोई लेना-देना नहीं है कि "डीफ़" कथन केवल एक बार निष्पादित होने पर निष्पादित किया जाता है।

[] एक वस्तु है, इसलिए अजगर को [] के संदर्भ में पास किया जाता है a, यानी, aकेवल एक संकेतक है [] जो एक वस्तु के रूप में स्मृति में निहित है। हालाँकि, [] की एक ही प्रति है, इसके कई संदर्भ हैं। पहले फू () के लिए, सूची [] को परिशिष्ट विधि द्वारा 1 में बदल दिया जाता है । लेकिन ध्यान दें कि सूची ऑब्जेक्ट की केवल एक प्रति है और यह ऑब्जेक्ट अब 1 हो गया है । दूसरे फू () को चलाने के दौरान, वेबबोट वेबपेज जो कहता है (आइटम का मूल्यांकन नहीं किया जाता है) गलत है। aसूची ऑब्जेक्ट होने के लिए मूल्यांकन किया जाता है, हालांकि अब ऑब्जेक्ट की सामग्री 1 है । यह संदर्भ से गुजरने का प्रभाव है! फू (3) का परिणाम उसी तरह आसानी से प्राप्त किया जा सकता है।

मेरे उत्तर को और मान्य करने के लिए, आइए दो अतिरिक्त कोडों पर एक नज़र डालते हैं।

====== नंबर 2 ========

def foo(x, items=None):
    if items is None:
        items = []
    items.append(x)
    return items

foo(1)  #return [1]
foo(2)  #return [2]
foo(3)  #return [3]

[]एक वस्तु है, इसलिए None(पूर्व परस्पर परिवर्तनशील है जबकि बाद वाला अपरिवर्तनीय है। लेकिन प्रश्न के साथ परिवर्तनशीलता का कोई लेना-देना नहीं है)। अंतरिक्ष में कोई नहीं है, लेकिन हम जानते हैं कि यह वहां है और वहां कोई नहीं की केवल एक प्रति है। इसलिए हर बार फू का आह्वान किया जाता है, वस्तुओं का मूल्यांकन किया जाता है (कुछ जवाब के विपरीत कि यह केवल एक बार मूल्यांकन किया जाता है) कोई नहीं, स्पष्ट होने के लिए, संदर्भ (या पते) में से कोई नहीं। फिर foo में, आइटम को बदल दिया जाता है [], अर्थात, किसी अन्य ऑब्जेक्ट को इंगित करता है जिसका एक अलग पता है।

====== नंबर 3 =======

def foo(x, items=[]):
    items.append(x)
    return items

foo(1)    # returns [1]
foo(2,[]) # returns [2]
foo(3)    # returns [1,3]

फू का आह्वान (1) आइटम को एक सूची वस्तु के लिए इंगित करता है [], एक पते के साथ, कहते हैं, 11111111। सूची की सामग्री अगली कड़ी में फू फ़ंक्शन में 1 से बदल जाती है , लेकिन पता नहीं बदला गया है, फिर भी 11111111 .फिर फू (2, []) आ रहा है। यद्यपि [] में फू (२, []] में डिफ़ॉल्ट पैरामीटर [] के समान सामग्री है, जब फू (१) कहते हैं, तो उनका पता अलग होता है! चूंकि हम स्पष्ट रूप से पैरामीटर प्रदान करते हैं, itemsइस नए का पता लेना है [], 2222222 कहते हैं, और कुछ बदलाव करने के बाद इसे वापस लौटाएं। अब foo (3) निष्पादित किया जाता है। तभी सेxप्रदान किया जाता है, आइटम को फिर से अपना डिफ़ॉल्ट मान लेना होगा। डिफ़ॉल्ट मान क्या है? यह फू फ़ंक्शन को परिभाषित करते समय सेट किया जाता है: सूची ऑब्जेक्ट 11111111 में स्थित है। इसलिए आइटम का मूल्यांकन 11111111 के तत्व के रूप में किया जाता है। 2222222 पर स्थित सूची में एक तत्व 2 भी शामिल है, लेकिन यह किसी भी आइटम द्वारा इंगित नहीं किया गया है। अधिक। नतीजतन, 3 का एक परिशिष्ट items[1,3] बना देगा ।

उपरोक्त स्पष्टीकरण से, हम देख सकते हैं कि स्वीकार किए गए उत्तर में सुझाए गए effbot वेबपृष्ठ इस प्रश्न का प्रासंगिक उत्तर देने में विफल रहे। क्या अधिक है, मुझे लगता है कि effbot वेबपेज में एक बिंदु गलत है। मुझे लगता है कि UI.Button के बारे में कोड सही है:

for i in range(10):
    def callback():
        print "clicked button", i
    UI.Button("button %s" % i, callback)

प्रत्येक बटन एक अलग कॉलबैक फ़ंक्शन को पकड़ सकता है जो अलग-अलग मूल्य प्रदर्शित करेगा i। मैं यह दिखाने के लिए एक उदाहरण प्रदान कर सकता हूं:

x=[]
for i in range(10):
    def callback():
        print(i)
    x.append(callback) 

यदि हम निष्पादित x[7]()करते हैं तो हमें उम्मीद के मुताबिक 7 मिलेंगे, और x[9]()9, दूसरा मूल्य देगा i


5
आपका अंतिम बिंदु गलत है। यह कोशिश करो और आप देखेंगे कि x[7]()है 9
डंकन

2
"पायथन पास प्राथमिक डेटा प्रकार मान से, यानी, स्थानीय चर के लिए मूल्य की एक स्थानीय प्रतिलिपि बनाएं" पूरी तरह से गलत है। मैं हैरान हूं कि कोई स्पष्ट रूप से अजगर को अच्छी तरह से जान सकता है, फिर भी बुनियादी बातों की इतनी भयानक गलतफहमी है। :-(
Veky

6

TLDR: परिभाषित समय चूक लगातार और सख्ती से अधिक अभिव्यंजक हैं।


परिभाषित करने गुंजाइश: एक समारोह को परिभाषित करना दो स्कोप को प्रभावित करता है युक्त समारोह, और निष्पादन गुंजाइश द्वारा निहित कार्य करते हैं। हालांकि यह स्पष्ट है कि मैप्स को ब्लॉक करने का तरीका, सवाल यह है कि यह कहां def <name>(<args=defaults>):है:

...                           # defining scope
def name(parameter=default):  # ???
    ...                       # execution scope

def nameभाग जाना चाहिए परिभाषित करने दायरे में मूल्यांकन - हम चाहते हैं name, वहाँ उपलब्ध होने की सब के बाद। केवल अपने अंदर के फंक्शन का मूल्यांकन करना इसे दुर्गम बना देगा।

चूंकि parameterएक निरंतर नाम है, हम इसे उसी समय "मूल्यांकन" कर सकते हैं def name। इसका यह भी फायदा है कि यह name(parameter=...):एक नंगे के बजाय एक ज्ञात हस्ताक्षर के साथ फ़ंक्शन का उत्पादन करता है name(...):

अब, कब मूल्यांकन करना है default?

संगति पहले से ही "परिभाषा पर" कहती है: बाकी सब कुछ def <name>(<args=defaults>):सबसे अच्छी तरह से परिभाषा में मूल्यांकन किया गया है। इसके कुछ हिस्सों में देरी करना आश्चर्यजनक विकल्प होगा।

दो विकल्प समान नहीं हैं: या तो default परिभाषा समय पर मूल्यांकन किया जाता है यह अभी भी निष्पादन समय को प्रभावित कर सकता है । यदि defaultनिष्पादन समय पर मूल्यांकन किया जाता है, तो यह परिभाषा समय को प्रभावित नहीं कर सकता है । "परिभाषा पर" चुनना दोनों मामलों को व्यक्त करने की अनुमति देता है, जबकि "निष्पादन पर" चुनना केवल एक को व्यक्त कर सकता है:

def name(parameter=defined):  # set default at definition time
    ...

def name(parameter=default):     # delay default until execution time
    parameter = default if parameter is None else parameter
    ...

"संगति पहले से ही" परिभाषा में "कहती है: बाकी सब कुछ def <name>(<args=defaults>):सबसे अच्छी तरह से परिभाषा में मूल्यांकन किया गया है।" मुझे नहीं लगता कि निष्कर्ष इस आधार से है। सिर्फ इसलिए कि दो चीजें एक ही लाइन पर हैं इसका मतलब यह नहीं है कि उन्हें एक ही दायरे में मूल्यांकन किया जाना चाहिए। defaultबाकी पंक्ति की तुलना में एक अलग बात है: यह एक अभिव्यक्ति है। किसी फ़ंक्शन को परिभाषित करने से एक अभिव्यक्ति का मूल्यांकन एक बहुत अलग प्रक्रिया है।
लार्स

@LarsH समारोह परिभाषाओं कर रहे हैं कर रहे हैं अजगर में मूल्यांकन। चाहे वह एक कथन ( def) या अभिव्यक्ति से हो ( lambda) नहीं बदलता है कि एक फ़ंक्शन बनाने का मतलब मूल्यांकन है - विशेष रूप से उसके हस्ताक्षर। और चूक एक फ़ंक्शन के हस्ताक्षर का हिस्सा हैं। इसका मतलब यह नहीं है कि चूक का तुरंत मूल्यांकन किया जाना चाहिए - उदाहरण के लिए, प्रकार के संकेत नहीं हो सकते हैं। लेकिन यह निश्चित रूप से सुझाव देता है कि उन्हें तब तक करना चाहिए जब तक कि कोई अच्छा कारण न हो।
मिस्टरमियागी

ठीक है, एक फ़ंक्शन बनाने का अर्थ कुछ अर्थों में मूल्यांकन है, लेकिन स्पष्ट रूप से इस अर्थ में नहीं है कि परिभाषा के समय इसके भीतर प्रत्येक अभिव्यक्ति का मूल्यांकन किया जाता है। ज्यादातर नहीं हैं। यह मेरे लिए स्पष्ट नहीं है कि किस अर्थ में हस्ताक्षर विशेष रूप से "मूल्यांकन" किया जाता है, परिभाषा के समय फ़ंक्शन शरीर की तुलना में किसी भी अधिक "मूल्यांकन" (एक उपयुक्त प्रतिनिधित्व में पार्स); जबकि फ़ंक्शन बॉडी में अभिव्यक्तियों को पूर्ण अर्थ में स्पष्ट रूप से मूल्यांकन नहीं किया गया है। इस दृष्टिकोण से, संगति कहेगी कि हस्ताक्षर में अभिव्यक्तियों का "या तो" पूरी तरह से मूल्यांकन नहीं किया जाना चाहिए।
लार्स

मेरा मतलब यह नहीं है कि आप गलत हैं, केवल यह कि आपका निष्कर्ष अकेले निरंतरता से नहीं चलता है।
15

@ लार्स डिफॉल्ट्स न तो शरीर का हिस्सा हैं, न ही मैं यह दावा कर रहा हूं कि निरंतरता एकमात्र मापदंड है। क्या आप एक सुझाव दे सकते हैं कि उत्तर को कैसे स्पष्ट किया जाए?
MisterMiyagi

3

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

हम एक डेकोरेटर के साथ इस व्यवहार को "ठीक" करेंगे जो अपने डिफ़ॉल्ट मान पर छोड़े गए प्रत्येक स्थितीय तर्क के लिए एक ही उदाहरण का पुन: उपयोग करने के बजाय डिफ़ॉल्ट मान को कॉपी करेगा।

import inspect
from copy import copy

def sanify(function):
    def wrapper(*a, **kw):
        # store the default values
        defaults = inspect.getargspec(function).defaults # for python2
        # construct a new argument list
        new_args = []
        for i, arg in enumerate(defaults):
            # allow passing positional arguments
            if i in range(len(a)):
                new_args.append(a[i])
            else:
                # copy the value
                new_args.append(copy(arg))
        return function(*new_args, **kw)
    return wrapper

अब इस डेकोरेटर का उपयोग करके हमारे फ़ंक्शन को फिर से परिभाषित करें:

@sanify
def foo(a=[]):
    a.append(5)
    return a

foo() # '[5]'
foo() # '[5]' -- as desired

यह कई तर्कों को लेने वाले कार्यों के लिए विशेष रूप से साफ है। की तुलना करें:

# the 'correct' approach
def bar(a=None, b=None, c=None):
    if a is None:
        a = []
    if b is None:
        b = []
    if c is None:
        c = []
    # finally do the actual work

साथ में

# the nasty decorator hack
@sanify
def bar(a=[], b=[], c=[]):
    # wow, works right out of the box!

यह ध्यान रखना महत्वपूर्ण है कि यदि आप कीवर्ड args का उपयोग करने का प्रयास करते हैं, तो उपरोक्त समाधान टूट जाता है, जैसे:

foo(a=[4])

डेकोरेटर को इसके लिए अनुमति देने के लिए समायोजित किया जा सकता है, लेकिन हम इसे पाठक के लिए एक अभ्यास के रूप में छोड़ते हैं;)

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