Unit-5-Python Programming
Unit-5-Python Programming
Unit – V
Classes and Functions: Time, Pure functions, Modifiers, Prototyping versus Planning
Classes and Methods: Object oriented features, Printing objects, The init method, The
__str__method, Operator overloading, Type-based Dispatch, Polymorphism, Interface and
Implementation
Inheritance: Card objects, Class attributes, Comparing cards, decks, Printing the Deck,
Add Remove shuffle and sort, Inheritance, Class diagrams, Data encapsulation.
------------------------------------------------------------------------------------------------------------
5.1. Classes and Functions:
5.1.1. Time:
A programmer-defined type, we’ll define a class called Time that records the time of day.
class Time:
#create a new Time object and assign attributes for hours, minutes, and seconds:
time = Time()
time.hour = 11
time.minute = 59
time.second = 30
# to printing values
print(time)
print(time.hour)
print(time.minute)
print(time.second)
Page 1
UNIT-5
A function that does not modify any of the objects it receives as arguments. Most
pure functions are fruitful.
class Time:
def print_time(time):
sum = Time()
return sum
start = Time()
start.hour = 9
start.minute = 45
start.second = 0
duration = Time()
duration.hour = 1
duration.minute = 35
duration.second = 0
done=add_time(start, duration)
print_time(done)
Output:
10:80:00
The function creates a new Time object, initializes its attributes, and returns a
reference to the new object. This is called a pure function because it does not modify
any of the objects passed to it as arguments and it has no effect, like displaying a value
or getting user input, other than returning a value.
Page 2
UNIT-5
5.1.3. Modifiers
A function that changes one or more of the objects it receives as arguments. Most
modifiers are void; that is, they return None.
class Time:
"""Represents the time of day.
attributes: hour, minute, second """
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
def increment(time,seconds):
time.second += seconds
if time.second >= 60:
time.second -= 60
time.minute += 1
if time.minute >= 60:
time.minute -= 60
time.hour += 1
time = Time()
time.hour = 10
time.minute = 59
time.second = 30
print_time(time) #10:59:30
done=increment(time,80)
print_time(done) #11:00:50
Note:
Programs that use pure functions are faster to develop and less error-prone than
programs that use modifiers.
Modifiers are convenient at times, and functional programs tend to be less
efficient.
In general, writing pure functions is reasonable and resort to modifiers only if there
is a compelling advantage. This approach might be called a functional
programming style.
Or
Functional programming style: A style of program design in which the majority of
functions are pure.
Page 3
UNIT-5
A development plan that involves writing a rough draft of a program, testing, and
correcting errors as they are found.
This approach can be effective, especially if you don’t yet have a deep
understanding of the problem.
But incremental corrections can generate code that is unnecessarily complicated
(since it deals with many special cases) and unreliable (since it is hard to know if
you have found all the errors).
Designed development:
A development plan that involves high-level insight into the problem and more
planning than incremental development or prototype development.
class Time:
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
def time_to_int(time):
minutes = time.hour * 60 + time.minute
seconds = minutes * 60 + time.second
return seconds
def int_to_time(seconds):
time = Time()
minutes, time.second = divmod(seconds, 60)
time.hour, time.minute = divmod(minutes, 60)
return time
def add_time(t1, t2):
seconds = time_to_int(t1) + time_to_int(t2)
return int_to_time(seconds)
start = Time()
start.hour = 9
start.minute = 45
start.second = 0
duration = Time()
duration.hour = 1
duration.minute = 35
duration.second = 0
done=add_time(start, duration)
print_time(done) #11:20:00
Page 4
UNIT-5
Characteristics:
class Time:
#To call this function, you have to pass a Time object as an argument:
>>> start.hour = 9
>>> start.minute = 45
>>> start.second = 00
>>> print_time(start)
Output: 09:45:00
-------------------------------------------------------------------------------
class Time:
time = Time()
time.hour = 11
time.minute = 59
time.second = 30
Note:
method: A function that is defined inside a class definition and is invoked on instances of
that class.
Page 5
UNIT-5
>>>time.print_time() #11:59:30
In this use of dot notation, Time is the name of the class, and print_time is the
name of the method. start is passed as a parameter.
>>>start.print_time() #09:45:00
In this use of dot notation, print_time is the name of the method (again), and
start is the object the method is invoked on, which is called the subject.
• Inside the method, the subject is assigned to the first parameter, so in this case
start is assigned to time.
• By convention, the first parameter of a method is called self, so it would be more
common to write print_time like this:
class Time:
def print_time(self):
print('%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second))
• The init method (short for “initialization”) is a special method that gets invoked when
an object is instantiated.
• Its full name is __init__ (two underscore characters, followed by init, and then two
more underscores).
class Time:
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
Page 6
UNIT-5
The parameters are optional, so if you call Time with no arguments, you get the default
values:
class Time:
def __str__(self):
return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)
Note:
To a new class, always start by writing __init__, which makes it easier to
instantiate objects, and __str__, which is useful for debugging.
Page 7
UNIT-5
Python operators work for built-in classes. But the same operator behaves
differently with different types. For example, the + operator will perform arithmetic
addition on two numbers, merge two lists, or concatenate two strings.
This feature in Python that allows the same operator to have different meaning
according to the context is called operator overloading.
class Time:
def __str__(self):
return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)
def int_to_time(seconds):
time = Time()
minutes, time.second = divmod(seconds, 60)
time.hour, time.minute = divmod(minutes, 60)
return time
def time_to_int(time):
minutes = time.hour * 60 + time.minute
seconds = minutes * 60 + time.second
return seconds
Page 8
UNIT-5
A programming pattern that checks the type of an operand and invokes different
functions for different types.
if isinstance(other, Time):
return self.add_time(other)
else:
return self.increment(other)
return int_to_time(seconds)
seconds += self.time_to_int()
return int_to_time(seconds)
The built-in function isinstance takes a value and a class object, and returns True
ifthe value is an instance of the class.
Here are examples that use the + operator with different types:
Page 9
UNIT-5
This method is invoked when a Time object appears on the right side of the +
operator.
Here’s the definition:
return self.__add__(other)
10:07:17
5.2.7: Polymorphism:
Pertaining to a function that can work with more than one type.
Example:
def histogram(s):
d = dict()
for c in s:
if c not in d:
d[c] = 1
else:
d[c] = d[c]+1
return d
This function also works for lists, tuples, and even dictionaries, as long as
the elements of s are hashable, so they can be used as keys in d:
>>> histogram(t)
Since Time objects provide an add method, they work with sum:
Page 10
UNIT-5
Inheritance
Inheritance is the ability to define a new class that is a modified version of an
existing class.
5.3.1. Card Objects:
There are 52 cards in a deck, each of which belongs to 1 of 4 suits and 1 of 13
ranks.
The suits are Spades, Hearts, Diamonds, and Clubs (in descending order in bridge).
The ranks are Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, and King.
Depending on the game, an Ace may be higher than King or lower than 2.
To define a new object to represent a playing card, attributes should be: rank and
suit. To use integers to encode the ranks and suits. In this context, “encode” means to
define a mapping between numbers and suits, or between numbers and ranks.
For example, this table shows the suits and the corresponding integer codes:
Spades ↦ 3
Hearts ↦ 2
Diamonds ↦ 1
Clubs ↦ 0
This code makes it easy to compare cards; because higher suits map to higher
numbers, the mapping for ranks is fairly obvious; each of the numerical ranks maps to the
corresponding integer, and for face cards:
Page 11
UNIT-5
Jack ↦ 11
Queen ↦ 12
King ↦ 13
class Card:
"""Represents a standard playing card."""
suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])
card1 = Card(2, 11)
print(card1)
Above diagram of the Card class object and one Card instance. Card is a class
object; its type is type. card1 is an instance of Card, so its type is Card.
Page 12
UNIT-5
5.3.4. Decks:
A deck is made up of cards, it is natural for each Deck to contain a list of cards as an
attribute.
Output:
Ace of Clubs
2 of Clubs
3 of Clubs
...
Jack of Spades
Queen of Spades
King of Spades
Page 13
UNIT-5
5.3.7. Inheritance:
The ability to define a new class that is a modified version of a previously defined class.
A hand is similar to a deck: both are made up of a collection of cards, and both
require operations like adding and removing cards. A hand is also different from a
deck.
This relationship between classes—similar, but different—lends itself to
inheritance.
To define a new class that inherits from an existing class, put the name of the existing
class in parentheses:
class Hand(Deck):
"""Represents a hand of playing cards."""
Hand inherits __init__ from Deck, instead of populating the hand with 52 new
cards, the init method for Handsshould initialize cards with an empty list.
To provide an init method in the Hand class, it overrides the one in the Deck class:
# inside class Hand:
class Hand(Deck):
def __init__(self, label=''):
Page 14
UNIT-5
self.cards = []
self.label = label
create a Hand, Python invokes this init method, not the one in Deck.
>>> hand = Hand('new hand')
>>> hand.cards
[]
>>> hand.label
'new hand'
The other methods are inherited from Deck,use pop_card and add_card to deal a card:
>>> deck = Deck()
>>> card = deck.pop_card()
>>> hand.add_card(card)
>>> print(hand)
Output: King of Spades
Class Diagrams:
Stack diagrams, which show the state of a program, and
Object diagrams, which show the attributes of an object and their values.
These diagrams represent a snapshot in the execution of a program, so they change
as the program runs.
A class diagram is a more abstract representation of the structure of a program.
Instead of showing individual objects, it shows classes and the relationships
between them.
There are several kinds of relationship between classes:
Objects in one class might contain references to objects in another class. For
example, each Rectangle contains a reference to a Point, and each Deck contains
references to many Cards. This kind of relationship is called HAS-A, as in, “a
Rectangle has a Point.”
One class might inherit from another. This relationship is called IS-A, as in, “a
Hand is a kind of a Deck.”
One class might depend on another in the sense that objects in one class take
objects in the second class as parameters, or use objects in the second class as
part of a computation. This kind of relationship is called a dependency.
A class diagram is a graphical representation of these relationships.
The arrow with a hollow triangle head represents an IS-A relationship; in this case
it indicates that Hand inherits from Deck.
Page 15
UNIT-5
The standard arrowhead represents a HAS-A relationship; in this case a Deck has
ref‐ erences to Card objects.
The star (*) near the arrowhead is a multiplicity; it indicates how many Cards a
Deck has. A multiplicity can be a simple number like 52, a range like 5..7 or a star,
which indicates that a Deck can have any number of Cards.
Data Encapsulation:
A program development plan that involves a prototype using global variables and a
final version that makes the global variables into instance attributes.
A program uses two global variables—suffix_map and prefix—that are read and written
from several functions.
suffix_map = {}
prefix = ()
Because these variables are global, we can only run one analysis at a time.
If read two texts, their prefixes and suffixes would be added to the same data
structures .
To run multiple analyses, and keep them separate, we can encapsulate the state
of each analysis in an object.
class Markov:
def __init__(self):
self.suffix_map = {}
self.prefix = ()
Note:
1. IS-A relationship: A relationship between a child class and its parent class.
2. HAS-A relationship: A relationship between two classes where instances of one
class contain references to instances of the other.
3. Dependency: A relationship between two classes where instances of one class use
instances of the other class, but do not store them as attributes.
4. Class diagram: A diagram that shows the classes in a program and the relationships
between them.
5. Multiplicity: A notation in a class diagram that shows, for a HAS-A relationship, how
many references there are to instances of another class.
Page 16
UNIT-5
The Goodies
1. for example:
if x > 0:
y = math.log(x)
else:
y = float('nan')
This statement checks whether x is positive.
If so, it computes math.log.
If not, math.log would raise a ValueError.
To avoid stopping the program, generate a “NaN”, which is a special floating-point
value that represents “Not a Number”.
To write above statement more concisely using a conditional expression:
y = math.log(x) if x > 0 else float('nan')
2. Recursive functions can sometimes be rewritten using conditional expressions. For
example, here is a recursive version of factorial:
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
def factorial(n):
return 1 if n == 0 else n * factorial(n-1)
5.4.2.List Comprehensions:
An expression with a for loop in square brackets that yields a new list.
1. For example, this function takes a list of strings, maps the string method capitalize to
the elements, and returns a new list of strings:
def capitalize_all(t):
res = []
for s in t:
res.append(s.capitalize())
return res
Page 17
UNIT-5
The bracket operators indicate that are constructing a new list. The expression
inside the brackets specifies the elements of the list, and the for clause indicates what
sequence we are traversing.
The result is a generator object. It does not compute the values all at once; it
waits to be asked.
The built-in function next gets the next value from the generator:
>>> next(g)
0
>>> next(g)
1
To go the end of the sequence, next raises a StopIteration exception.
for loop to iterate through the values:
>>> for val in g:
... print(val)
4
9
16
Once the generator is exhausted, it continues to raise StopException:
>>> next(g) StopIteration
StopIteration
Generator expressions are often used with functions like sum, max, and min:
>>> sum(x**2 for x in range(5))
30
Page 18
UNIT-5
Page 19
UNIT-5
5.4.6. Counters:
A Counter is like a set, except that if an element appears more than once, the
Counter keeps track of how many times it appears.
With the mathematical idea of a multiset, a Counter is a natural way to represent
a multiset.
Counter is defined in a standard module called collections, so have to import it.
>>> from collections import Counter
>>> count = Counter('parrot')
>>> count
O/P: Counter({'r': 2, 't': 1, 'o': 1, 'p': 1, 'a': 1})
Counters behave like dictionaries in many ways; they map from each key to the
number of times it appears. As in dictionaries, the keys have to be hashable.
Unlike dictionaries, Counters don’t raise an exception if you access an element
that doesn’t appear. Instead, they return 0:
>>> count['d']
0
Use Counters to rewrite is_anagram
def is_anagram(word1, word2):
return Counter(word1) == Counter(word2)
If two words are anagrams, they contain the same letters with the same counts, so
their Counters are equivalent.
Counters provide methods and operators to perform set-like operations, including
addition, subtraction, union and intersection.
5.4.7. Defaultdict:
The collections module also provides defaultdict, which is like a dictionary except
that if you access a key that doesn’t exist, it can generate a new value on the fly.
To create a defaultdict, provide a function that’s used to create new values.
A function used to create objects is sometimes called a factory.
The built-in functions that create lists, sets, and other types can be used as
factories:
>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> t = d['new key']
>>> t
[]
>>> t.append('new value')
Page 20
UNIT-5
>>> d
defaultdict(<class 'list'>, {'new key': ['new value']})
5.4.8.Named Tuples:
Named tuples provide a quick way to define simple classes.
The drawback is that sim‐ ple classes don’t always stay simple.
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return '(%g, %g)' % (self.x, self.y)
by using Named tuple :
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
>>> Point
<class '__main__.Point'>
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> p.x, p.y
(1, 2)
5.4.9. Gathering Keyword Args:
In “Variable-Length Argument Tuples” gathers its arguments into a tuple:
>>>def printall(*args):
print(args)
>>> printall(1, 2.0, '3')
(1, 2.0, '3')
But the * operator doesn’t gather keyword arguments:
>>> printall(1, 2.0, third='3')
TypeError: printall() got an unexpected keyword argument 'third'
To gather keyword arguments, use the ** operator:
>>>def printall(*args, **kwargs):
print(args, kwargs)
>>> printall(1, 2.0, third='3') #(1, 2.0) {'third': '3'}
The result is a dictionary that maps keywords to values.
Page 21
UNIT-5
a dictionary of keywords and values, to use the scatter operator, **, to call a
function:
>>> d = dict(x=1, y=2)
>>> Point (**d)
Output: Point(x=1, y=2)
Without the scatter operator,
>>> d = dict(x=1, y=2)
>>> Point(d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __new__() missing 1 required positional argument: 'y'
Page 22