When a function is defined, it's assigned the read-only attribute func_globals
(a.k.a. __globals__
since 2.6 and only that since 3.0) which corresponds to the current module namespace (like a view to the module.__dict__
). You can try, for example:
>>> square.func_globals
# get a big dictionary-like object here
This object is queried every time the function accesses a global variable. When you later import this function in some other module, it will still keep the reference to its original scope and will take the globals from there. In other words, the code:
def square(x):
return mul(x, x)
is roughly the same as:
def square(x):
return globals()['mul'](x, x)
or
def square(x):
return square.func_globals['mul'](x, x)
So, in one sentence: function's globals belong to the module where it is defined, and accessed through the function object's attributes.
UPDATE
More elaborated version of what happens underneath (applies to CPython). The most important thing to understand about Python is, that, unlike C etc, it has no clear separation between "declaration" and "execution". When you load a module, its code is simply executed. When the interpreter comes across a function definition, it creates the function object and assigns it to the variable. Whatever has been defined or imported into the module will become available to the function as global variables. Lets have a look at the function object:
>>> import dis
>>> dis.dis(square.func_code) # disassemble the function's bytecode
2 0 LOAD_GLOBAL 0 (mul)
3 LOAD_FAST 0 (x)
6 LOAD_FAST 0 (x)
9 CALL_FUNCTION 2
12 RETURN_VALUE
>>> square.func_code.co_names
('mul',)
You can see the code LOAD_GLOBAL 0
in the beginning. That means:
- find the name with the index
0
in co_names
, which is 'mul'
- find the name
'mul'
in func_globals
and load it to the stack
- do the rest of the logic