একটি লুপে ফাংশন তৈরি করা হচ্ছে


106

আমি একটি লুপের ভিতরে ফাংশন তৈরি করার চেষ্টা করছি:

functions = []

for i in range(3):
    def f():
        return i

    # alternatively: f = lambda: i

    functions.append(f)

সমস্যাটি হ'ল সমস্ত ফাংশন সমান হয়। 0, 1, এবং 2 ফেরতের পরিবর্তে তিনটি ফাংশনই 2:

print([f() for f in functions])
# expected output: [0, 1, 2]
# actual output:   [2, 2, 2]

কেন এটি হচ্ছে, এবং 3 টি বিভিন্ন ফাংশন পেতে আমি কী করব যা যথাক্রমে 0, 1 এবং 2 আউটপুট দেয়?


4
আমার মনে করিয়ে দেওয়ার মতো হিসাবে: ডকস.পিথন- গাইড.আর.ইন
স্লেস্ট

উত্তর:


170

দেরীতে বাইন্ডিং নিয়ে আপনি কোনও সমস্যায় পড়ছেন - প্রতিটি ফাংশন iযত তাড়াতাড়ি দেরী হবে (সুতরাং, যখন লুপটি শেষ হওয়ার পরে ডাকা iহবে তখন সেট হয়ে যাবে 2)।

প্রারম্ভিক বাইন্ডিং জোর করে সহজেই ঠিক করা: def f():এটি def f(i=i):পছন্দ করুন:

def f(i=i):
    return i

ডিফল্ট মান (ডানদিকের iমধ্যে i=iযুক্তি নামের জন্য একটি ডিফল্ট মান i, যা বাম-হাত iমধ্যে i=i) দিকে তাকিয়ে করছে defসময়, না এ callসময়, তাই মূলত তারা বিশেষভাবে গোড়ার দিকে বাঁধাই খুঁজছেন একটি উপায় করছি।

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

def make_f(i):
    def f():
        return i
    return f

এবং আপনার লুপটিতে বিবৃতিটির f = make_f(i)পরিবর্তে ব্যবহার করুন def


7
কীভাবে আপনি এই জিনিসগুলি ঠিক করতে জানেন?
alwbtc

4
@ অ্যালববিটিসি এটি বেশিরভাগ ক্ষেত্রে কেবল অভিজ্ঞতা, বেশিরভাগ লোকেরা নিজেরাই এই বিষয়গুলির মুখোমুখি হয়েছেন।
রুহোলা

দয়া করে কেন এটি কাজ করছে তা আপনি ব্যাখ্যা করতে পারেন? (আপনি আমাকে লুপে উত্পন্ন কলব্যাকে বাঁচান, যুক্তি সর্বদা লুপের স্থায়ী হয় তাই আপনাকে ধন্যবাদ!)
ভিনসেন্ট বনেট

23

ব্যাখ্যা

এখানে সমস্যাটি হ'ল iফাংশনটি fতৈরি হয়ে গেলে এর মান সংরক্ষণ করা হয় না । বরং কখন ডাকাf হবে তার মান সন্ধান করুন ।i

আপনি যদি এটির বিষয়ে চিন্তা করেন তবে এই আচরণটি সঠিক ধারণা দেয়। প্রকৃতপক্ষে, এটি একমাত্র যুক্তিযুক্ত উপায় যা কাজ করতে পারে work কল্পনা করুন যে আপনার এমন একটি ফাংশন রয়েছে যা বৈশ্বিক চলকটি অ্যাক্সেস করে, এর মতো:

global_var = 'foo'

def my_function():
    print(global_var)

global_var = 'bar'
my_function()

আপনি যখন এই কোডটি পড়বেন, আপনি অবশ্যই - এটি "বার" মুদ্রণ করবেন বলে আশা করবেন, "ফু" নয়, কারণ global_varফাংশন ঘোষণার পরে এর মান পরিবর্তিত হয়েছে। একই জিনিসটি আপনার নিজের কোডে ঘটছে: আপনি কল fকরার সময়টির মান iপরিবর্তন হয়ে গেছে এবং সেট হয়ে গেছে 2

সমাধান

এই সমস্যাটি সমাধান করার অনেক উপায় রয়েছে। এখানে কয়েকটি বিকল্প রয়েছে:

  • iএটিকে ডিফল্ট আর্গুমেন্ট হিসাবে ব্যবহার করে তাড়াতাড়ি বাইন্ডিং বাধ্য করুন

    ক্লোজার ভেরিয়েবলগুলি (যেমন i) এর বিপরীতে , ফাংশনটি সংজ্ঞায়িত হওয়ার সাথে সাথে ডিফল্ট আর্গুমেন্টগুলি তত্ক্ষণাত মূল্যায়ন করা হয়:

    for i in range(3):
        def f(i=i):  # <- right here is the important bit
            return i
    
        functions.append(f)
    

    এটি কীভাবে / কেন কাজ করে তা সম্পর্কে একটু অন্তর্দৃষ্টি দেওয়ার জন্য: একটি ফাংশনের ডিফল্ট আর্গুমেন্টগুলি ফাংশনের একটি বৈশিষ্ট হিসাবে সংরক্ষণ করা হয়; সুতরাং এর বর্তমান মান iস্ন্যাপশট এবং সংরক্ষণ করা হয়।

    >>> i = 0
    >>> def f(i=i):
    ...     pass
    >>> f.__defaults__  # this is where the current value of i is stored
    (0,)
    >>> # assigning a new value to i has no effect on the function's default arguments
    >>> i = 5
    >>> f.__defaults__
    (0,)
    
  • iবন্ধের বর্তমান মান ক্যাপচার জন্য একটি ফাংশন কারখানা ব্যবহার করুন

    আপনার সমস্যার মূল এটি iহ'ল একটি পরিবর্তনশীল যা পরিবর্তন করতে পারে। আমরা আর কোনও পরিবর্তনশীল তৈরি না করে যা কখনই পরিবর্তিত হবে না তার গ্যারান্টিযুক্ত এই সমস্যাটি ঘিরে কাজ করতে পারি - এবং এটি করার সবচেয়ে সহজ উপায় হ'ল একটি বন্ধ :

    def f_factory(i):
        def f():
            return i  # i is now a *local* variable of f_factory and can't ever change
        return f
    
    for i in range(3):           
        f = f_factory(i)
        functions.append(f)
    
  • ব্যবহার করুন functools.partialবর্তমান মূল্য জুড়তে অনুমতি iথেকেf

    functools.partialআপনাকে একটি বিদ্যমান ফাংশনে যুক্তি যুক্ত করতে দেয়। একরকম, এটিও এক ধরণের ফাংশন কারখানা।

    import functools
    
    def f(i):
        return i
    
    for i in range(3):    
        f_with_i = functools.partial(f, i)  # important: use a different variable than "f"
        functions.append(f_with_i)
    

ক্যাভ্যাট: আপনি যদি ভেরিয়েবলকে একটি নতুন মান নির্ধারণ করেন তবে এই সমাধানগুলি কেবলমাত্র কার্যকর হয়। আপনি যদি ভেরিয়েবলে সঞ্চিত বস্তুটি পরিবর্তন করেন তবে আপনি আবার একই সমস্যাটি অনুভব করবেন:

>>> i = []  # instead of an int, i is now a *mutable* object
>>> def f(i=i):
...     print('i =', i)
...
>>> i.append(5)  # instead of *assigning* a new value to i, we're *mutating* it
>>> f()
i = [5]

iআমরা এটি একটি ডিফল্ট যুক্তিতে রূপান্তরিত করেও কীভাবে পরিবর্তন হয়েছে তা লক্ষ্য করুন ! আপনার কোড যদি mutates i , তাহলে আপনি একটি কেনে কপি এর iআপনার ফাংশন, তাই পছন্দ:

  • def f(i=i.copy()):
  • f = f_factory(i.copy())
  • f_with_i = functools.partial(f, i.copy())
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.