Skip to content

lambda functions seem to be overwritten #145839

@fgansevl

Description

@fgansevl

Bug report

Bug description:

i have created a script that illustrates the bug.
in short, when storing lambda functions in a dict, in some cases all dict values have the same lambda.
this only occurs if the (local)-scoped vars are reused to create the lambda.

using uv, i have tested thist with:

  • cpython-3.8.20
  • cpython-3.9.25
  • cpython-3.10.19
  • cpython-3.11.14
  • cpython-3.12.12
  • cpython-3.13.11+freethreaded
  • cpython-3.13.11
  • cpython-3.14.2+freethreaded
  • cpython-3.14.2
  • cpython-3.14.3
  • cpython-3.15.0a5+freethreaded
  • cpython-3.15.0a5
  • graalpy-3.11.0
  • graalpy-3.12.0
  • pypy-3.8.16
  • pypy-3.9.19
  • pypy-3.10.16
  • pypy-3.11.13
from itertools import product


class Bug:
    funs = {}

    @classmethod
    def add(cls, tag, fun):
        cls.funs[tag] = fun

    def use(self, tag):
        return self.funs[tag]()


def a():
    return "a"


def b():
    return "b"


def test(assign, use_lambda, with_func):
    print(f"{assign=}, {use_lambda=} {with_func=}")
    ab = (("a", a), ("b", b))
    bug = Bug()

    def add_fun(bug, t, f):
        if not use_lambda:
            bug.add(t, f)
        else:
            bug.add(t, lambda: f())

    if assign == "no_var":
        # don't use vars, name the tag and function explicitly
        if with_func:
            add_fun(bug, "a", a)
            add_fun(bug, "b", b)
        else:
            if not use_lambda:
                bug.add("a", a)
                bug.add("b", b)
            else:
                bug.add("a", lambda: a())
                bug.add("b", lambda: b())

    if assign == "loop_var":
        # get the tag and function from the `ab` tuple in the loop vars t and f
        if with_func:
            for t, f in ab:
                add_fun(bug, t, f)
        else:
            for t, f in ab:
                if not use_lambda:
                    bug.add(t, f)
                else:
                    bug.add(t, lambda: f())

    if assign == "reuse_var":
        # get the tag and function from the `ab` tuple in the local vars t and f
        t, f = ab[0]
        if with_func:
            add_fun(bug, t, f)
        else:
            if not use_lambda:
                bug.add(t, f)
            else:
                bug.add(t, lambda: f())
        t, f = ab[1]
        if with_func:
            add_fun(bug, t, f)
        else:
            if not use_lambda:
                bug.add(t, f)
            else:
                bug.add(t, lambda: f())

    if assign == "new_var":
        # get the tag and function from the `ab` tuple in different local vars (t0, t1) and (f0, f1)
        t0, f0 = ab[0]
        if with_func:
            add_fun(bug, t0, f0)
        else:
            if not use_lambda:
                bug.add(t0, f0)
            else:
                bug.add(t0, lambda: f0())
        t1, f1 = ab[1]
        if with_func:
            add_fun(bug, t1, f1)
        else:
            if not use_lambda:
                bug.add(t1, f1)
            else:
                bug.add(t1, lambda: f1())

    a_ok = bug.use("a") == "a"
    b_ok = bug.use("b") == "b"

    print(
        "a", a_ok, end="\n" if a_ok else f"  # BUG: Expect 'a', got {bug.use('a')!r}\n"
    )
    print(
        "b", b_ok, end="\n" if b_ok else f"  # BUG: Expect 'b', got {bug.use('b')!r}\n"
    )
    print()


if __name__ == "__main__":
    for assign, use_lambda, with_func in product(
        ("no_var", "loop_var", "reuse_var", "new_var"), (False, True), (False, True)
    ):
        test(assign, use_lambda, with_func)

CPython versions tested on:

3.14

Operating systems tested on:

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions