# Python 101: Functions and Modules

Functions and Modules in Python

## Functions and Modules in Python

Hi, and welcome back to Python 101. We will discuss functions and modules in this chapter. In a nutshell, functions combine several lines of code into a single statement, which can be called as many times as you like. Modules, form the backbone of Modular Programming, which states that software should consist of separate, interchangeable components called modules, each of which accomplishes one functionality and contains everything necessary to accomplish the said functionality.

Let's see what all we will be covering by the end of this chapter:

### Programs to whet your appetite§

#### #1 Prime number or not

# PRIME NUMBER OR NOT
# A program to check whether the provided number is prime or not. The program should contain two functions: one for concluding whether a number is prime or not; the other a main program that reads a number and displays the conclusion.

# A prime number is an integer greater than 1 that is only divisible by one and itself e.g. 2, 3, 5, 7, 11, 3, 17, 19 etc.

# INPUT: any number
# OUTPUT: conclusion if the given number is prime or not
# Also, ensure that your file when imported into another file doesn't trigger your program automatically

# THOUGHT PROCESS: Construct a function that takes an argument, and returns True or False based on logic ->
# Declare a main function(call it anything you want) and have the user input a number ->
# Pass the number to the declared function ->
# Based on the value returned by the function, display the conclusion ->
# include the bit to run the main function if the file has not been imported i.e. it has been run directly.

# function to determine whether the supplied number is prime or not.
def isPrime(num):
if num <= 1:
return False
for factor in range(2, num):	# if there exists any number greater than 2 that
if num % factor == 0:		# divides the given number evenly(with remainder 0, that is),
return False			# then the given number is not prime
return True

# the main function
def mainProgram():
number = int(input("Enter the number to be tested: "))
if isPrime(number):
print(number, "is prime.")
else:
print(number, "is not prime.")

# if the file has been run directly AND not been imported, call the main function
if __name__ == "__main__":
mainProgram()

Try it here. You will have to call mainProgram() function yourself, since we have made the script independent by adding the if construct.

#### #2 Median of Three Values

# MEDIAN OF THREE VALUES

# A program to calculate median of three values. The program should contain two functions; one for computing the median; the other a main program that reads 3 numbers and displays the median

# The median value is the middle of the three values when they are sorted into ascending order.

# Input: 3 numbers
# Output: median of the three numbers
# Also, ensure that your file when imported into another file doesn't trigger your program automatically

# THOUGHT PROCESS: Devise two functions: one for computing the median of three supplied numbers and returning the median; second the main function to take three numbers as input, pass them to the median calculating function, and then display the returned median.

def medianOf(num1, num2, num3):
if num1 < num2 < num3 or num3 < num2 < num1:
return num2
if num1 < num3 < num2 or num2 < num3 < num1:
return num3
else:
return num1

def mainProgram():
number1 = int(input("Enter first number: "))
number2 = int(input("Enter second number: "))
number3 = int(input("Enter third number: "))

median = medianOf(number1, number2, number3)

print("Median of", str(number1), str(number2), str(number3), "is:", median)

# if the file has been run directly AND not been imported, call the main function
if __name__ == "__main__":
mainProgram()

Try it here. You will have to call mainProgram() function yourself, since we have made the script independent by adding the if construct.

#### #3 Center a string in the Terminal

# CENTER A STRING IN THE TERMINAL

# Write a function that takes a string as its first parameter,and the width of the terminal in characters(80) as its second parameter. The function should return a new string that consists of the original string padded by enough spaces on its left so that the original string will appear centered within the provided width when it is printed. Call your function via a main function.

# Input: Different strings and value of width of terminal in characters(80 by convention)
# Sample Strings to be centered:
# *** The Dark Knight ***
# Christian Bale as
# The Batman
# Directed by:
# Christopher Nolan

# Output: Centered strings
# Also, ensure that your file when imported into another file doesn't trigger your program automatically

# THOUGHT PROCESS: Two functions: one, that returns the string by padding it on the left with ( width of terminal - length of string ) // 2 spaces; second, the main function, that calls the function with various strings.

def center(string, width):
if width < len(string):
return string

numberOfSpaces = (width - len(string)) // 2         #  floored division to obtain an int to avoid:
resultantString = " " * numberOfSpaces + string     # TypeError: can't multiply sequence by non-int of type 'float'

return resultantString

def mainProgram():
widthOfOutputScreen = 80
print(center("*** The Dark Knight ***", widthOfOutputScreen))
print("\n")
print(center("Christian Bale as", widthOfOutputScreen))
print(center("The Batman", widthOfOutputScreen))
print("\n")
print(center("Directed by: ", widthOfOutputScreen))
print(center("Christopher Nolan", widthOfOutputScreen))

# If the file has been executed directly AND not been imported, call the main function
if __name__ == "__main__":
mainProgram()

Try it here. You will have to call mainProgram() function yourself, since we have made the script independent by adding the if construct.

### Functions: A Primer§

Theoretically, functions are reusable pieces of code, that perform, well, a function. You already know about the builtin print() function which prints stuff on the console. But Python, like other functional languages, allow you to create your own functions, known as user defined functions. Let's look at an example:

>>> def countdown():
print(3)
print(2)
print(1)
print("Let's go!")

>>> countdown()
3
2
1
Let's go!


We created a function using the def keyword, gave it a self-explanatory name countdown, wrote the body of the function(a few print statements), and then called the function by using the name we gave, followed by a pair of parentheses. So every time we want to display a countdown from 3 to 1, we simply call this function by countdown(), and Python will replace the function call by its body, enabling us to type less and make our code reusable.

In reality, you will use functions for a lot more practical purposes than a bunch of print statements like we did here. The programs above will give you a fair idea on how to use functions. Let's look at functions in more detail.

### Parts of a function§

• Definition
• Calling a function
• Parameters & Arguments(positional, keyword, default, variable-length)
• Return statements

### Function Definition§

What functionality will the function perform upon being called, and translating that into Python statements is what constitutes a function definition.

>>> def countdown():
'This function counts down from 3 to 1.'
print(3)
print(2)
print(1)
print("Let's go!")


Things to note:

• The definition begins with the def keyword followed by the function name, followed by parentheses i.e. def countdown()
• The parentheses contain any parameters or arguments, which we will discuss later. A function without parameters is perfectly valid, just like in the countdown function.
• For documentation purposes, the first line of the function, also known as documentation string or docstring, gives a brief explanation of the functionality of the function. Again, we will discuss the utility of the docstring later on.
• The function body is followed by a colon (:) and is appropriately indented to signify that it is a code-block.
• Functions often have a return statement, which returns the program control to the place where the function was called. A function with no return statements is as good as return None, and will work fine. Return statements are extremely handy, as we will discover in the later sections.

### Calling a Function§

Once you have defined what a function will do, you can execute it by calling it in the manner below.

>>> def countdown():
'This function counts down from 3 to 1.'
print(3)
print(2)
print(1)
print("Let's go!")

>>> countdown()
3
2
1
Let's go!


### Parameters & Arguments§

To make a function dynamic, we declare in the function definition what are called parameters. Consider the following function calculateArea(), without any parameter.

>>> def calculateArea():
length = 5

>>> calculateArea()
Area: 30


The above function, whenever called, will produce the same result i.e. Area: 30, since we have hard-coded the values of length and breadth. To make the function interactive and dynamic, we declare two parameters in the function definition, namely length and breadth.

>>> def calculateArea(length, breadth):

>>> calculateArea(5, 6)
Area: 30
>>> calculateArea(20, 3)
Area: 60

Difference between Parameters & Arguments: A parameter is a variable in a method definition. When a method is called, the arguments are the data you pass into the method's parameters. So, in the above example, length & breadth are parameters, whereas 5, 6, 20, 3 are the arguments.

Arguments are of three types:

1. Positional Arguments
2. Keyword Arguments
3. Variable Length Arguments

#### Positional Arguments

>>> def calculateArea(length, breadth):
print("Length:", length)

>>> calculateArea(5, 6)
Length: 5
Area: 30


The reason why these are called positional arguments, is that the order in which you specify is the order in which they get assigned to the parameters. That is, to say, that while calling the calculateArea() function, whatever you specify first gets assigned to the parameter length and whatever you specify second gets assigned to the parameter breadth. This is not the case with keyword arguments, which we will notice in due time.

Positional arguments are also known as Required arguments. If you declare a function with, say 4 parameters, but call it with less than 4 arguments, then Python will throw a TypeError. Again, this is in contrast to keyword arguments (with a default value).

>>> calculateArea(5)
Traceback (most recent call last):
# traceback info
calculateArea(5)
TypeError: calculateArea() missing 1 required positional argument: 'breadth'


#### Keyword Arguments

>>> def calculateArea(length, breadth):
print("Length:", length)

>>> calculateArea(breadth = 5, length = 6)
Length: 6
Area: 30


If you know the parameter names, you can ask Python to assign values to parameters by their names. You can specify the order in any which way you want since you have explicitly mentioned which argument will be assigned to which parameter.

#### Default Arguments: Giving keyword arguments a default value

>>> def calculateArea(length = 5, breadth = 6):
print("Length:", length)

>>> calculateArea()
Length: 5
Area: 30
>>>
>>> calculateArea(10, 3)
Length: 10
Area: 30
>>>
>>> calculateArea(12)          # assumes the provided value for first parameter, keeping the default value for the second.
Length: 12
Area: 72
>>>
Length: 5
Area: 50


Keyword arguments support default values in the event that the user doesn't provide a value for that parameter in the function call.

#### Variable Length Arguments

Sometimes, the situation demands you to handle more parameters than you define the function with. In such a case, you can declare a tuple as a parameter, which will hold the surplus arguments. Example below will make it clear.

>>> def collectParameters(length, breadth, *other_dimensions):
print("Length:",length)
print("Other Dimensions:", other_dimensions)
print(type(other_dimensions))

>>> collectParameters(10, 20, 30, 40, 50, 60)
Length: 10
Other Dimensions: (30, 40, 50, 60)
<class 'tuple'>

>>> collectParameters(10, 20)
Length: 10
Other Dimensions: ()
<class 'tuple'>


### Return Statement§

The return statement makes the control exit from a function, with an optional expression.

>>> def calculateArea(length, breadth):

>>> calculateArea(3, 4)
12

>>> def myMaxFunction(num1, num2):
if num1 > num2:
return num1
else:
return num2

>>> myMaxFunction(10, 20)
20

>>> maxValue = myMaxFunction(20, 90)
>>> print("Max Value:", maxValue)
Max Value: 90

>>> def isOdd(number):
if number % 2 == 1:
return True
else:
return False

>>> isOdd(5)
True
>>> isOdd(6)
False

• A return statement without an expression is as good as return None.
• Functions can return anything, from a string to an integer to a Boolean value to lists to sets and so on. In fact, they can even return another functions. I'll leave that to you to explore.
• Functions can return multiple values as well.

### Lambda Expressions (Anonymous functions)§

Lambda expressions(a.k.a. lambda forms) in Python are used to create anonymous functions. In a nutshell, these functions do not have the def keyword, instead have a lambda keyword, take any number of arguments and return a single value in the form of an expression.

# Syntax of a lambda expression:
lambda [arg1 [,arg2,.....argn]]:expression

# Example
>>> myLambdaExpr = lambda x: x + 2
>>> myLambdaExpr(5)
7

>>> myLambdaExpr
<function <lambda> at 0x02D21978>

>>> def myNormalFunc():
print()

>>> myNormalFunc
<function myNormalFunc at 0x02D219C0>

# Example
>>> add = lambda num1, num2: num1 + num2
7

• Lambda expressions may not necessarily enhance readability, but they help in making the code compact, as they replace elementary functions which return a single expression.
• In actuality, lambda is the basic form of a function definition. Any function, in principle, is a lambda assigned to a variable. The expression lambda arguments: expression yields a function object. The unnamed object behaves like a function object defined with

def (arguments):
return expression

• Lambda expressions cannot contain statements.
• Lambda expressions have their own local namespace and cannot access variables other than those in their parameter list and those in the global namespace.
• Lambda expression are extremely useful in GUI Programming. They serve as callback functions.
• In a broad sense, anything you can do with a lambdas, you can achieve the same with named functions, or lists. However, it's up to you to choose whether to use lambdas or not. You could decide based on the readability of your code.
• l = lambda x: x + 2 is the same as def l(x): return x + 2

### Docstring§

Python developers have laid great emphasis on making Python code readable, for the coder as well as for anyone who's reading the script. One endeavor to materialize this idea is docstrings(or documentation strings).

At the beginning of a function definition, you may declare a brief statement of the functionality that the function will deliver. This docstring can be invoked using the builtin help function, and by using the builtin __doc__ attribute of functions.

>>> def myMaxFunction(num1, num2):
'''Prints the maximum of the two values provided'''
if num1 > num2:
return num1
else:
return num2

>>> help(myMaxFunction)
Help on function myMaxFunction in module __main__:

myMaxFunction(num1, num2)
Prints the maximum of the two values provided

>>> myMaxFunction.__doc__
'Prints the maximum of the two values provided'


### Scope§

>>> def myFunc():
sum1 = 20

>>> myFunc()
>>> sum1
Traceback (most recent call last):
# traceback info
sum1
NameError: name 'sum1' is not defined


Scope of a variable determines the portion of your code where it is accessible. In the above example, the variable sum1 is only accessible within the function and is said to have a local scope i.e. it loses its identity as soon the control is returned outside of the function. This is in contrast to global scope wherein you can access the variable anywhere in the program.

You can make a variable global using the global keyword, as follows:

>>> def storeMyName():
global name
name = "Ethan"

>>> storeMyName()
>>> name
'Ethan'

# Now that the 'name' variable is globally accessible, you can use it anywhere, even in other functions.
>>> def printMyName():
print("My name is", name)

>>> printMyName()
My name is Ethan

• Note that if a global statement lies within a function like above, you have to call a function first in order to bring the concerned variable in the global namespace.
• Local variables can only be accessed inside the function in which they are declared
• Global variables can be accessed anywhere in the program.

### Modules§

A Module (or package as they are referred as in other languages) is a collection of code accessible using an import statement. Simply put, a module is a file containing Python code. It may have variables, functions, classes, print statements etc.

• Modules allow you to logically organize your code, putting related stuff in a single module. This makes the code easier to comprehend and easier to work with.
• You can import a module in 4 different ways, which we will explore in a minute.
• Python comes with a host of modules e.g. math, sys, os, random etc. You are free to create your own modules, which we will do in the next section. Furthermore, you can distribute your module so that everyone in the Python community can use it.
• The random module used to get random integers is also a builtin Python module.
>>> import math
>>> math.pow(2, 5)                     # The pow(a, b) function raises 'a' to the power 'b' and returns the result.
32.0


### Variants of import statement§

#### import some_module

Say, you have come up with an elementary yet handy function that tells whether the number supplied to it is odd or even. It should look something like this:

def oddOrEven(number):
if number % 2 == 0:
print(number,"is even.")
else:
print(number, "is odd.")


Now, you wish to use the above function in some other program without having to type it again. So you decide to put it inside a .py file, say myModule.py. Place it anywhere on your computer. In the same folder, create another file, say, main.py, and open it with IDLE.

import myModule

myModule.oddOrEven(31)

myModule.oddOrEven(18)

### OUTPUT ###
31 is odd.
18 is even.


This is the basic import statement in action i.e. import some_module. The basic import statement is executed in two steps:

• find the said module, load the code in it
• define a name or names in the local namespace for the scope where the import statement occurs.

We'll discuss namespaces soon enough. For now, consider the local namespace a place where all the identifiers of the program are kept. So the import statement brings the identifiers of the module in the namespace of the program in which the import statement is written.

In order to import multiple modules in a single statement:

>>> import module1, module2, module3


The above will execute the aforementioned dual steps for each module separately, just as if they were written in different import statements one after the other.

#### import some_module as alias

Python allows us to refer to the imported module by giving it a convenient name. The name thus given is known as the alias of the module in the current program.

import myModule as specialFunctions

specialFunctions.oddOrEven(31)
specialFunctions.oddOrEven(18)

### OUTPUT ###
31 is odd.
18 is even.



#### from some_module import *

This type of import statement is used when we do not want to qualify the imported functions i.e. if we are trying to call oddOrEven() imported from module myModule, we will call it by oddOrEven(num) and not by myModule.oddOrEven(num).

from myModule import *

oddOrEven(31)
oddOrEven(18)

### OUTPUT ###
31 is odd.
18 is even.



#### from some_module import some_func

To import a specific function from a module, from some_module import * is used. This variant is handy when only few selected portions of code are to be imported from a large module.

from myModule import oddOrEven

oddOrEven(31)
oddOrEven(18)

### OUTPUT ###
31 is odd.
18 is even.



### Modules that Python comes with§

Out of the box, Python comes with over 200 standard library modules. These modules can be really handy if you know what you are looking for. These modules are located in the Lib directory on the installation path. To view the list of available modules, execute help('modules') in the interpreter.

>>> # You may not see a few of these modules in your list. If you have installed any modules yourself via 'pip', then you will also see it here.
>>> help('modules')

Please wait a moment while I gather a list of all available modules...

AutoComplete        _struct             hmac                re
AutoCompleteWindow  _symtable           html                recorder
AutoExpand          _testbuffer         http                reprlib
Bindings            _testcapi           idle                requests
CallTipWindow       _testimportmultiple idle_test           rlcompleter
CodeContext         _tkinter            imaplib             run
ColorDelegator      _tracemalloc        imghdr              runpy
Debugger            _warnings           imp                 sched
Delegator           _weakref            importlib           scipy
EditorWindow        _weakrefset         inspect             screen
FileList            _webdebugger        inspector           select
FormatParagraph     _winapi             io                  selectors
IOBinding           aifc                ipython_genutils    shlex
IPython             antigravity         ipywidgets          shutil
IdleHistory         argparse            itertools           signal
MultiCall           array               itsdangerous        simplegeneric
MultiStatusBar      ast                 jinja2              site
ObjectBrowser       asynchat            json                six
OutputWindow        asyncio             jsonschema          smtpd
PIL                 asyncore            jupyter             smtplib
ParenMatch          atexit              jupyter_client      sndhdr
PathBrowser         audioop             jupyter_console     socket
PyParse             base64              keybinding          sqlite3
PyShell             bdb                 keybindingDialog    sre_compile
RemoteDebugger      binascii            keyword             sre_constants
RemoteObjectBrowser binhex              kivy                sre_parse
ReplaceDialog       bisect              lib2to3             ssl
RstripExtension     bs4                 linecache           stat
ScriptBinding       builtins            locale              statistics
ScrolledList        bz2                 logging             storemagic
SearchDialog        cProfile            lzma                string
SearchDialogBase    calendar            macosxSupport       stringprep
SearchEngine        cgi                 macpath             struct
StackViewer         cgitb               macurl2path         subprocess
ToolTip             chunk               mailbox             sunau
TreeWidget          cmath               mailcap             symbol
UndoDelegator       cmd                 markupsafe          sympyprinting
WidgetRedirector    code                marshal             symtable
WindowList          codecs              math                sys
ZoomHeight          codeop              matplotlib          sysconfig
__future__          collections         mimetypes           tabbedpages
__main__            colorsys            mistune             tabnanny
_ast                compileall          mmap                tarfile
_bisect             concurrent          modulefinder        telnetlib
_bootlocale         configDialog        monitor             tempfile
_bz2                configHandler       msilib              test
_codecs             configHelpSourceEdit msvcrt              test_path
_codecs_cn          configSectionNameDialog multiprocessing     testcode
_codecs_hk          configparser        nbconvert           tests
_codecs_iso2022     contextlib          nbformat            textView
_codecs_jp          copy                netrc               textwrap
_codecs_kr          copyreg             nntplib             this
_collections        crypt               nt                  time
_collections_abc    csv                 ntpath              timeit
_compat_pickle      ctypes              nturl2path          tkinter
_csv                curses              numbers             token
_ctypes             cx_Oracle           numpy               tokenize
_datetime           datetime            operator            touchring
_decimal            dateutil            optparse            trace
_elementtree        decimal             parser              tracemalloc
_functools          decorator           path                traitlets
_hashlib            difflib             pathlib             tty
_heapq              dis                 pdb                 turtle
_imp                distutils           pickle              turtledemo
_io                 django              pickleshare         types
_json               doctest             pickletools         unicodedata
_lzma               easy_install        pkg_resources       uu
_markerlib          email               pkgutil             uuid
_markupbase         encodings           platform            venv
_md5                ensurepip           plistlib            virtualenv
_msi                enum                poplib              virtualenv_support
_multibytecodec     errno               posixpath           warnings
_multiprocessing    faulthandler        pprint              wave
_opcode             filecmp             profile             weakref
_operator           fileinput           pstats              webbrowser
_overlapped         fnmatch             py_compile          werkzeug
_pickle             formatter           pyclbr              winreg
_pyio               fractions           pydoc               winsound
_random             ftplib              pydoc_data          wsgiref
_sha1               functools           pyexpat             xdrlib
_sha256             garden              pyglet              xml
_sha512             gc                  pygments            xmlrpc
_sitebuiltins       genericpath         pylab               xxsubtype
_socket             getopt              pyparsing           zipfile
_sqlite3            getpass             python              zipimport
_sre                gettext             pytz                zlib
_ssl                glob                qtconsole           zmq
_stat               gzip                queue
_string             hashlib             quopri
_strptime           heapq               random

Enter any module name to get more help.  Or, type "modules spam" to search
for modules whose name or summary contain the string "spam".


As the help utility says above, you can view the help on specific modules by entering the module name in the help prompt such as help>os, or search for all modules that contain a specific string in their name such as help> modules math. Here's a list of 50+ handy standard libary modules.

### Locating modules§

If you wish to dig a little deep into the code, you can always open these modules with a text editor. The modules are located in the Lib directory on the installation path.

### Namespaces§

You have already seen what scopes are. Namespaces, are a way to implement scope. A good way to understand Namespaces is to consider the fact we may have people around us with the same first name, but their surname helps us to distinguish among them. In this case, the surname is the namespace i.e. John Purcell falls in the namespace Purcell.

In Python, whatever you put inside a file, becomes that file’s namespace. That is to say, that a variable named name in a module called data will be accessed by data.name. This is to avoid ambiguity while referring to names and functions in different modules. A simple example:

# myData.py
name = 'Ethan Hunt'
def reveal_name():
return 'My name is ' + name

# myFriendsData.py
name = 'Arya Stark'
def reveal_name():
return 'My name is ' + name

# current_program.py
import myData
import myFriendsData

print( myData.name )
print( myFriendsData.name )

print( myData.reveal_name() )
print( myFriendsData.reveal_name() )


There are three namespaces:

• Builtin Namespace: the reason why you can call builtin functions such as len(), print() without having to prefix them is because this namespace is the container of other namespaces.
• Global Namespace: Namespace of a module
• Local Namespace: Namespace of a function/method or a class
name = 'Ethan'

def reveal_name():
name = "Arya"
print( "Local Namespace: ", len(name) ) # name is a variable in the local namespace of this function

print( "Builtin Namepace:", len('Dolores') ) # len is a function in the builtin namespace
print( "Global Namespace:", len(name) ) # name is a variable in the global namespace
reveal_name()

## OUTPUT
7 		# length of ‘Dolores’
5 		# length of ‘Ethan’
4		# length of ‘Arya’



FYI, if you define a function of the same name as that of a builtin function, then the function you defined takes precedence over the builtin function.

def len(num):
return 'Length of this square is ' + str(num)

len(32)                 # 'Length of this square is 32'


So what if you now wanted the len() function to behave like it used to? You can do that by referring to elements in the builtin namespace by prefixing the len() function by the string '__builtins__'.

len('Dolores')		# 'Length of this square is Dolores'
__builtins__.len('Dolores')			# 7


Reason why the above works is that the namespaces in Python are stored as dictionaries. If you type >>> globals() (which is a builtin function) in the interpreter, you will see something like this:

{'__builtins__': <module 'builtins' (built-in)>, '__package__': None, '__doc__': None, '__name__': '__main__', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None}


Note that the '__builtin__' variable refers to the builtin module, the very fact we used to call the length function above.

The Zen of Python (>>> import this) states that

Namespaces are one honking great idea -- let's do more of those!

This is in reference to making the code more readable. Consider an example where your program is importing 2 or more modules having methods by the same name. In such a case, it’s always helpful, that while you are calling these functions in your program, you state explicitly which function is being called, to avoid ambiguity and prevent errors, which enhances readability and understand-ability.

Let’s use the same example we used earlier:

# myData.py
name = 'Ethan Hunt'
def reveal_name():
return 'My name is ' + name

# myFriendsData.py
name = 'Arya Stark'
def reveal_name():
return 'My name is ' + name

######### PREFERRED WAY TO CALL THESE FUNCTIONS #############
# current_program.py
import myData
import myFriendsData

print( myData.name )				# Ethan Hunt
print( myFriendsData.name )			# Arya Stark

print( myData.reveal_name() )			# My name is Ethan Hunt
print( myFriendsData.reveal_name() )		# My name is Arya Stark

########### ANOTHER WAY TO CALL THESE FUNCTIONS, WHICH LEADS TO MORE BUGS ##########

# current_program.py
from myData import *
from myFriendsData import *

print( name )			# Arya Stark
print( name )			# Arya Stark

print( reveal_name() )		# My name is Arya Stark
print( reveal_name() )		# My name is Arya Stark



The above example is very minimalistic, and you may think this is not extremely helpful. Fair enough, let’s add a few more functions to these imported modules. For the sake of keeping it simple, I will not define the bodies of these functions.

# myData.py
name = 'Ethan Hunt'
def reveal_name():
return 'My name is ' + name
def someOtherFunction1(): pass
def someOtherFunction2(): pass
def someOtherFunction3(): pass
def someOtherFunction4(): pass

# myFriendsData.py
name = 'Arya Stark'
def reveal_name():
return 'My name is ' + name
def someOtherFunction5(): pass
def someOtherFunction6(): pass
def someOtherFunction7(): pass
def someOtherFunction8(): pass



Now imagine that the following program is not working as you think it should.

# current_program.py
from myData import *
from myFriendsData import *

someOtherFunction1()
someOtherFunction5()
someOtherFunction2()
someOtherFunction4()
someOtherFunction3()
someOtherFunction6()
someOtherFunction7()
someOtherFunction8()

print( name )			# Arya Stark
print( name )			# Arya Stark

print( reveal_name() )		# My name is Arya Stark
print( reveal_name() )		# My name is Arya Stark



Needless to say, you will find it much harder to fix any error thrown by it as the contents of the imported files increases.

Takeaway from this is that it is of significant use to explicitly mention the source of your functions. It makes the code easier to debug, and easier to comprehend for others and more so, for yourself when you are reading your own code 6 months from date you wrote it. It’s just good practice.

### A Few General Things§

#### dir(), globals() and locals()

The builtin dir() function allows the user to see all the classes, methods and variables defined in a module. You can use it in conjunction with the help() function for learning more about the builtin modules.

>>> import os
>>> dir(os)
['F_OK', 'MutableMapping', 'O_APPEND', 'O_BINARY', 'O_CREAT', 'O_EXCL', 'O_NOINHERIT', 'O_RANDOM', 'O_RDONLY', 'O_RDWR', 'O_SEQUENTIAL', 'O_SHORT_LIVED', 'O_TEMPORARY', 'O_TEXT', 'O_TRUNC', 'O_WRONLY', 'P_DETACH', 'P_NOWAIT', 'P_NOWAITO', 'P_OVERLAY', 'P_WAIT', 'R_OK', 'SEEK_CUR', 'SEEK_END', 'SEEK_SET', 'TMP_MAX', 'W_OK', 'X_OK', '_Environ', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_execvpe', '_exists', '_exit', '_get_exports_list', '_putenv', '_unsetenv', '_wrap_close', 'abort', 'access', 'altsep', 'chdir', 'chmod', 'close', 'closerange', 'cpu_count', 'curdir', 'defpath', 'device_encoding', 'devnull', 'dup', 'dup2', 'environ', 'errno', 'error', 'execl', 'execle', 'execlp', 'execlpe', 'execv', 'execve', 'execvp', 'execvpe', 'extsep', 'fdopen', 'fsdecode', 'fsencode', 'fstat', 'fsync', 'get_exec_path', 'get_handle_inheritable', 'get_inheritable', 'get_terminal_size', 'getcwd', 'getcwdb', 'getenv', 'getlogin', 'getpid', 'getppid', 'isatty', 'kill', 'linesep', 'link', 'listdir', 'lseek', 'lstat', 'makedirs', 'mkdir', 'name', 'open', 'pardir', 'path', 'pathsep', 'pipe', 'popen', 'putenv', 'read', 'readlink', 'remove', 'removedirs', 'rename', 'renames', 'replace', 'rmdir', 'sep', 'set_handle_inheritable', 'set_inheritable', 'spawnl', 'spawnle', 'spawnv', 'spawnve', 'st', 'startfile', 'stat', 'stat_float_times', 'stat_result', 'statvfs_result', 'strerror', 'supports_bytes_environ', 'supports_dir_fd', 'supports_effective_ids', 'supports_fd', 'supports_follow_symlinks', 'symlink', 'sys', 'system', 'terminal_size', 'times', 'times_result', 'umask', 'uname_result', 'unlink', 'urandom', 'utime', 'waitpid', 'walk', 'write']
>>> help(os.write)
Help on built-in function write in module nt:

write(...)
write(fd, data) -> byteswritten

Write bytes to a file descriptor.


The builtin globals() and locals() methods can be used to determine the global and local namespaces, from the place they are called. These functions return a dictionary of variables along with their values

varOne = 'Hello'

def funcOne():
varTwo = 'Hi'
print("Locals from function:", locals(),"\n")
print("Globals from function:", globals(), "\n")

funcOne()

print("Locals from module: ", locals(),"\n")
print("Globals from module: ", globals(), "\n")

### OUTPUT ###
Locals from function: {'varTwo': 'Hi'}

Globals from function: {'__name__': '__main__', '__spec__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__doc__': None, '__package__': None, 'varOne': 'Hello', 'funcOne': <function funcOne at 0x031099C0>, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\lakshaya\\Desktop\\file.py'}

Locals from module:  {'__name__': '__main__', '__spec__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__doc__': None, '__package__': None, 'varOne': 'Hello', 'funcOne': <function funcOne at 0x031099C0>, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\lakshaya\\Desktop\\file.py'}

Globals from module:  {'__name__': '__main__', '__spec__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__doc__': None, '__package__': None, 'varOne': 'Hello', 'funcOne': <function funcOne at 0x031099C0>, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'C:\\Users\\lakshaya\\Desktop\\file.py'}

## The latter two are the same because any top-level code in the module(i.e. code at the first indent level) goes in the local namespace of the module, constituting the global namespace.


One practical use of these functions is when you are getting a NameError, that is, you are trying to use a variable which is not in scope. You can test the variable for membership in locals() dictionary, and proceed accordingly. You can also handle these cases using a try-except clause, which we will see in the next chapter. For the sake of completeness, here's an example:

def myName():
name = 'Arya'

if 'name' in locals():
print(name)
else:
print("The variable is either not defined or not in scope.")

## Using a try-except block:
def myName():
name = 'Arya'

try:
print(name)
except NameError:
print("The variable is either not defined or not in scope.")


#### Making a local variable global

We have already seen how to do this in the scoping section, I'll reiterate over it. There will be instances where you are declaring a variable inside a function, and you will need the variable outside it as well. We can use the global keyword for this.

## BEFORE
>>> def storeMyName():
name = "Ethan"

>>> storeMyName()
>>> name
Traceback (most recent call last):
# Traceback Info
name
NameError: name 'name' is not defined

## AFTER
>>> def storeMyName():
global name
name = "Ethan"

>>> storeMyName()
>>> name
'Ethan'


#### The 'pass' keyword

You might have noticed that while explaining namespaces, I used the pass keyword while defining empty functions, i.e. without significant body. Well, that's precisely why this keyword exists in language, to help make a high-level design of a complicated program. The user can code on the functionality of the function of the later, he can emphasize solely on the structure of the program.

def functionalityOne():
pass

def functionalityTwo():
pass


Since code-blocks in Python are separated by indents and not by curly braces ( {} ), the pass keyword is convenient to make function skeletons.

#### The __name__attribute & __doc__ attribute of modules

Each object in Python has a few builtin attributes, methods/functions, classes etc. which are prefixed suffixed by double underscore i.e __. Hence they are also known as dunder atributes, methods etc. You can read about dunder elements here.

In modules, the __name__ and __doc__ attribute of modules are particularly useful. Let's go over them one by one.

#### __doc__

The __doc__ attribute provides a succinct description of the module. Let's see how we can set it and how someone else, who is importing your module, can use it effectively.

# to-be-imported module: prime.py

def primeOrNot(num):
if num <= 1:
return False
for factor in range(2, num):    # if there exists any number greater than 2 that
if num % factor == 0:       # divides the given number evenly(with remainder 0, that is),
return False            # then the given number is not prime
return True

__doc__ = 'This module provides a handy method to test whether a number is prime or not. Perform dir(prime) or help(prime) for support.'


Let's import the module.

# One way of importing the module is by going to Run Menu in IDLE window of above script > Python Shell
>>> import prime
>>> prime.__doc__
'This module provides a handy method to test whether a number is prime or not. Perform dir(prime) or help(prime) for support.'
>>> dir(prime)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'primeOrNot']
>>> help(prime)
Help on module prime:

NAME
prime - This module provides a handy method to test whether a number is prime or not. Perform dir(prime) to view the function directory.

FUNCTIONS
primeOrNot(num)

FILE
c:\users\lakshaya\desktop\prime.py


#### __name__

The __name__ attribute is incredibly useful. You may have encountered something like if __name__ == '__main__'. This if construct is used to execute the top-level code(i.e. at the first indent level) of a script ONLY IF it is being run directly(i.e. by double-clicking the .py file), and not if the module is being imported into another file.

Let's look at this in detail.

In other programming languages such as C++, there is a main() function (it has to named main, or else the code doesn't compile), declared explicitly by the user, from which the execution of a program begins. In Python, the main() function is composed of all the top-level code i.e. all statements written at indentation level 0. That’s it, you don’t have to declare the main() function, Python does it by itself.

The top-level code in a Python script gets executed as soon as it is run via command line (i.e. \$ python myfile.py) or run directly by executing a .py file.

The __name__ dunder attribute evaluates to the name of the module itself. However, if the module is being run directly by either of the two methods stated above, then the __name__ attribute is set to the string "__main__". This enables the user to check if the script is being run directly (command line or execution of .py file) or it is being imported. The developer can place the functionality in this if clause ensuring that importing the module doesn't trigger the functionality. Only if the module is being executed will the functionality be triggered.

Below is a simple Python script that contains a function, a call to print function, and the if construct. If the script is being run directly, the function is called with a sample value, and not if the script is being imported as a module.

# prime.py

def primeOrNot(num):
if num <= 1:
print(str(num), "is not a prime number.")
return False
for factor in range(2, num):    # if there exists any number greater than 2 that
if num % factor == 0:       # divides the given number evenly(with remainder 0, that is),
print(str(num), "is not a prime number.")
return False            # then the given number is not prime
print(str(num), "is a prime number.")
return True

print("Top-level code being executed...")

if __name__ == '__main__':
primeOrNot(23)


#### When the above script is run directly via command line or by executing prime.py

Top-level code being executed...
23 is a prime number.


#### When the above script is being imported as a module

# One way of importing the module is by going to Run Menu in IDLE window of above script > Python Shell
>>> import prime
Top-level code being executed...


### On the agenda in next chapter§

Kudos! You are now well-equipped to try the programs leading into this chapter. Give them a try and comment below in case you run into a road block.

In the penultimate chapter of the series, we will look at how to manipulate files using Python. Till next time!

#### Exercises§

• Write a Python program which generates a random password. The password may consist of alphabets, numbers & special symbols. Its length should be between 8 and 12. The program will take no inputs, it will output a random password when executed. Hint: You can use the randint() of random module to pick a random length for your password, and choice() function of random module to choose a random characters out of a predefined universal character set.. [ Solution ]

• Build a validatePassword() function which accepts a password as string and reports if it is a good password or not based on the following constraints:

• must contain at least 1 special character
• must contain at least 1 numeric character
• must contain at least 1 uppercase character
• must contain at least 1 lowercase character
• must be at least 8 characters in length but not more than 12 characters

[ Solution ]

• Compose a factorial() function which accepts an integer and returns its factorial. Factorial (denoted by an exclamation mark !) of a number x is the product of an x and all the integers below it till 1. Example: 3! = 3 * 2 * 1 i.e. 6. Also, by mathematical definition, 0! = 1. In order to build your analytical skills, do NOT use the factorial() function of builtin module math. [ Solution ]

• Write a Python function which gives nth term of Fibonacci series. Fibonacci series is a sequence of numbers in which each number is the sum of the two preceding numbers. For example: 0, 1, 1, 2, 3, 5, 8, 13, 21.... [ Solution ]