One object - many lives | Python lexical Scoping deep dive.
When you use a name in a program, Python creates, changes, or looks up the name in what is known as a namespace—a place where names live. When we talk about the search for a name’s value in relation to code, the term scope refers to a namespace: that is, the location of a name’s assignment in your source code determines the scope of the name’s visibility to your code. Just about everything related to names, including scope classification, happens at assignment time in Python. As i have explained here: names in Python spring into existence when they are first assigned values, and they must be assigned before they are used. Because names are not declared ahead of time, Python uses the location of the assignment of a name to associate it with (i.e., bind it to) a particular namespace. In other words, the place where you assign a name in your source code determines the namespace it will live in, and hence its scope of visibility.
Besides packaging code for reuse, functions add an extra namespace layer to your programs to minimize the potential for collisions among variables of the same name—by default, all names assigned inside a function are associated with that function’s namespace, and no other. This rule means that:
Names assigned inside a def can only be seen by the code within that def. You cannot even refer to such names from outside the function.
Names assigned inside a def do not clash with variables outside the def, even if the same names are used elsewhere. A name X assigned outside a given def (i.e., in a different def or at the top level of a module file) is a completely different variable from a name X assigned inside that def.
In all cases, the scope of a variable (where it can be used) is always determined by where it is assigned in the source code and has nothing to do with which functions call which.
Variables may be assigned in three different places, corresponding to three different scopes:
If a variable is assigned inside a def, it is local to that function.
If a variable is assigned in an enclosing def, it is nonlocal to nested functions.
If a variable is assigned outside all defs, it is global to the entire file.
We call this lexical scoping because variable scopes are determined entirely by the locations of the variables in the source code of program files, not by function calls. For example, in the following module file, the X = 3 assignment creates a global variable named X (visible everywhere in this file), but the X = 14 assignment creates a local variable X (visible only within the def statement):
X = 3 # Global (module) scope X def func(): X = 14 # Local (function) scope X: a different variable
Even though both variables are named X, their scopes make them different. The net effect is that function scopes help to avoid name clashes in your programs and help to make functions more self-contained program units—their code need not be concerned with names used elsewhere.
Functions define a local scope and modules define a global scope with the following properties:
1: The enclosing module is a global scope.
Each module is a global scope—that is, a namespace in which variables created (assigned) at the top level of the module file live. Global variables become attributes of a module object to the outside world after imports but can also be used as simple variables within the module file itself.
2: The global scope spans a single file only.
The word “global” here is slightly misleading.—names at the top level of a file are global to code within that single file only. There is really no notion of a single, all-encompassing global file-based scope in Python. Instead, names are partitioned into modules, and you must always import a module explicitly if you want to be able to use the names its file defines. When you hear “global” in Python, think “module.”
3: Assigned names are local unless declared global or nonlocal.
By default, all the names assigned inside a function definition are put in the local scope (the namespace associated with the function call). If you need to assign a name that lives at the top level of the module enclosing the function, you can do so by declaring it in a global statement inside the function. If you need to assign a name that lives in an enclosing def, as of Python 3.X you can do so by declaring it in a nonlocal statement.
4: All other names are enclosing function locals, globals, or built-ins.
Names not assigned a value in the function definition are assumed to be enclosing scope locals, defined in a physically surrounding def statement; globals that live in the enclosing module’s namespace; or built-ins in the predefined built-ins module Python provides.
5: Each call to a function creates a new local scope.
Every time you call a function, you create a new local scope—that is, a namespace in which the names created
inside that function will usually live. You can think of each def statement (and lambda expression) as defining a new local scope, but the local scope actually corresponds to a function call. Because Python allows functions to call themselves to loop—also known as recursion - each active call receives its own copy of the function’s local variables.
Fun fact - Where code typed at the interactive command prompt live?
It lives in a module, too, and follows the normal scope rules: they are global variables, accessible to the entire interactive session.
Below 3 points summarize everything i have said above. This is also called Python name resolution scheme LEGB
1: When you use an unqualified name inside a function, Python searches up to four scopes—the local (L) scope, then the local scopes of any enclosing (E) defs and lambdas, then the global (G) scope, and then the built-in (B) scope—and stops at the first place the name is found. If the name is not found during this search, Python reports an error.
2: When you assign a name in a function , Python always creates or changes the name in the local scope, unless it’s declared to be global or nonlocal in that function.
3: When you assign a name outside any function (i.e., at the top level of a module file, or at the interactive prompt), the local scope is the same as the global scope— the module’s namespace.
Lets cement our understand by going through an example:
X = 3 # Global X def func(): X = 14 # Local X: hides global, but we want this here func() print(X) # Prints 3: unchanged
Global allows us to change names that live outside a def at the top level of a module file.
X = 3 # Global X def func(): global X X = 14 # Global X: outside def func() print(X) # Prints 14
Hope this articles gives you an intuitive understanding of scoping used by python language.
If you liked the article - please do re-share using social icons below.