2

In Python, functions have access to variables in the enclosing scope.

As a test of the extent / consistency of this, I did an experiment with creating a simple namespace but did not get the expected behavior. I do not understand why this simple case should be treated differently, is there a good explanation?

Here is a simple example:

import types
bunch = types.SimpleNamespace(
    x = 5,
    y = 12,
    printx = lambda: print(x)
)

Executing bunch.printx() after running the above code will result in a NameError as x is not defined. But we have defined a function "printx" that is enclosed by the bunch namespace, yet the function does not have access to the variables in the enclosing namespace.

In comparison importing the code bunch.py as an external file will place the objects in the bunch namespace with the access to the variables in the enclosing namespace as expected:

bunch.py:

x = 5,
y = 12,
printx = lambda: print(x)

Running the following code will result in printing a 5 as expected:

import bunch
bunch.printx()

I did find this question, which may be related: Python: Access from within a lambda a name that's in the scope but not in the namespace The example I provide here is clearer specific to accessing variables within the enclosing scope.

petezurich
  • 9,280
  • 9
  • 43
  • 57
Dan Boschen
  • 265
  • 1
  • 11
  • 1
    Your `lambda`'s enclosing context is the entire module; it is certainly not enclosed in the `bunch` namespace, as that doesn't even exist yet (and isn't a syntactic construct anyway, so it can't actually "enclose" anything). – jasonharper Nov 25 '18 at 14:00
  • @jasonharper but when I import that module, it is indeed loaded into the bunch namespace. But in the first case I create the namespace explicitly. Specifically I am confused why the first case does not have access to the variables in the enclosing namespace, do you understand why? – Dan Boschen Nov 25 '18 at 14:03
  • 1
    The lambda does not and cannot know anything about a namespace that *doesn't exist yet*. The enclosing scope of a function is a static attribute that is entirely determined at compile time. – jasonharper Nov 25 '18 at 14:09
  • Got it, thank you. – Dan Boschen Nov 25 '18 at 14:15
  • @jasonharper Is there a way you know of that I can add a function to the bunch namespace where I create the namespace using types.SimpleNamespace? (maybe this is a separate question) – Dan Boschen Nov 25 '18 at 14:33
  • 1
    https://stackoverflow.com/questions/18184357/change-what-dictionary-serves-as-a-functions-global-scope answers that question. – jasonharper Nov 25 '18 at 14:40

1 Answers1

2

Each function-object in python has 3 properties:

  • constants
  • locals
  • globals

Locals are defined within the function, and globals are defined outside. Look at the bytecode you are trying to execute:

dis.dis(bunch.printx)
  6           0 LOAD_GLOBAL              0 (print)
              2 LOAD_GLOBAL              1 (x)
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

You are attempting a lookup, for the variable 'x' in the global scope. At the namespace you execute the function there is no such global variable.
The lambda is defined with the global variable 'x', and not the value which 'x' had during the definition of the lambda.

Dinari
  • 2,487
  • 13
  • 28
  • Thank you, I think I follow. Confirming this if I change the print lambda to execute print(bunch.x) there is no error. So in my first example, lambda is defined to print x from the global scope, not the scope within the bunch namespace. Executing this function later is simply interpreted as print(x), the global x, which there is none. However the second case, where I do an import works as the import must therefore map all the definitions to be within the bunch namespace whereas in my first case I have to do that explicitly (print(bunch.x)). Am I following this accurately? – Dan Boschen Nov 25 '18 at 14:14
  • If i followed you correctly, then yes, in the second case, bunch.printx() 'lives' in bunch namespace, thus he will search there first, before looking for a global x 'higher' up. – Dinari Nov 25 '18 at 16:29