Lab03: Functions, Recursion and Object Oriented Programming, Lecture notes of Artificial Intelligence

Designing and implementing Python programs that deal with: 1. Functions 2. Recursion 3. OOP 1. Functions

Typology: Lecture notes

2021/2022

Uploaded on 03/31/2023

javeria-noorani
javeria-noorani 🇵🇰

4 documents

1 / 17

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Lab03: Functions, Recursion and Object Oriented Programming
Designing and implementing Python programs that deal with:
1. Functions
2. Recursion
3. OOP
1. Functions
A functionis a group of statements that is executed when it is called from somepoint of the
program.
The following is its format:
where:
function_nameis the identifier by which it will be possible to call the function.
parameters(as many as needed): Each parameter consists of an identifier, like any regular
variableand which acts within the function as a regular local variable.They allow to pass
arguments to the function when it is called. The differentparameters are separated by
commas.
statementsis the function's body. It is a block of statements justified by identation.
Consider the following code and examine the repeated code with same functionality
for i in range(30):
print("*" , end='')
print()
print("Object Oriented Programming")
for i in range(30):
print("*",end='')
print()
print("Lab 02")
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff

Partial preview of the text

Download Lab03: Functions, Recursion and Object Oriented Programming and more Lecture notes Artificial Intelligence in PDF only on Docsity!

Lab03: Functions, Recursion and Object Oriented Programming

Designing and implementing Python programs that deal with:

**1. Functions

  1. Recursion**

3. OOP

1. Functions

A functionis a group of statements that is executed when it is called from somepoint of the

program.

The following is its format:

where:

  • function_name is the identifier by which it will be possible to call the function.
  • parameters (as many as needed): Each parameter consists of an identifier, like any regular

variableand which acts within the function as a regular local variable.They allow to pass

arguments to the function when it is called. The differentparameters are separated by

commas.

  • statements is the function's body. It is a block of statements justified by identation.

Consider the following code and examine the repeated code with same functionality

for i in range(30):

print("*" , end='')

print()

print("Object Oriented Programming")

for i in range(30):

print("*",end='')

print()

print("Lab 02")

for i in range(30):

print("*", end='')

print()

print("Department of Computer Science")

for i in range(30):

print("*",end='')

print()

1.1FunctionsWithout Return Type and No Argument

What we will do in this case for similar functionality code, create a function named drawLine()

and call that function at repeated code loctations.

defdrawLine():

for i in range(30):

print("*" , end='')

print()

drawLine()

print("Artificial Intelligence")

drawLine()

print("Lab 02")

drawLine()

print("Department of Computer Science")

drawLine()

1.2Functions Without Return Typeand witharguements

drawLine method with an argument to draw line according to the size provided

defdrawLine(n):

for i in range(n):

print("*" , end='')

print()

drawLine(30)

print("Artificial Intelligence")

drawLine(10)

3. Recursion

“A recursive method is a method that either directly or indirectly makes a call to itself.” [Weiss].

It does this by making problem smaller (simpler) at each call.

Recursive functions have two important components:

  1. Base case(s) , where the function directly computes an answer without calling itself. Usually

the base case deals with the simplest possible form of the problem you're trying to solve.

  1. Recursive case(s) , where the function calls itself as part of the computation.

Perhaps the simplest example is calculating factorial:

n! = n ∙ ( n − 1 ) ∙⋯ ∙ 2 1

. However, we can

also see that n! = n ∙ ( n − 1 )!. Thus, factorial is defined in terms of itself.

For example,

factorial( 5 ) = 5 * factorial( 4 )

= 5 * ( 4 * factorial( 3 ) )

= 5 * ( 4 * (3 * factorial( 2 ) ) )

= 5 * ( 4 * (3 * (2 * factorial( 1 ) ) ) )

= 5 * ( 4 * (3 * (2 * ( 1 * factorial( 0 ) ) ) ) )

Iterative Solution Recursive Solution

def factorial( n ):

sum = 1

for i in range(1,n+1):

*sum = i;

return sum;

print("Factorial of 3 is

" ,factorial(3))

def factorial( n ):

if( n <= 1 ):

return 1

else:

return n * factorial( n-1 )

print("Factorial of 3 is

" ,factorial(6))

sum =11=*

sum =12=*

sum =23=*

return sum=

Classes and Objects(OOP Concepts)

Designing and implementing Python programs that deal with:

**4. OOP Terminology

  1. Creating Classes
  2. Built-In Class Attributes
  3. Destroying Objects
  4. Class Inheritance
  5. Overloading Methods
  6. Base Overloading Methods
  7. Overloading Operators
  8. Data Hiding**

1. OOP Terminology

Class: A user-defined prototype for an object that defines a set of attributes that characterize

any object of the class. The attributes are data members (class variables and instance

variables) and methods, accessed via dot notation.

class Employee:

'Common base class for all employees'

empCount = 0

def init(self, name, salary):

self.name = name

self.salary = salary

Employee.empCount += 1

def displayCount(self):

print ("Total Employee ", Employee.empCount)

def displayEmployee(self):

print ("Name : ", self.name, ", Salary: ", self.salary)

 The variable empCount is a class variable whose value is shared among all instances of a

this class. This can be accessed as Employee.empCount from inside the class or outside

the class.

 The first method init() is a special method, which is called class constructor or

initialization method that Python calls when you create a new instance of this class.

[Note: Pyton has only one constructor there are no overloaded constructors]

 You declare other class methods like normal functions with the exception that the first

argument to each method is self. Python adds the self argument to the list for you; you do

not need to include it when you call the methods.

Creating Instance Objects

To create instances of a class, you call the class using class name and pass in whatever

arguments its init method accepts.

"This would create first object of Employee class"

emp1 = Employee("Zara", 2000)

"This would create second object of Employee class"

emp2 = Employee("Manni", 5000)

Accessing Attributes

You access the object's attributes using the dot operator with object. Class variable would be

accessed using class name as follows −

emp1.displayEmployee()

emp2.displayEmployee()

print ("Total Employee ", Employee.empCount)

Now, putting all the concepts together −

class Employee:

'Common base class for all employees'

empCount = 0

def init(self, name, salary):

self.name = name

self.salary = salary

Employee.empCount += 1

def displayCount(self):

print ("Total Employee ", Employee.empCount)

def displayEmployee(self):

print ("Name : ", self.name, ", Salary: ", self.salary)

"This would create first object of Employee class"

emp1 = Employee("Zara", 2000)

"This would create second object of Employee class"

emp2 = Employee("Manni", 5000)

emp1.displayEmployee()

emp2.displayEmployee()

print ("Total Employee ", Employee.empCount)

When the above code is executed, it produces the following result −

Name : Zara ,Salary: 2000

Name : Manni ,Salary: 5000

Total Employee 2

You can add, remove, or modify attributes of classes and objects at any time −

emp1.age = 7 # Add an 'age' attribute.

emp1.age = 8 # Modify 'age' attribute.

def displayCount(self):

print ("Total Employee ", Employee.empCount)

def displayEmployee(self):

print ("Name : ", self.name, ", Salary: ", self.salary)

print ("Employee.doc:", Employee.doc)

print ("Employee.name:", Employee.name)

print ("Employee.module:", Employee.module)

print ("Employee.bases:", Employee.bases)

print ("Employee.dict:", Employee.dict)

When the above code is executed, it produces the following result −

Employee.doc: Common base class for all employees

Employee.name: Employee

Employee.module: main

Employee.bases: ()

Employee.dict: {'module': 'main', 'displayCount':

<function displayCount at 0xb7c84994>, 'empCount': 2,

'displayEmployee': <function displayEmployee at 0xb7c8441c>,

'doc': 'Common base class for all employees',

'init': <function init at 0xb7c846bc>}

4. Destroying Objects (Garbage Collection)

Python deletes unneeded objects (built-in types or class instances) automatically to free the

memory space. The process by which Python periodically reclaims blocks of memory that no

longer are in use is termed Garbage Collection.

Python's garbage collector runs during program execution and is triggered when an object's

reference count reaches zero. An object's reference count changes as the number of aliases that

point to it changes.

An object's reference count increases when it is assigned a new name or placed in a container

(list, tuple, or dictionary). The object's reference count decreases when it's deleted with del , its

reference is reassigned, or its reference goes out of scope. When an object's reference count

reaches zero, Python collects it automatically.

a = 40 # Create object <40>

b = a # Increase ref. count of <40>

c = [b] # Increase ref. count of <40>

del a # Decrease ref. count of <40>

b = 100 # Decrease ref. count of <40>

c[0] = -1 # Decrease ref. count of <40>

You normally will not notice when the garbage collector destroys an orphaned instance and

reclaims its space. But a class can implement the special method del() , called a destructor,

that is invoked when the instance is about to be destroyed. This method might be used to clean

up any non memory resources used by an instance.

Example

This del() destructor prints the class name of an instance that is about to be destroyed −

#!/usr/bin/python

class Point:

def __init( self, x=0, y=0):

self.x = x

self.y = y

def del(self):

class_name = self.class.name

print (class_name, "destroyed")

pt1 = Point()

pt2 = pt

pt3 = pt

print (id(pt1), id(pt2), id(pt3)) # prints the ids of the obejcts

del pt

del pt

del pt

When the above code is executed, it produces following result −

class Child(Parent): # define child class

def init(self):

print ("Calling child constructor")

def childMethod(self):

print ('Calling child method')

c = Child() # instance of child

c.childMethod() # child calls its method

c.parentMethod() # calls parent's method

c.setAttr(200) # again call parent's method

c.getAttr() # again call parent's method

When the above code is executed, it produces the following result −

Calling child constructor

Calling child method

Calling parent method

Parent attribute : 200

Similar way, you can drive a class from multiple parent classes as follows −

class A: # define your class A

class B: # define your class B

class C(A, B): # subclass of A and B

You can use issubclass() or isinstance() functions to check a relationships of two classes and

instances.

 The issubclass(sub, sup) boolean function returns true if the given subclass sub is indeed a

subclass of the superclass sup.

 The isinstance(obj, Class) boolean function returns true if obj is an instance of class Class or

is an instance of a subclass of Class

6. Overriding Methods

You can always override your parent class methods. One reason for overriding parent's methods

is because you may want special or different functionality in your subclass.

Example

class Parent: # define parent class

def myMethod(self):

print ('Calling parent method')

class Child(Parent): # define child class

def myMethod(self):

print ('Calling child method')

c = Child() # instance of child

c.myMethod() # child calls overridden method

When the above code is executed, it produces the following result −

Calling child method

7. Base Overloading Methods

Following table lists some generic functionality that you can override in your own classes −

SN Method, Description & Sample Call

1 init ( self [,args...] )

Constructor (with any optional arguments)

Sample Call : obj = className(args)

2 del( self )

When the above code is executed, it produces the following result −

Vector(7,8)

9. Data Hiding

An object's attributes may or may not be visible outside the class definition. You need to name

attributes with a double underscore prefix , and those attributes then are not be directly visible

to outsiders.

Example

class JustCounter:

__secretCount = 0

def count(self):

self.__secretCount += 1

print (self.__secretCount)

counter = JustCounter()

counter.count()

counter.count()

print (counter.__secretCount)

When the above code is executed, it produces the following result −

Traceback (most recent call last):

File "test.py", line 12, in

print counter.__secretCount

AttributeError: JustCounter instance has no attribute

'__secretCount'

Python protects those members by internally changing the name to include the class name. You

can access such attributes as _object.className__attrName. If you would replace your last line

as following, then it works for you −

print counter._JustCounter__secretCount