Name conflicts happen all the time in real life. For example, every school that I ever went to had at least two students in my class who shared the same first name. If someone came into the class and asked for student X, we would enthusiastically ask, "Which one are you talking about? There are two students named X." After that, the inquiring person would give us a last name, and we would introduce him to the right X.
All this confusion and the process of determining the exact person we are talking about by looking for other information besides a first name could be avoided if everyone had a unique name. This is not a problem in a class of 30 students. However, it will become increasingly difficult to come up with a unique, meaningful and easy-to-remember name for every child in a school, town, city, country, or the whole world. Another issue in providing every child a unique name is that the process of determining if someone else has also named their child Macey, Maci or Macie could be very tiring.
A very similar conflict can also arise in programming. When you are writing a program of just 30 lines with no external dependencies, it is very easy to give unique and meaningful names to all your variables. The problem arises when there are thousands of lines in a program and you have loaded some external modules as well. In this tutorial, you will learn about namespaces, their importance, and scope resolution in Python.
What Are Namespaces?
A namespace is basically a system to make sure that all the names in a program are unique and can be used without any conflict. You might already know that everything in Python—like strings, lists, functions, etc.—is an object. Another interesting fact is that Python implements namespaces as dictionaries. There is a name-to-object mapping, with the names as keys and the objects as values. Multiple namespaces can use the same name and map it to a different object. Here are a few examples of namespaces:
- Local Namespace: This namespace includes local names inside a function. This namespace is created when a function is called, and it only lasts until the function returns.
- Global Namespace: This namespace includes names from various imported modules that you are using in a project. It is created when the module is included in the project, and it lasts until the script ends.
- Built-in Namespace: This namespace includes built-in functions and built-in exception names.
In the Mathematical Modules in Python series on Envato Tuts+, I wrote about useful mathematical functions available in different modules. For example, the math and cmath modules have a lot of functions that are common to both of them, like log10()
, acos()
, cos()
, exp()
, etc. If you are using both of these modules in the same program, the only way to use these functions unambiguously is to prefix them with the name of the module, like math.log10()
and cmath.log10()
.
What Is Scope?
Namespaces help us uniquely identify all the names inside a program. However, this doesn't imply that we can use a variable name anywhere we want. A name also has a scope that defines the parts of the program where you could use that name without using any prefix. Just like namespaces, there are also multiple scopes in a program. Here is a list of some scopes that can exist during the execution of a program.
- A local scope, which is the innermost scope that contains a list of local names available in the current function.
- A scope of all the enclosing functions. The search for a name starts from the nearest enclosing scope and moves outwards.
- A module level scope that contains all the global names from the current module.
- The outermost scope that contains a list of all the built-in names. This scope is searched last to find the name that you referenced.
In the coming sections of this tutorial, we will extensively use the built-in Python dir() function to return a list of names in the current local scope. This will help you understand the concept of namespaces and scope more clearly.
Scope Resolution
As I mentioned in the previous section, the search for a given name starts from the innermost function and then moves higher and higher until the program can map that name to an object. When no such name is found in any of the namespaces, the program raises a NameError exception.
Before we begin, try typing dir()
in IDLE or any other Python IDE.
dir() # ['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
All these names listed by dir()
are available in every Python program. For the sake of brevity, I will start referring to them as '__builtins__'...'__spec__'
in the rest of the examples.
Let's see the output of the dir()
function after defining a variable and a function.
a_num = 10 dir() # ['__builtins__' .... '__spec__', 'a_num'] def some_func(): b_num = 11 print(dir()) some_func() # ['b_num'] dir() # ['__builtins__' ... '__spec__', 'a_num', 'some_func']
The dir()
function only outputs the list of names inside the current scope. That's why inside the scope of some_func()
, there is only one name called b_num
. Calling dir()
after defining some_func()
adds it to the list of names available in the global namespace.
Now, let's see the list of names inside some nested functions. The code in this block continues from the previous block.
def outer_func(): c_num = 12 def inner_func(): d_num = 13 print(dir(), ' - names in inner_func') e_num = 14 inner_func() print(dir(), ' - names in outer_func') outer_func() # ['d_num'] - names in inner_func # ['c_num', 'e_num', 'inner_func'] - names in outer_func
The above code defines two variables and a function inside the scope of outer_func()
. Inside inner_func()
, the dir()
function only prints the name d_num
. This seems fair as d_num
is the only variable defined in there.
Unless explicitly specified by using global
, reassigning a global name inside a local namespace creates a new local variable with the same name. This is evident from the following code.
a_num = 10 b_num = 11 def outer_func(): global a_num a_num = 15 b_num = 16 def inner_func(): global a_num a_num = 20 b_num = 21 print('a_num inside inner_func :', a_num) print('b_num inside inner_func :', b_num) inner_func() print('a_num inside outer_func :', a_num) print('b_num inside outer_func :', b_num) outer_func() print('a_num outside all functions :', a_num) print('b_num outside all functions :', b_num) # a_num inside inner_func : 20 # b_num inside inner_func : 21 # a_num inside outer_func : 20 # b_num inside outer_func : 16 # a_num outside all functions : 20 # b_num outside all functions : 11
Inside both the outer_func()
and inner_func()
, a_num
has been declared to be a global variable. We are just setting a different value for the same global variable. This is the reason that the value of a_num
at all locations is 20. On the other hand, each function creates its own b_num
variable with a local scope, and the print()
function prints the value of this locally scoped variable.
Properly Importing Modules
It is very common to import external modules in your projects to speed up development. There are three different ways of importing modules. In this section, you will learn about all these methods, discussing their pros and cons in detail.
from module import *
: This method of importing a module imports all the names from the given module directly in your current namespace. You might be tempted to use this method because it allows you to use a function directly without adding the name of the module as a prefix. However, it is very error prone, and you also lose the ability to tell which module actually imported that function. Here is an example of using this method:
dir() # ['__builtins__' ... '__spec__'] from math import * dir() # ['__builtins__' ... '__spec__', 'acos', 'acosh', 'asin', 'asinh', # 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', # 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', # 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', # 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', # 'modf', 'nan', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', # 'tanh', 'trunc'] log10(125) # 2.0969100130080562 from cmath import * dir() # ['__builtins__' ... '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', # 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', # 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', # 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', # 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'phase', # 'pi', 'polar', 'pow', 'radians', 'rect', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', # 'trunc'] log10(125) # (2.0969100130080562+0j)
If you are familiar with the math and cmath modules, you already know that there are a few common names that are defined in both these modules but apply to real and complex numbers respectively.
Since we have imported the cmath module after the math module, it overwrites the function definitions of these common functions from the math module. This is why the first log10(125)
returns a real number and the second log10(125)
returns a complex number. There is no way for you to use the log10()
function from the math module now. Even if you tried typing math.log10(125)
, you will get a NameError exception because math
does not actually exist in the namespace.
The bottom line is that you should not use this way of importing functions from different modules just to save a few keystrokes.
from module import nameA, nameB
: If you know that you are only going to use one or two names from a module, you can import them directly using this method. This way, you can write the code more concisely while still keeping the namespace pollution to a minimum. However, keep in mind that you still cannot use any other name from the module by usingmodule.nameZ
. Any function that has the same name in your program will also overwrite the definition of that function imported from the module. This will make the imported function unusable. Here is an example of using this method:
dir() # ['__builtins__' ... '__spec__'] from math import log2, log10 dir() # ['__builtins__' ... '__spec__', 'log10', 'log2'] log10(125) # 2.0969100130080562
import module
: This is the safest and recommended way of importing a module. The only downside is that you will have to prefix the name of the module to all the names that you are going to use in the program. However, you will be able to avoid namespace pollution and also define functions whose names match the name of functions from the module.
dir() # ['__builtins__' ... '__spec__'] import math dir() # ['__builtins__' ... '__spec__', 'math'] math.log10(125) # 2.0969100130080562
Final Thoughts
I hope this tutorial helped you understand namespaces and their importance. You should now be able to determine the scope of different names in a program and avoid potential pitfalls.
Additionally, don’t hesitate to see what we have available for sale and for study in the marketplace, and don't hesitate to ask any questions and provide your valuable feedback using the feed below.
The final section of the article discussed different ways of importing modules in Python and the pros and cons of each of them. If you have any questions related to this topic, please let me know in the comments.
by Monty Shokeen via Envato Tuts+ Code