python "कम एस्टोनिश" और म्यूटेबल डिफॉल्ट तर्क




language-design least-astonishment (24)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

इसलिए:

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

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

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

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

मैं आपको एक संकेत दूंगा। यहां disassembly है ( 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

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

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


आप वस्तु को प्रतिस्थापित करके इसे गोल कर सकते हैं (और इसलिए दायरे के साथ टाई):

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

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


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

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]

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

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

गलत विधि (शायद ...) :

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

प्रति ब्रेट स्लैटकिन के "प्रभावी पायथन: बेहतर पायथन लिखने के लिए 5 9 विशेष तरीके", आइटम 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]

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


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

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()

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

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

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


AFAICS ने अभी तक किसी documentation के प्रासंगिक भाग को पोस्ट नहीं किया है:

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


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

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

def eat(food=fruits):
    ...

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

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

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

तो यदि फ़ंक्शन घोषणा के बजाए फ़ंक्शन निष्पादन पर डिफ़ॉल्ट पैरामीटर बाध्य किए गए थे तो फलों को बदल दिया गया था, यह जानने के लिए मैं आश्चर्यचकित हूं (बहुत बुरे तरीके से)। यह पता लगाने से अधिक आश्चर्यजनक आईएमओ होगा कि ऊपर आपका 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 बाहर निकालने का प्रयास किया है, उसमें से किसी एक के समान मूल्य का उपयोग करके, या वह व्यक्ति जो अपनी वस्तु को पुनर्प्राप्त नहीं कर सकता है, भले ही वे जिस कुंजी का उपयोग कर रहे हैं वह सचमुच समान है ऑब्जेक्ट जिसका उपयोग इसे मानचित्र में रखने के लिए किया गया था (यही कारण है कि पाइथन अपने उत्परिवर्तनीय अंतर्निहित डेटा प्रकारों को शब्दकोश कुंजी के रूप में उपयोग करने की अनुमति नहीं देता है)।

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

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


बस कार्य को बदलने के लिए:

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

मैं कभी-कभी निम्नलिखित व्यवहार के विकल्प के रूप में इस व्यवहार का शोषण करता हूं:

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()

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

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


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

1. प्रदर्शन

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

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

2. बाध्य पैरामीटर मजबूर करना

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

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

यह उन कार्यों की एक सूची देता है जो क्रमश: 0,1,2,3 ... लौटाते हैं। यदि व्यवहार बदल जाता है, तो वे 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 उपयोग करके तर्क और डिफ़ॉल्ट के बारे में जानकारी प्राप्त कर सकते हैं, जो

>>> 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. Noneअपने डिफ़ॉल्ट मान (या एक nonce object) के रूप में उपयोग करें , और रनटाइम पर अपने मान बनाने के लिए उस पर स्विच करें; या
  2. lambdaअपने डिफ़ॉल्ट पैरामीटर के रूप में उपयोग करें , और इसे डिफ़ॉल्ट मान प्राप्त करने के लिए प्रयास ब्लॉक के भीतर कॉल करें (यह ऐसी चीज है जो लैम्ब्डा अमूर्तता के लिए है)।

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


किसी का उपयोग कर एक सरल कामकाज

>>> 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]

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

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

यह उपरोक्त दोनों सुविधाओं को पकड़ने के लिए पूरी तरह से संगत है और अभी भी एक और बिंदु बना रहा है:

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

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

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

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


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

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

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

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

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

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


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

def a(): return []

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

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

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


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

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

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

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

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


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

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

class BananaBunch:
    bananas = []

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

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

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

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

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


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

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

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

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


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]"

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

एक समान 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 = [] More..

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


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

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

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

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

समतुल्य कोड:

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

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




least-astonishment