## Python Programming Fundamentals for Class 11 and 12 – Modules and Packages

As the program gets longer, it is a good option to split it into several files for easier maintenance. There might also be a situation when the programmer wants to use a handy function that is used in several programs without copying its definition into each program. To support such scenarios, Python has a way to put definitions in a file and use them in a script or in an interactive instance of the interpreter.

Module
A module is a file containing Python definitions and statements. The file name is the module name with the suffix .py appended. Definitions from a module can be imported into other modules. For instance, consider a script file called “fibo.py” in the current directory with the following code.

# Fibonacci numbers module
def fib(n): # write Fibonacci series up to n
a,b=0,1
while b<n:
print b,
a,b=b,a+b
def fib2(n): # return Fibonacci series up to n
result=[]
a,b=0,1
while b<n:
result.append(b)
a,b=b,a+b
return result

Now on the Python interpreter, import this module with the following command:

>> import fibo

Within a module, the module’s name (as a string) is available as the value of the global variable _name_:

>>> fibo._name_
'fibo'

Use the module’s name to access its functions:

>>> fibo.fib(1000)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
>>> fibo. fib2 (1000)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]

If it is intended to use a function often, assign it to a local name:

>>> Fibonacci=fibo.fib
>>> Fibonacci(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

This Fibonacci calculator is a tool for calculating the arbitrary terms of the Fibonacci sequence.

More on modules
A module can contain executable statements as well as function definitions. These statements are intended to initialize the module. They are executed only the first time the module name is encountered in an import statement; they also run if the file is executed as a script.
Each module has its own private symbol table, which is used as the global symbol table by all functions defined in the module. Thus, the author of a module can use global variables in the module without worrying about accidental clashes with a user’s global variables.
Modules can import other modules. It is customary but not required to place all import statements at the beginning of a module. The imported module names are placed in the importing module’s global symbol table. .
There is a variant of the import statement that import specific names from a module directly into the importing module’s symbol table. For example, specific attributes of fibo module are imported in local namespace as:

>>> from fibo import fib, fib2
>>> fib (500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

This does not introduce the module name from which the imports are taken in the local symbol table (so in the example, fibo is not defined).
There is even a variant to import all names that a module defines:

>>> from fibo import *
>>> f ib (500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377

This imports all names except those beginning with an underscore (_).
Note that in general, the practice of importing * from a module or package is discouraged, since it often causes poorly readable code. However, it is okay to use it to save typing in interactive session. For efficiency reason, each module is only imported once per interpreter session. If changes are made in many modules, it is a wise approach to restart the interpreter or if it is just one module that needs to be tested interactively, use reload (), e.g. reload (modulename).

Executing modules as scripts
When a Python module is run at command prompt

$python fibo.py the code in the module will be executed, just as if it imported, but with the name set to _main_. That means, by adding the following code at the end of the module: if _name_ == " _main_" : import sys fib(int(sys.argv[1 ] ) ) the file is made usable as a script as well as an importable module, because the code that parses the command line only runs if the module is executed as the “main” file: $ python fibo.py
50 1 1 2 3 5 8 13 21 34

The Module Search Path
When a module named f ibo is imported, the interpreter first searches for a built-in module with that name. If not found, it then searches for a file named fibo.py in a list of directories given by the variable sys .path. The variable sys .path is initialized from some of these locations:
– The current directory.
– PYTHONPATH environment variable (a list of directory names, with the same syntax as the shell variable PATH).
After initialization, Python programs can modify sys .path. The directory containing the script being run is placed at the beginning of the search path, ahead of the standard library path. This means that scripts in that directory will be loaded instead of modules of the same name in the library directory.

Math module
Python has math module and it is always available. The functions provided in this module cannot be used with complex numbers; use the functions of the same name from the cmath module for support of complex numbers. The distinction between functions which support complex numbers and those which does not is made since most users do not want to learn quite as much mathematics as required to understand complex numbers. Except when explicitly noted otherwise, all functions return float values.

Constants
math.pi
The mathematical constant π.

>>> math.pi
3.141592653589793

math.e
The mathematical constant e.

>>> math.e
2.718281828459045

Number-theoretic and representation functions
math.ceil(x)
Return the ceiling of x as a float, the smallest integer value greater than or equal to x.

>>> import math
>>> math.ceil(-3.2)
-3.0
>>> math.ceil(3.2)
4.0

math.copysign(x,y)
Return x with the sign of y.

>>> math.copysign(5.1,-2.8)
-5.1
>>> math.copysign(-5.1,2.8)
5.1

math.fabs(x)
Return the absolute value of x.

>>> math.fabs(-4.2 )
4.2

math.factorial(x)
Return x factorial. Raises ValueError, if x is not integral or is negative.

>>> math.factorial(5)
120
>>> math.factorial(-5)
Traceback (most recent call last):
File "", line 1, in
ValueError: factorial() not defined for negative values

math.floor(x)
Return the largest integer value less than or equal to x as a float.

>>> math.floor(-3.2)
-4.0
>>>. math. floor (3.2 )
3.0

math.fmod(x,y)
Return remainder of a division expression. Note that the Python expression x%y may not return the same result. The result of fmod (x, y) has same sign as x, while x%y returns a result with the sign of y instead.

>>> math.fmod(5.0,-2.0)
1.0
>>> 5.0%-2.0 -1.0
>>> math.fmod(-5.0,2.0)
-1.0
>>> -5.0%2.0
1.0

Consider the following interesting scenario.

>>> math.fmod(-le-100,le100)
-le-100
>>> -le-100 % le100
le+100

It can be observed that fmod (-le-100, le100) returns -le-100, but the result of Python’s – le-100%lel00 is lel00-le-100, which cannot be represented exactly as a float, and rounds to the surprising lelOO. For this reason, function fmod () is generally preferred when working with floats, while Python’s x%y is preferred when working with integers.

math.frexp(x)
Return the mantissa and exponent of x as the pair (m, e). The m is a float and e is an integer such that x = mxT exactly. If x is zero, returns (0.0, 0), otherwise 0.5<=abs (m) <1.

>>> math.frexp(4.0)
(0.5, 3)
>>> 0.5*2**3
4.0
>>> math.frexp(0.1)
(0.8, -3)
>>> 0.8*2**-3 0.1
>>> math.frexp(-4.0)
(-0.5, 3)
>>> -0.5*2**3 -4.0

math.fsum(iterable)
Return an accurate floating point sum of values in the iterable.

>>> math.fsum([.1, .1, .1, .1, .1, .1, .1, .1, .1, .1])
1.0

math.isinf(x)
Check if the float x is positive or negative infinity.

>>> a=le+300 >>> a le+300
>>> math.isinf(le + 300 )
False
>>> a=le+310
>>> a
inf
>>> math.isinf(le+310)
True

Calculating an exponent with floating point values, in particular, raises Overf lowError instead of preserving the inf result.

>>> a=10.0**310
Traceback (most recent call last):
File "<pyshell#l>", line 1, in
a=10.6**310
OverflowError: (34, 'Result too large')

math.isnan(x)
Check if the float x is a nan (not a number), nan does not compare as equal to any value, even itself, so nan should be checked using isnan () function.

>>> a=le+310
>>> a
inf
>>> b=a/a
>>> b
nan
>>> math.isnan(a)
False
>>> math.isnan(b)
True

math.ldexp(x,i)
Return x* (2 * * i). This function is reverse of function frexp ().

>>> math.ldexp(-0.5,3)
-4.0
>>> -0.5*2**3 -4.0
>>> math.ldexp(0.8, -3)
0.1
>>> 0.8*2**-3
0.1

math.modf(x)
Return the fractional and integer parts of x. Both results carry the sign of x and are floats.

>>> math.modf(1.5)
(0.5, 1.0)
>>> math.modf(-1.5)
(-0.5, -1.0)

math.trunc(x)
Return the real value x truncated to an integer.

>>> math.trunc(93.2508)
93
>>> math.trunc(-93.2508)
-93

Power and logarithmic functions
math.exp(x)
Return e^x .

>>> math.e**-3.2
0.04076220397836622
>>> math.pow(math.e,-3.2)
0.04076220397836622
>>> math.exp(-3.2)
0.04076220397836621

math.expml(x)
Return ex-l. For small floats x, the subtraction in exp(x)-l can result in a significant loss of precision; the expml () function provides a way to compute this quantity to full precision:

>>> x=0.0000000000000000000000001
>>> math.exp(x)-1
0.0
>>> math.expml(x)
le-25

math.log(x[,base]) .
With one argument, return the natural logarithm of x (to base e). With two arguments, return the logarithm of x to the given base, calculated as log (x) /log (base). .

>>> math.log(9)
2.1972245773362196
>>> math.log(9,2)
3.1699250014423126

math.loglp(x)
Return the natural logarithm of 1 + x (base e). The result is calculated in a way which is accurate for x near zero.

>> x=0.0000000000000000000000001
>>> x
le-25
>>> 1+x
1.0
>>> math.log(1+x)
0.0
>>> math.loglp(x)
le-25

math.log10(x)
Return the base-10 logarithm of x. This is usually more accurate than log (x, 10).

>>> math.log10(100)
2.0
>>> math.log10(10000)
4.0

math.pow(x,y)
Return x raised to the power y. In particular, pow(1.0,x) and pow(x, 0.0) always return 1.0, even when x is a zero or a NaN. If both x and y are finite, x is negative, and y is not an integer then pow (x, y) is undefined, and raises ValueError.

>>> math.pow(9.0, 0.5)
3.0
>>> math.pow(-9.0, 0.5)
Traceback (most recent call last):
File "", line 1, in
ValueError: math domain error

Unlike the built-in * * operator, math . pow () converts both its arguments to type float.

math.sqrt(x)
Return the square root of x. Computing the square roots of negative numbers requires complex numbers, which are not handled by math module. Any attempt to calculate a square root of a negative value results in ValueError.

>>> math.sqrt(9.0)
3.0
>>> math. sqrt (-9.0)
Traceback (most recent call last):
File "<pyshell#62>", line 1, in
math.sqrt(-9.0)
ValueError: math domain error

Trigonometric functions
math.acos(x)
Return the arc cosine of x, in radians.

>>> math.acos(0.5)
1.0471975511965979

math.asin(x)
Return the arc sine of x, in radians.

>>> math.asin(0.5)
0.5235987755982989

math.atan(x)
Return the arc tangent of x, in radians.

>>> math.atan(0.5)
0.4636476090008061

math.atan2(y,x)
Return atan(y/x), in radians. The result is between -n and TI. The vector in the plane from the origin to point (x, y) makes this angle with the positive X axis. The point of atan2 () is that the signs of both inputs are known to it, so it can compute the correct quadrant for the angle. For example,
atan (1) and atan2 (1,1) are both n /4, but atan2 (-1,-1) is -3pi/4.

>>> math.atan(1.0)
0.7853981633974483
>>> math.pi/4
0.7853981633974483

math.cos(x)
Return the cosine of x radians.

>>> math.cos(0.7853981633974483)
0.7071067811865476

math.hypot(x,y)
Return the Euclidean distance, √(x^2 + y^2 ). This is the length of the vector from the origin to point (x,y).

>>> math.hypot(3.0,4.0)
5.0

math.sin(x)
Return the sine of x radians.

>>> math.sin(0.7853981633974483)
0.7071067811865475

math.tan(x)
Return the tangent of x radians.

>>> math.tan(0.7853981633974483)
0.9999999999999999

Angular conversion
math.degrees(x)
Converts angle x from radians to degrees.

>>> math.degrees(1.5707963267948966)
90.0
>>> 1.5707963267948966*180/math.pi
90.0

Converts angle x from degrees to radians.

>>> math.radians(90)
1.5707963267948966
>>> (90*math.pi)/180
1.5707963267948966

Hyperbolic functions
math.acosh(x)
Return the inverse hyperbolic cosine of x.

>>> math.cosh(1.0)
1.5430806348152437

math.asinh(x)
Return the inverse hyperbolic sine of x.

>>> math.asinh(1.0 )
0.8813735870195429

math.atanh(x)
Return the inverse hyperbolic tangent of x.

>>> math.atanh(0.8)
1.0986122886681098

math.cosh(x)
Return the hyperbolic cosine of x.

>>> math, cosh (0.7853981633974483)
1.3246090892520057

math.sinh(x)
Return the hyperbolic sine of x.

>>> math.sinh(0.7853981633974483)
0.8686709614860095

math.tanh(x)
Return the hyperbolic tangent of x.

>>> math.tanh(0.7853981633974483)
0.6557942026326724

Special functions
math.erf(x)
Return the error function at x.

>>> math.erf(0.25)
0.2763263901682369

math.erfc(x)
Return the complementary error function at x i.e. 1-erf (x).

>>> math.erfc(0.25)
0.7236736098317631

math.gamma(x)
Return the gamma function at x.

>>> math . gamma (5.5)
52.34277778455352

math.lgamma(x)
Return the natural logarithm of the absolute value of the gamma function at x.

>>> math.lgamma(5.5)
3.9578139676187165

Random module
This module implements pseudo-random number generators for various distributions. Almost all module functions depend on the basic function random (), which generates a random float uniformly in the semi-open range [0.0, 1.0). Python uses the “Mersenne Twister” as the core generator. However, Mersenne Twister being completely deterministic, it is not suitable for all purposes, and is completely unsuitable for cryptographic purposes.

Functions for integers

random.randrange(stop)

Return a randomly selected integer element from range (0, stop).

>>> random.randrange(88)
17
>>> random.randrange(-88)
Traceback (most recent call last):
File "<pyshell#l>", line 1, in random.randrange(-88)
File "C:\Python27\lib\random.py", line 191, in randrange
raise ValueError, "empty range for randrange()"
ValueError: empty range for randrange()

random.randrange(start,stop[,step])
Return a randomly selected integer element from range (start, stop, step).

>>> random.randrange(3,100,5)
83

random.randint(a, b)
Return a random integer N such that a<=N<=b.

>>> random.randint(5, 86)
70

Functions for sequences
random.choice(seq)
Return a random element from the non-empty sequence seq. If seq is empty, raises IndexError.

>>> random.choice('abcdefghij')
' e '
>>> random.choice(['aa','bb','cc',11,22])
' cc'

random.shuffle(x[,random])
Shuffle the sequence x in place. The optional argument random is a O-argument function returning a random float in [ 0.0, 1.0 ); by default, this is the function random ().

>>> items=[1,2,3,4,5,6,7]
>>> random.shuffle(items)
>>> items
[4, 7, 2, 6, 3, 5, 1]

random.sample(population,k)
Return a k length list of unique elements chosen from the population sequence; used for random sampling without replacement. Return a new list containing elements from the population, while leaving the original population unchanged. If the population contain repeating elements, then each occurrence is a possible selection in the sample.

>>> random.sample([1,2,3,4,5],3)
[4, 5, 1]

To choose a sample from a range of integers, use an xrange () object as an argument. This is especially fast and space efficient for sampling from a large population.

>>> random.sample(xrange(10000000),5)
[2445367, 2052603, 975267, 3021085, 6098369]

Functions for floating point values
random.random()
Return the next random floating point number in the range [ 0.0,1.0).

>>> random.random()
0.6229016948897019

random.uniform(a,b)
Return a random floating point number N, such that a<=N<=b for a<=b and b<=N<=a for b

>> random.uniform(0.5,0.6)
0.5795193565565696

random.triangular(low,high,mode)
Return a random floating point number N such that low<=N<=high and with the specified mode between those bounds. The low and high bounds default to 0 and 1, respectively. The mode argument defaults to the midpoint between the bounds, giving a symmetric distribution.

>>> random.triangular(2.8,10.9,7.5)
6.676127015045406

random.betavariate(alpha,beta)
Beta distribution; conditions of the parameters are alpha>0 and beta>0. Returned values range between 0 and 1.

>>> random.betavariate(2.5,1.0)
0.543590525336106

random.expovariate(lambd)
Exponential distribution; lambd is 1.0 divided by the desired mean. It should be nonzero. Returned values range from 0 to positive infinity; if lambd is positive, and from negative infinity to 0, if lambd is negative.

>>> random.expovariate(0.5)
1.5287594548764503

random.gammavariate(alpha, beta)
Gamma distribution (not the gamma function). Conditions of the parameters are alpha>0 and beta>0.

>>> random.gammavariate(1.3, 0.5)
0.5893587279305473

random.gauss(mu,sigma)
Gaussian distribution; mu is the mean, and sigma is the standard deviation. This is slightly faster than the normalvariate () function defined below.

>>> random.gauss(0.5,1.9)
-1.8886943114939512

random.lognormvariate(mu,sigma)
Log normal distribution; if natural logarithm of this distribution is taken, a normal distribution with mean mu and standard deviation sigma is received, mu can have any value, and sigma must be greater than zero.

>>> random.lognormvariate(0.5, 1.9)
4.621063728160664

random.normalvariate(mu,sigma)
Normal distribution; mu is the mean, and sigma is the standard deviation.

>>> random.normalvariate(0.5,1.9)
1.6246107732503214

random.vonmisesvariate(mu,kappa)
mu is the mean angle, expressed in radians between 0 and 2n, and kappa is the concentration parameter, which must be greater than or equal to zero. If kappa is equal to zero, this distribution reduces to a uniform random angle over the range 0 to 2pi .

>>> random.vonmisesvariate(0.5,1.9)
-0.4664831190641767

random.paretovariate(alpha)
Pareto distribution; alpha is the shape parameter.

>>> random.paretovariate(0.5)
60.471412103322585

random.weibullvariate(alpha,beta)
Weibull distribution; alpha is the scale parameter and beta is the shape parameter.

>>> random.weibullvariate(0.5, 1.9 )
0.9229896561284915

Alternative generators
Apart from Mersenne Twister, there are more core random number generator, such as generator based on “Wichmann-Hill” algorithm.

>>> rnd=random.WichmannHill()
>>> rnd.random()
0.4065226158909223
>>>
>>> rnd=random.SystemRandom()
>>> rnd.random()
0.46579102190832355

Package
As discussed previously, functions and global variables usually reside inside a module. There might be a scenario where organizing modules needs to be done. That is where packages come into the picture.
Packages are just folders of modules with a special ” init .py” file, that intimate Python that this
folder is special because it contains Python modules. In the simplest c^se, ” init .py” can just be an
empty file, but it can also execute initialization code for the package or set the all variable,
described later.
Suppose there is an objective to design a collection of modules (a “package”) for the uniform handling of sound files and sound data. There are many different sound file formats (usually recognized by their extension, for example: .wav, .aiff, .au), so it might be needed to create and maintain a growing collection of modules for the conversion between the various file formats. There are also many different operations that are needed to perform on sound data (such as mixing, adding echo, applying an equalizer function, creating an artificial stereo effect), so there will be numerous modules to perform these operations. Here is a schematic structure for the package:

sound/ Top-level package
_init_.py Initialize the sound package
formats/ Subpackage for file format conversions
_init_.py
wavwrite.py
aiffwrite.py
auwrite.py
...
effects/ Subpackage for sound effects
_init_.py
echo.py
surround.py
reverse.py
filters/ Subpackage for filters
_init_.py
equalizer.py
vocoder.py
karaoke.py
...

When importing the package, Python searches through the directories on sys .path looking for the package sub-directory. Users of the package can import individual modules from the package, for example:

import sound.effects.echo

This loads the sub-module sound. effects . echo. It must be referenced with its full name.

sound.effects.echo.echofilter(input, output,delay=0.7, atten=4)

An alternative way of importing the sub-module is:

from sound.effects import echo

This loads the sub-module echo, and makes it available without its package prefix, so it can be used as follows:

echo.echofilter(input,output,delay=0.7,atten=4)

Yet another variation is to import the desired function or variable directly:

from sound.effects.echo import echofilter

This makes echof ilter () directly available:

echofilter(input,output,delay=0.7,atten=4)

Note that when using from package import item, the item can be either a sub-module (or sub-package) of the package, or some other name defined in the package, like a function, class or variable. The import statement first tests whether the item is defined in the package; if not, it assumes it is a module and attempts to load it. If it fails to find it, an ImportError exception is raised. Contrarily, when using syntax like import item. subitem. subsubitem, each item except for the last must be a package; the last item can be a module or a package but cannot be a class or function or variable defined in the previous item.

Importing from a package
What happens when the programmer writes from sound. effects import *? Ideally, one would hope that this somehow goes out to the file system, finds which sub-modules are present in the package, and imports them all. This could take a long time.

The only solution is for the package author to provide an explicit index of the package. The import statement uses the following convention: if a package’s _init_ . py code defines a list named _all_ , it is taken to be the list of module names that should be imported when from package import * is encountered. It is up to the package author to keep this list up-to-date when a new version of the package is released. Package authors may also decide not to support it, if they does not see a use for importing * from their package. For example, the file sounds/effects/_init_ . py could contain the following code:

_all _ = ["echo", "surround", "reverse"]

This would mean that from sound. effects import * would import the three named sub¬modules of the sound package.

If_all_ is not defined, the statement from sound. effects import * does not import all sub-modules from the package sound. effects into the current namespace; it only ensures that the package sound. effects has been imported (possibly running any initialization code in _init_.py) and then imports whatever names are defined in the package. This includes any
names defined (and sub-modules explicitly loaded) by init *py. It also includes any sub modules of the package that were explicitly loaded by previous import statements.

Remember, there is nothing wrong with using from Package import specif ic_submodule. In fact, this is the recommended notation, unless the importing module need to use sub-modules with the same name from different packages.

Intra-package references
The sub-modules often need to refer to each other. For example, the surround module might use the echo module. In fact, such references are so common that the import statement first looks in the containing package before looking in the standard module search path. Thus, the surround module can simply use import echo or from echo import echof ilter. If the imported module is not found in the current package (the package of which the current module is a sub¬module), the import statement looks for a top-level module with the given name.

When packages are structured into sub-packages (as with the sound package in the example), the programmer can use absolute imports to refer to sub-modules of siblings packages. For example, if the module sound. filters. vocoder needs to use the echo module in the sound. effects package, it can use from sound . effects import echo.
Starting with Python 2.5, in addition to the implicit relative imports described above, the programmer can write explicit relative imports with the from module import name form of import statement. These explicit relative imports use leading dots to indicate the current and parent packages involved in the relative import. From the surround module for example, you might use:

from . import echo
from .. import formats
from ..filters import equalizer

Packages in multiple directories
Package support one more special attribute, _path_ . This is initialized to be a list containing the name of the directory holding the package’s _init_ . py before the code in that file is executed. This variable can be modified, doing so affect future searches for modules and sub-packages contained in the package. While this feature is not often needed, it can be used to extend the set of modules found in a package.

>>> import numpy
>>> numpy._path_
['C:\\Python27\\lib\\site-packages\\numpy']