पायथन म्यूटेबल डिफ़ॉल्ट तर्क: क्यों?


20

मुझे पता है कि फ़ंक्शन आरंभीकरण समय पर डिफ़ॉल्ट तर्क बनाए जाते हैं और हर बार फ़ंक्शन को नहीं कहा जाता है। निम्नलिखित कोड देखें:

def ook (item, lst=[]):
    lst.append(item)
    print 'ook', lst

def eek (item, lst=None):
    if lst is None: lst = []
    lst.append(item)
    print 'eek', lst

max = 3
for x in xrange(max):
    ook(x)

for x in xrange(max):
    eek(x)

जो मुझे नहीं मिलता वह इस तरह से लागू किया गया। प्रत्येक कॉल समय पर यह व्यवहार एक लाभ से अधिक क्या लाभ प्रदान करता है?


यह पहले से ही स्टैक ओवरफ्लो पर आश्चर्यजनक विस्तार से चर्चा की गई है : stackoverflow.com/q/1132941/5419599
वाइल्डकार्ड

जवाबों:


14

मुझे लगता है कि इसका कारण कार्यान्वयन सादगी है। मुझे विस्तार से बताएं

फ़ंक्शन का डिफ़ॉल्ट मान एक अभिव्यक्ति है जिसका आपको मूल्यांकन करने की आवश्यकता है। आपके मामले में यह एक सरल अभिव्यक्ति है जो बंद होने पर निर्भर नहीं करता है, लेकिन यह कुछ ऐसा हो सकता है जिसमें मुफ्त चर शामिल हैं - def ook(item, lst = something.defaultList())। यदि आप पायथन को डिजाइन करने के लिए हैं, तो आपके पास एक विकल्प होगा - क्या आप इसे एक बार मूल्यांकन करते हैं जब फ़ंक्शन को परिभाषित किया जाता है या हर बार जब फ़ंक्शन को बुलाया जाता है। पायथन पहले को चुनता है (रूबी के विपरीत, जो दूसरे विकल्प के साथ जाता है)।

इसके लिए कुछ लाभ हैं।

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

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


3
दिलचस्प बिंदु - हालांकि यह आमतौर पर पायथन के साथ कम से कम आश्चर्य का सिद्धांत है। कुछ चीजें एक औपचारिक जटिलता-में-मॉडल के अर्थ में सरल हैं, फिर भी गैर-स्पष्ट और आश्चर्यजनक हैं, और मुझे लगता है कि यह मायने रखता है।
स्टीव ३४

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

स्कोप के बारे में अच्छी बात है
0xc0de

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

5

इसे लगाने का एक तरीका यह है कि पैरामीटर को म्यूट lst.append(item) नहीं किया जाता lstहै। lstअभी भी उसी सूची को संदर्भित करता है। यह सिर्फ इतना है कि उस सूची की सामग्री को बदल दिया गया है।

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

पूर्णांक की तरह, आप किसी संदर्भ को संशोधित नहीं कर सकते, आप केवल इसे बदल सकते हैं। लेकिन आप संदर्भित की जा रही वस्तु की सामग्री को संशोधित कर सकते हैं।

एक बार डिफ़ॉल्ट ऑब्जेक्ट बनाने के लिए, मुझे लगता है कि यह ज्यादातर एक अनुकूलन के रूप में है, ऑब्जेक्ट-निर्माण और कचरा संग्रह ओवरहेड्स को बचाने के लिए।


+1 बिल्कुल। अप्रत्यक्ष की परत को समझना महत्वपूर्ण है - कि एक चर एक मूल्य नहीं है; इसके बजाय यह एक मूल्य का संदर्भ देता है। एक चर को बदलने के लिए, मान को अदला-बदली की जा सकती है , या उत्परिवर्तित किया जा सकता है (यदि यह परिवर्तनशील है)।
जूनस पुलकका

जब अजगर में चर को शामिल करने वाली किसी भी चीज़ का सामना करना पड़ता है, तो मुझे "=" को "नेम-बाइंडिंग ऑपरेटर" के रूप में विचार करने में मदद मिलती है; नाम हमेशा रिबाउंड होता है, चाहे हम जिस चीज से उसे बांध रहे हों वह नई हो (ताजा वस्तु या अपरिवर्तनीय प्रकार का उदाहरण) या नहीं।
StarWeaver

4

प्रत्येक कॉल समय पर यह व्यवहार एक लाभ से अधिक क्या लाभ प्रदान करता है?

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


2

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

a = []
for i in range(3):
    a.append(lambda: i)
print [ f() for f in a ]

जो देता है [2, 2, 2]जहाँ आप उत्पादन के लिए एक सच्चे बंद होने की उम्मीद करेंगे [0, 1, 2]

काफी चीजें हैं जो मुझे पसंद हैं अगर पायथन को क्लोजर में डिफॉल्ट करने वाले पैरामीटर को लपेटने की क्षमता है। उदाहरण के लिए:

def foo(a, b=a.b):
    ...

अत्यंत उपयोगी होगा, लेकिन "a" फ़ंक्शन डेफिनिशन समय पर स्कोप नहीं है, इसलिए आप ऐसा नहीं कर सकते हैं और इसके बजाय क्लॉउन करना होगा:

def foo(a, b=None):
    if b is None:
        b = a.b

जो लगभग एक ही बात है ... लगभग।



1

एक बड़ा लाभ संस्मरण है। यह एक मानक उदाहरण है:

def fibmem(a, cache={0:1,1:1}):
    if a in cache: return cache[a]
    res = fib(a-1, cache) + fib(a-2, cache)
    cache[a] = res
    return res

और तुलना के लिए:

def fib(a):
    if a == 0 or a == 1: return 1
    return fib(a-1) + fib(a-2)

आईपीथॉन में समय मापन:

In [43]: %time print(fibmem(33))
5702887
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 200 µs

In [43]: %time print(fib(33))
5702887
CPU times: user 1.44 s, sys: 15.6 ms, total: 1.45 s
Wall time: 1.43 s

0

ऐसा इसलिए होता है क्योंकि पायथन में वर्णनात्मक कोड निष्पादित करके प्रदर्शन किया जाता है।

अगर एक ने कहा

def f(x = {}):
    ....

यह बहुत स्पष्ट होगा कि आप हर बार एक नई सरणी चाहते थे।

लेकिन अगर मैं कहूं तो:

list_of_all = {}
def create(stuff, x = list_of_all):
    ...

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

लेकिन कंपाइलर इसका अनुमान कैसे लगाएगा? तो कोशिश क्यों? हम इस पर भरोसा कर सकते थे कि यह नाम दिया गया था या नहीं, और यह कभी-कभी मदद कर सकता है, लेकिन वास्तव में यह सिर्फ अनुमान होगा। इसी समय, एक अच्छा कारण है कि कोशिश न करना - स्थिरता।

जैसा कि है, पायथन सिर्फ कोड निष्पादित करता है। चर list_of_all को पहले से ही एक ऑब्जेक्ट सौंपा गया है, ताकि ऑब्जेक्ट को कोड में संदर्भ द्वारा पारित किया जाए जो x को उसी तरह से डिफॉल्ट करता है जिससे किसी फ़ंक्शन को कॉल करने पर यहां नामित स्थानीय ऑब्जेक्ट का संदर्भ मिलेगा।

यदि हम नामांकित मामले से अनाम को अलग करना चाहते हैं, तो यह कोड को चलाने के समय में निष्पादित किए जाने की तुलना में काफी अलग तरीके से संकलन निष्पादन कार्य में शामिल करेगा। इसलिए हम विशेष मामला नहीं बनाते हैं।


-5

ऐसा इसलिए होता है क्योंकि पायथन में कार्य प्रथम श्रेणी की वस्तुएं हैं :

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

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

जिसका अर्थ है कि def foo(l=[])कॉल करने पर उस फ़ंक्शन का एक उदाहरण बन जाता है, और आगे की कॉल के लिए पुन: उपयोग किया जाता है। एक वस्तु की विशेषताओं के अतिरिक्त कार्य मापदंडों के बारे में सोचें।

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

class Foo(object):
    def bar(self, l=None):
        if not l:
            l = []
        l.append(5)
        return l

f = Foo()
print(f.bar())
print(f.bar())

g = Foo()
print(g.bar())
print(g.bar())

पैदावार:

[५] [५] [५] [५]

अप्रत्याशित के बजाय:

[५] [५, ५] [५, ५, ५] [५, ५, ५, ५]


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

1
तो फिर हमारे साथ इस जानकारी को साझा करें कि प्रत्येक कॉल के लिए डिफ़ॉल्ट arg का मूल्यांकन करने के लिए कार्यों को कैसे परिभाषित किया जाए, मैं पाइथन डॉक्स की सिफारिश करने की तुलना में एक सरल तरीका जानना चाहता हूं ।
पलटना

2
एक भाषा डिजाइन स्तर पर मेरा मतलब है। पायथन भाषा की परिभाषा में वर्तमान में कहा गया है कि डिफ़ॉल्ट तर्कों को जिस तरह से व्यवहार किया जाता है; यह समान रूप से अच्छी तरह से बता सकता है कि डिफ़ॉल्ट तर्कों को किसी अन्य तरीके से व्यवहार किया जाता है। IOW आप जवाब दे रहे हैं "यह है कि चीजें" कैसे हैं "सवाल" क्यों चीजें हैं जिस तरह से वे हैं "।

कॉफ़ी कैसे करता है, इसके समान ही पायथन डिफ़ॉल्ट पैरामीटर लागू कर सकता था। यह लापता मापदंडों की जांच करने के लिए बाइटकोड को सम्मिलित करेगा, और यदि वे लापता थे तो अभिव्यक्ति का मूल्यांकन करेंगे।
विंस्टन इवर्ट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.