Mailing List Archive

Re: A Function's name during its definition
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.
--
https://mail.python.org/mailman/listinfo/python-list
Re: A Function's name during its definition [ In reply to ]
Stefan Ram wrote:
> Mark Bourne <nntp.mbourne@spamgourmet.com> 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.
--
https://mail.python.org/mailman/listinfo/python-list
Re: A Function's name during its definition [ In reply to ]
On Wed, 8 Feb 2023 at 10:18, Mark Bourne <nntp.mbourne@spamgourmet.com> wrote:
>
> Stefan Ram wrote:
> > Mark Bourne <nntp.mbourne@spamgourmet.com> 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
--
https://mail.python.org/mailman/listinfo/python-list