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 you will be able to compose once you are through this webpage: Is a Number Prime, Median of three values, Center a string in the Terminal
- Functions: A Primer
- Parts of a function
- Function Definition
- Calling a Function
- Parameters & Arguments: positional, keyword, default and variable-length
- Return Statement
- Lambda Expressions (Anonymous functions)
- Docstring
- Scope
- Modules
- Variants of import statement: import some_module, import some_module as alias, from some_module import *, from some_module import some_func
- Modules that Python comes with
- Locating modules
- Namespaces
- A Few General Things: dir(), globals() and locals(), Making a local variable global, the pass keyword, __name__ & __doc__ attributes
- On the agenda in next chapter
- Exercises
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()
#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()
#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()
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 breadth = 6 print("Area:", length * breadth) >>> 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): print("Area:", length * breadth) >>> calculateArea(5, 6) Area: 30 >>> calculateArea(20, 3) Area: 60
Arguments are of three types:
- Positional Arguments
- Keyword Arguments
- Variable Length Arguments
Positional Arguments
>>> def calculateArea(length, breadth): print("Length:", length) print("Breadth:", breadth) print("Area:", length * breadth) >>> calculateArea(5, 6) Length: 5 Breadth: 6 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) print("Breadth:", breadth) print("Area:", length * breadth) >>> calculateArea(breadth = 5, length = 6) Length: 6 Breadth: 5 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) print("Breadth:", breadth) print("Area:", length * breadth) >>> calculateArea() Length: 5 Breadth: 6 Area: 30 >>> >>> calculateArea(10, 3) Length: 10 Breadth: 3 Area: 30 >>> >>> calculateArea(12) # assumes the provided value for first parameter, keeping the default value for the second. Length: 12 Breadth: 6 Area: 72 >>> >>> calculateArea(breadth = 10) Length: 5 Breadth: 10 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("Breadth:", breadth) print("Other Dimensions:", other_dimensions) print(type(other_dimensions)) >>> collectParameters(10, 20, 30, 40, 50, 60) Length: 10 Breadth: 20 Other Dimensions: (30, 40, 50, 60) <class 'tuple'> >>> collectParameters(10, 20) Length: 10 Breadth: 20 Other Dimensions: () <class 'tuple'>
Return Statement§
The return statement makes the control exit from a function, with an optional expression.
>>> def calculateArea(length, breadth): return 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 >>> add(3,4) 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 CallTips _thread idlelib rmagic ClassBrowser _threading_local idlever rpc 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 GrepDialog abc ipaddress setuptools HyperParser aboutDialog ipykernel shelve 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 Percolator autoreload jupyter_core socketserver 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 _codecs_tw coverage notebook threading _collections crypt nt time _collections_abc csv ntpath timeit _compat_pickle ctypes nturl2path tkinter _csv curses numbers token _ctypes cx_Oracle numpy tokenize _ctypes_test cythonmagic opcode tornado _datetime datetime operator touchring _decimal dateutil optparse trace _dummy_thread dbm os traceback _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 _locale dummy_threading pip unittest _lsprof dynOptionMenuWidget pipes urllib _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 _osx_support flask pty webdebugger _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 ]
See also:
- Object Oriented Python
- Design Patterns in Python
- 50+ Handy Standard Library Modules
- 60+ Handy Third Library Modules
- 50+ Tips & Tricks for Python Developers
- 50+ Know-How(s) Every Pythonista Must Know