अजगर नेस्टेड फ़ंक्शन को क्लोजर क्यों नहीं कहा जाता है?


249

मैंने पायथन में नेस्टेड फ़ंक्शंस को देखा और इस्तेमाल किया है, और वे एक क्लोजर की परिभाषा से मेल खाते हैं। तो उन्हें nested functionsइसके बजाय क्यों बुलाया जाता है closures?

क्या नेस्टेड फ़ंक्शन बंद नहीं होते हैं क्योंकि वे बाहरी दुनिया द्वारा उपयोग नहीं किए जाते हैं?

अद्यतन: मैं क्लोजर के बारे में पढ़ रहा था और यह मुझे इस अवधारणा के बारे में पायथन के संबंध में सोच रहा था। मैंने किसी के द्वारा उल्लिखित लेख को नीचे टिप्पणी में खोजा और पाया, लेकिन मैं उस लेख में स्पष्टीकरण को पूरी तरह से समझ नहीं पाया, इसलिए मैं यह प्रश्न पूछ रहा हूं।


8
दिलचस्प बात यह है कि कुछ गुगली ने मुझे यह पाया, दिनांक 2006 दिसंबर: effbot.org/zone/closure.htm । मुझे यकीन नहीं है कि "SO पर" बाहरी डुप्लिकेट हैं?
hbw

जवाबों:


394

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

def make_printer(msg):
    def printer():
        print msg
    return printer

printer = make_printer('Foo!')
printer()

जब make_printerकहा जाता है, एक printerस्थिर और msgएक स्थानीय के रूप में फ़ंक्शन के लिए संकलित कोड के साथ एक नया फ्रेम स्टैक पर रखा जाता है। यह तब फ़ंक्शन को बनाता और वापस करता है। क्योंकि फ़ंक्शन चर का printerसंदर्भ देता msgहै, make_printerफ़ंक्शन के वापस आने के बाद इसे जीवित रखा जाता है ।

इसलिए, यदि आपके नेस्टेड कार्य नहीं करते हैं

  1. एक्सेस वैरिएबल जो स्थानीय रूप से स्कोप को घेरने के लिए हैं,
  2. ऐसा तब करें जब उन्हें उस दायरे से बाहर किया जाए,

तब वे बंद नहीं होते हैं।

यहां एक नेस्टेड फ़ंक्शन का एक उदाहरण है जो एक बंद नहीं है।

def make_printer(msg):
    def printer(msg=msg):
        print msg
    return printer

printer = make_printer("Foo!")
printer()  #Output: Foo!

यहां, हम मान को एक पैरामीटर के डिफ़ॉल्ट मान से बांध रहे हैं। यह तब होता है जब फ़ंक्शन printerबनाया जाता है और इसलिए रिटर्न के बाद बनाए रखने के लिए msgबाहरी के मूल्य का कोई संदर्भ नहीं होता है। इस संदर्भ में फ़ंक्शन का सिर्फ एक सामान्य स्थानीय चर है ।printermake_printermsgprinter


2
आप जवाब देते हैं कि मेरी तुलना में बहुत बेहतर है, आप एक अच्छा बिंदु बनाते हैं, लेकिन अगर हम सख्त कार्यात्मक प्रोग्रामिंग परिभाषाओं द्वारा जाने वाले हैं, तो क्या आपके उदाहरण भी कार्य कर रहे हैं? यह एक समय हो गया है, और मुझे याद नहीं है कि क्या सख्त कार्यात्मक प्रोग्रामिंग उन कार्यों के लिए अनुमति देता है जो मूल्यों को वापस नहीं करते हैं। यदि आप रिटर्न वैल्यू को कोई नहीं मानते हैं, तो यह बिंदु लूट है, लेकिन यह एक अन्य विषय है।
mikerobi

6
@mikerobi, मुझे यकीन नहीं है कि हमें कार्यात्मक प्रोग्रामिंग को ध्यान में रखना होगा क्योंकि अजगर वास्तव में एक कार्यात्मक भाषा नहीं है, हालांकि यह निश्चित रूप से इस तरह के रूप में इस्तेमाल किया जा सकता है। लेकिन, नहीं, आंतरिक कार्य उस अर्थ में कार्य नहीं हैं क्योंकि उनका पूरा बिंदु दुष्प्रभाव पैदा करना है। एक ऐसा फंक्शन बनाना आसान है जो पॉइंट्स को वैसे ही दिखाता है,
aaronasterling

31
@mikerobi: कोड की एक बूँद है या नहीं, यह बंद होना इस बात पर निर्भर करता है कि यह अपने वातावरण पर बंद है या नहीं, जिसे आप इसे कहते हैं। यह एक दिनचर्या, कार्य, कार्यविधि, विधि, ब्लॉक, सबरूटीन, जो भी हो सकता है। रूबी में, तरीकों को बंद नहीं किया जा सकता है, केवल ब्लॉक कर सकते हैं। जावा में, तरीकों को बंद नहीं किया जा सकता है, लेकिन कक्षाएं कर सकते हैं। यह उन्हें किसी बंद से कम नहीं बनाता है। (हालांकि यह तथ्य कि वे केवल कुछ चर पर बंद होते हैं, और वे उन्हें संशोधित नहीं कर सकते, उन्हें बेकार कर देता है।) आप तर्क दे सकते हैं कि एक विधि सिर्फ एक प्रक्रिया है जो बंद है self। (जावास्क्रिप्ट / पायथन में यह लगभग सच है।)
जोर्ग डब्ल्यू मित्तग

3
@ JörgWMittag कृपया "बंद हो जाता है" को परिभाषित करें।
इवगेनी सर्गेव

4
@EvgeniSergeev "एक परिक्षेत्र के दायरे से" यानी "संदर्भित करता है" एक स्थानीय चर [कहना, i]। संदर्भित करता है, अर्थात iमान का निरीक्षण (या परिवर्तन) कर सकता है , भले ही / जब उस गुंजाइश ने "इसका निष्पादन समाप्त कर दिया हो", अर्थात किसी प्रोग्राम का निष्पादन कोड के अन्य भागों में चला गया हो। जिस खंड iको परिभाषित किया गया है, वह अधिक नहीं है, फिर भी फ़ंक्शन (रों) iअभी भी ऐसा कर सकता है। यह आमतौर पर "चर पर समापन" के रूप में वर्णित है i। विशिष्ट चर के साथ सौदा नहीं करने के लिए, इसे पूरे पर्यावरण फ्रेम पर बंद करने के रूप में लागू किया जा सकता है जहां उस चर को परिभाषित किया गया है।
विल नेस

103

प्रश्न का उत्तर पहले ही aaronasterling द्वारा दिया जा चुका है

हालांकि, किसी को दिलचस्पी हो सकती है कि हुड के नीचे चर कैसे संग्रहीत किए जाते हैं।

स्निपेट में आने से पहले:

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

  • यदि कोई फ़ंक्शन फ्री वैरिएबल का उपयोग नहीं करता है तो यह क्लोजर नहीं बनता है।

  • यदि कोई अन्य आंतरिक स्तर है जो मुफ्त चर का उपयोग करता है - पिछले सभी स्तर शाब्दिक वातावरण को बचाते हैं (उदाहरण के अंत में)

  • pythonfunc_closure में फ़ंक्शन विशेषताएँ <3.X या __closure__python> 3.X में मुक्त चर बचाते हैं।

  • अजगर के हर फंक्शन में यह क्लोजर गुण होते हैं, लेकिन अगर कोई फ्री वैरिएबल नहीं है तो यह किसी भी कंटेंट को सेव नहीं करता है।

उदाहरण: क्लोजर विशेषताएँ लेकिन अंदर कोई सामग्री नहीं है क्योंकि कोई मुफ्त चर नहीं है।

>>> def foo():
...     def fii():
...         pass
...     return fii
...
>>> f = foo()
>>> f.func_closure
>>> 'func_closure' in dir(f)
True
>>>

नायब: नि : शुल्क वैरिएबल एक ग्राहक बनाने के लिए आवश्यक है।

मैं ऊपर के समान स्निपेट का उपयोग करके समझाऊंगा:

>>> def make_printer(msg):
...     def printer():
...         print msg
...     return printer
...
>>> printer = make_printer('Foo!')
>>> printer()  #Output: Foo!

और सभी पायथन फ़ंक्शन में एक क्लोजर विशेषता होती है, तो चलो एक क्लोजर फ़ंक्शन के साथ जुड़े एन्कोडिंग चर की जांच करते हैं।

यहाँ func_closureफ़ंक्शन के लिए विशेषता हैprinter

>>> 'func_closure' in dir(printer)
True
>>> printer.func_closure
(<cell at 0x108154c90: str object at 0x108151de0>,)
>>>

closureविशेषता सेल वस्तुओं जो चर संलग्न दायरे में परिभाषित के विवरण शामिल की एक टपल देता है।

फंक_क्लोजर में पहला तत्व जो कोई नहीं हो सकता है या कोशिकाओं का एक समूह हो सकता है जिसमें फ़ंक्शन के मुक्त चर के लिए बाइंडिंग होती है और यह केवल पढ़ने के लिए होती है।

>>> dir(printer.func_closure[0])
['__class__', '__cmp__', '__delattr__', '__doc__', '__format__', '__getattribute__',
 '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', 
 '__setattr__',  '__sizeof__', '__str__', '__subclasshook__', 'cell_contents']
>>>

यहाँ उपरोक्त आउटपुट में आप देख सकते हैं cell_contents, देखते हैं कि यह क्या स्टोर करता है:

>>> printer.func_closure[0].cell_contents
'Foo!'    
>>> type(printer.func_closure[0].cell_contents)
<type 'str'>
>>>

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

फिर मैं कुछ बदलावों के साथ उपरोक्त स्निपेट का उपयोग करके समझाऊंगा:

 >>> def make_printer(msg):
 ...     def printer():
 ...         pass
 ...     return printer
 ...
 >>> printer = make_printer('Foo!')
 >>> printer.func_closure
 >>>

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

अब मैं सब कुछ बाहर खाली करने के लिए एक और अलग टुकड़ा समझा जाएगा Free Variableसाथ Closure:

>>> def outer(x):
...     def intermediate(y):
...         free = 'free'
...         def inner(z):
...             return '%s %s %s %s' %  (x, y, free, z)
...         return inner
...     return intermediate
...
>>> outer('I')('am')('variable')
'I am free variable'
>>>
>>> inter = outer('I')
>>> inter.func_closure
(<cell at 0x10c989130: str object at 0x10c831b98>,)
>>> inter.func_closure[0].cell_contents
'I'
>>> inn = inter('am')

इसलिए, हम देखते हैं कि एक func_closureसंपत्ति क्लोजर कोशिकाओं का एक समूह है , हम उन्हें और उनकी सामग्री को स्पष्ट रूप से संदर्भित कर सकते हैं - एक सेल में संपत्ति "cell_contents" है

>>> inn.func_closure
(<cell at 0x10c9807c0: str object at 0x10c9b0990>, 
 <cell at 0x10c980f68: str object at   0x10c9eaf30>, 
 <cell at 0x10c989130: str object at 0x10c831b98>)
>>> for i in inn.func_closure:
...     print i.cell_contents
...
free
am 
I
>>>

यहां जब हमने कॉल किया inn, तो यह सभी सेव फ्री वैरिएबल को संदर्भित करेगा ताकि हम प्राप्त करेंI am free variable

>>> inn('variable')
'I am free variable'
>>>

9
पायथन 3 में, func_closureअब कहा जाता है __closure__, इसी तरह अन्य विभिन्न func_*विशेषताओं के लिए।
पीवीसी

3
__closure_पाइथन 3 के साथ संगतता के लिए पाइथन 2.6+ में भी उपलब्ध है।
पियरे

क्लोजर उस रिकॉर्ड को संदर्भित करता है जो फ़ंक्शन ऑब्जेक्ट से जुड़ी, क्लोज-ओवर चर को संग्रहीत करता है। यह स्वयं कार्य नहीं है। पायथन में, यह वह __closure__वस्तु है जो बंद है।
मार्टिन पीटर्स

धन्यवाद @MartijnPieters आप स्पष्टीकरण के लिए।
जेम्स सपम

71

अजगर को बंद करने के लिए एक कमजोर समर्थन है। यह देखने के लिए कि मेरा क्या मतलब है कि जावास्क्रिप्ट के साथ क्लोजर का उपयोग करते हुए काउंटर का निम्नलिखित उदाहरण लें:

function initCounter(){
    var x = 0;
    function counter  () {
        x += 1;
        console.log(x);
    };
    return counter;
}

count = initCounter();

count(); //Prints 1
count(); //Prints 2
count(); //Prints 3

क्लोजर काफी सुरुचिपूर्ण है क्योंकि यह इस तरह लिखे गए कार्यों को "आंतरिक मेमोरी" की क्षमता देता है। पाइथन 2.7 के रूप में यह संभव नहीं है। अगर तुम कोशिश करो

def initCounter():
    x = 0;
    def counter ():
        x += 1 ##Error, x not defined
        print x
    return counter

count = initCounter();

count(); ##Error
count();
count();

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

यह वाकई शर्म की बात है। लेकिन केवल पढ़ने के लिए बंद होने के साथ आप कम से कम फ़ंक्शन डेकोरेटर पैटर्न को लागू कर सकते हैं जिसके लिए पायथन सिंटैक्टिक चीनी प्रदान करता है।

अपडेट करें

जैसा कि बताया गया है, अजगर के कार्यक्षेत्र की सीमाओं से निपटने के तरीके हैं और मैं कुछ को उजागर करूंगा।

1.global कीवर्ड का उपयोग करें (सामान्य रूप से अनुशंसित नहीं)।

2. पायथन 3.x में, nonlocalकीवर्ड का उपयोग करें (@unutbu और @leewz द्वारा सुझाया गया)

3. एक साधारण परिवर्तनीय वर्ग को परिभाषित करेंObject

class Object(object):
    pass

और वैरिएबल को स्टोर करने Object scopeके initCounterलिए भीतर बनाएं

def initCounter ():
    scope = Object()
    scope.x = 0
    def counter():
        scope.x += 1
        print scope.x

    return counter

चूंकि scopeवास्तव में केवल एक संदर्भ है, इसके क्षेत्रों के साथ की गई कार्रवाइयां वास्तव में scopeस्वयं को संशोधित नहीं करती हैं, इसलिए कोई त्रुटि उत्पन्न नहीं होती है।

4. जैसा कि @unutbu ने बताया, एक वैकल्पिक तरीका, प्रत्येक चर को एक सरणी ( x = [0]) के रूप में परिभाषित करना और इसे पहले तत्व ( x[0] += 1) में बदलना होगा । फिर से कोई त्रुटि उत्पन्न नहीं होती है क्योंकि xस्वयं को संशोधित नहीं किया जाता है।

5. जैसा कि @raxacoricofallapatorius द्वारा सुझाया गया है, आप xएक संपत्ति बना सकते हैंcounter

def initCounter ():

    def counter():
        counter.x += 1
        print counter.x

    counter.x = 0
    return counter

27
इसके चारों ओर रास्ते हैं। Python2 में, आप x = [0]बाहरी दायरे में बना सकते हैं , और x[0] += 1आंतरिक दायरे में उपयोग कर सकते हैं । Python3 में, आप अपना कोड रख सकते हैं जैसा कि यह है और नॉनक्लॉक कीवर्ड का उपयोग करें ।
अनटुब

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

@ ThanhDK का मतलब यह नहीं है कि आप चर को नहीं लिख सकते हैं? जब आप एक परिवर्तनशील वस्तु से कॉल विधि का उपयोग करते हैं, तो आप इसे स्वयं को संशोधित करने के लिए कह रहे हैं, आप वास्तव में चर को संशोधित नहीं कर रहे हैं (जो केवल वस्तु के लिए एक संदर्भ रखता है)। दूसरे शब्दों में, संदर्भ जो चर को xइंगित करता है, भले ही आप कॉल करें inc()या जो भी हो, और आप प्रभावी रूप से चर को नहीं लिखते हैं।
user193130

4
एक और विकल्प है, कड़ाई से # 2 की तुलना में बेहतर है, imv, की संपत्ति बनाने xकाcounter
ओवोम

9
पायथन 3 में nonlocalकीवर्ड है, जो globalबाहरी फ़ंक्शन के चर के लिए जैसा है लेकिन है। यह एक आंतरिक फ़ंक्शन को अपने बाहरी फ़ंक्शन (ओं) से एक नाम को विद्रोह करने की अनुमति देगा। मुझे लगता है कि "नाम को बांधना" चर को संशोधित करने की तुलना में अधिक सटीक है।
लीव्ज

16

पाइथन 2 में क्लोजर नहीं थे - इसमें वर्कआर्डॉइड थे जो क्लोजर से मिलते जुलते थे

पहले से दिए गए उत्तरों में बहुत सारे उदाहरण हैं - चर को आंतरिक फ़ंक्शन में कॉपी करना, आंतरिक फ़ंक्शन पर किसी वस्तु को संशोधित करना, आदि।

पायथन 3 में, समर्थन अधिक स्पष्ट है - और रसीला:

def closure():
    count = 0
    def inner():
        nonlocal count
        count += 1
        print(count)
    return inner

उपयोग:

start = closure()
start() # prints 1
start() # prints 2
start() # prints 3

nonlocalकीवर्ड यह enclosing, बाहरी चर स्पष्ट रूप से उल्लेख किया भीतरी समारोह बांधता प्रभाव में। इसलिए अधिक स्पष्ट रूप से एक 'बंद'।


1
दिलचस्प है, संदर्भ के लिए: docs.python.org/3/reference/… । मैं नहीं जानता कि क्यों python3 प्रलेखन में क्लोज़र के बारे में अधिक जानकारी प्राप्त करना आसान नहीं है (और आप जेएस से आने वाले व्यवहार के बारे में उनसे कैसे उम्मीद कर सकते हैं)?
user3773048

9

मेरे पास एक ऐसी स्थिति थी जहां मुझे एक अलग लेकिन लगातार नाम स्थान की आवश्यकता थी। मैंने कक्षाएं लगाईं। मैं अन्यथा नहीं। अलग लेकिन लगातार नाम बंद कर रहे हैं।

>>> class f2:
...     def __init__(self):
...         self.a = 0
...     def __call__(self, arg):
...         self.a += arg
...         return(self.a)
...
>>> f=f2()
>>> f(2)
2
>>> f(2)
4
>>> f(4)
8
>>> f(8)
16

# **OR**
>>> f=f2() # **re-initialize**
>>> f(f(f(f(2)))) # **nested**
16

# handy in list comprehensions to accumulate values
>>> [f(i) for f in [f2()] for i in [2,2,4,8]][-1] 
16

6
def nested1(num1): 
    print "nested1 has",num1
    def nested2(num2):
        print "nested2 has",num2,"and it can reach to",num1
        return num1+num2    #num1 referenced for reading here
    return nested2

देता है:

In [17]: my_func=nested1(8)
nested1 has 8

In [21]: my_func(5)
nested2 has 5 and it can reach to 8
Out[21]: 13

यह एक उदाहरण है कि एक क्लोजर क्या है और इसका उपयोग कैसे किया जा सकता है।


0

मैं अजगर और जेएस उदाहरण के बीच एक और सरल तुलना की पेशकश करना चाहता हूं, अगर यह चीजों को स्पष्ट करने में मदद करता है।

जे एस:

function make () {
  var cl = 1;
  function gett () {
    console.log(cl);
  }
  function sett (val) {
    cl = val;
  }
  return [gett, sett]
}

और निष्पादन:

a = make(); g = a[0]; s = a[1];
s(2); g(); // 2
s(3); g(); // 3

अजगर:

def make (): 
  cl = 1
  def gett ():
    print(cl);
  def sett (val):
    cl = val
  return gett, sett

और निष्पादन:

g, s = make()
g() #1
s(2); g() #1
s(3); g() #1

कारण: जैसा कि कई अन्य लोगों ने कहा है, अजगर में, यदि एक ही नाम के साथ एक चर के भीतर के दायरे में एक असाइनमेंट है, तो आंतरिक दायरे में एक नया संदर्भ बनाया जाता है। जेएस के साथ ऐसा नहीं है, जब तक कि आप varकीवर्ड के साथ स्पष्ट रूप से घोषणा नहीं करते हैं ।

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