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




language-design least-astonishment (20)

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

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

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

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

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

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

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

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 लाइन बाध्यकारी के हिस्से में "हाइब्रिड" होगी (फ़ंक्शन ऑब्जेक्ट का) फ़ंक्शन आमंत्रण समय पर परिभाषा, और भाग (डिफ़ॉल्ट पैरामीटर का असाइनमेंट) पर होगा।

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


आर्किटेक्चर

फ़ंक्शन कॉल में डिफ़ॉल्ट मान असाइन करना एक कोड गंध है।

def a(b=[]):
    pass

यह एक ऐसे फ़ंक्शन का हस्ताक्षर है जो कि अच्छा नहीं है। न केवल अन्य उत्तरों द्वारा वर्णित समस्याओं के कारण। मैं यहां उसमें नहीं जाऊंगा।

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

दो चीजें करने वाले कार्य खराब कार्य होते हैं, क्योंकि हम स्वच्छ कोड प्रथाओं से सीखते हैं।

पॉलिमॉर्फिज्म के साथ इस समस्या पर हमला करते हुए, हम पाइथन सूची का विस्तार करेंगे या कक्षा में एक को लपेटेंगे, फिर हमारे कार्य को उस पर करें।

लेकिन आप कहते हैं, मुझे एक-लाइनर पसंद है।

अच्छा अंदाजा लगाए। कोड हार्डवेयर के व्यवहार को नियंत्रित करने के लिए सिर्फ एक तरीका से अधिक है। यह एक तरीका है:

  • एक ही कोड पर काम कर रहे अन्य डेवलपर्स के साथ संवाद।

  • नई आवश्यकताएं उत्पन्न होने पर हार्डवेयर के व्यवहार को बदलने में सक्षम होना।

  • ऊपर वर्णित परिवर्तन करने के लिए दो साल बाद कोड को फिर से लेने के बाद कार्यक्रम के प्रवाह को समझने में सक्षम होना।

बाद में लेने के लिए अपने लिए समय-बम मत छोड़ो।

इस कार्य को दो चीजों में अलग करना, हमें कक्षा की आवश्यकता है

class ListNeedsFives(object):
    def __init__(self, b=None):
        if b is None:
            b = []
        self.b = b

    def foo():
        self.b.append(5)

द्वारा सम्पन्न किया गया

a = ListNeedsFives()
a.foo()
a.b

और यह उपर्युक्त कोड को एक ही फ़ंक्शन में मैश करने से बेहतर क्यों है।

def dontdothis(b=None):
    if b is None:
        b = []
    b.append(5)
    return b

ऐसा क्यों नहीं करते?

जब तक आप अपनी परियोजना में असफल न हों, आपका कोड चालू रहेगा। सबसे अधिक संभावना है कि आपका काम इससे अधिक कर देगा। रखरखाव कोड बनाने का उचित तरीका कोड को परमाणु भागों में उचित रूप से सीमित दायरे से अलग करना है।

कक्षा का निर्माता किसी भी व्यक्ति के लिए एक बहुत ही मान्यता प्राप्त घटक है जिसने ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग किया है। कन्स्ट्रक्टर में सूची तात्कालिकता को नियंत्रित करने वाले तर्क को रखने से संज्ञानात्मक भार समझता है कि कोड छोटा क्या करता है।

विधि foo()सूची वापस नहीं करती है, क्यों नहीं?

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

def dontdothis(b=None):हस्ताक्षर समारोह इन फायदों में से कोई भी नहीं है।


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

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

एक साधारण छोटे फ़ंक्शन func परिभाषित किया गया है:

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

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

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

निष्पादन से पहले कार्य:

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

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

>>> func.__defaults__
([],)

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

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

आइए अब इस फ़ंक्शन को निष्पादित करें:

>>> func()

अब, चलो उन __defaults__ फिर से देखें:

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

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

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

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

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

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

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

यह सत्यापित करने के लिए कि __defaults__ में सूची फ़ंक्शन func में उपयोग की तरह ही है, आप फ़ंक्शन बॉडी के अंदर उपयोग की गई सूची की id को वापस करने के लिए बस अपना फ़ंक्शन बदल सकते हैं। फिर, __defaults__ (स्थिति [0] में __defaults__ में सूची में इसकी तुलना करें) और आप देखेंगे कि ये वास्तव में एक ही सूची उदाहरण के लिए कैसे __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 में बाध्य किया जाता है।


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

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


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

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

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

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

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


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

  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 परिवर्तन - ऑपरेशन एक ही ऑब्जेक्ट पर किया जाता है - और यह मुद्रित होता है

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

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

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

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


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

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

a = []

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

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

def x(a=[]):

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

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

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

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

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

def x(static a=b):

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

ऐसे 3 मामले हैं जहां मैं देखता हूं कि आप इस समस्या में कहां भाग सकते हैं:

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

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


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

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

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


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

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

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


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

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 notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a

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

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

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

[] एक वस्तु है, इसलिए अजगर [] से संदर्भित करता है a, यानी, aकेवल एक सूचक है [] जो एक वस्तु के रूप में स्मृति में निहित है। हालांकि, [] की केवल एक प्रति है, इसके संदर्भ में कई संदर्भ हैं। पहले foo () के लिए, सूची [] को विधि संलग्न करके 1 में बदल दिया गया है । लेकिन ध्यान दें कि सूची वस्तु की केवल एक प्रति है और यह ऑब्जेक्ट अब 1 हो जाता है । दूसरा foo () चलाते समय, कौन सा effbot वेबपृष्ठ कहता है (आइटम का मूल्यांकन नहीं किया जाता है) गलत है। aसूची वस्तु होने के लिए मूल्यांकन किया जाता है, हालांकि अब वस्तु की सामग्री 1 है । संदर्भ से गुजरने का यह प्रभाव है! Foo (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 का आह्वान किया जाता है, वस्तुओं का मूल्यांकन किया जाता है (जैसा कि कुछ जवाबों के विपरीत है कि इसे केवल एक बार मूल्यांकन किया जाता है) स्पष्ट नहीं होना चाहिए, किसी का संदर्भ (या पता) स्पष्ट नहीं होना चाहिए। फिर foo में, आइटम को [] में बदल दिया जाता है, यानी, किसी अन्य ऑब्जेक्ट को इंगित करता है जिसमें एक अलग पता होता है।

====== संख्या 3 =======

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

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

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

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

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


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

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. असाइनमेंट प्रयासों पर पढ़ने-योग्य वर्ग विशेषताओं का व्यवहार, और वह
  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

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

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

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


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 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 a(): return []

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

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

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







least-astonishment