"कम से कम विस्मय" और म्यूटेबल डिफ़ॉल्ट तर्क
पायथन के साथ लंबे समय से छेड़छाड़ करने वाले किसी भी व्यक्ति को निम्नलिखित मुद्दे से काट दिया गया है (या टुकड़े टुकड़े करना):
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
लाइन इस अर्थ में "हाइब्रिड" होगी कि बंधन का हिस्सा ( फ़ंक्शन ऑब्जेक्ट) परिभाषा, और फ़ंक्शन इनवोकेशन समय पर (डिफ़ॉल्ट पैरामीटर का असाइनमेंट) पर होगा।
वास्तविक व्यवहार अधिक सुसंगत है: उस रेखा के निष्पादित होने पर उस पंक्ति की हर चीज का मूल्यांकन किया जाता है, जिसका अर्थ फ़ंक्शन परिभाषा है।
जवाब
दरअसल, यह एक डिज़ाइन दोष नहीं है, और यह आंतरिक, या प्रदर्शन के कारण नहीं है।
यह बस इस तथ्य से आता है कि पायथन में कार्य प्रथम श्रेणी की वस्तुएं हैं, और न केवल कोड का एक टुकड़ा।
जैसे ही आप इस तरह से सोचते हैं, तो यह पूरी तरह से समझ में आता है: एक फ़ंक्शन एक ऐसी वस्तु है जिसका मूल्यांकन इसकी परिभाषा पर किया जा रहा है; डिफ़ॉल्ट पैरामीटर "सदस्य डेटा" की तरह होते हैं और इसलिए उनका राज्य एक कॉल से दूसरे कॉल में बदल सकता है - बिल्कुल किसी अन्य ऑब्जेक्ट की तरह।
किसी भी स्थिति में, पायथन में डिफ़ॉल्ट पैरामीटर मानों में इस व्यवहार के कारणों का एक बहुत अच्छा विवरण है ।
मुझे यह बहुत स्पष्ट लगा, और मैं वास्तव में इसे बेहतर ज्ञान के लिए पढ़ने का सुझाव देता हूं कि फ़ंक्शन ऑब्जेक्ट कैसे काम करते हैं।
मान लें कि आपके पास निम्नलिखित कोड है
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
उपयोग करने की कोशिश की है, उसी के समान, जिसे वे इसमें डालते हैं, या वह व्यक्ति जो अपनी वस्तु को प्राप्त नहीं कर सकता है, भले ही वे जिस कुंजी का उपयोग कर रहे हों वह वस्तुतः वही वस्तु हो। इसका उपयोग इसे मानचित्र में करने के लिए किया गया था (यह वास्तव में यही कारण है कि पायथन अपने परस्पर निर्मित डेटा प्रकारों को शब्दकोश कुंजियों के रूप में उपयोग करने की अनुमति नहीं देता है)।
आपका उदाहरण एक अच्छा मामला है जहां पायथन नए लोगों को आश्चर्यचकित और काट देगा। लेकिन मेरा तर्क है कि अगर हम इसे "ठीक" कर देते हैं, तो यह केवल एक अलग स्थिति पैदा करेगा जहां उन्हें इसके बजाय काट दिया जाएगा, और यह भी कम सहज होगा। इसके अलावा, यह हमेशा परिवर्तनशील चर के साथ काम करते समय होता है; आप हमेशा ऐसे मामलों में भाग लेते हैं, जहां कोई व्यक्ति सहजता से एक या विपरीत व्यवहार की उम्मीद कर सकता है कि वे किस कोड पर लिख रहे हैं।
मैं व्यक्तिगत रूप से पायथन के वर्तमान दृष्टिकोण को पसंद करता हूं: फ़ंक्शन को परिभाषित करते समय डिफ़ॉल्ट फ़ंक्शन तर्क का मूल्यांकन किया जाता है और वह ऑब्जेक्ट हमेशा डिफ़ॉल्ट होता है। मुझे लगता है कि वे एक खाली सूची का उपयोग करके विशेष-मामला कर सकते थे, लेकिन उस तरह के विशेष आवरण के कारण और भी विस्मय होगा, जिसका उल्लेख पीछे की ओर असंगत नहीं होना चाहिए।
प्रलेखन का प्रासंगिक हिस्सा :
फ़ंक्शन परिभाषा निष्पादित होने पर डिफ़ॉल्ट पैरामीटर मानों को बाएं से दाएं पर मूल्यांकन किया जाता है। इसका मतलब है कि अभिव्यक्ति का मूल्यांकन एक बार किया जाता है, जब फ़ंक्शन को परिभाषित किया जाता है, और प्रत्येक कॉल के लिए समान "पूर्व-गणना" मूल्य का उपयोग किया जाता है। यह समझना विशेष रूप से महत्वपूर्ण है जब एक डिफ़ॉल्ट पैरामीटर एक परिवर्तनशील वस्तु है, जैसे कि एक सूची या एक शब्दकोश: यदि फ़ंक्शन ऑब्जेक्ट को संशोधित करता है (जैसे कि किसी सूची में किसी आइटम को जोड़कर), तो डिफ़ॉल्ट मान प्रभावी रूप से संशोधित होता है। यह आम तौर पर नहीं था कि क्या इरादा था। इसका एक तरीका
None
डिफ़ॉल्ट के रूप में उपयोग करना है, और फ़ंक्शन के शरीर में इसके लिए स्पष्ट रूप से परीक्षण करना है, जैसे:def whats_on_the_telly(penguin=None): if penguin is None: penguin = [] penguin.append("property of the zoo") return penguin
मुझे पता है कि पायथन दुभाषिया आंतरिक कामकाज के बारे में कुछ भी नहीं है (और मैं कंपाइलर और दुभाषियों में विशेषज्ञ नहीं हूं) तो मुझे दोष न दें अगर मैं कुछ भी असंभावना या असंभव का प्रस्ताव करता हूं।
बशर्ते कि अजगर की वस्तुएं आपस में जुड़ी हों, मुझे लगता है कि डिफ़ॉल्ट तर्क सामग्री को डिजाइन करते समय इस पर ध्यान दिया जाना चाहिए। जब आप किसी सूची को त्वरित करते हैं:
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):
ठीक है, कारण काफी सरल है कि जब कोड निष्पादित किया जाता है, तो बाइंडिंग की जाती है, और फ़ंक्शन परिभाषा को निष्पादित किया जाता है, ठीक है ... जब फ़ंक्शन परिभाषित होता है।
इसकी तुलना करें:
class BananaBunch:
bananas = []
def addBanana(self, banana):
self.bananas.append(banana)
यह कोड ठीक उसी अप्रत्याशित घटना से ग्रस्त है। केले एक वर्ग विशेषता है, और इसलिए, जब आप इसमें चीजें जोड़ते हैं, तो यह उस वर्ग के सभी उदाहरणों में जोड़ा जाता है। कारण बिल्कुल एक जैसा है।
यह सिर्फ "हाउ इट वर्क्स" है, और इसे फंक्शन के मामले में अलग तरीके से काम करना संभवत: जटिल होगा, और क्लास केस में असंभव होने की संभावना है, या कम से कम ऑब्जेक्ट इंस्टेंशन को बहुत धीमा कर देगा, क्योंकि आपको क्लास कोड रखना होगा। और ऑब्जेक्ट्स बनाए जाने पर इसे निष्पादित करें।
हां, यह अप्रत्याशित है। लेकिन एक बार पैसा गिर जाता है, यह पूरी तरह से फिट बैठता है कि अजगर सामान्य रूप से कैसे काम करता है। वास्तव में, यह एक अच्छी शिक्षण सहायता है, और एक बार जब आप समझ जाते हैं कि ऐसा क्यों होता है, तो आप अजगर को बेहतर तरीके से समझेंगे।
यह किसी भी अच्छे पायथन ट्यूटोरियल में प्रमुखता से होना चाहिए। क्योंकि जैसा कि आप उल्लेख करते हैं, हर कोई इस समस्या में जल्द या बाद में भागता है।
आप आत्मनिरीक्षण क्यों नहीं करते?
मैं वास्तव में आश्चर्यचकित हूं कि किसी ने कॉलबॉल्स पर पायथन ( 2
और 3
लागू) द्वारा पेश किए गए आनंददायक आत्मनिरीक्षण का प्रदर्शन नहीं किया है ।
दिए गए एक साधारण छोटे कार्य को func
निम्न प्रकार से दिया गया है:
>>> def func(a = []):
... a.append(5)
जब पायथन इसका सामना करता है, तो पहली चीज जो यह करेगी वह code
इस फ़ंक्शन के लिए एक ऑब्जेक्ट बनाने के लिए इसे संकलित करता है। जबकि यह संकलन कदम किया जाता है, पायथन * का मूल्यांकन करता है और फिर फ़ंक्शन ऑब्जेक्ट में डिफ़ॉल्ट तर्क (यहां एक खाली सूची ) संग्रहीत करता []
है । जैसा कि शीर्ष उत्तर में उल्लेख किया गया है: सूची a
को अब फ़ंक्शन का सदस्य माना जा सकता है func
।
तो, चलो कुछ आत्मनिरीक्षण करते हैं, पहले और बाद में यह जांचने के लिए कि फ़ंक्शन ऑब्जेक्ट के अंदर सूची का विस्तार कैसे होता है। मैं इसके लिए उपयोग कर रहा हूँ Python 3.x
, Python 2 के लिए समान लागू होता है (उपयोग __defaults__
या func_defaults
Python 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. प्रदर्शन
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=[]
हालाँकि, हम आत्मनिरीक्षण करने की क्षमता खो चुके हैं, और देखते हैं कि डिफ़ॉल्ट तर्क क्या हैं । क्योंकि वस्तुओं का निर्माण नहीं किया गया है, हम वास्तव में फ़ंक्शन को कॉल किए बिना कभी भी उन्हें पकड़ नहीं सकते हैं। सबसे अच्छा हम कर सकते हैं कि स्रोत कोड को स्टोर करें और इसे एक स्ट्रिंग के रूप में लौटाएं।
पायथन की रक्षा में 5 अंक
सरलता : व्यवहार निम्नलिखित अर्थों में सरल है: अधिकांश लोग इस जाल में केवल एक बार गिरते हैं, कई बार नहीं।
संगति : पायथन हमेशा वस्तुओं को पारित करता है, नामों को नहीं। डिफ़ॉल्ट पैरामीटर, जाहिर है, फ़ंक्शन हेडिंग का हिस्सा है (फ़ंक्शन बॉडी नहीं)। इसलिए इसे मॉड्यूल लोड समय पर मूल्यांकन किया जाना चाहिए (और केवल मॉड्यूल लोड समय पर, जब तक नेस्ट नहीं किया जाता है), फ़ंक्शन कॉल समय पर नहीं।
उपयोगिता : जैसा कि फ्रेडरिक लुंड "पायथन में डिफ़ॉल्ट पैरामीटर मान" की अपनी व्याख्या में बताते हैं , वर्तमान व्यवहार उन्नत प्रोग्रामिंग के लिए काफी उपयोगी हो सकता है। (किफायत से इस्तेमाल करो।)
पर्याप्त दस्तावेज : सबसे बुनियादी पायथन प्रलेखन में, ट्यूटोरियल, इस मुद्दे को जोर से "महत्वपूर्ण कार्यों को परिभाषित करने वाले खंड " के पहले उपधारा में "महत्वपूर्ण चेतावनी" के रूप में घोषित किया गया है । चेतावनी यहां तक कि बोल्डफेस का उपयोग करती है, जो शायद ही कभी शीर्षकों के बाहर लागू होती है। RTFM: ठीक मैनुअल पढ़ें।
मेटा-लर्निंग : जाल में गिरना वास्तव में एक बहुत ही उपयोगी क्षण है (कम से कम यदि आप एक चिंतनशील शिक्षार्थी हैं), क्योंकि आप बाद में ऊपर दिए गए बिंदु "संगति" को बेहतर ढंग से समझ पाएंगे और यह आपको पायथन के बारे में बहुत कुछ सिखाएगा।
इस व्यवहार को आसान तरीके से समझाया गया है:
- फ़ंक्शन (वर्ग आदि) घोषणा केवल एक बार निष्पादित की जाती है, सभी डिफ़ॉल्ट मान ऑब्जेक्ट बनाते हैं
- सब कुछ संदर्भ द्वारा पारित किया गया है
इसलिए:
def x(a=0, b=[], c=[], d=0):
a = a + 1
b = b + [1]
c.append(1)
print a, b, c
a
बदलता नहीं है - प्रत्येक असाइनमेंट कॉल नई int ऑब्जेक्ट बनाता है - नई ऑब्जेक्ट मुद्रित होती हैb
परिवर्तित नहीं होता है - डिफ़ॉल्ट मान और मुद्रित से नए सरणी का निर्माण होता हैc
परिवर्तन - ऑपरेशन एक ही वस्तु पर किया जाता है - और यह मुद्रित होता है
आप यह क्यों पूछ रहे हैं:
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) "म्यूटेबल डिफॉल्ट आर्ग्यूमेंट" की तथाकथित समस्या सामान्य रूप से एक विशेष उदाहरण है जो यह प्रदर्शित करता है कि:
"इस समस्या वाले सभी कार्य वास्तविक पैरामीटर पर समान साइड इफेक्ट समस्या से ग्रस्त हैं ,"
जो कि कार्यात्मक प्रोग्रामिंग के नियमों के खिलाफ है, आमतौर पर अवांछनीय है और दोनों को एक साथ तय किया जाना चाहिए।
उदाहरण:
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) कुछ मामलों में डिफ़ॉल्ट मापदंडों का परस्पर व्यवहार उपयोगी है ।
इसका वास्तव में डिफ़ॉल्ट मानों से कोई लेना-देना नहीं है, इसके अलावा यह अक्सर एक अप्रत्याशित व्यवहार के रूप में सामने आता है जब आप परिवर्तनशील डिफ़ॉल्ट मानों के साथ फ़ंक्शन लिखते हैं।
>>> 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 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
यह एक प्रदर्शन अनुकूलन है। इस कार्यक्षमता के परिणामस्वरूप, आपको लगता है कि इन दो फ़ंक्शन कॉल में से कौन सा तेज़ है?
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
#
२
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(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
सबसे छोटा उत्तर शायद "परिभाषा निष्पादन" है, इसलिए पूरे तर्क का कोई मतलब नहीं है। एक और अधिक उदाहरण के रूप में, आप इसका हवाला दे सकते हैं:
def a(): return []
def b(x=a()):
print x
उम्मीद है कि यह दिखाने के लिए पर्याप्त है कि def
कथन के निष्पादन के समय डिफ़ॉल्ट तर्क अभिव्यक्तियों को निष्पादित करना आसान नहीं है या समझ में नहीं आता है, या दोनों।
जब आप डिफ़ॉल्ट कंस्ट्रक्टर का उपयोग करने का प्रयास करते हैं तो मैं मानता हूं कि यह एक गोच है।
यदि आप निम्नलिखित बातों को ध्यान में रखते हैं तो यह व्यवहार आश्चर्यजनक नहीं है:
- असाइनमेंट के प्रयासों पर रीड-ओनली क्लास विशेषताओं का व्यवहार, और वह
- कार्य ऑब्जेक्ट हैं (स्वीकृत उत्तर में अच्छी तरह से समझाया गया है)।
इस थ्रेड में (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 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]
मैं एक फ़ंक्शन के लिए डिफ़ॉल्ट सूची मान पास करने के लिए एक वैकल्पिक संरचना प्रदर्शित करने जा रहा हूं (यह शब्दकोशों के साथ समान रूप से अच्छी तरह से काम करता है)।
जैसा कि अन्य लोगों ने बड़े पैमाने पर टिप्पणी की है, सूची पैरामीटर फ़ंक्शन के लिए बाध्य है जब इसे निष्पादित होने पर विरोध के रूप में परिभाषित किया जाता है। चूँकि सूचियाँ और शब्दकोश परस्पर भिन्न हैं, इसलिए इस पैरामीटर में कोई भी परिवर्तन इस फ़ंक्शन के अन्य कॉल को प्रभावित करेगा। नतीजतन, फ़ंक्शन को बाद की कॉलें इस साझा सूची को प्राप्त करेंगी जो फ़ंक्शन के किसी भी अन्य कॉल द्वारा बदल दी गई हो सकती है। इससे भी बदतर, दो पैरामीटर एक ही समय में इस फ़ंक्शन के साझा पैरामीटर का उपयोग दूसरे द्वारा किए गए परिवर्तनों से अनजान हैं।
गलत तरीका (शायद ...) :
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]
'गलत पद्धति' के लिए वैध उपयोग के मामले हो सकते हैं जिससे प्रोग्रामर ने डिफ़ॉल्ट सूची पैरामीटर को साझा करने का इरादा किया है, लेकिन यह नियम की तुलना में अधिक संभावना है।
यहाँ समाधान हैं:
None
अपने डिफ़ॉल्ट मान (या एक गैरobject
) के रूप में उपयोग करें , और रनटाइम पर अपने मूल्यों को बनाने के लिए उस पर स्विच करें; याlambda
अपने डिफ़ॉल्ट पैरामीटर के रूप में उपयोग करें , और इसे डिफ़ॉल्ट मान प्राप्त करने के लिए एक कोशिश ब्लॉक के भीतर कॉल करें (यह इस तरह की चीज है कि लैम्ब्डा एब्स्ट्रक्शन के लिए है)।
दूसरा विकल्प अच्छा है क्योंकि फ़ंक्शन के उपयोगकर्ता एक कॉल करने योग्य में गुजर सकते हैं, जो पहले से मौजूद हो सकता है (जैसे कि type
)
आप ऑब्जेक्ट के स्थान पर इसे गोल कर सकते हैं (और इसलिए गुंजाइश के साथ टाई):
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]
मैं कभी-कभी इस व्यवहार का निम्न पैटर्न के विकल्प के रूप में शोषण करता हूं:
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 बनाते हैं, लेकिन 3 नहीं, या बिंदु 3 और नीचे बिंदु 1 और 2 बनाते हैं। लेकिन तीनों सत्य हैं।
यह सच हो सकता है कि घोड़ों को मिडस्ट्रीम में बदलना महत्वपूर्ण टूटना होगा, और यह कि स्टीफन के शुरुआती स्निपेट को सहज रूप से संभालने के लिए अजगर को बदलकर और अधिक समस्याएं पैदा हो सकती हैं। और यह सच हो सकता है कि जो कोई पायथन इंटर्न को अच्छी तरह से जानता था, वह परिणामों की एक खान की व्याख्या कर सकता है। हालाँकि,
मौजूदा व्यवहार pythonic नहीं है, और अजगर सफल हुआ क्योंकि बहुत कम भाषा के बारे में कम से कम विस्मय कहीं के सिद्धांत का उल्लंघन करता है के पास इस बुरी तरह से। यह एक वास्तविक समस्या है, कि इसे उखाड़ना बुद्धिमानी होगी या नहीं। यह एक डिजाइन दोष है। यदि आप व्यवहार का पता लगाने की कोशिश करके भाषा को बहुत बेहतर समझते हैं, तो मैं कह सकता हूं कि C ++ यह सब और बहुत कुछ करता है; उदाहरण के लिए, सूक्ष्म सूचक त्रुटियों के लिए आप नेविगेट करके बहुत कुछ सीखते हैं। लेकिन यह पायथोनिक नहीं है: जो लोग इस व्यवहार के सामने दृढ़ता के लिए अजगर की परवाह करते हैं, वे लोग हैं जो भाषा के लिए तैयार हैं क्योंकि पायथन में अन्य भाषा की तुलना में बहुत कम आश्चर्य है। डाबब्लर्स और जिज्ञासु तब पाइथोनिस्टस बन जाते हैं जब वे चकित होते हैं कि किसी काम को करने में कितना कम समय लगता है - न कि एक डिज़ाइन फ़्ल के कारण - मेरा मतलब है, छिपी हुई तर्क पहेली - जो प्रोग्रामर के अंतर्ज्ञान के खिलाफ कटौती करता है जो पाइथन के लिए खींचे जाते हैं क्योंकि यह काम करता है ।
यह एक डिजाइन दोष नहीं है । जो भी इस पर यात्रा करता है वह कुछ गलत कर रहा है।
आपके द्वारा इस समस्या में भाग लेने के 3 मामले हैं:
- आप फ़ंक्शन के साइड इफेक्ट के रूप में तर्क को संशोधित करने का इरादा रखते हैं। इस मामले में यह कभी नहीं होता कि डिफ़ॉल्ट तर्क हो। एकमात्र अपवाद तब होता है जब आप फ़ंक्शन विशेषताओं का उपयोग करने के लिए तर्क सूची का दुरुपयोग कर रहे होते हैं, जैसे
cache={}
, और आपको फ़ंक्शन को वास्तविक तर्क के साथ कॉल करने की उम्मीद नहीं होगी। - आप तर्क को बिना तर्क के छोड़ने का इरादा रखते हैं, लेकिन आपने गलती से इसे संशोधित किया है। यह एक बग है, इसे ठीक करें।
- आप फ़ंक्शन के अंदर उपयोग के लिए तर्क को संशोधित करने का इरादा रखते हैं, लेकिन फ़ंक्शन के बाहर संशोधन देखने योग्य होने की उम्मीद नहीं करता है। उस मामले में आपको तर्क की एक प्रति बनाने की आवश्यकता है , चाहे वह डिफ़ॉल्ट थी या नहीं! अजगर कॉल-बाय-वैल्यू भाषा नहीं है, इसलिए यह आपके लिए कॉपी नहीं बनाता है, आपको इसके बारे में स्पष्ट होना चाहिए।
प्रश्न में उदाहरण श्रेणी 1 या 3 में गिर सकता है। यह विचित्र है कि यह पारित सूची को संशोधित करता है और इसे वापस लौटाता है; आपको एक या दूसरे को चुनना चाहिए।
इस "बग" ने मुझे बहुत सारे ओवरटाइम काम के घंटे दिए! लेकिन मुझे इसका संभावित उपयोग दिखाई देने लगा है (लेकिन मुझे इसका निष्पादन समय पर होना पसंद है, फिर भी)
मैं आपको एक उपयोगी उदाहरण के रूप में जो कुछ देखता हूं वह आपको देने वाला हूं।
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
बस फ़ंक्शन को होने के लिए बदलें:
def notastonishinganymore(a = []):
'''The name is just a joke :)'''
a = a[:]
a.append(5)
return a
मुझे लगता है कि इस प्रश्न का उत्तर यह है कि कैसे अजगर डेटा को पैरामीटर (मान या संदर्भ द्वारा पास) में परिवर्तित करता है, न कि पारस्परिकता या कैसे अजगर "डीफ़" कथन को संभालता है।
एक संक्षिप्त परिचय। सबसे पहले, अजगर में दो प्रकार के डेटा प्रकार होते हैं, एक सरल प्राथमिक डेटा प्रकार है, जैसे संख्याएं, और एक अन्य डेटा प्रकार ऑब्जेक्ट हैं। दूसरा, जब मापदंडों को डेटा पास करते हैं, तो अजगर मूल्य के आधार पर प्राथमिक डेटा प्रकार को पास करता है, अर्थात, मूल्य की एक स्थानीय प्रति एक स्थानीय चर के लिए बनाता है, लेकिन संदर्भ द्वारा वस्तु को पास करता है, अर्थात, ऑब्जेक्ट को इंगित करता है।
उपरोक्त दो बिंदुओं को स्वीकार करते हुए, आइए बताते हैं कि अजगर कोड का क्या हुआ। यह केवल वस्तुओं के लिए संदर्भ से गुजरने के कारण है, लेकिन परस्पर / अपरिवर्तनीय, या निश्चित रूप से इस तथ्य से कोई लेना-देना नहीं है कि "डीफ़" कथन केवल एक बार निष्पादित होने पर निष्पादित किया जाता है।
[] एक वस्तु है, इसलिए अजगर को [] के संदर्भ में पास किया जाता है 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 कहते हैं। सूची की सामग्री अगली कड़ी में foo फ़ंक्शन में 1 से बदल जाती है , लेकिन पता नहीं बदला गया है, फिर भी 11111111 .फिर फू (2, []) आ रहा है। हालाँकि [] in foo (2, []) में डिफ़ॉल्ट पैरामीटर [] के समान ही सामग्री है, जब foo (१) कहते हैं, तो उनका पता अलग होता है! चूंकि हम स्पष्ट रूप से पैरामीटर प्रदान करते हैं, 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
।
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
...
हर दूसरा जवाब बताता है कि यह वास्तव में एक अच्छा और वांछित व्यवहार क्यों है, या आपको वैसे भी इसकी आवश्यकता क्यों नहीं होनी चाहिए। मेरा उन जिद्दी लोगों के लिए है जो अपनी इच्छा से भाषा को मोड़ने के अधिकार का प्रयोग करना चाहते हैं, न कि दूसरे तरीके से।
हम एक डेकोरेटर के साथ इस व्यवहार को "ठीक" करेंगे जो अपने डिफ़ॉल्ट मान पर छोड़े गए प्रत्येक स्थितीय तर्क के लिए एक ही उदाहरण का पुन: उपयोग करने के बजाय डिफ़ॉल्ट मान को कॉपी करेगा।
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])
डेकोरेटर को इसके लिए अनुमति देने के लिए समायोजित किया जा सकता है, लेकिन हम इसे पाठक के लिए एक अभ्यास के रूप में छोड़ते हैं;)