[Go to site: main page, start]

0% found this document useful (0 votes)
6 views17 pages

Python Programming Module 4 Notes

This document covers the concept of modules in Python, including the use of the random module for generating random numbers and the importance of repeatability in testing. It explains how to create custom modules, the significance of namespaces, and the scope of identifiers. Additionally, it discusses mutable versus immutable data types and introduces object-oriented programming principles in Python.

Uploaded by

darshanking2303
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views17 pages

Python Programming Module 4 Notes

This document covers the concept of modules in Python, including the use of the random module for generating random numbers and the importance of repeatability in testing. It explains how to create custom modules, the significance of namespaces, and the scope of identifiers. Additionally, it discusses mutable versus immutable data types and introduces object-oriented programming principles in Python.

Uploaded by

darshanking2303
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Python Programming 1BPLC105B

Module 4
4. Modules
A module is a file containing Python definitions and statements intended for use in other
Python programs. There are many Python modules that come with Python as part of the
standard library.
Eg: the string module

4.1 Random numbers

The uses of random numbers in programs:

• To play a game of chance where the computer needs to throw some dice, pick a
number or flip a coin.
• To shuffle a deck of playing cards randomly
• To allow an enemy spaceship appear at a random location and start shooting at the
player
• To simulate possible rainfall when we make a computerized model for estimating the
environment impact of building a dam
• For encrypting banking sessions on the Internet
Python provides a module called random that helps in performing these tasks.

import random loads Python’s built-in module that can generate random numbers.
rng = [Link]() creates a random number generator object. This object has methods
like .randrange, .random and .shuffle and keeps its own internal state.
The randrange method call generates an integer between its lower and upper argument, using
the semantics as range which means the lower bound is included but the upper bound is not
included. All the values have an equal probability of occurring i.e., the results are uniformly
distributed.
When we need random odd number less than 100 we could use the following:

Other methods can also generate other distributions. E.g.: a bell-shaped or normal distribution
might be more appropriate for estimating seasonal rainfall or the concentration of a
compound in the body after taking a dose of medicine.
The random method returns a floating point number in the interval [0.0, 1.0) — the square
bracket means “closed interval on the left” and the round parenthesis means “open interval on
the right”. In other words, 0.0 is possible, but all returned numbers will be strictly less than 1.0.
It is usual to scale the results after calling this method, to get them into an interval suitable for
your application. In the case shown here, we’ve converted the result of the method call to a
number in the interval [0.0, 5.0). Once more, these are uniformly distributed numbers—
numbers close to 0 are just as likely to occur as numbers close to 0.5, or numbers close to 1.0.

The example given below shows how to shuffle a list

[Link](cards) randomly permutes the list in place, meaning it rearranges the elements of
cards so that every possible ordering is equally likely.

4.2 Repeatability and Testing

Random number generators are based on a deterministic algorithm — repeatable and


predictable. So they’re called pseudo-random generators — they are not genuinely random.
They start from an initial value called seed value.
For debugging, it is very useful if a program that uses randomness behaves the same way each
time we run it, then a failing test can be reproduced and fixed reliably.

drng = [Link](123) creates a random generator with a starting value. In the previous
example, the parenthesis was empty but here, we have an initial value called the seed value. In
python, creating a random generator with a fixed seed such as drng = [Link](123),
guarantees that all random values drawn from that generator follow the same sequence on every
run of the program.
Calling methods like [Link](), [Link]() or [Link]() will always produce
the same sequence of results each time the program is run, as long as we use that same seed
and call the methods in the same order.
When we print the result, Python does not show the numbers the generator will produce, instead
it prints a representation such as <[Link] object at 0x00000170CC1C87C0>

which tells the type(Random) and its memory location. This representation is mainly for the
programmer’s reference during debugging, indicating that drng is a random object that is ready
to generate numbers but hiding its internal state.

4.3 Picking balls from bags, throwing dice, shuffling a pack of cards

This is an example to generate a list containing n random integers between a lower and an
upper bound:
We will get 5 random integers between 1 to 13. But it can be noticed that we get duplicates in
the result i.e., 12 is repeated twice.

To get rid of the duplicates, we can use the code given below:

This will print the output without any repetitions.

Shuffle and Slice


The shuffle and slice algorithm would not be so great if you only wanted a few elements, but
from a very large domain. Suppose I wanted five numbers between one and ten million, without
duplicates. Generating a list of ten million items, shuffling it, and then slicing off the first five
would be a performance disaster. This can be done using the following code:

This example produces 5 random numbers between 1 and 1000000000.


Example

The range contains only integers 1, 2, 3, 4, 5. There are 5 distinct values. But the function is
being asked to generate 10 distinct values from a set that only has 5 possible values.
Logically, it is impossible. We cannot have 10 distinct integers if only 5 different integers exist
in the range.
What happens in the code?
1. For the first 5 iterations, it can find new values. It will take integers 1, 2, 3, 4, 5
randomly.
2. From the 6th iteration result contains all possible values since duplicates are not
allowed.
3. The code calls [Link](1, 6) inside while True, but every possible candidate (1
to 5) is already in result.
4. The condition if candidate not in result: is never satisfied again.
5. The while True loop never executes the break, so it runs forever and the function never
returns.
This implementation does not end gracefully it just hangs in the infinite loop.

4.4 The time module

The time module has a function called clock. Whenever clock is called, it returns a floating
point number representing how many seconds have elapsed since your program started running.
The way to use it is to call clock and assign the result to a variable, say t0, just before you start
executing the code you want to measure. Then after execution, call clock again, (this time we’ll
save the result in variable t1). The difference t1-t0 is the time elapsed, and is a measure of how
fast your program is running.
Python has a built-in sum function that can sum the elements in a list. We can also write our
own custom sum function.

Output:

Here, we are comparing both the custom sum function and built-in sum function. my_sum is
the result for custom function whereas their_sum is the result for built-in function.
Both methods compute the same numerical result, so they are functionally equivalent. The
custom Python loop is slower (about 1.5567 s) than the built-in sum (about 0.9897 s) i.e.,
roughly 57% slower.
4.5 The math module
The math module contains the kinds of mathematical functions you’d typically find on your
calculator (sin, cos, sqrt, asin, log, log10) and some mathematical constants like pi and e.

4.6 Creating your own modules

All we need to do to create our own modules is to save our script as a file with a .py
extension. Suppose, for example, this script is saved as a file named [Link]

We can now use our module, both in scripts we write, or in the interactive Python interpreter.
To do so, we must first import the module.

We do not include the .py file extension when importing. Python expects the file names of
Python modules to end in .py, so the file extension is not included in the import statement.
The use of modules makes it possible to break up very large programs into manageable sized
parts, and to keep related parts together.

4.7 Namespaces
A namespace is a collection of identifiers that belong to a module, or to a function. It maps
names to objects, for example, mapping the name question to a specific string or answer to a
number. Generally, we like a namespace to hold “related” things, e.g. all the math functions,
or all the typical things we would do with random numbers.
Each module has its own namespace, so we can use the same identifier name in multiple
modules without causing an identification problem.

We can now import both modules and access question and answer in each

Output:

Functions also have their own namespaces:

Output:
The three n’s here do not collide since they are each in a different namespace—they are three
names for three different variables.
Namespaces permit several programmers to work on the same project without having naming
collisions.

How are namespaces, files and modules related?


Namespaces, files, and modules are three related but distinct ideas.
File: A file is something on disk, like [Link]. This belongs to the operating system’s file
system , it is literally where our code is stored.
Module: A module is a programming unit that Python can import. When we import math,
Python loads the code from the file and treats it as a module.
Namespace: A namespace is a mapping from names to objects. For a module, its namespace
is the set of names defined inside it: functions, classes, variables etc.

In Python,
• Each .py file is normally one module.
• The module name is taken from the filename without .py
[Link] → module math → namespace is accessed as math.<name>
• When that module is imported, Python creates a module namespace whose name is
also math, and all top-level definitions (sin, cos, etc.) live inside that namespace
as [Link], [Link], and so on.

So in everyday Python use:


• “file [Link]”
• “module math”
• “namespace math”

But we will encounter other languages (e.g. C#), that allow one module to span multiple files,
or one file to have multiple namespaces, or many files to all share the same namespace. So
the name of the file doesn’t need to be the same as the namespace.
The key message is – do not assume that file name = module name = namespace name” is
always true in every language, even though Python makes it look that way.

So in Python, if you rename the file [Link], its module name also changes, your import
statements would need to change, and your code that refers to functions or attributes inside
that namespace would also need to change. For example, if you rename [Link] to
[Link]:
• The module name changes from math to mymath
• All import math statements must change to import mymath
• All code that referred to [Link] must now use [Link]

4.8 Scope and lookup rules

The scope of an identifier (names) is the region of program code in which the identifier can
be accessed, or used.
There are three important scopes in Python.
• Local scope refers to names created inside a function (parameters or variables
assigned in the function body). They exist only while the function runs and are
invisible outside.
• Global scope refers to all the names declared within the current module or file. It is
defined at the top level of a module (outside any function/class). They are visible
everywhere in that file after their definition.
• Built-in scope refers to all names built into Python itself like range, len, min, print. It
can be used without importing anything.

When the same name appears in more than one scope, Python follows search order called
lookup rules. It is often called as LEGB : Local, Enclosing, Global and Built-in. The priority
is in this order: Local Global Built-in.

A new function called range is defined at the top level, so this name goes into the global
scope.
The parameter n is a local name that will exist only when the function runs.
When print(range(10)) runs, based on the lookup rules, our own range function, not the built-
in one, is called, because our function range is in the global namespace, which takes
precedence over the built-in names.

So although names likes range and min are built-in, they can be “hidden” from your use if
you choose to define your own variables or functions that reuse those names.

Another example:

This prints 17 10 3. 𝑚 = 3 and 𝑛 = 10 are defined at the top level. So, they are global
variables or global scope. Inside the function, new variables called 𝑛 and 𝑚 are created just
for the duration of the execution of 𝑓. So, they are called local variables or local scope. This
local 𝑚 and 𝑛 is different from global 𝑚 and 𝑛.

def f(n):
m=7
return 2*n+m
Inside the function, 𝑚 & 𝑛 are local to 𝑓. So 𝑚 & 𝑛 both refer to these local variables and not
the global scope.

When f(5) is called:


• The parameter 𝑛 inside 𝑓 becomes 5. Also we know that inside the function, 𝑚 = 7.
Therefore, we get the answer as 2 ∗ 𝑛 + 𝑚 = 2 ∗ 5 + 7 = 17.
Now the execution of 𝑓 ends, the local variables 𝑛 & 𝑚 disappear. They do not affect global
𝑛 & 𝑚.
For the final 𝑝𝑟𝑖𝑛𝑡(𝑓(5), 𝑛, 𝑚)
• The first value is computed using the local names inside 𝑓.
• The second and third values, 𝑛 & 𝑚 are looked up in the global scope. So, the
corresponding 𝑛 & 𝑚 values are 10 & 3 respectively.
• The local 𝑛 and 𝑚 used inside 𝑓 no longer exist after the function finishes.

4.9 Attributes and the dot operator

Variables defined inside a module are called attributes of the module. For example, in math
module, sqrt, cos, sin etc are attributes of math. To access an attribute, Python uses the dot
operator (.).
Example: The question attribute of module1 and module2 is accessed using [Link]
and [Link].

Modules contain functions as well as attributes, and the dot operator is used to access them in
the same way. seqtools.remove_at refers to the remove_at function in the seqtools module.
When we use a dotted name, we often refer to it as a fully qualified name, because we’re
saying exactly which question attribute we mean.

4.10 Three import statement variants

Here are three different ways to import names into the current namespace, and to use them:

1.

If we want to access one of the functions in the module, we need to use the dot notation to get
to it.

2.

The names are added directly to the current namespace, and can be used without qualification.
The name math is not itself imported, so trying to use the qualified form [Link] would give
an error.

3.
Of these three, the first method is generally preferred, even though it means a little more
typing each time.

Giving shorter names

Example:

This gives an error. Here we imported math, but we imported it into the local namespace of
area. So the name is usable within the function body, but not in the enclosing script, because
it is not in the global namespace.

4.11 Mutable versus immutable and aliasing

Some datatypes in Python are mutable. This means their contents can be changed after they
have been created. Lists and dictionaries are good examples of mutable datatypes.

Tuples and strings are examples of immutable datatypes, their contents can not be changed
after they have been created:

Mutability is usually useful, but it may lead to something called aliasing. In this case, two
variables refer to the same object and mutating one will also change the other:

This happens, because both list_one and list_two refer to the same memory address
containing the actual list. You can check this using the built-in function id:
You can escape this problem by making a copy of the list:

Classes and Objects – The Basics


4.12 Object-oriented programming (OOP)

Object-oriented programming has its roots in the 1960s, but it wasn’t until the mid 1980s that
it became the main programming paradigm used in the creation of new software. It was
developed as a way to handle the rapidly increasing size and complexity of software systems,
and to make it easier to modify these large and complex systems over time.

Python is called an object-oriented language because it lets you define your own classes an
create objects from them, and it already provides many built-in classes (like list, dict, str).
In OOP, a program is viewed as a collection of interacting objects rather than just a sequence
of functions and global variables.

Up to now, most of the programs we have been writing use a procedural programming
paradigm. In procedural programming the focus is on writing functions or procedures which
operate on data. In object-oriented programming the focus is on the creation of objects which
contain both data and functionality together.

Classes and objects


A class is a kind of blueprint describing what data an object will store (its attributes) and what
it can do (its methods).
An object is a specific “instance” built from a class.

4.13 User-defined compound data types

We have already seen classes like str, int and float. We are now ready to create our own user-
defined class: the Point.

Consider the concept of a mathematical point. In two dimensions, a point is two numbers
(coordinates) that are treated collectively as a single object. Points are often written in between
parentheses with a comma separating the coordinates. For example, (0, 0) represents the origin,
and (𝑥, 𝑦) represents the point x units to the right and y units up from the origin.

Some of the typical operations that one associates with points might be calculating the distance
of a point from the origin, or from another point, or finding a midpoint of two points, or asking
if a point falls within a given rectangle or circle.

A natural way to represent a point in Python is with two numeric values. A tuple is used to
group these two values into a compound object.
An alternative way is to define a new class. We will want our points to each have an 𝑥 and a 𝑦
attribute, so our class definition looks like this:

Class definitions can appear anywhere in a program, but they are usually near the beginning
(after the import statements). Some programmers and languages prefer to put every class in a
module of its own — we won’t do that here. The syntax rules for a class definition are the
same as for other compound statements. There is a header which begins with the keyword,
class, followed by the name of the class, and ending with a colon. Indentation levels tell us
where the class ends.

If the first line after the class header is a string, it becomes the docstring of the class, and will
be recognized by various tools.

Every class should have a method with the special name __init__. This initializer method is
automatically called whenever a new instance of Point is created. Here, setting both
coordinates 𝑥 and 𝑦 to 0, so new points start at the origin.

The self parameter (we could choose any other name, but self is the convention) is
automatically set to reference the newly created object that needs to be initialized.

So let’s use our new Point class now:


Example:

The answer will be printed as 0 0 0 0. This is because during the initialization of the objects,
we created two attributes called 𝑥 and 𝑦 for each, and gave them both the value 0.

The variables p and q are assigned references to two new Point objects. A function like Point
that creates a new object instance is called a constructor, and every class automatically
provides a constructor function which is named the same as the class. The combined process
of creating a concrete object from a class and getting it initialized is called instantiation. For
example, here, p = Point() and q = Point() are instantiating the class Point.

4.14 Attributes

Like real world objects, object instances have both attributes and methods. An attribute is a
variable that “lives inside” an object and is stored in that object’s namespace rather than the
global namespace. For a Point instance p, the attributes are the coordinates. So, p.x and p.y
are two separate numbers stored as part of that particular point. We can modify the attributes
in an instance using dot notation:
The expression p.x means, “Go to the object p refers to and get the value of x”.

We can use dot notation as part of any expression, so the following statements are legal:

4.15 Improving our initializer

To create a point at position (7, 6) currently needs three lines of code:

We can make our class constructor more general by placing extra parameters into the
__init__ method, as shown in this example:

The x and y parameters here are both optional. If the caller does not supply arguments, they’ll
get the default values of 0. Here is our improved class in action:

4.16 Adding other methods to our class

The key advantage of using a class like Point rather than a simple tuple (6, 7) now becomes
apparent. We can add methods to the Point class that are sensible operations for points, but
which may not be appropriate for other tuples like (25, 12). In tuples, this could represent,
say, a day and a month, e.g. Christmas day. So being able to calculate the distance from the
origin is sensible for points, but not for (day, month) data. For (day, month) data, we’d like
different operations, perhaps to find what day of the week it will fall on in 2020.
Now, let us add a method called distance_from_origin, to see better how methods work:

Example:
Let’s create a few point instances, look at their attributes, and call our new method on them.

When defining a method, the first parameter refers to the instance being manipulated. As
already noted, it is customary to name this parameter self.
4.17 Instances as arguments and parameters

We can pass an object as an argument in the usual way. Here is a simple function involving
our new Point objects:

Parameter – the name in the function definition. Eg: pt is the parameter here.
Argument – the actual value you pass when calling the function. Eg: p is the argument here.

Explanation
Here pt is a parameter that is expected to be a Point object and inside the function the code
uses pt.x and pt.y to access that object’s attributes and print them in coordinate form.

When we later call print_point(p), where p is a Point instance, Python binds the parameter
name pt to the same object that p refers to, and then executes the body of the function.

4.18 Converting an instance to a string

It converts an object (a Point instance) into a string. First with a normal method like to_string,
then with the special method __str__ that Python uses automatically when you print an object
or call str() on it.

A custom method like to_string

This is just an ordinary instance method. It builds and returns a string that shows the point’s
coordinates in a readable form. We can then write p = Point(3, 4) and print([Link] string()),
which prints (3, 4) because to_string returns that text and print displays it.

The str method or default string conversion

Python already has a built-in way to convert anything to a string:


But without customization, Python gives the output as:

Python falls back to a default implementation inherited from object, which produces that
memory-address output.

To solve this issue and get the output exactly as we want, then do the following:

4.19 Instances as return values

Functions and methods can return instances. For example, given two Point objects, find their
midpoint. First we will write this as a regular function:

Suppose we have a point object, and wish to find the midpoint halfway between
it and some other target point.
While this example assigns each point to a variable, this need not be done. Just as function
calls are composable, method calls and object instantiation are also composable, leading to
this alternative that uses no variables:

4.20 A change of perspective

The original syntax for a function call, print_time(current_time), suggests that the function is
the active agent. It says something like, “Hey, print_time! Here’s an object for you to print.”
In object-oriented programming, the objects are considered the active agents. An invocation
like current_time. print_time() says “Hey current_time! Please print yourself!”

This change in perspective might be more polite, but it may not initially be obvious that it is
useful. But sometimes shifting responsibility from the functions onto the objects makes it
possible to write more versatile functions, and makes it easier to maintain and reuse code.
The most important advantage of the object-oriented style is that it fits our mental chunking
and real-life experience more accurately.

You might also like