
On Sun, Oct 31, 2021 at 6:36 PM Steven D'Aprano <steve@pearwood.info> wrote:
On Sun, Oct 31, 2021 at 02:24:10PM +1100, Chris Angelico wrote:
That last part is the most important here: it has to be evaluated *in the context of the function*. That's the only way for things like "def f(a, n=len(a)):" to be possible.
Agreed so far.
Every piece of code in Python is executed, if it is ever executed, in the context that it was written in.
I don't think that's quite right. We can eval() and exec() source code, ASTs and code objects in any namespace we have access to, including plain old dicts, with some limitations. (E.g. we can't get access to other function's namespace, not even if we have their locals() dict. At least not in CPython.)
True, I was a bit sloppy with my definitions there; let me try that again. Every piece of compiled Python code is executed, if it is ever executed, in a context defined by the location where it was compiled. With eval/exec, they're compiled in their own dedicated context (at least, as of Py3 - I don't think I fully understand what Py2 did there). You can provide a couple of dictionaries to help define that context, but it's still its own dedicated context. ASTs don't have contexts yet, but at the point where you compile it the rest of the way, it gets one. Code objects have their contexts fully defined. To my knowledge, there is no way to run code in any context other than the one it was compiled in, although you can come close by updating a globals dictionary. You can't get closure references (nonlocals) for eval/exec, and I don't think it's possible to finish compiling AST to bytecode in any way that allows you to access more nonlocals.
In the case of default expressions:
def func(spam=early_expression, @eggs=late_expression):
early_expression is evaluated in the scope surrounding func (it has to be since func doesn't exist yet!) and late_expression needs to be evaluated inside func's scope, rather than the scope it was written in.
Actually, by the time you're compiling that line of code, func DOES exist, to some extent. You can't compile a def statement without simultaneously compiling both its body (the scope of func) and its surrounding context (whatever that was written in). It's a little messier now since you can have each of those contexts getting code added to it, but that's still a limited number of options - for instance, you can't have a default expression that gets evaluated in the context of an imported module, nor one that's evaluated in the *caller's* context. (I'm aware that I'm using the word "context" here to mean something that exists at compilation time, and elsewhere I've used the same word to mean something that only exists at run time. Unfortunately, English has only so many ways to express the same sorts of concepts, so we end up reusing. Sorry.) ChrisA