(ল্যাম্বডা) ফাংশন ক্লোজারগুলি ক্যাপচার করে?


249

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

adders=[0,1,2,3]

for i in [0,1,2,3]:
   adders[i]=lambda a: i+a

print adders[1](3)

এটি ফাংশনগুলির একটি সহজ অ্যারে তৈরি করে যা একটি একক ইনপুট নেয় এবং কোনও সংখ্যার দ্বারা যুক্ত হওয়া ইনপুটটি ফিরিয়ে দেয়। ফাংশনগুলি forলুপে তৈরি করা হয় যেখানে পুনরুক্তিটি iথেকে চালানো 0হয় 3। এই প্রতিটি সংখ্যার জন্য একটি lambdaফাংশন তৈরি করা হয় যা iএটি ফাংশনের ইনপুটটিতে ক্যাপচার করে যোগ করে। শেষ লাইনটি প্যারামিটার হিসাবে দ্বিতীয় lambdaফাংশনটিকে কল করে 3। আমার আশ্চর্যজনক আউটপুট ছিল 6

আমি প্রত্যাশিত একটি 4। আমার যুক্তিটি ছিল: পাইথনে সবকিছুই একটি বস্তু এবং সুতরাং প্রতিটি পরিবর্তনশীল এটির জন্য একটি পয়েন্টার প্রয়োজনীয় essential এর lambdaজন্য ক্লোজারগুলি তৈরি করার সময় i, আমি এটি দ্বারা প্রত্যাশিত পূর্ণসংখ্যার অবজেক্টে একটি পয়েন্টার সঞ্চয় করার আশা করেছি i। এর অর্থ হ'ল iকোনও নতুন পূর্ণসংখ্যার অবজেক্টটি নির্ধারিত হওয়ার পরে এটি পূর্ববর্তী নির্মিত বন্ধের প্রভাব ফেলবে না। দুঃখের বিষয়, addersএকটি ডিবাগারের মধ্যে অ্যারের পরিদর্শন করা দেখায় যে এটি করে। সকল lambdaফাংশন সর্বশেষ মান পড়ুন i, 3মধ্যে যার পরিণতিতে adders[1](3)ফেরার 6

যা আমাকে নিম্নলিখিত সম্পর্কে অবাক করে তোলে:

  • ক্লোজারগুলি ঠিক কী ধরবে?
  • lambdaবর্তমান মানটি iএমনভাবে ধরতে ফাংশনগুলিকে বোঝানোর সর্বাধিক মার্জিত উপায় কী যা iএর মান পরিবর্তন করলে প্রভাবিত হবে না ?

34
ইউআই কোডে আমার এই সমস্যা হয়েছে। আমাকে বাদাম চালাও কৌশলটি মনে রাখতে হবে যে লুপগুলি নতুন সুযোগ তৈরি করে না।
স্পষ্টত

3
@ টিমএমবি কীভাবে iনাম স্থান ছেড়ে যাবে?
স্পষ্টভাবে

3
@ খুব ভাল আমি বলতে যাচ্ছি যে print iলুপ পরে কাজ করবে না। তবে আমি এটি নিজের জন্য পরীক্ষা করেছিলাম এবং এখন দেখছি আপনি কী বোঝাতে চেয়েছিলেন - এটি কার্যকর হয়। আমার কোনও ধারণা ছিল না যে পাইপ ভেরিয়েবল অজগরে লুপের বডি পরে ge
টিম এমবি

1
@ টিএমএমবি - হ্যাঁ, আমি এটাই বোঝাতে চাইছি। জন্য একই if, with, tryইত্যাদি
detly

13
এটি সরকারী পাইথন এফএকিউ-এর অধীনে, লাম্বডাস কেন বিভিন্ন মান সহ একটি লুপে সংজ্ঞায়িত হয়? , উভয় একটি ব্যাখ্যা এবং সাধারণ workaround সঙ্গে।
অবসর

উত্তর:


161

আপনার দ্বিতীয় প্রশ্নের উত্তর দেওয়া হয়েছে, তবে আপনার প্রথম হিসাবে:

বন্ধটি ঠিক কী ধরবে?

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

সম্পাদনা: কীভাবে এটি কাটিয়ে উঠতে হবে সে সম্পর্কে আপনার অন্যান্য প্রশ্ন সম্পর্কে, দুটি উপায় যা মনে মনে আসে:

  1. সবচেয়ে সংক্ষিপ্ত কিন্তু কঠোরভাবে সমতুল্য উপায় Adrien Plisson দ্বারা বাঞ্ছনীয় এক । একটি অতিরিক্ত যুক্তি সহ একটি ল্যাম্বডা তৈরি করুন এবং আপনি সংরক্ষণ করতে চান এমন অবজেক্টটিতে অতিরিক্ত যুক্তির ডিফল্ট মান সেট করুন।

  2. ল্যাম্বদাটি তৈরি করার সময় আরও কিছুটা ভার্বোজ কিন্তু কম হ্যাকি হ'ল নতুন সুযোগ তৈরি করা:

    >>> adders = [0,1,2,3]
    >>> for i in [0,1,2,3]:
    ...     adders[i] = (lambda b: lambda a: b + a)(i)
    ...     
    >>> adders[1](3)
    4
    >>> adders[2](3)
    5

    এখানে সুযোগটি একটি নতুন ফাংশন (বংশবৃদ্ধির জন্য একটি ল্যাম্বডা) ব্যবহার করে তৈরি করা হয়েছে, যা তার যুক্তিকে আবদ্ধ করে এবং আপনি যে মানটি যুক্তি হিসাবে বেঁধে রাখতে চান তা পাস করে। বাস্তব কোডে, যদিও, আপনার নতুন সম্ভাবনাটি ল্যাম্বদার পরিবর্তে একটি সাধারণ ফাংশন হবে:

    def createAdder(x):
        return lambda y: y + x
    adders = [createAdder(i) for i in range(4)]

1
সর্বাধিক, আপনি যদি আমার অন্যান্য (সহজ প্রশ্ন) এর জন্য একটি উত্তর যুক্ত করেন তবে আমি এটিকে একটি গৃহীত উত্তর হিসাবে চিহ্নিত করতে পারি। ধন্যবাদ!
বোয়াজ

3
পাইথনের স্থিতিশীল স্কোপিং রয়েছে, ডায়নামিক স্কোপিং নয় .. এটি কেবলমাত্র সমস্ত ভেরিয়েবলের উল্লেখ so একই জিনিস আপনি যদি স্কিম হয় set!। ডায়নামিক স্কোপটি আসলে কী তা এখানে দেখুন: voidspace.org.uk/python/articles/code_blocks.shtml
ক্লদিউ

6
বিকল্প 2 এর সাথে সাদৃশ্যযুক্ত যেগুলি ক্রিয়ামূলক ভাষাগুলি "ক্রিড ফাংশন" নামে পরিচিত।
ক্রাশওয়ার্কস

205

আপনি একটি ডিফল্ট মান সহ একটি যুক্তি ব্যবহার করে একটি ভেরিয়েবল ক্যাপচার জোর করতে পারেন:

>>> for i in [0,1,2,3]:
...    adders[i]=lambda a,i=i: i+a  # note the dummy parameter with a default value
...
>>> print( adders[1](3) )
4

ধারণাটি হ'ল একটি প্যারামিটার (চতুরতার সাথে নাম দেওয়া হয়েছে i) ঘোষণা করা এবং আপনি যে পরিবর্তনশীলটি ক্যাপচার করতে চান তার একটি ডিফল্ট মান প্রদান করুন (এর মান i)


7
ডিফল্ট মান ব্যবহারের জন্য +1। ল্যাম্বদা সংজ্ঞায়িত হলে মূল্যায়ন করা তাদের এ ব্যবহারের জন্য নিখুঁত করে তোলে।
কোর্নিয়ান

21
+1 এছাড়াও এটি কারণ সরকারী FAQ এর দ্বারা অনুমোদিত সমাধান ।
অবতারিত

23
এটা চমৎকার. ডিফল্ট পাইথন আচরণটি অবশ্য তা নয়।
সিসিল কারি

1
এটি কেবল কোনও ভাল সমাধানের মতো বলে মনে হচ্ছে না ... আপনি কেবল ভেরিয়েবলের একটি অনুলিপি ক্যাপচারের জন্য ফাংশনের স্বাক্ষর পরিবর্তন করছেন। এবং এছাড়াও ফাংশন প্রার্থনা যারা আই ভেরিয়েবল সঙ্গে জগাখিচুড়ি করতে পারেন, তাই না?
ডেভিড কলানান

@ ডেভিডক্যালানান আমরা একটি ল্যাম্বডা সম্পর্কে কথা বলছি: এক ধরণের অ্যাডহক ফাংশন যা আপনি সাধারণত নিজের কোডে একটি গর্ত প্লাগ করতে সংজ্ঞায়িত করেন, আপনি কোনও সম্পূর্ণ এসডিকে মাধ্যমে ভাগ করেন না এমন কিছু not আপনার যদি আরও শক্তিশালী স্বাক্ষর দরকার হয় তবে আপনার আসল ফাংশনটি ব্যবহার করা উচিত।
এড্রিয়েন প্লিসন

33

সম্পূর্ণতার জন্য আপনার দ্বিতীয় প্রশ্নের অন্য উত্তর: আপনি ব্যবহার করতে পারে আংশিক মধ্যে functools মডিউল।

ক্রিস লুটজ প্রস্তাবিত হিসাবে অপারেটর থেকে অ্যাড আমদানির সাথে উদাহরণটি হয়ে যায়:

from functools import partial
from operator import add   # add(a, b) -- Same as a + b.

adders = [0,1,2,3]
for i in [0,1,2,3]:
   # store callable object with first argument given as (current) i
   adders[i] = partial(add, i) 

print adders[1](3)

23

নিম্নলিখিত কোড বিবেচনা করুন:

x = "foo"

def print_x():
    print x

x = "bar"

print_x() # Outputs "bar"

আমি মনে করি বেশিরভাগ মানুষ এই বিভ্রান্তিকরটি মোটেই খুঁজে পাবে না। এটি প্রত্যাশিত আচরণ।

সুতরাং, লোকে লুপটি করা হয়ে গেলে কেন লোকেরা আলাদা হবে বলে মনে করেন? আমি জানি যে আমি নিজেই সেই ভুলটি করেছি, তবে কেন জানি না। এটা কি লুপ? নাকি লাম্বদা সম্ভবত?

সর্বোপরি, লুপটি এর একটি সংক্ষিপ্ত সংস্করণ:

adders= [0,1,2,3]
i = 0
adders[i] = lambda a: i+a
i = 1
adders[i] = lambda a: i+a
i = 2
adders[i] = lambda a: i+a
i = 3
adders[i] = lambda a: i+a

11
এটি লুপ, কারণ অন্যান্য অনেক ভাষায় একটি লুপ একটি নতুন সুযোগ তৈরি করতে পারে।
স্পষ্টত

1
এই উত্তরটি ভাল কারণ এটি iপ্রতিটি ল্যাম্বদা ফাংশনের জন্য কেন একই পরিবর্তনশীল অ্যাক্সেস করা হচ্ছে তা ব্যাখ্যা করে।
ডেভিড কলানান

3

আপনার দ্বিতীয় প্রশ্নের উত্তরে এটি করার সর্বাধিক মার্জিত উপায় হ'ল একটি ফাংশন ব্যবহার করা যা অ্যারের পরিবর্তে দুটি পরামিতি নেয়:

add = lambda a, b: a + b
add(1, 3)

তবে এখানে ল্যাম্বদা ব্যবহার করা কিছুটা নির্বোধ। পাইথন আমাদের operatorমডিউল দেয় যা বুনিয়াদি অপারেটরগুলিকে একটি কার্যকরী ইন্টারফেস সরবরাহ করে। উপরের ল্যাম্বডায় অতিরিক্ত সংযোজনকারীকে কল করার জন্য অপ্রয়োজনীয় ওভারহেড রয়েছে:

from operator import add
add(1, 3)

আমি বুঝতে পারি যে আপনি চারপাশে খেলছেন, ভাষাটি অন্বেষণ করার চেষ্টা করছেন, তবে আমি এমন পরিস্থিতিটি কল্পনা করতে পারি না যেখানে আমি পাইথনের স্কোপিং অদ্ভুততা পেতে পারে এমন একটি ক্রিয়া ব্যবহার করব।

আপনি যদি চান, আপনি একটি ছোট শ্রেণি লিখতে পারেন যা আপনার অ্যারে-ইনডেক্সিং সিনট্যাক্স ব্যবহার করে:

class Adders(object):
    def __getitem__(self, item):
        return lambda a: a + item

adders = Adders()
adders[1](3)

2
ক্রিস, অবশ্যই উপরের কোডটির আমার মূল সমস্যাটির সাথে কোনও সম্পর্ক নেই। এটি আমার বক্তব্যকে একটি সহজ উপায়ে চিত্রিত করার জন্য নির্মিত হয়েছে। এটি অবশ্যই অর্থহীন এবং নির্বোধ।
বোয়াজ

3

এখানে একটি নতুন উদাহরণ রয়েছে যা সংলগ্ন প্রসঙ্গটি "সংরক্ষণ করা হয়েছে" তা পরিষ্কার করার জন্য ডেটা কাঠামো এবং একটি বন্ধের বিষয়বস্তু হাইলাইট করে।

def make_funcs():
    i = 42
    my_str = "hi"

    f_one = lambda: i

    i += 1
    f_two = lambda: i+1

    f_three = lambda: my_str
    return f_one, f_two, f_three

f_1, f_2, f_3 = make_funcs()

বন্ধে কী আছে?

>>> print f_1.func_closure, f_1.func_closure[0].cell_contents
(<cell at 0x106a99a28: int object at 0x7fbb20c11170>,) 43 

উল্লেখযোগ্যভাবে, my_str এফ 1 এর বন্ধে নেই।

F2 এর বন্ধে কী আছে?

>>> print f_2.func_closure, f_2.func_closure[0].cell_contents
(<cell at 0x106a99a28: int object at 0x7fbb20c11170>,) 43

লক্ষ্য করুন (মেমরি ঠিকানাগুলি থেকে) যে দুটি ক্লোজারেই একই জিনিস রয়েছে। সুতরাং, আপনি ল্যাম্বডা ফাংশনটিকে সুযোগের একটি রেফারেন্স হিসাবে ভাবতে শুরু করতে পারেন । তবে, আমার_এসআরটি f_1 বা f_2 এর ক্লোজারে নেই, এবং আমি f_3 (দেখানো হয়নি) এর ক্লোজারে নেই, যা বোঝায় যে ক্লোজার অবজেক্টগুলি তারা স্বতন্ত্র বস্তু।

বন্ধের জিনিসগুলি কি একই জিনিস?

>>> print f_1.func_closure is f_2.func_closure
False

এনবি আউটপুট int object at [address X]>আমাকে ভাবিয়েছে যে বন্ধটি [ঠিকানা এক্স] একে একে একটি রেফারেন্স সংরক্ষণ করছে। তবে ল্যাম্বদা স্টেটমেন্টের পরে ভেরিয়েবলটি পুনরায় নিয়োগ করা হলে [অ্যাড্রেস এক্স] পরিবর্তন হবে।
জেফ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.