0% found this document useful (0 votes)
5 views113 pages

13 Classes

Uploaded by

tej desai
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)
5 views113 pages

13 Classes

Uploaded by

tej desai
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
You are on page 1/ 113

Department of Mathematical and Physical Sciences

Concordia University of Edmonton

Creating Classes, Modules and Packages


Part 1
Baidya Nath Saha

Difference between Python Class , Module


and package
Class
 The concept of class, which occurs in many programming
languages, is fundamental to object-oriented programming
and is easy to understand.
 Class is used to abstract the common characteristics of
different objects, classify objects that are similar to each
other into the same concept according to the similarity
principle.
 Class encapsulates data and operations for future reuse
class TestClass (Object):
def func_1(self):
......
def func_2(self):
2
Baidya Nath Saha

Difference between Python Class ,


Module and package
Module
 In Python a .py file is considered as a module.
 After a script file is created, certain functions and variables are
defined.
 You can reuse these functions and variables by importing the module
in other files that require these functions or variables.
 The module name is the file name minus .py suffix.

3
Baidya Nath Saha

Module
 Modules can also be simply divided into built-in modules and
custom modules.
 Built-in modules are built into Python, such as sys, os and
other basic modules. Built-in function dir (module_name)
 Let you see what data the module defines (include variable
name, module name, function name, etc.).
 dir () function will return all currently defined names when no
arguments are present.

4
Baidya Nath Saha

Module Search Path

 When importing a module, the interpreter looks for the module


in the current package.
 If not find, it will find it in the built-in module.
 Python has many modules (such as re) that perform a specific
set of actions.
 You can call the functions of this module and get results, and the
module as a whole has an idea behind it (in this case, dealing
with regular expressions).
 If not find also, then find the corresponding module file
(module_name.py) according to the path in sys.path value.

5
Baidya Nath Saha

Package

 A package is a hierarchical file directory structure. It defines


a python application execution environment consisting of n
modules or n subpackages. A package is a directory
containing the _init_.py file, which must contain the _init_.py
file and other modules or subpackages.
 Package can import other packages using import command,
or import some of the modules in other package use from +
import command. import django.http
from django.conf.urls import url
 The first file in the package directory should be _init_.py .
 If there is also _init_.py in the subdirectory then it is a sub package
of the current package.

6
Difference between Class and Module

 Python has many modules (such as re) that perform a


specific set of actions.
 A python module is nothing but a package to encapsulate
reusable code. Modules usually, but not always, reside in a
folder with a _init_.py file inside of it.
 Modules can contain functions but also classes. Modules
are imported using the import keyword.

7
Difference between Class and Module

 Python has a way to put definitions in a file and use them in


a script or in an interactive instance of the interpreter. Such
a file is called a module; definitions from a module can be
imported into other modules or into the main module.
 Classes seem to do almost the exact same thing, but they
also seem to use properties quite a bit more than modules.
 Classes, in the other hand, can be defined in your main
application code or inside modules imported by your
application. Classes are the code of Object Oriented
Programming and can contain properties and methods.

8
Difference between Class and Module

 If it's a bunch of pure functions, e.g. re I put them in a


module. I use a class when I know there's state I want to
keep with that code (e.g. a file that gets read in once, but
whose data is used by several of the functions).
 You can create multiple instances of a class, but you cannot
create instances of a module.
 You could compare modules to static classes or singletons.
 It's generally good form to just keep static functions at the
module level. Classes imply instances with their own state.

9
Module and Packages

 Functions, modules and packages are all constructs in Python


that promote code modularization.
 Python modules and Python packages, are two mechanisms that
facilitate modular programming.
 Modular programming refers to the process of breaking a large,
unwieldy programming task into separate, smaller, more
manageable subtasks or modules. Individual modules can then be
cobbled together like building blocks to create a larger
application.

10
Advantage of Modular Programming

There are several advantages to modularizing code in a large application:


Simplicity: Rather than focusing on the entire problem at hand, a module typically
focuses on one relatively small portion of the problem. If you’re working on a
single module, you’ll have a smaller problem domain to wrap your head around.
This makes development easier and less error-prone.
Maintainability: Modules are typically designed so that they enforce logical
boundaries between different problem domains. If modules are written in a way
that minimizes interdependency, there is decreased likelihood that modifications to
a single module will have an impact on other parts of the program. (You may even
be able to make changes to a module without having any knowledge of the
application outside that module.) This makes it more viable for a team of many
programmers to work collaboratively on a large application.

11
Advantage of Modular Programming

Reusability: Functionality defined in a single module can be


easily reused (through an appropriately defined interface) by
other parts of the application. This eliminates the need to
recreate duplicate code.
Scoping: Modules typically define a separate namespace,
which helps avoid collisions between identifiers in different
areas of a program. (One of the tenets in the Zen of
Python is Namespaces are one honking great idea—let’s do
more of those!)
Functions, modules and packages are all constructs in
Python that promote code modularization.

12
Baidya Nath Saha

Class

 Classes are an important part of the object-oriented nature


of python.
 Classes are constructors which allow the programmer to
structure their code in a particular way.
 They allow for cleaner code and allow for certain features
unique to class, such as inheritance from base classes
(which will be covered later on).

Constructor is a subroutine designed to create an


object in a particular class.

13
Baidya Nath Saha

Defining a Class

 In order to create a class you must define it with the constructor


class followed by the desired name (be careful not use any
reserved words) and then brackets, usually containing the needed
parameters.
class example():
def __init__(self,name):
self.name = name
pass
 Within the class it is a good idea to initialize the class using the
def __init__() method. Within the initialization you can establish
parameters, such as self, that will be used within the class and any
other classes that stem (are children of) the base class.
14
Defining a Class

 Self here refers to the class itself. If you notice the self.name
method, this is a key feature of classes. You must create an
object of the class and then use .function(), calling it to the
created class object.
 Any functions defined within a class are considered to be art of
the local scope and cannot be used outside of the class unless in
relation to a class object such as in the following example
where addition is an object of the class Addition and therefore
can use the add() function of the class.

15
Creating class instances

 Classes and objects go hand in hand in Python.


 In fact to even use a class in a program you must create an object
instance of it.
 This is the basis of object oriented programming (OOP).
 Creating an object instance is exactly like creating a variable,
except that the value assigned to it is the class. This is how you
initialize the class as an object. class CreateClass():
def function(self):
...do something...
object = CreateClass()
object.function()
16
Baidya Nath Saha

Initializing Objects

 Some classes will have __init__ in the class. This will


initialize the object created, allowing you to utilize the class.
It is the constructor method of the class.
 Though this is not strictly necessary, it is good coding
practice when writing more complex classes. It is only
necessary if you need to specify a parameter when initializing
the class itself as an object. It is used in class inheritance. For
simple classes you can forgo putting __init__ in.

17
Baidya Nath Saha

Class Variables

 Class variables are variables that are unique to each class


and only exist within that class.
 You can think of them as local variables.
 In order for a class variable to be a class variable it must be
declared within the class.

18
Class Variables

class ExampleClass():
 Notice how even though greeting exists in
both the class and on a global scope, the
def greeting(self):
two do not interact with one another nor do
greeting = 'hello'
they change the value of each other.
return(greeting)
example = ExampleClass()
 Whenever you call the class object, the
value of greeting will always be the value
greeting = 'yo'
given inside the class because it is counted
as a local variable which has a higher
print(greeting)
precedence over the global variable
print(example.greeting())
greeting. Though once executed greeting
print(greeting)
will not have changed value outside of the
class.

19
Accessing a function within a class

class ExampleClass():  This idea of variables being


def hello(self): specific to classes only is also
print("Hello!") shared with functions in much
the same way.
example = ExampleClass()
example.hello()

20
Functions within and outside of the class

class ExampleClass():  Classes, like all functions, allow for


def greeting(self):
print("Hello!")
the use of outside input, making
def greeting(x): them diverse and flexible for the
print(x) programmer to use.
example = ExampleClass()
greet = 'yo'
greeting(greet)
example.greeting()
greeting(greet)

21
Class using outside input

class ExampleClass():
def hello_name(self, name):
print("Hello " + name + "!")

example = ExampleClass()
your_name = input()
example.hello_name(your_name)

22
Create a class with methods

 _init_
 The __init__ method is known as the constructor and it is called
automatically when an instance of an object is created. In order to
use the values defined in __init__ you must initialize them with
self.parameter in __init__ and then refer to them as such
throughout the class.
class example():
def __init__(self, x, y): def mult (self, w):
self.x = x return (self.y * w) - self.x
self.y = y e = example(3,7)
print(e.sub())
def sub(self):
return self.y - self.x
24
Assign object

 Assigning an object is done the same way as assigning a variable.


It follows the structure:
object = class()

 This allows you to use the class in your code as demonstrated in


the above examples.

25
Read object variable data using accessors

 Accessor methods are used to get information from the object


instance variables.
 They are simply methods that get information from the object
instance and return it. Usually mutators and accessors are used
together. # Accessor Methods
class triangle(): def get_height(self):
def __init__(self, height, base, r_side, l_side): return self.height
self.height = height def get_area(self):
self.base = base area = (self.height * self.base)/2
self.r_side = r_side return area
self.l_side = l_side equilateral = triangle(6,3,3,3)
print( equilateral.get_height() )
print( equilateral.get_area() )
26
Modify object variable data using mutators

 Mutators are used to modify the object variable data (change a


current value of the object).
class triangle():
def __init__(self, height, base, r_side, l_side):
self.height = height
self.base = base
self.r_side = r_side
self.l_side = l_side

27
Baidya Nath Saha

Modify object variable data using mutators

# Accessor Methods equilateral = triangle(6,3,3,3)


def get_height(self): equilateral.increase_height(5)
return self.height print( equilateral.get_height() )
def get_area(self): print( equilateral.get_area() )
area = (self.height * self.base)/2
return area

# Mutator Methods
def increase_height(self, factor):
self.height = self.height * factor

28
Input Object

 Some classes require input in order to work that is not part of


the parameters implemented. You can ask for input within the
class itself and avoid using a global variable for input.

29
Output object information

 Output objects are pretty simple in concept.


 They output a value or result as shown in some examples with the
print statements.
# Input and output objects
class example():
def name_please (self):
name = input("What is your name? ")
print("Welcome to this example class
{}!".format(name))
e = example()
e.name_please()
30
Department of Mathematical and Physical Sciences
Concordia University of Edmonton

Creating Classes, Modules and Packages


Part 2
Namespace in Python
Namespaces are one honking great idea -- let's do more of those!

 Name (also called identifier) is simply a name given to objects.


Everything in Python is an object. Name is a way to access the
underlying object.
 Namespace is a collection of names.
 In Python, you can imagine a namespace as a mapping of every
name, you have defined, to corresponding objects.
 Different namespaces can co-exist at a given time but are
completely isolated.
 A namespace containing all the built-in names is created when we
start the Python interpreter and exists as long we don't exit.
https://www.programiz.com/python-programming/namespace
2
Namespace in Python
Namespaces are one honking great idea -- let's do more of those!

 This is the reason that built-in functions like id(), print() etc. are
always available to us from any part of the program.
 Each module creates its own global namespace.
 These different namespaces are isolated. Hence, the same name
that may exist in different modules do not collide.
 Modules can have various functions and classes. A local
namespace is created when a function is called, which has all the
names defined in it. Similar, is the case with class. Following
diagram may help to clarify this concept.

https://www.programiz.com/python-programming/namespace
3
Baidya Nath Saha

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

https://www.programiz.com/python-programming/namespace
4
Design problem

 It is desired to design an object-oriented employee record system for a company.


Each employee has a name, unique id and salary. Employees belong to different
categories and their salary is determined by their category. The functions to get
Name, getld and compute salary are required. Given the class hierarchy below,
possible locations for these functions are: i. getld is implemented in the
superclass ii. getld is implemented in the subclass iii. getName is an abstract
function in the superclass iv. getName is implemented in the superclass v.
getName is implemented in the subclass vi. getSalary is an abstract function in
the superclass vii. getSalary is implemented in the superclass viii. getSalary is
implemented in the subclass

5
Create a class with methods

Choose the best design


A. (i), (iv), (vi), (viii)
B. (i), (iv), (vii)
C. (i), (iii), (v), (vi), (viii)
D. (ii), (v), (viii)

6
Difference between Abstraction
and Encapsulation

Hide
details Hide details
in design in
level implementation
level

7
Class

 Classes provide a means of bundling data and


functionality together.
 Creating a new class creates a new type of object,
allowing new instances of that type to be made.
 Each class instance can have attributes attached to it for
maintaining its state.
 Class instances can also have methods (defined by its
class) for modifying its state.

8
Class

 Compared with other programming languages, Python’s class


mechanism adds classes with a minimum of new syntax and
semantics.
 It is a mixture of the class mechanisms found in C++ and Modula-3.
 Python classes provide all the standard features of Object Oriented
Programming: the class inheritance mechanism allows multiple base
classes, a derived class can override any methods of its base class or
classes, and a method can call the method of a base class with the
same name.
 Objects can contain arbitrary amounts and kinds of data. As is true
for modules, classes partake of the dynamic nature of Python: they
are created at runtime, and can be modified further after creation.

9
Public, Private and Protected Variables

 Classical object-oriented languages, such as C++ and Java, control


the access to class resources by public, private and protected
keywords. Private members of a class are denied access from the
environment outside the class. They can be handled only from
within the class.
 Public members (generally methods declared in a class) are
accessible from outside the class. The object of the same class is
required to invoke a public method. This arrangement of private
instance variables and public methods ensures the principle of data
encapsulation.

10
Public, Private and Protected Variables

 Protected members of a class are accessible from within the class


and are also available to its sub-classes. No other environment is
permitted access to it. This enables specific resources of the parent
class to be inherited by the child class.
 Python doesn't have any mechanism that effectively restricts access
to any instance variable or method. Python prescribes a convention
of prefixing the name of the variable/method with single or double
underscore to emulate the behavior of protected and private access
specifiers.
 All members in a Python class are public by default. Any member
can be accessed from outside the class environment.

11
Example: Public Attributes

 You can access employee class's


class employee: attributes and also modify their
def __init__(self, name, sal): values, as shown below.
self.name=name
self.salary=sal
e1=employee("Kiran",10000)
print(e1.salary)
e1.salary=20000
(e1.salary)

12
Example: Protected Attributes

class employee:
def __init__(self, name, sal):
self._name=name # protected attribute
self._salary=sal # protected attribute

e1=employee(“Swati",10000)
 Python's convention to make an
instance variable protected is to
print(e1._salary)
add a prefix _ (single underscore)
e1._salary=20000
to it. This effectively prevents it
print(e1._salary)
to be accessed, unless it is from
within a sub-class.

13
Example: Protected Attributes

 In fact, this doesn't prevent instance variables from


accessing or modifying the instance.
 Hence, the responsible programmer would refrain from
accessing and modifying instance variables prefixed with _
from outside its class.

14
Example: Private Attributes

class employee:
def __init__(self, name, sal):
self.__name=name # private attribute
self.__salary=sal # private attribute

e1=employee(“Bill",10000)
 Similarly, a double underscore __
prefixed to a variable makes
print(e1.__salary)
it private. It gives a strong
suggestion not to touch it from
AttributeError: 'employee' object
outside the class. Any attempt to
has no attribute '__salary'
do so will result in an Attribute
Error:
15
Private variables

 “Private” instance variables that cannot be accessed except from


inside an object don’t exist in Python. However, there is a
convention that is followed by most Python code: a name prefixed
with an underscore (e.g. _spam) should be treated as a non-public
part of the API (whether it is a function, a method or a data
member). It should be considered an implementation detail and
subject to change without notice.

16
Private variables : Name mangling

 Python performs name mangling of private variables.


Every member with double underscore will be changed
to object._class__variable. If so required, it can still be
accessed from outside the class, but the practice should
be refrained.

E1=employee(“Bill",10000)
print(e1._employ__salary
e1._employee__salary=20000
print(e1._employee__salary)

17
Private variables: Name mangling

 Since there is a valid use-case for class-private members (namely


to avoid name clashes of names with names defined by
subclasses), there is limited support for such a mechanism, called
name mangling. Any identifier of the form __spam (at least two
leading underscores, at most one trailing underscore) is textually
replaced with _classname__spam, where classname is the current
class name with leading underscore(s) stripped. This mangling is
done without regard to the syntactic position of the identifier, as
long as it occurs within the definition of a class.

18
Private variables: Name mangling
 Name mangling is helpful for letting subclasses override methods
without breaking intraclass method calls. For example:
class Mapping: class MappingSubclass(Mapping):
def __init__(self, iterable): def update(self, keys, values):
self.items_list = [] # provides new signature for
self.__update(iterable) update()
def update(self, iterable): # but does not break __init__()
for item in iterable: for item in (keys, values): s
self.items_list.append(item) self.items_list.append(item)
__update = update
# private copy of original update() method

19
Private variables: Name mangling

 The above example would work even if MappingSubclass were to


introduce a __update identifier since it is replaced with
_Mapping__update in the Mapping class and
_MappingSubclass__update in the MappingSubclass class
respectively.
 Note that the mangling rules are designed mostly to avoid
accidents; it still is possible to access or modify a variable that is
considered private. This can even be useful in special
circumstances, such as in the debugger.

20
Private variables: Name mangling

 Notice that code passed to exec() or eval() does not consider


the classname of the invoking class to be the current class;
this is similar to the effect of the global statement, the effect
of which is likewise restricted to code that is byte-compiled
together. The same restriction applies to getattr(), setattr()
and delattr(), as well as when referencing __dict__ directly.

21
Odds and Ends

 Sometimes it is useful to have a data type similar to the


Pascal “record” or C “struct”, bundling together a few
named data items. An empty class definition will do nicely:
class Employee:
pass
john = Employee() # Create an empty employee record
# Fill the fields of the record
john.name = 'John Doe‘
john.dept = 'computer lab'
john.salary = 1000

22
Odds and Ends
 A piece of Python code that expects a particular abstract
data type can often be passed a class that emulates the
methods of that data type instead. For instance, if you have
a function that formats some data from a file object, you can
define a class with methods read() and readline() that get the
data from a string buffer instead, and pass it as an argument.

 Instance method objects have attributes, too: m.__self__ is


the instance object with the method m(), and m.__func__ is
the function object corresponding to the method.

23
Iterators
By now you have probably noticed that most container
objects can be looped over using a for statement:

for element in [1, 2, 3]: for char in "123":


print(element) print (char)
for element in (1, 2, 3): for line in ("myfile.txt"):
print(line, end='')
print(element)
for key in {'one':1, 'two':2}:
print(key)

24
Iterators

 This style of access is clear, concise, and convenient.


 The use of iterators pervades and unifies Python.
 Behind the scenes, the for statement calls iter() on the container
object.
 The function returns an iterator object that defines the method
__next__() which accesses elements in the container one at a
time.
 When there are no more elements, __next__() raises a
StopIteration exception which tells the for loop to terminate.
 You can call the __next__() method using the next() built-in
function; this example shows how it all works:

25
Iterators
>>> s = 'abc‘
>>> it = iter (s)
>>> it Having seen the mechanics behind
<iterator object at 0x00A1DB50> the iterator protocol, it is easy to
>>> next(it) add iterator behavior to your
'a'
>>> next(it)
classes. Define an __iter__()
'b' method which returns an object
>>> next(it) with a __next__() method. If the
'c' class defines __next__(), then
>>> next (it) __iter__() can just return self:
Traceback (most recent call last):
File “<stdin>” , line 1, in <module>
next(it)
StopIteration
26
Iterators
class Reverse: >>> rev = Reverse('spam')
"""Iterator for looping over a sequence >>> iter (rev)
backwards."""
<__main__.Reverse object at
def __init__(self, data):
0x00A1DB50>
self.data = data
self.index = len(data) >>> for char in rev:
def __iter__(self): ... Print (char)
return self
...
def __next__(self):
if self.index == 0: m
raise StopIteration a
self.index = self.index - 1 p
return self.data[self.index]
s

27
Generators
 Generators are a simple and powerful tool for creating iterators.
They are written like regular functions but use the yield
statement whenever they want to return data. Each time next()
is called on it, the generator resumes where it left off (it
remembers all the data values and which statement was last
executed). An example shows that generators can be trivially
easy to create: >>> for char in reverse('golf'):
... print (char)
def reverse(data):
...
for index in range(len(data)-1, -1, -1): f
yield data[index] l
o
g

28
Generators
 Anything that can be done with generators can also be done with
class-based iterators as described in the previous section. What
makes generators so compact is that the __iter__() and __next__()
methods are created automatically.
 Another key feature is that the local variables and execution state
are automatically saved between calls. This made the function
easier to write and much more clear than an approach using
instance variables like self.index and self.data.
 In addition to automatic method creation and saving program state,
when generators terminate, they automatically raise StopIteration.
In combination, these features make it easy to create iterators with
no more effort than writing a regular function.

29
Generator Expressions
Some simple generators can be coded succinctly as expressions using
a syntax similar to list comprehensions but with parentheses instead
of square brackets. These expressions are designed for situations
where the generator is used right away by an enclosing function.
Generator expressions are more compact but less versatile than full
generator definitions and tend to be more memory friendly than
equivalent list comprehensions.
Examples:

30
Generator Expressions

>>> sum (i*i for i in range(10)) # sum of squares


285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum (x*y for x,y in (xvec, yvec)) # dot product
260
>>> unique_words = set (word for line in page for word in line.split())
>>> valedictorian = max((student.gpa, student.name) for student in graduates)
>>> data = 'golf‘
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

31
Department of Mathematical and Physical Sciences
Concordia University of Edmonton

Implementing Inheritance
Inheritance

 Inheritance allows a programmer to create a new class; a subclass,


derived class or child class, of an already existing class.
 This new class is a modified version of the existing class; the
superclass, base class or parent class.
 The child class inherits the properties and methods of the parent
class but also has its own properties and methods, that are added by
the programmer.
 A popular use of this is in games, for example when you have a
class for an item and wish to make a subclass for key items, which
have all the properties of an item and then some.

2
Inheritance

 Inheritance is extremely important and useful in any OOP


language. Python is special in the sense that it can support multiple
inheritances.
 A language feature would not be worthy of the name “class”
without supporting inheritance. The syntax for a derived class
definition looks like this:

class DerivedClassName(BaseClassName):
<statement-1>
...
<statement-N>

3
Inheritance

 The name BaseClassName must be defined in a scope containing


the derived class definition. In place of a base class name, other
arbitrary expressions are also allowed. This can be useful, for
example, when the base class is defined in another module:
class DerivedClassName (modname.BaseClassName):
 Execution of a derived class definition proceeds the same as for a
base class. When the class object is constructed, the base class is
remembered. This is used for resolving attribute references: if a
requested attribute is not found in the class, the search proceeds to
look in the base class. This rule is applied recursively if the base
class itself is derived from some other class.
4
Inheritance

 There’s nothing special about instantiation of derived classes


DerivedClassName() creates a new instance of the class. Method
references are resolved as follows: the corresponding class attribute
is searched, descending down the chain of base classes if necessary,
and the method reference is valid if this yields a function object.
 Execution of a derived class definition proceeds the same as for a
base class. When the class object is constructed, the base class is
remembered. This is used for resolving attribute references: if a
requested attribute is not found in the class, the search proceeds to
look in the base class. This rule is applied recursively if the base
class itself is derived from some other class.
5
Inheritance

 There’s nothing special about instantiation of derived classes:


DerivedClassName() creates a new instance of the class. Method
references are resolved as follows: the corresponding class attribute
is searched, descending down the chain of base classes if necessary,
and the method reference is valid if this yields a function object.
 Derived classes may override methods of their base classes.
Because methods have no special privileges when calling other
methods of the same object, a method of a base class that calls
another method defined in the same base class may end up calling a
method of a derived class that overrides it.

6
Inheritance

 An overriding method in a derived class may in fact want to extend


rather than simply replace the base class method of the same name.
There is a simple way to call the base class method directly: just
call BaseClassName.methodname (self, arguments). . This is
occasionally useful to clients as well. (Note that this only works if
the base class is accessible as BaseClassName in the global scope.)
 Python has two built-in functions that work with inheritance:
 Use isinstance () to check an instance’s type: isinstance (obj, int)
will be True only if obj.__class__ is int or some class derived from
int.
 Use issubclass () to check class inheritance: issubclass (bool, int)
is True since bool is a subclass of int. However, issubclass (float,
int) is False since float is not a subclass of int.
7
Inheritance

class parent():
def __init__(self, width, length):
self.width = width
self.length = length

def perimeter(self):
per = (2*self.width) + (2*self.length)
return(per)

def area(self):
ar = (self.width*self.length)
return(ar)

8
Inheritance

class child(parent):
def __init__(self, width, length, height):
parent.__init__(self, width, length)
self.height = height
def volume(self):
vol = (self.area()*self.height)
return(vol)
square = child(3,3,3)
print(square.volume())
print(square.area())
print(square.perimeter())

9
Inheritance

Note:
class child(parent):
def __init__(self, width, length, height):
parent.__init__(self, width, length)
self.height = height

The bolded code can be written as super().__init__(width, length .


super() can replace the name of the parent class, super()
automatically calls to the superclass method.

10
Exercise 1

Exercise 1

Write a subclass of the following class which calculates the hourly


pay of an employee. The parent class info will take a name, hourly
pay rate and provide an employee number and allows the employee
to enter in the number of hours they have worked for the day. The
child class should be able to access those hours and calculate the
weekly pay. The child class will need to take days_worked in its class
parameter. The result must also be formatted correctly, as in returning
the answer with a dollar sign and two decimals, indicating cents.

11
_str_

The __str__ method is very useful when it comes to inheritance


in the child class. It helps to make cleaner code, allowing us to
just simply print out instances without having need to call to a
function.

12
_str_

Example __str__ vs no __str__

13
_str_

# Notice how no function call is needed to print out the result, just #
a call to the class object.

# note that because the ID number is randomly generated, every time


# the code is executed it will be random, enhance the difference #
between the two examples.

14
Exercise 2

Exercise 2

Take the code from the first example and change it so that it uses the
__str__ method. You can also employ super() in your code. When
you call the child class you must override the __str__ function in the
child class. Return the same thing as in the parent class and add to it
the properly formatted and calculated weekly pay from the first
exercises.

15
Overriding

What we have demonstrated in the above use


of __str__ and __init__ with inheritance is a
concept called overriding, an OOP feature
which allows you to give your subclass a
different method implementation of an already
existing method in a parent class. This
implementation by the subclass works to
override the existing method within the parent
class by giving the subclass method the same
return type, same name and parameters as that
of the parent class.

16
Overloading

Overloading is similar to overriding but overloading allows us to


redefine the same function with different types of parameters and/or a
different amount of parameters. It is the ability of a function (or
method, in case of classes) to perform its task depending on the
parameters and their types. The terms overriding and overloading are
often interchanged by people, but note that they are not the same.

17
Exercise 3

Exercise 3

Write a class that converts a given dollar amount into a number of


cents, provide an option which allows the user to pick which value of
currency they want: nickels, dimes, quarters, or pennies. Create a
second class which converts the result of the previous class back into
the original dollar value.

18
Department of Mathematical and Physical Sciences
Concordia University of Edmonton

Multiple Inheritance
Multiple Inheritance

 Python supports a form of multiple inheritance as well.


 A class definition with multiple base classes looks like this:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>

2
Multiple Inheritance

 For most purposes, in the simplest cases, you can think of the
search for attributes inherited from a parent class as depth-first,
left-to-right, not searching twice in the same class where there is an
overlap in the hierarchy. Thus, if an attribute is not found in
DerivedClassName, it is searched for in Base1, then (recursively)
in the base classes of Base1, and if it was not found there, it was
searched for in Base2, and so on.
 In fact, it is slightly more complex than that; the method resolution
order changes dynamically to support cooperative calls to super().
This approach is known in some other multiple-inheritance
languages as call-next-method and is more powerful than the super
call found in single-inheritance languages.
3
Multiple Inheritance

 Dynamic ordering is necessary because all cases of multiple


inheritance exhibit one or more diamond relationships (where at
least one of the parent classes can be accessed through multiple
paths from the bottommost class). For example, all classes inherit
from object, so any case of multiple inheritance provides more than
one path to reach object.
 To keep the base classes from being accessed more than once, the
dynamic algorithm linearizes the search order in a way that preserves
the left-to-right ordering specified in each class, that calls each
parent only once, and that is monotonic (meaning that a class can be
subclassed without affecting the precedence order of its parents).
Taken together, these properties make it possible to design reliable
and extensible classes with multiple inheritance.
4
Diamond Problem
(Deadly Diamond of Death)

5
Diamond Problem
(Deadly Diamond of Death)

6
Diamond Problem
(Deadly Diamond of Death)
“Multiple Inheritance (object-oriented programming) was widely
supposed to be very difficult to implement efficiently. For example,
in a summary of C++ in his book on objective C Brd.Cox actually
claimed that adding Multiple inheritance to C++ was impossible.
Thus, multiple inheritance seemed more of a challenge. Since I had
considered multiple inheritance as early as 1982 and found a
simple and efficient implementation technique in 1984. I couldn't
resist the challenge. I suspect this to be the only case in which
fashion affected the sequence of events.”
- Bjarne Stroustrup
Stroustrup, Bjarne (1999). Multiple Inheritance for C++. Proceedings of the Spring
1987 European Unix Users Group Conference.
7
Diamond Problem
(Deadly Diamond of Death)

Method Resolution Order (MRO) is the order in which Python looks


for a method in a hierarchy of classes. Especially it plays vital role in
the context of multiple inheritance as single method may be found in
multiple super classes.
To understand the concept of MRO and its need, lets examine a few
cases.

8
Case I

This is a simple case where we have class C derived from both A and
B. When method process() is called with object of class C then
process() method in class A is called.
Python constructs the order in which it will look for a method in the
hierarchy of classes. It uses this order, known as MRO, to determine
which method it actually calls.
It is possible to see MRO of a class using mro() method of the class.

9
Case I

class A:
def process(self):
print('A process()')
class B:
pass
class C(A, B): From MRO of class C, we get to know that
pass Python looks for a method first in class C.
obj = C() Then it goes to A and then to B. So, first it
goes to super class given first in the list
obj.process() then second super class, from left to right
print(C.mro()) order. Then finally Object class, which is a
# print MRO for class C super class for all classes.

10
Case II

Now, let’s make it a little more complicated by adding process() method


to class B also.
When you run the above code, it prints the
class A: following: A process()
def process(self): Python calls process() method in class A.
print('A process()') According to MRO, it searches A first and then B.
class B: So if method is found in A then it calls that method.
def process(self): However, if we remove process() method from
print('B process()') class A then process() method in class B will be
class C(A, B): called as it is the next class to be searched
pass according to MRO.
obj = C() The ambiguity that arises from multiple inheritance
obj.process() is handled by Python using MRO.

11
Case III

In this case, we create D from C and B. Classes C and B have


process() method and as expected MRO chooses method from C.
Remember it goes from left to right. So it searches C first and all its
super classes of C and then B and all its super classes. We can observe
that in MRO of the output given below. class D(C,B):
class A: pass
def process(self): obj = D()
print('A process()') obj.process()
class B: print(D.mro())
Running the above program will
def process(self): produce the following output:
print('B process()') C process()
class C(A, B): [<class '__main__.D'>, <class
def process(self): '__main__.C'>, <class '__main__.A'>,
<class '__main__.B'>, <class
print('C process()') 'object'>]
12
Case IV

Now, lets change the hierarchy. We create B and C from A and then D
from B and C. Method process() is present in both A and C.
class A:
def process(self):
print('A process()')
class B(A):
pass obj = D()
class C(A): obj.process()
def process(self):
print('C process()')
class D(B,C):
pass
13
Case IV

Output of the above program is:


C process()
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class
'object'>]
When we call process() with an object of class D, it should start with first Super class – B (and its super
classes) and then second super class – C (and its super classes). If that is the case then we will call
process() method from class A as B doesn’t have it and A is super class for B. However, that is
contradictory to rule of inheritance, as most specific version must be taken first and then least specific
(generic) version. So, calling process() from A, which is super class of C, is not correct as C is a direct
super class of D. That means C is more specific than A. So method must come from C and not from A.
This is where Python applies a simple rule that says (known as good head question) when in MRO we
have a super class before subclass then it must be removed from that position in MRO.
So the original MRO will be: D -> B -> A -> C -> A
If you include object class also in MRO then it will be: D -> B-> A -> object -> C -> A -> object
But as A is super class of C, it cannot be before C in MRO. So, Python removes A from that position,
which results in new MRO as follows:
D -> B -> C -> A -> object
The output of the above program proves that.

14
Case V

There are cases when Python cannot construct MRO owing to


complexity of hierarchy. In such cases it will throw an error as
demonstrated by the following code.
class A: When you run the above code, the
def process(self): following error is shown:
print('A process()') TypeError: Cannot create a consistent
class B(A): method resolution
def process(self): order (MRO) for bases A, B
print('B process()') The problem comes from the fact that class
class C(A, B): A is a super class for both C and B. If you
pass construct MRO then it should be like this:
obj = C() C -> A -> B -> A
obj.process()

15
Case V

Then according to the rule (good head) A should NOT be ahead of B as A is super
class of B. So new MRO must be like this:
C -> B -> A But A is also direct super class of C. So, if a method is in both A and B
classes then which version should class C call? According to new MRO, the version
in B is called first ahead of A and that is not according to inheritance rules (specific
to generic) resulting in Python to throw error.
Understanding MRO is very important for any Python programmer. I strongly
recommend trying more cases until you completely understand how Python
constructs MRO. Do not confuse yourself by taking old way of constructing MRO
used in earlier versions of Python. It is better to consider only Python 3.

http://www.srikanthtechnologies.com/blog/python/mro.aspx

16
Department of Mathematical and Physical Sciences
Concordia University of Edmonton

Polymorphism
Polymorphism

 In literal sense, Polymorphism means the ability to take various


forms.
 In programming, polymorphism means same function name (but
different signatures) being uses for different types.
Example of inbuilt polymorphic functions :

# Python program to demonstrate in-built polymorphic functions


# len() being used for a string
print(len("geeks"))
# len() being used for a list
print(len([10, 20, 30]))

2
Polymorphism

Example of user defined polymorphic functions :


# A simple Python function to demonstrate
# Polymorphism

def add(x, y, z = 0):


return x + y+z

# Driver code
print (add(2, 3))
print(add(2, 3, 4))

3
Polymorphism with class methods

Below code shows how python can use two different class types, in
the same way. We create a for loop that iterates through a tuple of
objects. Then call the methods without being concerned about which
class type each object is. We assume that these methods actually
exist in each class.

4
Polymorphism with class methods

class India():
def capital(self:
print("New Delhi is the capital of India.")
def language(self):
print("Hindi the primary language of India.")
def type(self):
print("India is a developing country.")
class USA():
def capital(self):
print("Washington, D.C. is the capital of USA.")

5
Polymorphism with class methods
def language(self):
print("English is the primary language of USA.")
def type(self):
print("USA is a developed country.")

obj_ind = India()
obj_usa = USA()
for country in (obj_ind, obj_usa):
country.capital()
country.language()
country.type()

6
Polymorphism with Inheritance

In Python, Polymorphism lets us define methods in the child class


that have the same name as the methods in the parent class. In
inheritance, the child class inherits the methods from the parent
class. However, it is possible to modify a method in a child class that
it has inherited from the parent class. This is particularly useful in
cases where the method inherited from the parent class doesn’t quite
fit the child class. In such cases, we re-implement the method in the
child class. This process of re-implementing a method in the child
class is known as Method Overriding.

7
Polymorphism with Inheritance

class Bird:
def intro(self):
print("There are many types of birds.")
def flight(self):
print("Most of the birds can fly but some
cannot.")
class sparrow(Bird):
def flight(self):
print("Sparrows can fly.")
class ostrich(Bird):
def flight(self):
print("Ostriches cannot fly.")
8
Polymorphism with Inheritance

obj_bird = Bird()
obj_spr = sparrow()
obj_ost = ostrich()
obj_bird.intro()
obj_bird.flight()
obj_spr.intro()
obj_spr.flight()
obj_ost.intro()
obj_ost.flight()

9
Polymorphism with Function and Objects

It is also possible to create a function that can take any object,


allowing for polymorphism. In this example, let’s create a function
called “func()” which will take an object which we will name “obj”.
Though we are using the name ‘obj’, any instantiated object will be
able to be called into this function. Next, lets give the function
something to do that uses the ‘obj’ object we passed to it. In this case
lets call the three methods, viz., capital(), language() and type(), each
of which is defined in the two classes ‘India’ and ‘USA’. Next, let’s
create instantiations of both the ‘India’ and ‘USA’ classes if we don’t
have them already. With those, we can call their action using the
same func() function:

10
Polymorphism with Function and Objects

class India():
def capital(self):
print("New Delhi is the capital of India.")
def language(self):
print("Hindi the primary language of India.")
def type(self):
print("India is a developing country.")

class USA():
def capital(self):
print("Washington, D.C. is the capital of
USA.")
Code : Implementing Polymorphism with a Function
11
Polymorphism with Function and Objects

def language(self):
print("English is the primary language of USA.")
def type(self):
print("USA is a developed country.")
def func(obj):
obj.capital()
obj.language()
obj.type()
obj_ind = India()
obj_usa = USA()
func(obj_ind)
func(obj_usa)
12
Polymorphism

 In Python, Polymorphism allows us to define methods in the child


class with the same name as defined in their parent class.
 A child class inherits all the methods from the parent class.
 However, you will encounter situations where the method inherited
from the parent class doesn’t quite fit into the child class.
 In such cases, you will have to re-implement method in the child
class. This process is known as Method Overriding.

13
Polymorphism

 If you have overridden a method in the child class, then the version
of the method will be called based upon the type of the object used
to call it.
 If a child class object is used to call an overridden method then the
child class version of the method is called.
 On the other hand, if parent class object is used to call an
overridden method, then the parent class version of the method is
called.

14
Polymorphism

 The following program demonstrates method overriding in action:


class A:
def explore(self):
print("explore() method from class A")
class B(A):
def explore(self):
print("explore() method from class B")
b_obj = B()
a_obj = A()
b_obj.explore()
a_obj.explore()

15
Polymorphism

 Here b_obj is an object of class B (child class), as a result,


class B version of the explore() method is called. However, the
variable a_obj is an object of class A (parent class), as a result,
class A version of the explore() method is called.
 If for some reason you still want to access the overridden method
of the parent class in the child class, you can call it using
the super() function as follows:

16
Polymorphism

class A:
def explore(self):
print("explore() method from class A")

class B(A):
def explore(self):
super().explore() # calling the parent class explore() method
print("explore() method from class B")

b_obj = B()
b_obj.explore()
17
Object – The Base Class

 In Python, all classes inherit from the object class implicitly.


 It means that the following two class definitions are equivalent.
class MyClass:
pass
class MyClass(object):
pass
 The __new__() method creates the object. After creating the object it
calls the __init__() method to initialize attributes of the object.
Finally, it returns the newly created object to the calling program.
Normally, we don’t override __new__() method, however, if you
want to significantly change the way an object is created, you should
definitely override it.
18
Object – The Base Class

 The __str__() method is used to return a nicely formatted string


representation of the object. The object class version of __str__()
method returns a string containing the name of the class and its
memory address in hexadecimal. For example:
class Jester:
class Jester:
def laugh(self):
def laugh(self):
return "laugh() called"
return print("laugh() called")
def __str__(self):
obj = Jester()
return "A more helpful description"
print(obj)
obj = Jester()
print(obj)
Sure, it is not very helpful. We can easily override this method by
defining a method named __str__() in the Jester class.
19

You might also like