লেক্সিকাল ক্লোজারগুলি কীভাবে কাজ করবে?


149

আমি জাভাস্ক্রিপ্ট কোডে লাক্ষিক বন্ধ হওয়ার সাথে আমার যে সমস্যাটি ছিল তা তদন্ত করার সময় পাইথনে আমি এই সমস্যাটি নিয়ে এসেছি:

flist = []

for i in xrange(3):
    def func(x): return x * i
    flist.append(func)

for f in flist:
    print f(2)

মনে রাখবেন যে এই উদাহরণটি মনঃসুলভভাবে এড়িয়ে চলে lambda। এটি "4 4 4" মুদ্রণ করে, যা অবাক করে। আমি "0 2 4" আশা করব।

এই সমতুল্য পার্ল কোডটি এটি সঠিকভাবে করে:

my @flist = ();

foreach my $i (0 .. 2)
{
    push(@flist, sub {$i * $_[0]});
}

foreach my $f (@flist)
{
    print $f->(2), "\n";
}

"0 2 4" মুদ্রিত হয়েছে।

আপনি দয়া করে পার্থক্য ব্যাখ্যা করতে পারেন?


হালনাগাদ:

সমস্যাটি বিশ্বব্যাপী হওয়ার সাথে নয়i । এটি একই আচরণ প্রদর্শন করে:

flist = []

def outer():
    for i in xrange(3):
        def inner(x): return x * i
        flist.append(inner)

outer()
#~ print i   # commented because it causes an error

for f in flist:
    print f(2)

যেমন মন্তব্য করা লাইনটি দেখায়, iসেই সময়ে অজানা। তবুও, এটি "4 4 4" মুদ্রণ করে।


2
এখানে দুটি সম্পর্কিত প্রশ্ন যা সহায়তা করতে পারে: আর্লি এবং লেট বাইন্ডিং কী? মানচিত্রের ফলাফল () এবং তালিকা অনুধাবন কেন পৃথক?
jfs

3
এখানে এই ইস্যুতে একটি খুব ভাল নিবন্ধ। me.veekun.com/blog/2011/04/24/gotcha-python-scoping-closures
updateogliu

উত্তর:


151

পাইথন আসলে সংজ্ঞায়িত হিসাবে আচরণ করে। তিনটি পৃথক ফাংশন তৈরি করা হয় তবে তাদের প্রত্যেকটির পরিবেশ নির্ধারিত পরিবেশ বন্ধ হয়ে যায় - এক্ষেত্রে বৈশ্বিক পরিবেশ (বা লুপটি যদি অন্য ফাংশনের অভ্যন্তরে স্থাপন করা হয় তবে বাইরের ফাংশনের পরিবেশ)। এটি হুবহু সমস্যা, যদিও - এই পরিবেশে, আমি রূপান্তরিত হয়েছি এবং বন্ধগুলি সমস্ত একই আইকে বোঝায়

এখানে আমি যে সর্বোত্তম সমাধানটি নিয়ে আসতে পারি তা এখানে রয়েছে - একটি ফাংশন ক্রিয়েটার তৈরি করুন এবং তার পরিবর্তে প্রার্থনা করুন। এটি প্রতিটি একটিতে আলাদা আই সহ তৈরি করা প্রতিটি কার্যের জন্য আলাদা পরিবেশকে বাধ্য করবে ।

flist = []

for i in xrange(3):
    def funcC(j):
        def func(x): return x * j
        return func
    flist.append(funcC(i))

for f in flist:
    print f(2)

আপনি পার্শ্ব প্রতিক্রিয়া এবং ফাংশনাল প্রোগ্রামিং মিশ্রিত করার পরে এটি ঘটে।


5
আপনার সমাধানটি জাভাস্ক্রিপ্টে ব্যবহৃত এক।
এলি বেন্ডারস্কি

9
এটি খারাপ ব্যবহার নয়। এটি ঠিক যেমন সংজ্ঞায়িত আচরণ করছে।
অ্যালেক্স কভেন্ট্রি

6
আইএমও পির এর একটি উন্নততর সমাধান আছে stackoverflow.com/questions/233673/...
JFS

2
আমি স্পষ্টতার জন্য সম্ভবত অন্তর্নিহিত 'i' কে 'j' তে পরিবর্তন করব।
ডিমসাইক্টেক্স

7
কেবল এটির মতো এটির সংজ্ঞা দেওয়ার বিষয়ে কী:def inner(x, i=i): return x * i
ড্যাশসি

152

লুপে সংজ্ঞায়িত ফাংশনগুলি iএর মান পরিবর্তন করার সাথে সাথে একই পরিবর্তনশীল অ্যাক্সেস করে keep লুপের শেষে, সমস্ত ফাংশন একই ভেরিয়েবলের দিকে ইঙ্গিত করে, যা লুপের সর্বশেষ মানটি ধারণ করে: প্রভাবটি উদাহরণটিতে উল্লিখিত হয়েছে।

iএর মানটি মূল্যায়ন ও ব্যবহার করার জন্য একটি সাধারণ প্যাটার্নটিকে প্যারামিটার ডিফল্ট হিসাবে সেট করা হয়: defবিবৃতিটি কার্যকর করা হলে প্যারামিটার ডিফল্টগুলি মূল্যায়ন করা হয় এবং সুতরাং লুপ ভেরিয়েবলের মান হিমায়িত হয়।

নিম্নলিখিতটি প্রত্যাশার মতো কাজ করে:

flist = []

for i in xrange(3):
    def func(x, i=i): # the *value* of i is copied in func() environment
        return x * i
    flist.append(func)

for f in flist:
    print f(2)

7
এস / সংকলনের সময় / মুহূর্তে যখন defবিবৃতি কার্যকর করা হয় /
jfs

23
এটি একটি উদ্ভাবনী সমাধান, যা এটি ভয়ঙ্কর করে তোলে।
স্ট্যাভ্রোস করোকিঠাকিস

এই সমাধানটিতে একটি সমস্যা রয়েছে: ফানকের এখন দুটি পরামিতি রয়েছে। এর অর্থ এটি একটি পরিবর্তনশীল পরিমাণের পরামিতিগুলির সাথে কাজ করে না। সবচেয়ে খারাপ, আপনি যদি দ্বিতীয় পরামিতি দিয়ে ফানকে কল করেন তবে iএটি সংজ্ঞা থেকে মূলটিকে ওভাররাইট করে দেবে । :-(
পাস্কাল

34

আপনি functoolsলাইব্রেরিটি ব্যবহার করে এটি কীভাবে করবেন তা এখানে (যা আমি নিশ্চিত নই যে প্রশ্ন উত্থাপনের সময় উপলব্ধ ছিল)।

from functools import partial

flist = []

def func(i, x): return x * i

for i in xrange(3):
    flist.append(partial(func, i))

for f in flist:
    print f(2)

আশানুরূপ ফলাফল আউটপুট 0 2 4


আমি সত্যিই এটি ব্যবহার করতে চেয়েছিলাম কিন্তু আমার ফাংশনটি আসলে এক শ্রেণির পদ্ধতি এবং উত্তীর্ণ প্রথম মানটি স্বয়ং। এদিক ওদিক কি আছে?
মাইকেল ডেভিড ওয়াটসন

1
একেবারে। ধরুন আপনার কাছে একটি পদ্ধতি যুক্ত (স্ব, ক, খ) সহ একটি ক্লাস ম্যাথ রয়েছে এবং আপনি 'ইনক্রিমেন্ট' পদ্ধতি তৈরি করতে একটি = 1 সেট করতে চান। তারপরে, আপনি 'my_math' শ্রেণীর একটি উদাহরণ তৈরি করুন এবং আপনার বর্ধিত পদ্ধতিটি হবে 'বৃদ্ধি = আংশিক (my_math.add, 1)'।
লুকা ইনভার্নিজি

2
এই কৌশলটি কোনও পদ্ধতিতে প্রয়োগ করতে আপনি functools.partialmethod()অজগর 3.4 হিসাবে ব্যবহার করতে পারেন
ম্যাট এডিং

13

এটা দেখ:

for f in flist:
    print f.func_closure


(<cell at 0x00C980B0: int object at 0x009864B4>,)
(<cell at 0x00C980B0: int object at 0x009864B4>,)
(<cell at 0x00C980B0: int object at 0x009864B4>,)

এর অর্থ তারা সকলে একই আই ভেরিয়েবল উদাহরণগুলিতে নির্দেশ করে, লুপ শেষ হয়ে গেলে যার মান 2 হবে।

একটি পঠনযোগ্য সমাধান:

for i in xrange(3):
        def ffunc(i):
            def func(x): return x * i
            return func
        flist.append(ffunc(i))

1
আমার প্রশ্নটি আরও "জেনারেল"। পাইথন কেন এই ত্রুটি? আমি বর্ণা would্য সমাপ্তিগুলির (যেমন পার্ল এবং পুরো লিস্প রাজবংশ) সমর্থন করে এমন একটি ভাষা প্রত্যাশা করব যা এটি সঠিকভাবে কাজ করবে।
এলি বেন্ডারস্কি

2
কেন কোনও কিছুতে ত্রুটি রয়েছে তা জিজ্ঞাসা করা এটি ত্রুটি নয়।
নাল 303

7

যা ঘটছে তা হ'ল ভেরিয়েবলটি আমি ক্যাপচার করেছি এবং ফাংশনগুলি এটির সময়টিকে বলা হয় যা এটির সাথে আবদ্ধ হয় returning কার্যকরী ভাষায় এই ধরণের পরিস্থিতি কখনই উত্থিত হয় না, কারণ আমি পুনরায় প্রত্যাবর্তন করব না। তবে অজগর দিয়ে এবং আপনি যেমন লিস্পের সাথে দেখেছেন, এটি আর সত্য নয়।

আপনার স্কিম উদাহরণের সাথে পার্থক্যটি হ'ল ডু লুপের শব্দার্থক শব্দগুলি করা। স্কিম কার্যকরভাবে লুপের মাধ্যমে প্রতিবার একটি নতুন আই ভেরিয়েবল তৈরি করছে, অন্য ভাষার মতো বিদ্যমান আই বাইন্ডিংটিকে পুনরায় ব্যবহার না করে। আপনি যদি লুপের বাহ্যিক ভিন্ন ভিন্ন চলক ব্যবহার করেন এবং এটিকে রূপান্তর করেন তবে আপনি স্কিমে একই আচরণ দেখতে পাবেন। আপনার লুপটি প্রতিস্থাপনের চেষ্টা করুন:

(let ((ii 1)) (
  (do ((i 1 (+ 1 i)))
      ((>= i 4))
    (set! flist 
      (cons (lambda (x) (* ii x)) flist))
    (set! ii i))
))

এ সম্পর্কে আরও কিছু আলোচনার জন্য এখানে একবার দেখুন ।

[সম্পাদনা] সম্ভবত এটি বর্ণনার আরও ভাল উপায় হ'ল নিম্নলিখিত পদক্ষেপগুলি সম্পাদন করে এমন একটি ম্যাক্রো হিসাবে ডুপ লুপটি ভাবুন:

  1. লুপের বডি দ্বারা সংজ্ঞায়িত একটি বডি সহ, একটি একক প্যারামিটার গ্রহণ করা একটি ল্যাম্বডাকে সংজ্ঞায়িত করুন,
  2. এর পরামিতি হিসাবে যথাযথ মান সহ সেই ল্যাম্বদার তাত্ক্ষণিক কল।

অর্থাত। নীচের অজগর সমতুল্য:

flist = []

def loop_body(i):      # extract body of the for loop to function
    def func(x): return x*i
    flist.append(func)

map(loop_body, xrange(3))  # for i in xrange(3): body

আমি আর প্যারেন্ট স্কোপ থেকে এক নই তবে তার নিজস্ব সুযোগে একেবারে নতুন ভেরিয়েবল (যেমন ল্যাম্বডায় প্যারামিটার) রয়েছে এবং তাই আপনি যে আচরণটি পর্যবেক্ষণ করেন তা পেয়ে যান। পাইথনের এই অন্তর্নিহিত নতুন সুযোগ নেই, সুতরাং for লুপের মূল অংশটি আই ভেরিয়েবলটি ভাগ করে।


মজাদার. ডু লুপের শব্দার্থবিজ্ঞানের পার্থক্য সম্পর্কে আমি অবগত ছিলাম না। ধন্যবাদ
এলি বেন্ডারস্কি

4

আমি এখনও পুরোপুরি নিশ্চিত নই যে কিছু ভাষায় কেন এটি একরকম এবং অন্য কোনও উপায়ে কাজ করে। কমন লিস্পে এটি পাইথনের মতো:

(defvar *flist* '())

(dotimes (i 3 t)
  (setf *flist* 
    (cons (lambda (x) (* x i)) *flist*)))

(dolist (f *flist*)  
  (format t "~a~%" (funcall f 2)))

"6 6 6" মুদ্রণ করুন (নোটটি এখানে 1 থেকে 3 অবধি রয়েছে এবং বিপরীতে নির্মিত ") স্কিমে থাকা অবস্থায় এটি পার্লের মতো কাজ করে:

(define flist '())

(do ((i 1 (+ 1 i)))
    ((>= i 4))
  (set! flist 
    (cons (lambda (x) (* i x)) flist)))

(map 
  (lambda (f)
    (printf "~a~%" (f 2)))
  flist)

"6 4 2" মুদ্রণ

এবং আমি ইতিমধ্যে উল্লেখ করেছি যে, জাভাস্ক্রিপ্টটি পাইথন / সিএল শিবিরে রয়েছে। এখানে উপস্থিতি বাস্তবায়নের সিদ্ধান্ত রয়েছে, যা বিভিন্ন ভাষায় স্বতন্ত্র উপায়ে পৌঁছে। সিদ্ধান্তটি ঠিক কী তা আমি বুঝতে পছন্দ করব।


8
পার্থক্যটি স্কোপিংয়ের নিয়মের পরিবর্তে (কর ...) এর মধ্যে। স্কিমে লুপের মাধ্যমে প্রতিটি পাসে একটি নতুন ভেরিয়েবল তৈরি করে, অন্য ভাষাগুলি বিদ্যমান বাইন্ডিংটিকে পুনরায় ব্যবহার করে। আরও বিবরণের জন্য আমার উত্তর এবং লিস্প / পাইথনের অনুরূপ আচরণ সহ একটি স্কিম সংস্করণের উদাহরণ দেখুন।
ব্রায়ান

2

সমস্যাটি হ'ল স্থানীয় সমস্ত ফাংশন একই পরিবেশ এবং এভাবে একই iভেরিয়েবলের সাথে আবদ্ধ । সমাধান (workaround) প্রতিটি ফাংশন (বা ল্যাম্বদা) জন্য পৃথক পরিবেশ (স্ট্যাক ফ্রেম) তৈরি করা হয়:

t = [ (lambda x: lambda y : x*y)(x) for x in range(5)]

>>> t[1](2)
2
>>> t[2](2)
4

1

ভেরিয়েবলটি iএকটি গ্লোবাল, যার ফাংশনটি fডাকা হওয়ার সময় যার মান 2 হয় ।

নীচে আপনি যে আচরণটি করছেন তার পরে আমি প্রয়োগ করতে আগ্রহী হব:

>>> class f:
...  def __init__(self, multiplier): self.multiplier = multiplier
...  def __call__(self, multiplicand): return self.multiplier*multiplicand
... 
>>> flist = [f(i) for i in range(3)]
>>> [g(2) for g in flist]
[0, 2, 4]

আপনার আপডেটের প্রতিক্রিয়া : এটি i প্রতি বিশ্বব্যাপী বিশ্বব্যাপী নয় যা এই আচরণের কারণ হয়ে দাঁড়ায় , এটি সত্য যে এটি কোনও এনকোলেসিং স্কোপ থেকে একটি পরিবর্তনশীল যার চলাচলের সময়গুলির একটি নির্দিষ্ট মান রয়েছে। আপনার দ্বিতীয় উদাহরণে, এর iকার্যটি kkkকার্যকারিতা থেকে নেওয়া হয় এবং আপনি যখন ফাংশনগুলি কল করেন তখন কিছুই পরিবর্তন হয় না flist


0

আচরণের পিছনে যুক্তিটি ইতিমধ্যে ব্যাখ্যা করা হয়েছে, এবং একাধিক সমাধান পোস্ট করা হয়েছে, তবে আমি মনে করি এটি সর্বাধিক পাইথোনিক (মনে রাখবেন, পাইথনের প্রতিটি জিনিসই একটি বস্তু!):

flist = []

for i in xrange(3):
    def func(x): return x * func.i
    func.i=i
    flist.append(func)

for f in flist:
    print f(2)

ফাংশন জেনারেটর ব্যবহার করে ক্লুডির উত্তরটি বেশ ভাল, তবে পিরোর উত্তর হ্যাক, সত্যি বলতে কী, কারণ এটি আমাকে একটি "লুকানো" যুক্তি হিসাবে একটি ডিফল্ট মান দিয়ে তোলে (এটি ভাল কাজ করবে, তবে এটি "পাইথোনিক" নয়) ।


আমি মনে করি এটি আপনার অজগর সংস্করণের উপর নির্ভর করে। এখন আমি আরও অভিজ্ঞ এবং আমি আর এটি করার উপায়টিকে আর পরামর্শ দেব না। পাইথনে ক্লোদিয়ু বন্ধ করার উপযুক্ত উপায়।
অন্ধকাররেখা

1
এটি পাইথন 2 বা 3 এর মধ্যে কাজ করবে না (তারা উভয়ই "4 4 4" আউটপুট দেয়)। funcমধ্যে x * func.iসবসময় গত ম ফাংশন পূর্বনির্ধারণ পড়ুন হবে। সুতরাং প্রতিটি ফাংশনের স্বতন্ত্রভাবে সঠিক নম্বর এটি আটকে থাকলেও তারা সকলেই যাইহোক শেষের পড়া থেকে শেষ করে।
লাম্বদা পরী
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.