UNIT-4 (functional and OOPS) Flashcards
(33 cards)
Functional Programming
programs made using function
uses pure functions, recursive functions, nested functions ,lambda functions etc.
treats functions as values and pass them to functions as parameters
more readable and easily understandable.
function signature => information about them i.e.their arguments and return type etc
HIGHER ORDER FUNCTIONs
* functions that take functions as arguments, also return functions. (Ex: Callback Functions)
* understood very easily
* Testing and debugging is easier. use immutable values
* better modularity with a shorter code
- lazy evaluation avoids repeated evaluation because the valueis evaluated and stored only when it is needed.
- Example: Lazy Map & Lists ,tuples, sets etc
lamda
functions without the name.
=> anonymous/throw away functions.
=> number of arguments but only one expression.
=>(Implicit return)
=>used when you need a particular function for a short period of time.
ADV:
useful in defining the in-line functions.
Most suitable when it is used as a part of another function(especially with map, filter and reduce functions)
Less Code (Concise code)
Syntax: lambda input arguments: expression / Output
Input arguments: can have multiple arguments by use of commas
* Expression or output: Expression gets evaluated and results are returned
EXAMPLE:
square=lambda n : n*n
print(square(4)) #Function Call
ANOTHER EXAMPLE maximum=lambda a,b : a if a>b else b
We can use the same function definition to make two functions
def myfunc(n):
return lambda a : a * n
double_N = myfunc(2)
triple_N = myfunc(3)
print(double_N(15))
print(triple_N(15))
Output
30,45
Map()
applies a function to each element of an iterable (like a list or a tuple) and returns a new iterable object.
Syntax: map(function, iterables)
- causes iteration through iterable
is lazy
can force an iteration of the map object by passing the map object as an argument for the list or set constructor.
- We can pass one or more iterable to the map() function.
LAZY EVALUTION
Lazy and Eager objects:
object is created when it is needed whereas eager an object is created as soon it is instantiated.
As map object can be iterated one item in eachloop. for list, all the values at one time.
optimizing code and improving performance
used to delay or avoid computations, save memory by avoiding intermediate results,
explore multiple possibilities without committing to one.
* eager evaluation is preferable when you want to perform computations as soon as possible
EXAMPLE CODE
num = (1, 2, 3, 4)
result = map(lambda x: x + x, num)
print(list(result))
Output:
<class ‘map’>
[2, 4, 6, 8]
ANOTHER EXAMPLE
num = [1, 2, 3, 4, 5]
def double_num(n):
if n% 2 == 0:
return n * 2
else:
return n
result = list(map(double_num,num)
print(result)
Output: The modified list is [1, 4, 3, 8, 5]
stFilter()
filters sequence with function that tests each element in the sequence to be true or not.
filter(function,sequence/iterable)
*sequence: which needs to be filtered, it can be sets, lists, tuples, or containers of any iterators.
*Returns: an iterator that is already filtered.
CHARACTERISTICSInput : an iterable of some number of elements
- Output: a lazy iterable
- Elements in the output: apply the callback on each element of the iterable – if the function returns true, then the input element is
selected else input element is rejected.
EXAMPLE
list the marks greater than 70
marks = [55, 78, 90, 87, 65, 45]
def myFunc(m):
if m <70 :
return False
else:
return True
Distinction = list(filter(myFunc, marks))
print(“Students with marks greater than 70 are”,Distinction)
Output:
Students with marks greater than 70 are [78, 90, 87]
reduce
Syntax
applies a given function to the elements of an iterable ,reducing them to a single value
defined in “functools” module.
=>function argument is a function that takes two arguments and returns a single value.
=>first argument is the accumulated value, and the second argument is the current value from the iterable.
=>iterable argument is the sequence of values to be reduced.
=> initializer argument is => initial value. else, first element of the iterable is used as the initial value.
Working of reduce function:
first two elements of the sequence are picked and the result is obtained.
same function is applied to the previous result and the number after 2nd element and result is again stored
will be n – 1 calls if no initializer is specified.
final result is returned as a single value.
EXAMPLE
from functools import reduc
numbers = [1, 2, 3, 4]
def add(a, c)
return a + c
result = reduce(add, numbers)
functools.reduce(function, iterable,[initializer])
print(result)
Zip()
function zip => two or more iterables into a single lazy iterable of tuples.
* It does not have any callback function.
used to map the similar index of multiple containers so that they can be used just using a single entity
Elements from corresponding positions are paired
together.
The resulting iterable contains tuples
EXAMPLE without ZIP
Consider the code below which pairs the two lists:
m=[1,2,3]
n=[4,5,6]
l_new=[]
for i in range(len(m)):
l_new.append((m[i],n[i]))
print(l_new)
less code using zip. We
can observe the same output.
Syntax : zip(*iterators)
print(list(zip(m,n)))
Output
[(1, 4), (2, 5), (3, 6)]
[(1, 4), (2, 5), (3, 6)]
zipped=[(1, 4), (2, 5), (3, 6)]
SYNTAX=zip(*zipped)
UNEQUAL SIZE
Example 5: Zipping list with unequal size.(Two ways)
lis1 = [1,2,3]
lis2 = [4,5,6,7]
print(list(zip(lis1,lis2)))
The same can be acheived
using the directory comprehension as:
result={k:v for k,v in zip(lis1,lis2)}
print(result)
Output
[(1, 4), (2, 5), (3, 6)]
{1: 4, 2: 5, 3: 6}
List comprehension
concise way of defining and creating a list
used to create functionality within a single line of code.
Return value is always a new list obtained by evaluating the expression in the context of for and if
*faster in processing than a list using for loop.
list = [expression for <var> in <iterable> [if condition]]</iterable></var>
m=[1,2,3]
n=[4,5,6]
print([x+y for x in m for y in n])
Output:
[5, 6, 7, 6, 7, 8, 7, 8, 9]
POP(Procedure Oriented Programming)
Programming Paradigm: Style to write solution
=> uses procedures.
* Procedure: instructions used to accomplish a specific task.
=> routines, sub-routines, functions etc
Object Oriented Programming (OOP)
Focus => data and the operations that manipulate the data.
code by representing real- entities as objects
to develop big and complex projects
FEATURES:
Data Abstraction:
* The way you view an object(Ex: apples)
* essential features without background details
Data encapsulation:
* Binding of data and procedures as a single unit.
* Encapsulation: a way to implement data abstraction.
Data Hiding:
* who can access the data .
=> using access specifiers.
Inheritance:
* one class (child) to derive the capabilities of another class(parent)
* Benefit: Code reusability
Polymorphism:
* object of a class to behave diff in response to the data.
KEY CONCEPTS
Class:
* method to create => entity.
* Blueprint/template for creating objects.
*a type and implementation.
* specifies set of instance vars/attributes(data each object of the class will store) and methods
(functions that define the behavior of the objects) that objects of that class has
Syntax OOP
Syntax: (To create a class)
class ClassName:
<statement-1> . <statement-N>
Example 1
class Car:
#first letter should be CAP
def\_\_init\_\_(self,make,color,year ):
#Special method that creates objects
self.make=make
self.color=color
self.year=year
def drive(self):
print(""+self.make+" go vroom")
#this is a method
#self => object that uses this method
TO CREATE OBJECT
Car1=Car("BMW","Blue", "2014" )
Car1.drive()
OUTPUT:
BMW
Blue
2014
BMW go vroom
</statement-N></statement-1>
OOPS Instances
instance => single object from a class. class = blueprint, and an instance => specific object built using that blueprint.
- Represents real entities
- Have their own attributes attributes and methods as defined by the class.
Objects have:
1.Identity: Each object = unique identity
2.Type: type() =>type of an object.
3.Value: Objects have a value that can be modified or accessed (like integers,
strings, lists)
EXAMPLE
class Car:
pass
c1 = Car()
print(c1)
print(type(c1))
print(id(c1))
Output:
<__main__.Car object at 0x000001C9E2200DC0>
<class ‘__main__.Car’>
1966593805760
Instantiation
Constructor
Instantiation
* existence of a class doesn’t create
* Object must be created explicitly
* To create an object => c1= Car() => object(c1) of class Car
* (.)Dot operator -> access attributes of the class.
Ex: c1.car_name
Constructor
* special func of the class called when object is created
* __init__ => name
*invoked automatically and implicitly when object is created.
* used to initialize the internal state of an object, and create instance vars
self => reference to the object used to access
attributes and methods and should be the first parameter
Destructor
* special func => __del__, performs cleanup => when an object is deleted.
* automatically called just before an object is removed by the garbage collector.
* often used to release resources before an object is removed
module garbage collector automatically manages memory and calls destructors when objects are not needed
Destructor: Called when the object is deleted
def __del__(self):
print(f”Person {self.name} is deleted.”)
Types Of constructors
Fixed actual value
Types of Constructors :
1. Non Parameterized constructor
class Person:
def __init__(self):
self.name = “Joe”
#Fixed actual value
self.age = 25
def display(self): print(f"Name: {self.name}, Age: {self.age}") person1 = Person() person1.display()
- Parameterized constructor
class Car:
def__init__(self,make,color,year ):
self.make=make
self.color=color
self.year=year
def drive(self):
print(““+self.make+” go vroom”)
Car1=Car(“BMW”,”Blue”, “2014” )
Car1.drive()
can add instance vars outside the class
p.transmission=‘M’
Getter and Setter Method:
Getter:
* Used to retrieve the value of attribute of a class without directly exposing it.
Setter:
*to modify attribute of a class.
*controlled modification of attribute’s value by performing checks or
validations before assigning new value
Getter syntax:
def get_attribute(self):
return self.__attribute
Setter Syntax
def set_attribute(self, value):
self.__attribute = value
predefined functions
1 setattr() sets the value of attribute of the
Syntax setattr(object, attribute, value)
2 getattr() returns the value of the specified attribute
Syntax: getattr(object, attribute, default)
3 hasattr() returns True if the specified object has specific attribute, else False.
Syntax hasattr(object, attribute)
4 delattr() will delete the specified attribute
Syntax delattr(object, attribute)
Getter and Setter code
class Person:
def __init__(self, name):
self.__name = name # Private attribute
# Getter for name def get_name(self): return self.\_\_name # Setter for name def set_name(self, name): self.\_\_name = name
Create an object of Person
person = Person(“Alice”)
Access name using getter
print(person.get_name()) # Output: Alice
Modify name using setter
person.set_name(“Bob”)
print(person.get_name()) # Output: Bob
Inheritance
Acquiring the features of one type in another type.
- can define a new class which inherits almost all the the properties of existing class.
- Two relationships:
Is – a -> parent-child relationship - Has – a relationship => nothing but collaboration
Benefits of inheritance:
* allows to inherit the properties of a base class, to another class
(Benefits of inheritance:
* It allows to inherit the properties of a base class, to another class
*reusability of a code.
* Allows us to add more features to a class without modifying it.
* Transitive in nature, which means that if class B inherits from class A,
then all the subclasses of B would automatically inherit from class A.
* Less development and maintenance expenses
Is-a relationship
disp() method is overridden to change its behavior. Even though B inherits from A, it doesn’t call A’s disp() method but instead uses its own version.
one class gets most or all of its features from parent class.
three ways parent and child can interact.
1. Action on child imply an action on the parent
class A:
def disp(self):
print(“in disp A”)
class B(A):
pass
a1=A()
a1.disp()
b1=B()
b1.disp()
OUT: in disp A
in disp A
- Action on the child override the action on the parent
class A:
def disp(self):
print(“in disp A”)
class B(A):
def disp(self):
print(“in disp B”)
a1=A()
a1.disp()
b1=B()
b1.disp()
Out:in disp A
in disp B
- Action on the child alter the action on the parent’
class A:
def disp(self):
print(“in disp A”)
class B(A):
def disp(self):
A.disp(self)
print(“in disp B”)
a1=A()
a1.disp()
b1=B()
b1.disp()
Output:
in disp A
in disp A
in disp B
Types of Is-a Relationship
- Single level inheritance:
Sub classes inherit one super class. - Multi Level inheritance: inherited from another class which is inherited from another class
flexibility to inherit from more than one class
turn inherited from another class and so on.
Multiple inheritance: can have more than one super class and inherit the features from all parent classes.
subclass inherits from another subclass, forming a hierarchical chain of classes.
- Hierarchical inheritance: One class serves as super class for more than one
sub classes - Hybrid inheritance: A mix of two or more above types of inheritance. => Diamond shaped inheritance
single level inheritance /parent child example
Superclass
class Person:
def __init__(self, name, id_no):
self.name = name
self.id_no = id_no
def Display(self): print(f"Person: {self.name}, ID: {self.id_no}")
Subclass
class stud(Person):
def __init__(self, name, id_no):
# Explicitly call the parent class constructor
Person.__init__(self, name, id_no)
def Print(self): print("stud class called")
Creating an object of Person
p = Person(“Akash”, 1001)
p.Display()
Creating an object of stud
student = stud(“Madan”, 103)
student.Print()
student.Display()
super function()
Creating an object of Person
built-in function => access methods and properties from a parent class => subclass.
* overridden method as well and parent method is required. => super()
Superclass
class Person:
def __init__(self, name, id_no):
self.name = name
self.id_no = id_no
def Display(self): print(f"Person: {self.name}, ID: {self.id_no}")
Subclass
class Student(Person): # More meaningful name
def __init__(self, name, id_no):
super().__init__(name, id_no) # Using super()
def Print(self): print("Student class called")
p = Person(“Akash”, 1001)
p.Display()
Creating an object of Student
student = Student(“Madan”, 103)
student.Print()
student.Display()
OVERIDDING
Parent Class
class Person:
def __init__(self, name):
self.name = name
def display(self): print(f"Name: {self.name}")
Child Class
class Employee(Person):
def __init__(self, name, salary):
super().__init__(name) # Call the parent class constructor
self.salary = salary
def display(self): super().display() # Call the parent class method print(f"Salary: {self.salary}")
Create an Employee object
emp = Employee(“Alice”, 50000)
emp.display()
# Call the display method from Employee (which overrides Person’s display)
OUTPUT:
Name: Alice
Salary: 50000
Multiple inheritance
Parent Class 1
class Person:
def __init__(self, name):
self.name = name
Parent Class 2
class Employee:
def __init__(self, salary):
self.salary = salary
Child Class inheriting from both Person and Employee
class Manager(Person, Employee):
def __init__(self, name, salary, department):
Person.__init__(self, name)
Employee.__init__(self, salary)
self.department = department
Create Manager object
manager = Manager(“Alice”, 75000, “HR”)
print(manager.name)
# From Person
print(manager.salary)
# From Employee
print(manager.department) # From Manager
OUTPUT:
Alice
75000
HR
Hierarchical inheritance
Parent Class
class Person:
def __init__(self, name):
self.name = name
def display_person(self): print(f"Name: {self.name}")
Child Class 1 inheriting from Person
class Employee(Person):
def __init__(self, name, salary):
super().__init__(name) # Call the Parent class constructor
self.salary = salary
def display_employee(self): print(f"Salary: {self.salary}")
Child Class 2 inheriting from Person
class Student(Person):
def __init__(self, name, grade):
super().__init__(name) # Call the Parent class constructor
self.grade = grade
def display_student(self): print(f"Grade: {self.grade}")
Create objects of Employee and Student
employee = Employee(“Alice”, 50000)
student = Student(“Bob”, “A”)
Call methods from the parent and child classes
employee.display_person() # From Person class
employee.display_employee() # From Employee class
student.display_person() # From Person class
student.display_student() # From Student class
OUTPUT
Name: Alice
Salary: 50000
Name: Bob
Grade: A
Hybrid inheritance
Parent Class 1
class Person:
def __init__(self, name):
self.name = name
def display_person(self): print(f"Name: {self.name}")
Parent Class 2
class Employee:
def __init__(self, salary):
self.salary = salary
def display_employee(self): print(f"Salary: {self.salary}")
Child Class 1 inheriting from Person (Single Inheritance)
class Manager(Person):
def __init__(self, name, department):
super().__init__(name)
self.department = department
def display_manager(self): print(f"Department: {self.department}")
Child Class 2 inheriting from both Person and Employee (Multiple Inheritance)
class Executive(Manager, Employee):
def __init__(self, name, salary, department, position):
Manager.__init__(self, name, department)
Employee.__init__(self, salary)
self.position = position
def display_executive(self): print(f"Position: {self.position}")
Create an object of Executive (Hybrid Inheritance)
exec = Executive(“John”, 100000, “Finance”, “CEO”)
Call methods from different levels and classes
exec.display_person() # From Person class
exec.display_manager() # From Manager class
exec.display_employee() # From Employee class
exec.display_executive() # From Executive class
Name: John
Department: Finance
Salary: 100000
Position: CEO
issubclass() and isinstance() methods
issubclass(sub, sup)
checks the btw the specified classes.
Returns True if the first class is the subclass of the second else False
isinstance(obj,class)
checks the relationship btw the objects and classes.
Returns True if the object is the instance of the specified class.
EX CODE
# Parent Class
class Person:
def __init__(self, name):
self.name = name
Child Class
class Employee(Person):
def __init__(self, name, salary):
super().__init__(name)
self.salary = salary
Create an object of Employee class
emp = Employee(“Alice”, 50000)
Using issubclass() to check class inheritance
print(issubclass(Employee, Person)) # True, Employee is a subclass of Person
print(issubclass(Person, Employee)) # False, Person is not a subclass of Employee
Using isinstance() to check if an object is an instance of a class
print(isinstance(emp, Employee)) # True, emp is an instance of Employee
print(isinstance(emp, Person)) # True, emp is also an instance of Person (since Employee inherits from Person)
print(isinstance(emp, object)) # True, everything is an instance of object in Python