Wednesday, 7 December 2016

Python Namespace & Variable Scope - Local, Enclosed, Global

Python Namespace and Variable Scope - In this article, we will be learning about local and global variables. This article is related to our last article on Python function, so I recommend to have a read over it, so that things will be easier to understand. In this article, we are going to understand namespace first, where all the declared variables are actually stored and retrieved as and when required. Then we will be knowing about two important terms - local variables and global variables.

python-namespace-variable-scope

Name and Namespace

We know that, everything in Python is an object and to identify an object easily, we assign a name to it, which we call as a variable. In our article on Python variables, we studied that, a variable is an identifier of an object and the object can be accessed using the name assigned to it. Consider that, we have declared a variable as myString = "I <3 Python!". With this, a str type of object "I <3 Python!" is stored in the memory, which can also be accesses using the name myString. Now, we can perform any operation on the object or call corresponding methods on it, by using it's name. For example, in order to capitalize every letter in the string, we can use myString.upper().

Now, if we create some more objects and assign names to them, those will be stored in memory locations and will be available to be accessed using the names assigned to them. With this, you can consider a 'namespace' to be a place where all these names are stored. In simple words, a namespace is the collection of all the names in the form of dictionary items as name : object. To more understand this, I would like to introduce globals() function here, that will display the mapping. For now, don't bother about why the name globals() and all, we will see it later. This is just to show you how the namespace looks like.

>>> myString = "I <3 Python!"
>>> myList = [1, 2, 3, 4]
>>> myTuple = ('A', 'B', 'C')
>>> globals()
{'myTuple': ('A', 'B', 'C'), 'myList': [1, 2, 3, 4], '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'myString': 'I <3 Python!', '__name__': '__main__', '__doc__': None}

In above example, we have created three objects and assigned names myString, myList and myTuple to them. To display the namespace, we called Python built-in function globals(), which returned a dict type, with items in name : object format, 'myTuple': ('A', 'B', 'C') being one of them. Thus, as soon as we create a name and assign a value to it, it becomes a part of namespace. We have zero or more local namespaces and a global namespace. Based on where in the code the assignment happens, the namespace which the variable will be a part of, is decided. Let us discuss this in the next section.

Global Namespace and Local Namespace

As mentioned above, a name is a part of which namespace, is decided by where the variable is declared in the program. This arises one more question in our mind that, Where can we declare a name?. Answer will be, a name can be defined in the main program body, in a function or in a module (a module can be considered as a pre-existing Python program file, from where an object can be imported). If a name is declared in the main program body, it will be a part of global namespace, while a name definition inside a function makes it a part of local namespace. Every function declared has its own local namespace, so that the values assigned to a name inside a function do not get overwritten with the names declared in other functions or main program body (global namespace). In conclusion -

1. We have a global namespace, where all the names declared in the main program body reside.
2. Every function is associated with its own namespace, known as local namespace. We can have multiple local namespaces in a program, as we can have zero or more functions in our piece of code.
3. A name declared in a function can only be accessed within that function
4. A name can be a part of one or more namespaces, but their values do not collide with each other. For example, name myVar declared in main program body (global namespace) and a name myVar declared in a function myFunc are absolutely different entities and their value do not clash with one another.
5. This also means that, in case of nested functions (function inside another function) will have individual namespaces associated with each of them. In this case, the namespace of enclosing function is often called as Enclosed namespace.
6. Every call to a function creates a new local namespace.
7. We also have Built-in namespace, which is reserved by Python, consisting mainly of exceptions and built-in functions.

There might be numerous namespaces created in a program, but all the namespaces are not available to be accessed from anywhere in the program. This introduces a new term Scope, that defines the section of the program from where the namespace can be accessed. We discuss this in the next section.

Scope

In the above section, we have come to know that a program may have a number of namespaces, which are independent from each other and not accessible from everywhere in the program. A scope decides the part of the program from where the name can be accessible. So, based on where the name is referenced in the program, the name is searched in the namespaces in a specific order as per LEGB (Local -> Enclosed -> Global -> Built-in) rule.

As per LEGB rule, the order of search should be -
1. Local scope (or namespace) - The name is searched in the local namespace, if it is referenced inside a def.
2. Enclosed scope - If not present in local namespace, the namespace of enclosing def is searched.
3. Global scope - If not found, global namespace - the top level namespace in a file, is looked into.
4. Built-in scope - If still not found, it searches the Built-in scope, which is nothing but the Python builtin module.
5. If the name is not found in any of above mentioned scopes, a KeyError exception is raised. We see some examples in order to verify everything we studied in this article, so far.

Example 1 : Local and Global Scope

>>> myVar = "Global"
>>> def myFunction() :
...     myVar = "Local"
...     print 'Value of myVar inside function = ' + myVar
...
>>> print 'Value of myVar outside function = ' + myVar
Value of myVar outside function = Global
>>> myFunction()
Value of myVar inside function = Local

In above example, we have declared a name myVar = "Global" in the main program body and myVar = "Local" inside a function myFunction. When we use the variable in the main program, its value from the global namespace is referred, whereas when the function is called, it's value from local namespace is referred, as per LEGB rule.

Example 2 : Local, Enclosed and Global Scope

>>> myVar = "Global"
>>> def outerFunction():
...     myVar = "Enclosed"
...     print 'Value of myVar in outerFunction() = ' + myVar
...     def innerFunction():
...             myVar = "Local"
...             print 'Value of myVar in innerFunction() = ' + myVar
...     innerFunction()
...
>>> print 'Value of myVar in main program = ' + myVar
Value of myVar in main program = Global
>>> outerFunction()
Value of myVar in outerFunction() = Enclosed
Value of myVar in innerFunction() = Local

In above example, we have myVar declared thrice with values "Global" in the main program body, "Enclosed" inside outerFunction and "Local" in innerFunction, to indicate which namespace value myVar takes when referenced at different locations in the program.

Example 3 : Local, Enclosed, Global and Built-in Scope

As discussed earlier, the __builtin__ module contains all keywords reserved by Python. To check the all of them, we import the module and provide it as an argument to dir() function. We will observe some exceptions and built-in functions in the long list returned.
# Snipping the long list
>>> dir(__builtin__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', ... , 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

Now, we create our own pow() function that does the reverse operation i.e. Exponent to the power Base and check if it gets executed before the one in built-in scope.

>>> def pow(base, exp):
...     print 'Result from the pow function = ' + str(exp ** base)
...
>>> def myFunction(base, exp):
...     print 'Calling pow() from myFunction...'
...     pow(base, exp)
...     print 'Result from myFunction = ' + str(base ** exp)
...
>>> myFunction(3, 4)
Calling pow() from myFunction...
Result from the pow function = 64
Result from myFunction = 81

From the above results, we observe that, the pow() function from the global scope is executed before the pow() function in the built-in scope.

With this, we've come to an end of this article. In this article, we learned mainly about namespace and scope. Essentially, we understood how the names are searched in the scopes, based on where they are referenced in the program. In the next article, we study about return values and return statement. Please share your views and opinions in the comment section below and stay tuned for more articles. Thank you.

This article is originally published at www.codeninja.in - Python Namespace and Scope

0 comments:

Post a Comment