• A Function's name during its definition

    From Stefan Ram@21:1/5 to All on Tue Feb 7 17:08:10 2023
    When one defines a function, sometimes its name is only
    half-existent.

    One can implicitly evaluate the name of the function:

    main.py

    def g():
    def f():
    print( f )
    f()
    g()

    output

    <function g.<locals>.f at ...

    , but one gets an error when one tries to evaluate it explicitly:

    main.py

    def g():
    def f():
    print( eval( 'f' ))
    f()
    g()

    error output

    NameError: name 'f' is not defined

    .

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to Mark Bourne on Tue Feb 7 20:45:05 2023
    Mark Bourne <[email protected]> writes:
    In the second case, eval() only gets the globals and immediate locals,

    Yes, I think you are right. Curiously, the following program would
    mislead one to thing that eval /does/ see the intermediate names:

    main.py

    def f():
    x = 22
    def g():
    print( x )
    print( eval( 'x' ))
    g()
    f()

    output

    22
    22

    . But "print( x )" had the effect of making that x local.
    Without it, we see the error:

    main.py

    def f():
    x = 22
    def g():
    print( eval( 'x' ))
    g()
    f()

    error output

    NameError: name 'x' is not defined

    .

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark Bourne@21:1/5 to Stefan Ram on Tue Feb 7 20:38:00 2023
    Stefan Ram wrote:
    When one defines a function, sometimes its name is only
    half-existent.

    One can implicitly evaluate the name of the function:

    main.py

    def g():
    def f():
    print( f )
    f()
    g()

    output

    <function g.<locals>.f at ...

    , but one gets an error when one tries to evaluate it explicitly:

    main.py

    def g():
    def f():
    print( eval( 'f' ))
    f()
    g()

    error output

    NameError: name 'f' is not defined

    I'm guessing that's because the name "f", referencing that function, is
    in the local scope of the "g" function.

    In the first version, the interpreter searches the scope of the code
    which calls "print(f)". It doesn't find anything named "f" there, so
    searches the next scope out, that of the function identified by "g".
    That contains "f", so it's found and can be printed.

    In the second case, eval() only gets the globals and immediate locals,
    in this case the locals of "f". The name "f" doesn't exist in the local
    scope of "f", and it doesn't exist in the global scope. So the code
    executed by eval() can't see it.

    The following does work:

    def g():
    def f():
    print(eval('g'))
    f()
    g()

    ...because in this case "g" is defined in the global scope, so the code
    in the eval call can see it.

    The following also works:

    def g():
    def f():
    pass
    print(eval('f'))
    g()

    ...because in this case, eval() is called from within the scope of "g",
    so it can see the function "f" defined in that scope.


    --
    Mark.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark Bourne@21:1/5 to Stefan Ram on Tue Feb 7 23:04:54 2023
    Stefan Ram wrote:
    Mark Bourne <[email protected]> writes:
    In the second case, eval() only gets the globals and immediate locals,

    Yes, I think you are right. Curiously, the following program would
    mislead one to thing that eval /does/ see the intermediate names:

    main.py

    def f():
    x = 22
    def g():
    print( x )
    print( eval( 'x' ))
    g()
    f()

    output

    22
    22

    . But "print( x )" had the effect of making that x local.
    Without it, we see the error:

    main.py

    def f():
    x = 22
    def g():
    print( eval( 'x' ))
    g()
    f()

    error output

    NameError: name 'x' is not defined

    That is interesting. I know assigning to a value creates a local
    version (and the non-local then can't be accessed, even before the new
    value was assigned), but hadn't realised just referencing it brought it
    into local scope for reading. I guess that's to do with closures, where
    any variables referenced within the function get bound to the function's
    scope. e.g. if g() includes a reference to x [as it does in the first
    example above], and f() returned a reference to g(), calling g() later
    [from outside of f()] by using that reference would still be able to
    access x.

    With just the eval() call and no other reference to x, the interpreter
    doesn't know at that time the closure is created that there is a
    reference to x. At that point, the 'x' is just text in a string. It's
    only when eval() gets called that it tries to execute that string as
    Python code - and finds that x isn't in scope.

    I'm not all that familiar with the concept of closures, so may have got
    some of the terminology and details wrong - but it's something along
    those lines. I'm sure others will correct me...

    --
    Mark.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Chris Angelico@21:1/5 to Mark Bourne on Wed Feb 8 10:33:44 2023
    On Wed, 8 Feb 2023 at 10:18, Mark Bourne <[email protected]> wrote:

    Stefan Ram wrote:
    Mark Bourne <[email protected]> writes:
    In the second case, eval() only gets the globals and immediate locals,

    Yes, I think you are right. Curiously, the following program would
    mislead one to thing that eval /does/ see the intermediate names:

    main.py

    def f():
    x = 22
    def g():
    print( x )
    print( eval( 'x' ))
    g()
    f()

    output

    22
    22

    . But "print( x )" had the effect of making that x local.
    Without it, we see the error:

    main.py

    def f():
    x = 22
    def g():
    print( eval( 'x' ))
    g()
    f()

    error output

    NameError: name 'x' is not defined

    Don't know if Stefan realises it, but newsgroup text that gets replies
    ends up on the mailing list. So if this is a problem of copyright, you
    should probably stop posting to the newsgroup - unless you want people
    like Mark to unintentionally violate your copyright.

    That is interesting. I know assigning to a value creates a local
    version (and the non-local then can't be accessed, even before the new
    value was assigned), but hadn't realised just referencing it brought it
    into local scope for reading. I guess that's to do with closures, where
    any variables referenced within the function get bound to the function's scope. e.g. if g() includes a reference to x [as it does in the first example above], and f() returned a reference to g(), calling g() later
    [from outside of f()] by using that reference would still be able to
    access x.

    Yep, that's to do with closures. Your analysis is spot on. When a name
    is local to an outer function and referenced in an inner function, it
    becomes a nonlocal in both functions. You can also declare it with
    "nonlocal x" in the inner function, which - like with a global
    statement - allows you to assign to it.

    With just the eval() call and no other reference to x, the interpreter doesn't know at that time the closure is created that there is a
    reference to x. At that point, the 'x' is just text in a string. It's
    only when eval() gets called that it tries to execute that string as
    Python code - and finds that x isn't in scope.

    Mostly true, yes. The eval function doesn't get any magic ability to
    refer to nearby variables, so what it does is it depends on the
    globals() and locals() mappings (which can be the same dictionary
    under some circumstances).

    I think you'll agree that globals() does not and should not contain a
    reference to x here. It's definitely not a global.

    But is x a local? Well, technically it's a nonlocal; but more
    importantly, there's a difference between "theoretically could be a
    nonlocal" and "is actually a nonlocal", as demonstrated by this:

    def f():
    x = 42
    def g():
    print("without", locals())
    g()
    def h():
    x
    print("with", locals())
    h()

    So, can eval() find x?

    x = 1234
    def f():
    x = 42
    def g():
    print("without", eval("x"))
    g()
    def h():
    x
    print("with", eval("x"))
    h()

    f()
    without 1234
    with 42

    The compiler knows exactly which names are *theoretically available*
    (which is a lexical concept), but at run time, the only names that are
    actually available are those which have been captured.

    ChrisA

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to Mark Bourne on Tue Feb 7 23:36:37 2023
    Mark Bourne <[email protected]> writes:
    I'm not all that familiar with the concept of closures, so may have got
    some of the terminology and details wrong - but it's something along
    those lines. I'm sure others will correct me...

    About ten years ago, I posted something about closures:

    |Newsgroups: comp.lang.lisp
    |Subject: Re: What are closures in Lisp, exactly?
    |From: [email protected] (Stefan Ram)
    |Message-ID: <[email protected]>
    |
    |"Yves S. Garret" <[email protected]> writes:
    So, my question, in this context, what are lexical closures?
    |
    | I do not distinguish between »lexical closure« and
    | »closure«.
    |
    | To me, it's a pair of code and an environment for
    | the free variables of that code, it is the value
    | of lambda expression, which are used in an environment
    | and have said free variables.
    |
    | It was the solution to the »upward funarg« problem, an
    | »upward funarg« being a function value returned as the
    | result of a call.
    |
    | »Closed« means that all variables are bound. While code with
    | free variables is »open«, the pair of such code and an
    | environment for those variables is »closed«.
    |
    | It is a special kind of that which in OOP is called an
    | »object«. But an object with only one method (the code)
    | and with a special kind of notation and creation, that is
    | possible in languages where function definitions can be
    | nested in scopes and function values (functions as
    | first-class values) exist.
    |
    | The term »closure« was coined in 1964 by Landin.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)