2009-10-30 13 views
32

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

कोड इस प्रकार है:

class Node(object): 
    def __init__(self, children = []): 
     self.children = children 

समस्या यह है कि इस तरह के रूप में नोड वर्ग के शेयरों में एक ही children विशेषता, विशेषता को स्पष्ट रूप से नहीं दिया जाता है, तो, के प्रत्येक उदाहरण:

>>> n0 = Node() 
>>> n1 = Node() 
>>> id(n1.children) 
Out[0]: 25000176 
>>> id(n0.children) 
Out[0]: 25000176 

मैं इस डिजाइन के फैसले के तर्क को समझ नहीं पा रहा हूं? पायथन डिजाइनरों ने फैसला क्यों किया कि परिभाषा समय पर डिफ़ॉल्ट तर्कों का मूल्यांकन किया जाना चाहिए? यह मेरे लिए बहुत प्रतिद्वंद्वी लगता है।

+0

मेरा अनुमान प्रदर्शन होगा। कल्पना करें कि हर बार एक समारोह को बुलाया जाता है यदि इसे दिन में 15 मिलियन बार कहा जाता है। –

उत्तर

38

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

def ack(m, n, _memo={}): 
    key = m, n 
    if key not in _memo: 
    if m==0: v = n + 1 
    elif n==0: v = ack(m-1, 1) 
    else: v = ack(m-1, ack(m, n-1)) 
    _memo[key] = v 
    return _memo[key] 

... उपरोक्त की तरह एक ज्ञापन कार्य लिखना काफी प्राथमिक कार्य है।इसी तरह:

for i in range(len(buttons)): 
    buttons[i].onclick(lambda i=i: say('button %s', i)) 

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

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

दूसरे शब्दों में, "ऐसा होना चाहिए, और अधिमानतः केवल एक, इसे करने का स्पष्ट तरीका [1]": जब आप देर से बाध्यकारी चाहते हैं, तो इसे प्राप्त करने के लिए पहले से ही एक बिल्कुल स्पष्ट तरीका है (क्योंकि सभी कार्य कोड केवल कॉल समय पर निष्पादित किया जाता है, जाहिर है कि का मूल्यांकन किया गया सब कुछ देर से बाध्य है); प्रारंभिक बाध्यकारी उत्पादन डिफ़ॉल्ट डिफेंस मूल्यांकन करने से आपको देर से बाध्यकारी होने के लिए दो स्पष्ट तरीकों और प्रारंभिक बाध्यकारी (एक ऋण!) प्राप्त करने के लिए कोई स्पष्ट तरीका देने के बजाय प्रारंभिक बाध्यकारी (प्लस!) प्राप्त करने का एक स्पष्ट तरीका मिलता है।

[1]: "हालांकि यह तरीका तब तक स्पष्ट नहीं हो सकता जब तक कि आप डच न हों।"

+0

उत्कृष्ट उत्तर, मेरे द्वारा +1 करती हैं। एक बहुत ही मामूली टाइपो: यह एक प्रमुख अंडरस्कोर के साथ _memo [key] वापस होना चाहिए। – Francesco

+0

@ फ्रांसेस्को, टाइपो को इंगित करने के लिए टीएक्स (और मैं कल्पना करता हूं कि इसे तुरंत ठीक करने के लिए tx @novelocrat! -)। –

+0

क्या देरी मूल्यांकन के बजाय गहरे रंग के मामले में ओवरहेड अभी भी निषिद्ध होगा? –

0

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

public static void foo() { bar(); } 
public static void main(String[] args) { foo(); } 
public static void bar() {} 

लेकिन अजगर में

def foo(): bar() 
foo() # boom! "bar" has no binding yet 
def bar(): pass 
foo() # ok 

तो, डिफ़ॉल्ट तर्क पल में मूल्यांकन किया जाता है कि कोड की है कि रेखा मूल्यांकन किया जाता है!

+1

खराब समानता। आपके जावा नमूने के बराबर पाइथोनिक 'अगर __name__ ==' __main__ 'है: मुख्य() 'फ़ाइल के अंत में – hasen

7

इस के लिए वैकल्पिक हल, discussed here (और बहुत ठोस), है:

class Node(object): 
    def __init__(self, children = None): 
     self.children = [] if children is None else children 

क्यों वॉन Löwis से एक जवाब के लिए देखो के रूप में, लेकिन यह की वजह से क्योंकि समारोह परिभाषा एक कोड वस्तु बना देता है की संभावना है पायथन की वास्तुकला, और डिफ़ॉल्ट तर्कों में संदर्भ प्रकारों के साथ काम करने की सुविधा नहीं हो सकती है।

+1

हाय जेड, कुछ (दुर्लभ) समस्या हो सकती है जब [] के अलावा अन्य इनपुट का मूल्यांकन हो सकता है असत्य। फिर एक वैध इनपुट को [] में बदल दिया जा सकता है। बेशक यह तब तक नहीं हो सकता जब तक कि चिलरेन एक सूची होनी चाहिए। – Juergen

+0

... भूल गए: अधिक सामान्य "यदि बच्चे कोई नहीं हैं ..." – Juergen

+0

"अगर बच्चे कोई नहीं हैं: बच्चे = []" ("self.children = children" के बाद) समकक्ष (लगभग- - अलग-अलग मूल्य अलग होंगे) और बहुत अधिक पठनीय। –

7

बेशक आपकी स्थिति में यह समझना मुश्किल है। लेकिन आपको देखना होगा कि डिफ़ॉल्ट तर्कों का मूल्यांकन हर बार सिस्टम पर भारी रनटाइम बोझ डालेगा।

इसके अलावा आपको पता होना चाहिए, कि कंटेनर प्रकार के मामले में इस समस्या तब हो सकती है - लेकिन आप बात स्पष्ट करके इसे दरकिनार कर सकते हैं:

def __init__(self, children = None): 
    if children is None: 
     children = [] 
    self.children = children 
+3

आप इसे if.s कथन होने के बजाय 'self.children = बच्चे या []' को भी छोटा कर सकते हैं। –

+0

यदि मैं इसे (बच्चों = कोई नहीं) कहता हूं तो क्या होगा। फिर यह गलत तरीके से बच्चों को बना देगा = []। इसे ठीक करने के लिए एक सेंटीनेल मान का उपयोग करने की आवश्यकता होगी। –

+0

इस मामले में मैंने चुपचाप मान लिया कि कोई भी उचित प्रेषित मूल्य नहीं है। बेशक, यदि कोई भी वैध मूल्य नहीं हो सकता है (बच्चों के मामले में (संभवतः चीजों की एक सूची) संभावना नहीं है), एक अलग प्रेषित मूल्य का उपयोग किया जाना चाहिए। यदि कोई मानक मान मौजूद नहीं है, तो इसके लिए विशेष रूप से बनाए गए ऑब्जेक्ट का उपयोग करें। गोलाकारता डरावनी – Juergen

4

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

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

funcs = [] 
for x in xrange(5): 
    def foo(x=x, lst=[]): 
     lst.append(x) 
     return lst 
    funcs.append(foo) 
for func in funcs: 
    print "1: ", func() 
    print "2: ", func() 

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

1: [0] 
2: [0, 0] 
1: [1] 
2: [1, 1] 
1: [2] 
2: [2, 2] 
1: [3] 
2: [3, 3] 
1: [4] 
2: [4, 4] 

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

संपादित करने के लिए: अधिक चर्चा के लिए इस पर, यहाँ effbot के आलेख देखें: http://effbot.org/zone/default-values.htm, और भाषा संदर्भ, यहाँ: http://docs.python.org/reference/compound_stmts.html#function

10

मुद्दा यह है।

फ़ंक्शन को नामक हर बार प्रारंभकर्ता के रूप में फ़ंक्शन का मूल्यांकन करना बहुत महंगा है।

  • 0 एक साधारण शाब्दिक है। इसे एक बार मूल्यांकन करें, इसे हमेशा के लिए उपयोग करें।

  • int एक फ़ंक्शन (जैसे सूची) है जिसे प्रत्येक बार प्रारंभकर्ता के रूप में आवश्यक होने पर मूल्यांकन करना होगा।

निर्माण [] शाब्दिक है, 0 की तरह है, जिसका अर्थ है कि "इस सटीक वस्तु"।

समस्या यह है कि कुछ लोगों को उम्मीद है कि इसका मतलब है कि list "मेरे लिए इस फ़ंक्शन का मूल्यांकन करें, कृपया, प्रारंभकर्ता ऑब्जेक्ट प्राप्त करने के लिए"।

यह मूल्यांकन हर समय मूल्यांकन करने के लिए आवश्यक if कथन जोड़ने के लिए एक क्रशिंग बोझ होगा। फ़ंक्शन मूल्यांकन करने की कोशिश करने के हिस्से के रूप में सभी तर्कों को अक्षर के रूप में लेना बेहतर है और कोई अतिरिक्त फ़ंक्शन मूल्यांकन नहीं करना बेहतर है।

इसके अलावा, अधिक मौलिक रूप से, यह तकनीकी मूल्यांकन असंभव फ़ंक्शन मूल्यांकन के रूप में तर्क डिफ़ॉल्ट को लागू करने के लिए है।

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

[इस तरह collections.defaultdict काम करता है समानांतर होगी।]

def aFunc(a=another_func): 
    return a*2 

def another_func(b=aFunc): 
    return b*3 

another_func() का मूल्य क्या है? b के लिए डिफ़ॉल्ट प्राप्त करने के लिए, इसे aFunc का मूल्यांकन करना होगा, जिसके लिए another_func का एक eval आवश्यक है। उफ़।

+0

+1 मुझे "यह महंगा होगा" भाग मिलता है, लेकिन "यह असंभव है" भाग मुझे यह नहीं मिलता है। यह असंभव नहीं हो सकता है जब अन्य व्याख्या की गई गतिशील भाषाएं हैं जो – hasen

+3

के लिए –

5

मैंने सोचा कि यह भी counterintuitive था, जब तक कि मैंने सीखा कि पाइथन डिफ़ॉल्ट तर्क लागू करता है।

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

उदाहरण के लिए:

>>> class C(): 
     pass 

>>> def f(x=C()): 
     pass 

>>> f.func_defaults 
(<__main__.C instance at 0x0298D4B8>,) 

तो f के लिए सभी कॉल कि एक तर्क C का एक ही उदाहरण का उपयोग करेगा प्रदान नहीं करते हैं, क्योंकि उस डिफ़ॉल्ट मान है।

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

वास्तविक व्यवहार वास्तव में सरल है। और वहाँ एक छोटी सी वैकल्पिक हल, मामले में जहां कोई डिफ़ॉल्ट मान चाहते क्रम में आयोजित एक समारोह कॉल द्वारा उत्पादित किया जा रहा है:

def f(x = None): 
    if x == None: 
     x = g() 
0

अगर वे किया था, तो किसी से प्रश्न पूछने कारण है कि यह 'नहीं था पोस्ट होगा क्योंकि टी के आसपास दूसरी तरफ: -पी

मान लीजिए कि उनके पास अब था। यदि आवश्यक हो तो आप वर्तमान व्यवहार को कैसे लागू करेंगे?फ़ंक्शन के अंदर नई ऑब्जेक्ट्स बनाना आसान है, लेकिन आप उन्हें "एकजुट नहीं कर सकते" (आप उन्हें हटा सकते हैं, लेकिन यह वही नहीं है)।

संबंधित मुद्दे

 संबंधित मुद्दे