Monday, June 6, 2022

Importing modules

Modules are also called libraries or packages. Modules are modular, often self-contained Python programs that are commonly utilized in other programs, hence the need to import them for access.

Modules are used to separate code to make a program easier to work with, as each module can be designed to do one thing well, rather than having to make a single program that is responsible for all logic.

The Python Package Index (PyPI) website (https://pypi.org) is the official repository of third-party Python libraries. There are more than 150,000 packages available for download from PyPI. Most of these  packages are designed to be imported into a Python project to provide additional, or easier, functionality than can be achieved with the default Python libraries.

Another benefit of modules is that they create additional namespaces for code. Namespaces (also called scopes) are the hierarchy of control that a module has. Normally, objects outside of a module aren't visible to code within the module; thus, they can't be accessed or utilized within the current module.

The benefit of this segregation is that variable shadowing is less likely. Variable shadowing is the creation of duplicate variable names in different blocks of code, such that one variable is hidden (shadowed) by an identical variable and cannot be accessed, or the Python interpreter may call the incorrect variable.

Using a module allows the same variable name to be used in multiple locations without requiring shadowing to occur, as a specific variable is identified by the module it resides in. Of course, there is nothing to stop a programmer from using the same variable within a module, with the potential of shadowing occurring, but the namespace hierarchy makes that unlikely.

Global variables are an option, but aren't recommended. Global variables allow a programmer to define a variable that can be accessed within any namespace; they are commonly used to contain data that is used in multiple locations, such as a counter. Of course, this leaves open the possibility that a global variable will be overwritten without the programmer realizing, causing a problem later on in the program.

Program scope works inside-out. As a module is typically made with multiple functions or methods, when an object is called, the Python interpreter will look for the correct reference within the current function/method. If the object isn't defined there, the interpreter will move to the enclosing container, if one exists (such as another function or a method's class). If the variable can't be found there, the interpreter looks for a global variable. Not finding one, the interpreter will look in the built-in libraries. If still not found, Python will generate an error. The flow looks like this: local container|enclosing container|global scope|built-in module|error.

The following is a simple program that should help explain this idea a little better. We haven't directly talked about functions or if...else statements yet, but hopefully this won't be too confusing: 

# scope_example.py (part 1)

1 var1 = 1 # global variable

2

3 if var1 == 1:

4 var2 = 0 # also a global variable

5 print("Unmodified var2: {}".format(var2))

6

7 def my_funct():

8 var3 = 3 # local variable

9 var1 = 42 # shadows global var1 variable

10 global var2

11 var2 = 80

12 print("Inside function, var1: {}".format(var1))

13 print("Inside function, var2: {}".format(var2))

14 print("Inside function, var3: {}".format(var3))

In line 1, a global variable is created. Line 3 is a test to see whether var1 is equal to 1; if so, then a new global variable (var2) is created, and its value is printed.

Line 7 is the start of a Python function. Within this function, a variable that is only accessible within the function is created (var3). In addition, a new variable (var1) is created; this variable hides the previously made global variable var1, so when the value of this local var1 is printed in line 12, the local value is printed, rather than the value of the global variable.

Line 10 explicitly calls the global variable var2; this allows the function to manipulate the global variable in line 11 without attempting to make a local variable that would shadow it. 

Lines 12-14 print the values of the variables as seen within the scope of the function:

# scope_example.py (part 2)

1 my_funct()

2

3 print("Outside function, var1: {}".format(var1))

4 print("Outside function, var2: {}".format(var2))

5 print("Outside function, var3: {}".format(var3))

In the second part, line 1 is the call to the function to actually run it. Lines 3-5 print the values of the variables as seen outside the function.

The following screenshot displays the output of the previous program.


The print() calls show the value of each variable at its respective location within the program. Initially, var1 and var2 are the values of the globally defined variables. Once the function has been called and performs its operations, the local var1 and var3 variables are printed, along with the global var2 variable, whose value has been replaced.

When the function is complete and we are back outside the function, we see that the globally defined variable var1 is back to its original value, and is no longer hidden by the local function variable of the same name. However, because var2 was explicitly called to reference the global variable, rather than shadow it, var2 retains the value it was assigned while within the function.

Finally, because var3 doesn't exist outside the function, the interpreter doesn't know what to do with it. Since we are no longer within the function, there is no local reference to it. Moving up the namespace, there is no encapsulating function or other object, and there is no global reference to var3. Since Python doesn't have a var3 object in any of its built-in libraries, the only thing Python can do with the call is to give up and throw an error.

Share:

0 comments:

Post a Comment