在循环(或理解)中创建功能(或lambdas)

发布于 2025-02-07 22:13:09 字数 603 浏览 3 评论 0原文

我正在尝试在循环内部创建功能:

functions = []

for i in range(3):
    def f():
        return i
    functions.append(f)

或者使用lambda:

functions = []

for i in range(3):
    functions.append(lambda: i)

问题是所有功能最终都相同。而不是返回0、1和2,所有三个功能都返回2:

print([f() for f in functions])
  • 预期输出:[0,1,2]
  • 实际输出:[2,2,2]

为什么会发生这种情况,我该怎么做才能分别输出3个不同的功能,分别输出0、1和2?

I'm trying to create functions inside of a loop:

functions = []

for i in range(3):
    def f():
        return i
    functions.append(f)

Alternatively, with lambda:

functions = []

for i in range(3):
    functions.append(lambda: i)

The problem is that all functions end up being the same. Instead of returning 0, 1, and 2, all three functions return 2:

print([f() for f in functions])
  • Expected output: [0, 1, 2]
  • Actual output: [2, 2, 2]

Why is this happening, and what should I do to get 3 different functions that output 0, 1, and 2 respectively?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

╰沐子 2025-02-14 22:13:10

您正在遇到 late binding 的问题 - 每个功能都会尽可能晚(因此,在循环结束后调用时, I 将设置为2)。

通过强迫早期绑定而轻松修复:更改def f(): to def f(i = i):喜欢这样:

def f(i=i):
    return i

默认值(默认值i in i = i是参数name i的默认值,这是in i的左手i = i)查找def时间,而不是call时间,因此,从本质上讲,它们是专门寻找早期绑定的一种方式。

如果您担心f获取额外的参数(因此可能被错误地称为),则有一种更复杂的方式涉及使用闭合作为“功能工厂”:

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

在您的循环中使用<代码> f = make_f(i)而不是def语句。

You're running into a problem with late binding -- each function looks up i as late as possible (thus, when called after the end of the loop, i will be set to 2).

Easily fixed by forcing early binding: change def f(): to def f(i=i): like this:

def f(i=i):
    return i

Default values (the right-hand i in i=i is a default value for argument name i, which is the left-hand i in i=i) are looked up at def time, not at call time, so essentially they're a way to specifically looking for early binding.

If you're worried about f getting an extra argument (and thus potentially being called erroneously), there's a more sophisticated way which involved using a closure as a "function factory":

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

and in your loop use f = make_f(i) instead of the def statement.

臻嫒无言 2025-02-14 22:13:10

这里的说明

是,当创建函数f时,i的值不会保存。相反,f查找i的值时,它被称为。

如果您考虑一下,这种行为是完全合理的。实际上,这是唯一合理的函数可以使用的方法。想象一下,您具有访问全局变量的函数,例如:

global_var = 'foo'

def my_function():
    print(global_var)

global_var = 'bar'
my_function()

当您读取此代码时,您当然会期望它打印“ bar”,而不是“ foo”,因为global_var的值声明功能后已更改。同样的事情正在您自己的代码中发生:当您调用f时,i的值已更改并设置为2

解决方案

实际上有很多解决此问题的方法。以下是一些选项:

  • i的强制绑定将其用作默认参数

    与闭合变量不同(例如i),定义函数时会立即评估默认参数:

     在范围(3)中为i:
        def f(i = i):#&lt;  - 在这里是重要的位
            返回i
    
        functions.append(f)
     

    给出一点洞察力,以了解它的工作方式/原因:函数的默认参数被存储为函数的属性;因此,当前 i的值是快照并保存的。

     &gt;&gt;&gt; i = 0
    &gt;&gt;&gt; def f(i = i):
    ... 经过
    &gt;&gt;&gt; f .__默认值__#这是存储i的当前值
    (0,)
    &gt;&gt;&gt; #将新值分配给我没有影响该函数的默认参数
    &gt;&gt;&gt; i = 5
    &gt;&gt;&gt; f .__默认
    (0,)
     
  • 使用功能工厂在闭合中捕获i的当前值

    您问题的根源是i是一个可以更改的变量。我们可以通过创建一个保证永远不会改变的另一个变量来解决这个问题 - 最简单的方法是 cloture

      def f_factory(i):
        def f():
            返回我#我现在是一个 * local * f_factory的变量,永远无法更改
        返回f
    
    对于我在范围(3)中:           
        f = f_factory(i)
        functions.append(f)
     
  • 使用

    使用funct> Function functools 。

    functorools.partials.partials.partial 让您将参数附加到现有函数。从某种意义上说,这也是一种功能工厂。

     导入函数
    
    def f(i):
        返回i
    
    对于我在范围(3)中:    
        f_with_i = functools.partial(f,i)#重要:使用与“ 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如何更改,即使我们将其变成了默认参数!如果您的代码突变 i,则必须将 copy i绑定到您的功能,例如:

  • <代码> def f(i = i.copy()):
  • f = f_factory(i.copy())
  • f_with_i = functools.partials.partial(f,i.copy(i.copy)( )

The Explanation

The issue here is that the value of i is not saved when the function f is created. Rather, f looks up the value of i when it is called.

If you think about it, this behavior makes perfect sense. In fact, it's the only reasonable way functions can work. Imagine you have a function that accesses a global variable, like this:

global_var = 'foo'

def my_function():
    print(global_var)

global_var = 'bar'
my_function()

When you read this code, you would - of course - expect it to print "bar", not "foo", because the value of global_var has changed after the function was declared. The same thing is happening in your own code: By the time you call f, the value of i has changed and been set to 2.

The Solution

There are actually many ways to solve this problem. Here are a few options:

  • Force early binding of i by using it as a default argument

    Unlike closure variables (like i), default arguments are evaluated immediately when the function is defined:

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

    To give a little bit of insight into how/why this works: A function's default arguments are stored as an attribute of the function; thus the current value of i is snapshotted and saved.

    >>> 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,)
    
  • Use a function factory to capture the current value of i in a closure

    The root of your problem is that i is a variable that can change. We can work around this problem by creating another variable that is guaranteed to never change - and the easiest way to do this is a closure:

    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)
    
  • Use functools.partial to bind the current value of i to f

    functools.partial lets you attach arguments to an existing function. In a way, it too is a kind of function factory.

    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)
    

Caveat: These solutions only work if you assign a new value to the variable. If you modify the object stored in the variable, you'll experience the same problem again:

>>> 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]

Notice how i still changed even though we turned it into a default argument! If your code mutates i, then you must bind a copy of i to your function, like so:

  • def f(i=i.copy()):
  • f = f_factory(i.copy())
  • f_with_i = functools.partial(f, i.copy())
薄荷梦 2025-02-14 22:13:10

对于那些使用lambda来解决这个问题的人:

该解决方案仅替换lambda:i by lambda i = i:i

functions = []
for i in range(3): 
    functions.append(lambda i=i: i)
print([f() for f in functions])
# [0, 1, 2]

示例用例:要具有lambda函数,请评估一个变量(而不是推迟)

For those coming to this question using lambda:

The solution is simply to replace lambda: i by lambda i=i: i.

functions = []
for i in range(3): 
    functions.append(lambda i=i: i)
print([f() for f in functions])
# [0, 1, 2]

Example use-case: How to have a lambda function evaluate a variable now (and not postponed)

当梦初醒 2025-02-14 22:13:10

不是答案,而是笔记。

这种现象也可以在循环之外观察到,很清楚发生了什么:

g = 'hello'

def foo():
    return lambda x : (g,x)

f = foo()

print(f('world')); g = 'goodbye'; print(f('earth'))

我们(显然是?)得到:

('hello', 'world')
('goodbye', 'earth')

或者可以在问题中查看代码,但通过调用

functions = []

print(f"Before loop: local i = {locals().get('i')}, global i = {globals().get('i')}")

for i in range(3):
    print(f"Inside loop: local i = {locals().get('i')}, global i = {globals().get('i')}")
    def f():
        print(f"Inside f(): local i = {locals().get('i')}, global i = {globals().get('i')}")
        return i
    functions.append(f)

print(f"After loop: local i = {locals().get('i')}, global i = {globals().get('i')}")

print([f() for f in functions])

我们得到了以下内容,并解释了很多:

Before loop: local i = None, global i = None
Inside loop: local i = 0, global i = 0
Inside loop: local i = 1, global i = 1
Inside loop: local i = 2, global i = 2
After loop: local i = 2, global i = 2
Inside f(): local i = None, global i = 2
Inside f(): local i = None, global i = 2
Inside f(): local i = None, global i = 2
[2, 2, 2]

最终要取决于问题的i在问题中的 locap 而不是本地对于程序(以及具有可变状态的语言闭合螺栓固定的语言具有尴尬的角落)。另外,设计Algol60后的Algol风格语言的“有问题”的决定,该语言不必正确地宣布变量无济于事,即这使得阅读Python程序很困难(更不用说大型Python程序在经济上是危险的) 。显然 pep-3104 有一个想法,可以在2006年进行必要的改进,但没有占用。

奖金回合:Java

由于我不确定Java会做什么,这里与Java中出现的问题相同。请注意,编译器需要付出额外的努力来检测闭合所看到的变量是可变的(非比赛),并且在外部环境中还是可见的,还是本地或最终的。整个现象不仅是偶然的。

package org.example;

import java.util.*;

public class Main {


    private static String mutableOuterContextString;

    private static void print(String str) {
        System.out.println("  '" + str + "'");
    }

    private static void printStringWithIdentity(String str) {
        System.out.println("  " + stringWithIdentity(str));
    }

    private static String stringWithIdentity(String str) {
        return "'" + str + "' at " + Objects.toIdentityString(str);
    }

    private final static List<String> numberStrings = Collections.unmodifiableList(Arrays.asList("one", "two", "three"));

    // Here, closures will use the 'reference to String' as given
    // by 'str' at 'closure build time'. Each has been given a specific 'str'.
    // At 'closure call time', the closures created will properly print
    // "one", "two", "three".
    // This corresponds to "function returning function" approach in Python.

    public static List<Runnable> one_two_three_as_expected_1() {
        final List<Runnable> funcs = new ArrayList<>();
        numberStrings.forEach(str -> funcs.add(
                () -> print(str)
        ));
        return funcs;
    }

    // This is the same code as above, just more explicit.

    public static List<Runnable> one_two_three_as_expected_2() {
        final List<Runnable> funcs = new ArrayList<>();
        for (final String str : numberStrings) {
            funcs.add(
                    () -> print(str)
            );
        }
        return funcs;
    }

    // This is the same code as above, just even more explicit.
    // The closure is in fact "just a class" created by the compiler.

    private static class RunnableX implements Runnable {

        private final String str;

        public RunnableX(final String str) {
            this.str = str;
        }

        @Override
        public void run() {
            print(str);
        }
    }

    public static List<Runnable> one_two_three_as_expected_3() {
        final List<Runnable> funcs = new ArrayList<>();
        for (final String str : numberStrings) {
            funcs.add(new RunnableX(str));
        }
        return funcs;
    }

    // As in Python, an interaction between "mutable state in an
    // outside context" and closures leads to surprises.
    //
    // Syntactically, there is not much difference between the
    // closure closing over a local/final variable (str) or a
    // mutable variable from the outside context (mutableOuterContextString)
    // but the compiler must create some different code indeed.

    public static List<Runnable> threethreethree_by_accessing_outside_context_1() {
        final List<Runnable> funcs = new ArrayList<>();
        for (final String str : numberStrings) {
            mutableOuterContextString = str;
            funcs.add(
                    () -> printStringWithIdentity(mutableOuterContextString)
            );
        }
        return funcs;
    }


    // This should be the same code as above, just more explicit.
    // The closure is in fact "just a class" created by the compiler.

    private static class RunnableY implements Runnable {

        @Override
        public void run() {
            printStringWithIdentity(mutableOuterContextString);
        }
    }

    public static List<Runnable> threethreethree_by_accessing_outside_context_2() {
        final List<Runnable> funcs = new ArrayList<>();
        for (final String str : numberStrings) {
            mutableOuterContextString = str;
            funcs.add(new RunnableY());
        }
        return funcs;
    }

    // If the try to reproduce the "three three three" effect with a
    // variable in the local context, we get something that will not compile:
    // "Variable used in lambda expression should be final or effectively final"
    // at "System.out.println(curString2)"

    /*
    public static List<Runnable> three_three_three_this_will_not_compile() {
        final List<Runnable> funcs = new ArrayList<>();
        String curString2;
        for (final String str : numberStrings) {
            curString2 = str;
            funcs.add(() -> print(curString2)); // <--- won't compile
        }
        return funcs;
    }
    */

    // Fixing it Python-style
    // Note that we do not even need to declare a local variable inside the build_..() method.
    // Directly using the variable "outerStr" that has been passed-in is good enough.
    // It is not important whether it has been declared "final" or not in the method declaration.

    public static Runnable build_closure_with_its_own_local_variable(final String outerStr) {
        System.out.println("  Creating closure with a local reference for " + stringWithIdentity(outerStr));
        return () -> printStringWithIdentity(outerStr);
    }

    public static List<Runnable> three_three_three_fixed() {
        final List<Runnable> funcs = new ArrayList<>();
        for (final String str : numberStrings) {
            mutableOuterContextString = str;
            funcs.add(build_closure_with_its_own_local_variable(mutableOuterContextString));
        }
        return funcs;
    }

    public static void main(String[] args) {
        System.out.println("Print 'one', 'two', 'three' as expected, take 1");
        one_two_three_as_expected_1().forEach(r -> r.run());
        System.out.println("Print 'one', 'two', 'three' as expected, take 2");
        one_two_three_as_expected_2().forEach(r -> r.run());
        System.out.println("Print 'one', 'two', 'three' as expected, take 3");
        one_two_three_as_expected_3().forEach(r -> r.run());
        System.out.println("Print 'three', 'three', 'three', unexpectedly, take 1");
        threethreethree_by_accessing_outside_context_1().forEach(r -> r.run());
        System.out.println("Print 'three', 'three', 'three', unexpectedly, take 2");
        threethreethree_by_accessing_outside_context_2().forEach(r -> r.run());
        System.out.println("Print 'one', 'two', 'three' again by creating a local variable");
        three_three_three_fixed().forEach(r -> r.run());
    }
}

输出

Print 'one', 'two', 'three' as expected, take 1
  'one'
  'two'
  'three'
Print 'one', 'two', 'three' as expected, take 2
  'one'
  'two'
  'three'
Print 'one', 'two', 'three' as expected, take 3
  'one'
  'two'
  'three'
Print 'three', 'three', 'three', unexpectedly, take 1
  'three' at java.lang.String@77459877
  'three' at java.lang.String@77459877
  'three' at java.lang.String@77459877
Print 'three', 'three', 'three', unexpectedly, take 2
  'three' at java.lang.String@77459877
  'three' at java.lang.String@77459877
  'three' at java.lang.String@77459877
Print 'one', 'two', 'three' again by creating a local variable
  Creating closure with a local reference for 'one' at java.lang.String@87aac27
  Creating closure with a local reference for 'two' at java.lang.String@6ce253f1
  Creating closure with a local reference for 'three' at java.lang.String@77459877
  'one' at java.lang.String@87aac27
  'two' at java.lang.String@6ce253f1
  'three' at java.lang.String@77459877

Not an answer, but a note.

This phenomenon can also be observed outside of a loop, where it becomes quite clear what happens:

g = 'hello'

def foo():
    return lambda x : (g,x)

f = foo()

print(f('world')); g = 'goodbye'; print(f('earth'))

We (evidently?) get:

('hello', 'world')
('goodbye', 'earth')

Or one can look at the code in the question, but add printout by calling locals() and globals()

functions = []

print(f"Before loop: local i = {locals().get('i')}, global i = {globals().get('i')}")

for i in range(3):
    print(f"Inside loop: local i = {locals().get('i')}, global i = {globals().get('i')}")
    def f():
        print(f"Inside f(): local i = {locals().get('i')}, global i = {globals().get('i')}")
        return i
    functions.append(f)

print(f"After loop: local i = {locals().get('i')}, global i = {globals().get('i')}")

print([f() for f in functions])

We get the following, explaining a lot:

Before loop: local i = None, global i = None
Inside loop: local i = 0, global i = 0
Inside loop: local i = 1, global i = 1
Inside loop: local i = 2, global i = 2
After loop: local i = 2, global i = 2
Inside f(): local i = None, global i = 2
Inside f(): local i = None, global i = 2
Inside f(): local i = None, global i = 2
[2, 2, 2]

Ultimately it is down to the fact that the i of the loop in the question is not local to the loop but global to the program (and that languages with mutable state with closures bolted-one have awkward corners). Also, the 'problematic' decision to design a post-ALGOL60 ALGOL-style language where variables need not be properly declared doesn't help, i.e. it makes reading a Python program quite difficult (not to mention large-ish Python programs economically hazardous). Apparently PEP-3104 had an idea for a necessary improvement in 2006, but it wasn't taken up.

Bonus round: Java

As I was unsure what Java would do, here is same problem as it appears in Java. Note that the compiler needs to do extra effort to detect whether the variable the closure sees is mutable (non-final) and visible in an outside context or just local or final. The whole phenomenon is not just accidental.

package org.example;

import java.util.*;

public class Main {


    private static String mutableOuterContextString;

    private static void print(String str) {
        System.out.println("  '" + str + "'");
    }

    private static void printStringWithIdentity(String str) {
        System.out.println("  " + stringWithIdentity(str));
    }

    private static String stringWithIdentity(String str) {
        return "'" + str + "' at " + Objects.toIdentityString(str);
    }

    private final static List<String> numberStrings = Collections.unmodifiableList(Arrays.asList("one", "two", "three"));

    // Here, closures will use the 'reference to String' as given
    // by 'str' at 'closure build time'. Each has been given a specific 'str'.
    // At 'closure call time', the closures created will properly print
    // "one", "two", "three".
    // This corresponds to "function returning function" approach in Python.

    public static List<Runnable> one_two_three_as_expected_1() {
        final List<Runnable> funcs = new ArrayList<>();
        numberStrings.forEach(str -> funcs.add(
                () -> print(str)
        ));
        return funcs;
    }

    // This is the same code as above, just more explicit.

    public static List<Runnable> one_two_three_as_expected_2() {
        final List<Runnable> funcs = new ArrayList<>();
        for (final String str : numberStrings) {
            funcs.add(
                    () -> print(str)
            );
        }
        return funcs;
    }

    // This is the same code as above, just even more explicit.
    // The closure is in fact "just a class" created by the compiler.

    private static class RunnableX implements Runnable {

        private final String str;

        public RunnableX(final String str) {
            this.str = str;
        }

        @Override
        public void run() {
            print(str);
        }
    }

    public static List<Runnable> one_two_three_as_expected_3() {
        final List<Runnable> funcs = new ArrayList<>();
        for (final String str : numberStrings) {
            funcs.add(new RunnableX(str));
        }
        return funcs;
    }

    // As in Python, an interaction between "mutable state in an
    // outside context" and closures leads to surprises.
    //
    // Syntactically, there is not much difference between the
    // closure closing over a local/final variable (str) or a
    // mutable variable from the outside context (mutableOuterContextString)
    // but the compiler must create some different code indeed.

    public static List<Runnable> threethreethree_by_accessing_outside_context_1() {
        final List<Runnable> funcs = new ArrayList<>();
        for (final String str : numberStrings) {
            mutableOuterContextString = str;
            funcs.add(
                    () -> printStringWithIdentity(mutableOuterContextString)
            );
        }
        return funcs;
    }


    // This should be the same code as above, just more explicit.
    // The closure is in fact "just a class" created by the compiler.

    private static class RunnableY implements Runnable {

        @Override
        public void run() {
            printStringWithIdentity(mutableOuterContextString);
        }
    }

    public static List<Runnable> threethreethree_by_accessing_outside_context_2() {
        final List<Runnable> funcs = new ArrayList<>();
        for (final String str : numberStrings) {
            mutableOuterContextString = str;
            funcs.add(new RunnableY());
        }
        return funcs;
    }

    // If the try to reproduce the "three three three" effect with a
    // variable in the local context, we get something that will not compile:
    // "Variable used in lambda expression should be final or effectively final"
    // at "System.out.println(curString2)"

    /*
    public static List<Runnable> three_three_three_this_will_not_compile() {
        final List<Runnable> funcs = new ArrayList<>();
        String curString2;
        for (final String str : numberStrings) {
            curString2 = str;
            funcs.add(() -> print(curString2)); // <--- won't compile
        }
        return funcs;
    }
    */

    // Fixing it Python-style
    // Note that we do not even need to declare a local variable inside the build_..() method.
    // Directly using the variable "outerStr" that has been passed-in is good enough.
    // It is not important whether it has been declared "final" or not in the method declaration.

    public static Runnable build_closure_with_its_own_local_variable(final String outerStr) {
        System.out.println("  Creating closure with a local reference for " + stringWithIdentity(outerStr));
        return () -> printStringWithIdentity(outerStr);
    }

    public static List<Runnable> three_three_three_fixed() {
        final List<Runnable> funcs = new ArrayList<>();
        for (final String str : numberStrings) {
            mutableOuterContextString = str;
            funcs.add(build_closure_with_its_own_local_variable(mutableOuterContextString));
        }
        return funcs;
    }

    public static void main(String[] args) {
        System.out.println("Print 'one', 'two', 'three' as expected, take 1");
        one_two_three_as_expected_1().forEach(r -> r.run());
        System.out.println("Print 'one', 'two', 'three' as expected, take 2");
        one_two_three_as_expected_2().forEach(r -> r.run());
        System.out.println("Print 'one', 'two', 'three' as expected, take 3");
        one_two_three_as_expected_3().forEach(r -> r.run());
        System.out.println("Print 'three', 'three', 'three', unexpectedly, take 1");
        threethreethree_by_accessing_outside_context_1().forEach(r -> r.run());
        System.out.println("Print 'three', 'three', 'three', unexpectedly, take 2");
        threethreethree_by_accessing_outside_context_2().forEach(r -> r.run());
        System.out.println("Print 'one', 'two', 'three' again by creating a local variable");
        three_three_three_fixed().forEach(r -> r.run());
    }
}

Output

Print 'one', 'two', 'three' as expected, take 1
  'one'
  'two'
  'three'
Print 'one', 'two', 'three' as expected, take 2
  'one'
  'two'
  'three'
Print 'one', 'two', 'three' as expected, take 3
  'one'
  'two'
  'three'
Print 'three', 'three', 'three', unexpectedly, take 1
  'three' at java.lang.String@77459877
  'three' at java.lang.String@77459877
  'three' at java.lang.String@77459877
Print 'three', 'three', 'three', unexpectedly, take 2
  'three' at java.lang.String@77459877
  'three' at java.lang.String@77459877
  'three' at java.lang.String@77459877
Print 'one', 'two', 'three' again by creating a local variable
  Creating closure with a local reference for 'one' at java.lang.String@87aac27
  Creating closure with a local reference for 'two' at java.lang.String@6ce253f1
  Creating closure with a local reference for 'three' at java.lang.String@77459877
  'one' at java.lang.String@87aac27
  'two' at java.lang.String@6ce253f1
  'three' at java.lang.String@77459877
幽梦紫曦~ 2025-02-14 22:13:10

问题在于i被绑定到循环变量,该变量会在每次迭代中都会发生变化。对于此问题有各种各样的解决方案,可读性水平都不同。在下面的4个片段中,底部3个都是正确的,但我认为只有底部是最可读的:

# Original: wrong
f_list = [
    (lambda x: x + i) for i in range(10)
]
print([f(3) for f in f_list])

# Correct, but not so intuitive
f_list = [
    (lambda x, i=i: x + i) for i in range(10)
]
print([f(3) for f in f_list])

# More intuitive, but not so readable
f_list = [
    (lambda i: (lambda x: x + i))(i) for i in range(10)
]
print([f(3) for f in f_list])

# More readable
get_f = lambda i: (lambda x: x + i)
f_list = [
    get_f(i) for i in range(10)
]
print([f(3) for f in f_list])

The problem is that i gets bound to the loop variable, which changes on every iteration. There are various solutions to this problem, with varying levels of readability. Of the 4 snippets below, the bottom 3 are all correct, but I think only the bottom one is the most readable:

# Original: wrong
f_list = [
    (lambda x: x + i) for i in range(10)
]
print([f(3) for f in f_list])

# Correct, but not so intuitive
f_list = [
    (lambda x, i=i: x + i) for i in range(10)
]
print([f(3) for f in f_list])

# More intuitive, but not so readable
f_list = [
    (lambda i: (lambda x: x + i))(i) for i in range(10)
]
print([f(3) for f in f_list])

# More readable
get_f = lambda i: (lambda x: x + i)
f_list = [
    get_f(i) for i in range(10)
]
print([f(3) for f in f_list])
中性美 2025-02-14 22:13:10

您可以这样尝试:

l=[]
for t in range(10):
    def up(y):
        print(y)
    l.append(up)
l[5]('printing in 5th function')

You can try like this:

l=[]
for t in range(10):
    def up(y):
        print(y)
    l.append(up)
l[5]('printing in 5th function')
寒冷纷飞旳雪 2025-02-14 22:13:10

您必须将每个i值保存在内存中的单独空间中:

class StaticValue:
    val = None

    def __init__(self, value: int):
        StaticValue.val = value

    @staticmethod
    def get_lambda():
        return lambda x: x*StaticValue.val


class NotStaticValue:
    def __init__(self, value: int):
        self.val = value

    def get_lambda(self):
        return lambda x: x*self.val


if __name__ == '__main__':
    def foo():
        return [lambda x: x*i for i in range(4)]

    def bar():
        return [StaticValue(i).get_lambda() for i in range(4)]

    def foo_repaired():
        return [NotStaticValue(i).get_lambda() for i in range(4)]

    print([x(2) for x in foo()])
    print([x(2) for x in bar()])
    print([x(2) for x in foo_repaired()])

Result:
[6, 6, 6, 6]
[6, 6, 6, 6]
[0, 2, 4, 6]

You have to save the each of the i value in a separate space in memory e.g.:

class StaticValue:
    val = None

    def __init__(self, value: int):
        StaticValue.val = value

    @staticmethod
    def get_lambda():
        return lambda x: x*StaticValue.val


class NotStaticValue:
    def __init__(self, value: int):
        self.val = value

    def get_lambda(self):
        return lambda x: x*self.val


if __name__ == '__main__':
    def foo():
        return [lambda x: x*i for i in range(4)]

    def bar():
        return [StaticValue(i).get_lambda() for i in range(4)]

    def foo_repaired():
        return [NotStaticValue(i).get_lambda() for i in range(4)]

    print([x(2) for x in foo()])
    print([x(2) for x in bar()])
    print([x(2) for x in foo_repaired()])

Result:
[6, 6, 6, 6]
[6, 6, 6, 6]
[0, 2, 4, 6]
咋地 2025-02-14 22:13:10

要添加到 @aran-fey的出色答案中,在第二个解决方案中,您可能还希望修改函数内部的变量,可以使用关键字 nonlocal

def f_factory(i):
    def f(offset):
      nonlocal i
      i += offset
      return i
    return f

for i in range(3):           
    f = f_factory(i)
    print(f(10))

To add onto @Aran-Fey's excellent answer, in the second solution you might also wish to modify the variable inside your function which can be accomplished with the keyword nonlocal:

def f_factory(i):
    def f(offset):
      nonlocal i
      i += offset
      return i
    return f

for i in range(3):           
    f = f_factory(i)
    print(f(10))
铜锣湾横着走 2025-02-14 22:13:10

只需修改编辑的最后一行

functions.append(f())

:这是因为f是一个函数-Python将功能视为一流的公民,您可以将它们传递到变量中以稍后调用。因此,您的原始代码正在执行的操作是将功能本身附加到列表中,而您想做的是将函数的 result 附加到列表中,这是上面的行通过调用呼叫来实现的功能。

just modify the last line for

functions.append(f())

Edit: This is because f is a function - python treats functions as first-class citizens and you can pass them around in variables to be called later on. So what your original code is doing is appending the function itself to the list, while what you want to do is append the results of the function to the list, which is what the line above achieves by calling the function.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文