परिवर्तनशील फ़ंक्शन तर्क डिफ़ॉल्ट मानों के लिए अच्छा उपयोग?


84

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

>>> def bad_append(new_item, a_list=[]):
        a_list.append(new_item)
        return a_list
>>> print bad_append('one')
['one']
>>> print bad_append('two')
['one', 'two']

ऐसा क्यों होता है इसकी व्याख्या यहाँ दी गई है

और अब मेरे सवाल के लिए: क्या इस वाक्यविन्यास के लिए एक अच्छा उपयोग-मामला है?

मेरा मतलब है, अगर हर कोई जो इसका सामना करता है, वही गलती करता है, इसे डिबग करता है, मुद्दे को समझता है और उसमें से बचने की कोशिश करता है, ऐसे सिंटैक्स के लिए क्या उपयोग है?


1
इसके लिए मुझे जो सबसे अच्छा विवरण पता है, वह जुड़े हुए प्रश्न में है: फ़ंक्शन प्रथम श्रेणी की वस्तुएं हैं, जैसे कक्षाएं। कक्षाओं में परिवर्तनशील विशेषता डेटा है; फ़ंक्शंस में परिवर्तनशील डिफ़ॉल्ट मान होते हैं।
कातिल

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

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

1
एफडब्ल्यूआईडब्ल्यू मैं प्रश्न के उत्तर में उनका उपयोग करता हूं एक फ़ंक्शन के कुशल तरीके से केवल एक बार लूप में निष्पादित होता है
मार्टीन्यू

2
मैं अभी एक और अधिक यथार्थवादी उदाहरण के साथ आया हूं जिसमें मुझे ऊपर बताई गई समस्या नहीं है। डिफ़ॉल्ट __init__एक वर्ग के लिए फ़ंक्शन का एक तर्क है , जो एक उदाहरण चर में सेट हो जाता है; यह एक पूरी तरह से मान्य चीज है, जो करना चाहता है, और यह सब एक स्थूल डिफ़ॉल्ट के साथ बहुत गलत हो जाता है। stackoverflow.com/questions/43768055/…
मार्क रैनसम

जवाबों:


61

आप इसे फ़ंक्शन कॉल के बीच मानों को कैश करने के लिए उपयोग कर सकते हैं:

def get_from_cache(name, cache={}):
    if name in cache: return cache[name]
    cache[name] = result = expensive_calculation()
    return result

लेकिन आमतौर पर उस तरह की चीज एक वर्ग के साथ बेहतर तरीके से की जाती है क्योंकि आपके पास कैश आदि को साफ़ करने के लिए अतिरिक्त गुण हो सकते हैं।


12
... या एक आकर्षक डेकोरेटर।
डैनियल रोजमैन

29
@functools.lru_cache(maxsize=None)
कैटरील

3
पायथन 3.2 में @katrielalex lru_cache नया है, इसलिए हर कोई इसका उपयोग नहीं कर सकता है।
डंकन

2
FYI करें अब backports.functools_lru_cache pypi.python.org/pypi/backports.functools_lru_cache
पांडा

1
lru_cacheयदि आपके पास उपलब्ध नहीं हैं तो अनुपलब्ध है।
सिनारेडाकस

14

कैनोनिकल का जवाब है यह पृष्ठ: http://effbot.org/zone/default-values.htm

इसमें म्यूटेबल डिफॉल्ट तर्क के लिए 3 "अच्छे" मामलों का भी उल्लेख किया गया है:

  • कॉलबैक में बाहरी वैरिएबल के वर्तमान मान के लिए स्थानीय वैरिएबल को बाइंड करना
  • कैश / Memoization
  • वैश्विक नामों की स्थानीय रीबाइंडिंग (अत्यधिक अनुकूलित कोड के लिए)

12

हो सकता है कि आप परिवर्तनशील तर्क को म्यूट न करें, लेकिन एक परस्पर तर्क की अपेक्षा करें:

def foo(x, y, config={}):
    my_config = {'debug': True, 'verbose': False}
    my_config.update(config)
    return bar(x, my_config) + baz(y, my_config)

(हां, मुझे पता है कि आप config=()इस विशेष मामले में उपयोग कर सकते हैं , लेकिन मुझे लगता है कि कम स्पष्ट और कम सामान्य है।)


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

11
import random

def ten_random_numbers(rng=random):
    return [rng.random() for i in xrange(10)]

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


7
लेकिन यह बहुत महत्वपूर्ण उपयोग मामला भी नहीं है।
इवगेनी सर्गेव

3
मुझे लगता है कि पायथन के "एक बार संदर्भ प्राप्त करें" और पायथन के " randomप्रति कार्य एक बार देखने की" के बीच व्यवहार में कोई अंतर नहीं है । दोनों एक ही वस्तु का उपयोग करते हुए समाप्त होते हैं।
nyanpasu64

4

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

प्रदर्शन के लिए अनुकूलन:

def foo(sin=math.sin): ...

चर के बजाय किसी बंद में ऑब्जेक्ट मान हथियाना।

callbacks = []
for i in range(10):
    def callback(i=i): ...
    callbacks.append(callback)

7
पूर्णांक और बिलियन फ़ंक्शन परस्पर नहीं हैं!
मोनिका

2
@ जोनाथन: शेष उदाहरण में अभी भी कोई परिवर्तनशील डिफ़ॉल्ट तर्क नहीं है, या क्या मैं इसे नहीं देखता हूं?
मोनिका

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

2
@katriealex: ठीक है, लेकिन कृपया अपने उत्तर में ऐसा कहें कि आप मान लें कि तर्कों का पुनर्मूल्यांकन करना होगा, और आप यह दर्शाते हैं कि यह क्यों बुरा होगा। Nit-pick: डिफ़ॉल्ट तर्क मान संकलन-समय पर संग्रहीत नहीं होते हैं, लेकिन जब फ़ंक्शन परिभाषा कथन निष्पादित होता है।
मोनिका

@WolframH: सच: पी! हालांकि दोनों अक्सर मेल खाते हैं।
कैट्रील

0

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

खदान जैसी स्थितियों में आप क्या कर सकते हैं, उन कस्टम वस्तुओं वाले मॉड्यूल में कुछ कोड जोड़ें:

custom_objects = {}

def custom_object(obj, storage=custom_objects):
    storage[obj.__name__] = obj
    return obj

फिर, मैं बस किसी भी वर्ग / कवक को सजा सकता हूं जिसे शब्दकोश में होना चाहिए

@custom_object
def some_function(x):
    return 3*x*x + 2*x - 2

इसके अलावा, कहो कि मैं अपनी कस्टम हानि क्युकिंस को अपने कस्टम केरस परतों की तुलना में एक अलग शब्दकोश में संग्रहीत करना चाहता हूं। Functionalools.partial का उपयोग करने से मुझे एक नए डेकोरेटर के लिए आसान पहुँच मिलती है

import functools
import tf

custom_losses = {}
custom_loss = functools.partial(custom_object, storage=custom_losses)

@custom_loss
def my_loss(y, y_pred):
    return tf.reduce_mean(tf.square(y - y_pred))

-1

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

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

इन दो उदाहरणों पर विचार करें:

def dittle(cache = []):

    from time import sleep # Not needed except as an example.

    # dittle's internal cache list has this format: cache[string, counter]
    # Any argument passed to dittle() that violates this format is invalid.
    # (The string is pure storage, but the counter is used by dittle.)

     # -- Error Trap --
    if type(cache) != list or cache !=[] and (len(cache) == 2 and type(cache[1]) != int):
        print(" User called dittle("+repr(cache)+").\n >> Warning: dittle() takes no arguments, so this call is ignored.\n")
        return

    # -- Initialize Function. (Executes on first call only.) --
    if not cache:
        print("\n cache =",cache)
        print(" Initializing private mutable static cache. Runs only on First Call!")
        cache.append("Hello World!")
        cache.append(0)
        print(" cache =",cache,end="\n\n")
    # -- Normal Operation --
    cache[1]+=1 # Static cycle count.
    outstr = " dittle() called "+str(cache[1])+" times."
    if cache[1] == 1:outstr=outstr.replace("s.",".")
    print(outstr)
    print(" Internal cache held string = '"+cache[0]+"'")
    print()
    if cache[1] == 3:
        print(" Let's rest for a moment.")
        sleep(2.0) # Since we imported it, we might as well use it.
        print(" Wheew! Ready to continue.\n")
        sleep(1.0)
    elif cache[1] == 4:
        cache[0] = "It's Good to be Alive!" # Let's change the private message.

# =================== MAIN ======================        
if __name__ == "__main__":

    for cnt in range(2):dittle() # Calls can be loop-driven, but they need not be.

    print(" Attempting to pass an list to dittle()")
    dittle([" BAD","Data"])
    
    print(" Attempting to pass a non-list to dittle()")
    dittle("hi")
    
    print(" Calling dittle() normally..")
    dittle()
    
    print(" Attempting to set the private mutable value from the outside.")
    # Even an insider's attempt to feed a valid format will be accepted
    # for the one call only, and is then is discarded when it goes out
    # of scope. It fails to interrupt normal operation.
    dittle([" I am a Grieffer!\n (Notice this change will not stick!)",-7]) 
    
    print(" Calling dittle() normally once again.")
    dittle()
    dittle()

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

परिवर्तनशील चूक का उपयोग करने के लिए महत्वपूर्ण कुछ भी नहीं है जो चर को स्मृति में पुन: सौंप देगा, लेकिन हमेशा चर को जगह में बदलने के लिए।

इस तकनीक की संभावित शक्ति और उपयोगिता को वास्तव में देखने के लिए, "DITTLE.py" नाम के तहत इस पहले प्रोग्राम को अपनी वर्तमान निर्देशिका में सहेजें, फिर अगला प्रोग्राम चलाएं। यह आयात करता है और याद रखने या प्रोग्राम करने के लिए किसी भी चरण की आवश्यकता के बिना हमारे नए डिटल () कमांड का उपयोग करता है।

यहाँ हमारा दूसरा उदाहरण है। इसे एक नए प्रोग्राम के रूप में संकलित करें और चलाएं।

from DITTLE import dittle

print("\n We have emulated a new python command with 'dittle()'.\n")
# Nothing to declare, nothing to instantize, nothing to remember.

dittle()
dittle()
dittle()
dittle()
dittle()

अब नहीं है कि के रूप में के रूप में चालाक और साफ हो सकता है? ये परिवर्तनशील चूक वास्तव में काम आ सकती हैं।

========================

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

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

from time import sleep

class dittle_class():

    def __init__(self):
        
        self.b = 0
        self.a = " Hello World!"
        
        print("\n Initializing Class Object. Executes on First Call only.")
        print(" self.a = '"+str(self.a),"', self.b =",self.b,end="\n\n")
    
    def report(self):
        self.b  = self.b + 1
        
        if self.b == 1:
            print(" Dittle() called",self.b,"time.")
        else:
            print(" Dittle() called",self.b,"times.")
        
        if self.b == 5:
            self.a = " It's Great to be alive!"
        
        print(" Internal String =",self.a,end="\n\n")
            
        if self.b ==3:
            print(" Let's rest for a moment.")
            sleep(2.0) # Since we imported it, we might as well use it.
            print(" Wheew! Ready to continue.\n")
            sleep(1.0)

cl= dittle_class()

def dittle():
    global cl
    
    if type(cl.a) != str and type(cl.b) != int:
        print(" Class exists but does not have valid format.")
        
    cl.report()

# =================== MAIN ====================== 
if __name__ == "__main__":
    print(" We have emulated a python command with our own 'dittle()' command.\n")
    for cnt in range(2):dittle() # Call can be loop-driver, but they need not be.
    
    print(" Attempting to pass arguments to dittle()")
    try: # The user must catch the fatal error. The mutable default user did not. 
        dittle(["BAD","Data"])
    except:
        print(" This caused a fatal error that can't be caught in the function.\n")
    
    print(" Calling dittle() normally..")
    dittle()
    
    print(" Attempting to set the Class variable from the outside.")
    cl.a = " I'm a griefer. My damage sticks."
    cl.b = -7
    
    dittle()
    dittle()

अपनी वर्तमान निर्देशिका में इस कक्षा-आधारित कार्यक्रम को DITTLE.py के रूप में सहेजें और फिर निम्न कोड (जो पहले जैसा हो) चलाएं।

from DITTLE import dittle
# Nothing to declare, nothing to instantize, nothing to remember.

dittle()
dittle()
dittle()
dittle()
dittle()

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.