0% found this document useful (0 votes)
142 views

Oops in Python

OOP stands for Object-Oriented Programming. Python is an object-oriented programming language where almost all code is implemented using classes. Object-oriented programming focuses on creating objects that contain both data and methods, while procedural programming focuses on writing procedures or methods that operate on data. Some key concepts of OOP include objects, classes, inheritance, polymorphism, abstraction, and encapsulation.

Uploaded by

Syed Salman
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
142 views

Oops in Python

OOP stands for Object-Oriented Programming. Python is an object-oriented programming language where almost all code is implemented using classes. Object-oriented programming focuses on creating objects that contain both data and methods, while procedural programming focuses on writing procedures or methods that operate on data. Some key concepts of OOP include objects, classes, inheritance, polymorphism, abstraction, and encapsulation.

Uploaded by

Syed Salman
Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 64

OOP stands for 

Object-Oriented Programming.

Python is an “object-oriented programming language.” This means that almost all the code is implemented using a
special construct called classes.

Procedural programming is about writing procedures or methods that perform operations on the data, while object-
oriented programming is about creating objects that contain both data and methods.
Object-oriented programming has several advantages over procedural programming:

 OOP is faster and easier to execute


 OOP provides a clear structure for the programs
 OOP helps to keep the Java code DRY "Don't Repeat Yourself", and makes the code easier to maintain, modify
and debug
 OOP makes it possible to create full reusable applications with less code and shorter development time
providing some concepts :

1. Object
2. Class
3. Inheritance
4. Polymorphism
5. Abstraction
6. Encapsulation
Classes and Objects
Classes and objects are the two main aspects of object-oriented programming.

Class Car objects


Volvo
attributes weight and color, Audi
methods drive and brake. Toyota

Class Fruit objects


Apple
Banana
Mango

A class is a template for objects ,  a "blueprint" for creating objects.


an object is an instance of a class.

When the individual objects are created, they inherit all the variables and methods from
the class.
Object − Objects have states and behaviors. Example: A dog has states - color, name, breed as
well as behavior such as wagging their tail, barking, eating. An object is an instance of a class.
Class − A class can be defined as a template/blueprint that describes the behavior/state that the
object of its type supports.
Methods − A method is basically a behavior. A class can contain many methods. It is in methods
where the logics are written, data is manipulated and all the actions are executed.
Instance Variables − Each object has its unique set of instance variables. An object's state is
created by the values assigned to these instance variables.
Major principles of object-oriented programming system are given below.

Object : The object is an entity that has state and behavior. It may be any real-world object like the mouse, keyboard, chair, table, pen, etc.

Class : A class is a blueprint for the objects. For example, Ram, Shyam, Steve, Rick are all objects so we can define a template (blueprint) class Human for these objects. The class can define the common attributes (properties) and behaviours of all the objects.

Method : The method is a function that is associated with an object. In Python, a method is not unique to class instances. Any object type can have methods.

Class

every class name in Python should start with a capital alphabet and the first alphabet of a multi-word name should be in the capital too. For eg : class MyFirstJavaClass

Every method defined in a Python class should be in lowercase and multi-word names are separated by an underscore. For eg : myMethodName()

Every instance variable defined in a Python class should be in lowercase and multi-word names are separated by an underscore.

 
Note :

• Python compiler doesn't throw a compile error if a class name starts with a lowercase
letter, but it's still better to follow the naming conventions defined in Python.
• Python is a case-sensitive language, hence a class containing two instance variables
with one named a and the other named A, are treated differently.

Class is a blueprint for the object to define a class keyword class is used : class classname
Object is instance of a class when class is defined only the description for the object is defined
therefore no memory/ storage is allocated for class.

Objname= classname() allocate memory ( instance means copy of the class with actual values)

Attributes(properties): attributes are the variables that belong to class they are always public can be
accessed using dot (.) operator.
Methods(behaviour): methods are functions defined inside the body of a class they are used to define the behaviour of an object

To acces attribute and method of a class the syntax is


Objectname.attributename
Objectname.methodname

to modify object properties


Objectname.attributename= value
P1.age =40

To delete object properties


del objectname.attributename
del p1.age

To delete object
del objectname
del p1
To create a class syntax
Class classname:
attributes
Methods

Eg class myclass:
x-=5
To create a object syntax
P1 =myclass ()
Print(p1.x)

Complete program

class myclass:
x-=5 attribute variable
P1 =myclass () object creation
Print(p1.x)
class myclass:
def _ _init_ _ (self ,name, age):
self.name = name
self.age = age
def myfun(self):
print(“name”, self.name)
print(“age”, self .age)
P1 = myclass( “sjc”,138)
P1.myfun()

Note : self is a reference parameter to the current instance of the class and is used to access variable
that belong to the class. It helps to differentiate between the methods and attribute of a class with
localyou can think of ‘self’ as the ‘this’ keyword which is used for the current object. It unhides the
current instance variable.’self’ mostly work like ‘this’ variable.
It has to be first parameter of any function in the class . It does not have to be named self it can be
anything
Class vect:
x=0.0
y=0.0
def set(self ,x,y): method definition
self.x= x
self.y =y
V= vect()
v.set(10,20) method call
Print(v.x,v.y)

Class variable and instance variable


Class variable are variable whose value is assigned within a class , class variables are for attributes and methods shared by all
instances of the class:
Instance variable are variable whose value is assigned inside with in a constructor or method instance variables are for data
unique to each instance
class std:
stream =“BCA” class variable shared by all the instance
def set(self, roll):
self.roll=roll instance variable unique for each instance
A=std()
B=std()
A.set(101)
B.set(102)
Print(A.stream)
Print(A.roll)
Print(B.stream) note: class variable can be accessed by classname also Print(std.stream)
Print(B.roll)
Features of a static variable

A static variable belongs to the class in which it is defined, hence, a static
variable is called a class variable.

Only one copy of a static variable is shared by all the objects of its class.

A static variable can be accessed by its class name with a dot operator or even by
an object of its class(only when there is no instance with the same name as a
static variable).
Accessing a static variable using its class name.

A static variable can be accessed directly by using the object of its class with a dot


operator, but with a condition that there should be no instance variable with the same
name. In this case, compiler internally replaces the object name with its class name.
Features of a static method

A static method belongs to the class in which it is defined.

Only one copy of a static method is shared by all the objects of its class.

A static method should be accessed by its class name with a dot operator, though in some
cases it can even be accessed by an object of its class.

Unlike the definition of a general method of a class, we do not have to specify self  word in the
parameters of a static method.
• We could create a static method of a class by using the @staticmethod decorator. This
decorator is placed just before we are going to define the static method in a class.

# Python - Defining a static method class Test:


# Defining a static method using @staticmethod decorator @staticmethod
Class Test:
def stat():
print('Hello from the static method')

we have not specified self  word in the parameters of a static method, because self refers to the
currently executing object, while a static method is a class method and has got nothing to do with its
objects.
A static method can be accessed directly by using its class name and a dot operator Test.stat().
Class variable also known as static variable
instance variable also known as non static variable : are different for different object (every object has a copy of it)

changing class member

class std:
stream =“cms”
def _ _ init_ _ (self , name , roll):
self.name = name
self.roll= roll
a= std(“xy” 10)
b=std(“ab”,20)

print (a.stream)
print(b.stream)

a.stream =“Bca” only object a will have value Bca


print (a.stream)
print(b.stream)

std.stream =”Bsc” all the object will have same value

print (a.stream)
print(b.stream)
Constructor
•_ _ init _ _ function
• all classes have a function called _ _init _ _ function is always created and executed when a class is initiated
• it is a method /constructor in python this method is automatically called to allocate memory when a new object /instance of a class is
created all classes have __ init __ method
It is used to assign value to object that necessary to do when the object is created

Syntax of constructor declaration :


def __init__(self):
# body of the constructor

For example
Class myclass: The constructor (init) is just another method. What is special about it, is
def _ _init_ _ (self ): that its called each time a new object is created.
self.name = “SJC” We create one object C with the line:
self.age = 123
P1 = myclass( )
Print (p1.name)
Print(p1.age)
In Python, internally, the __new__ is
the method that creates the object
constructors
Constructors are generally used for instantiating an object.
The task of constructors is to initialize(assign values) to the data members of the class when an object of
class is created. In Python the __init__() method is called the constructor and is always called when an
object is created.

There are a few rules for constructors:


•A constructor must be defined with the name __init__.
•A constructor must be defined with the self keyword in its parameters.
•A constructor cannot return any value, except None.
•Only one constructor is allowed for a class.
•Only for object initialization

Types of constructor
1. Default constructor : the default constructor is simple constructor which doesn’t accept any argument
its definition has only one argument which is a reference to the instance of the object
2. Parameterized constructor: constructor with parameter is known as parameterized constructor . The
parameterized constructor takes its first argument as a reference to the instance being constructor
known as self and other argument are provided by the programmer.
Default constructor
class bank:
def _ _init_ _(self):
self.balance =0
def withdraw(self , amount):
self.balance= amount
a= bank()
b=bank()
a.withdraw(100)
b.withdraw(200)
Print( “withdraw” : a.balance)
Print( “withdraw” : b.balance)
Parameterised constructor
Class rect():
def _ _ init _ _(self , bre , leng):
self .bre =bre
self. Leng =leng
def area(self):
return (self.bre * self.leng)
A=int(input(“enter breath of rectangle”)
B = int(input(“enter length of rectangle”)
obj = rect(A,B)
Print(“area”,obj.area())
Destructor
_ _ del _ _ method is known as a destructor method in python it called when all the reference of the object
have been deleted
Syntax def _ _ del _ _ (self)
Class std:
def _ _init _ _ (self):
print(“ object created”)
def _ _ del_ _ (self):
Print( “object destroyed”)
E=std()
del E
Inheritance  : Important aspects of Object Oriented Programming. While programming, many a times, situations
arise where we have to write a few classes with some common features and some unique, class-specific features,
which include both variables and methods.

we can take out the common part and put it in a separate class, and make all the other classes inherit this class, to use
its methods and variables, hence reducing re-writing the common features in every class, again and again.

The class which inherits another class is generally known as the Child class, while the class which is inherited by
other classes is called as the Parent class.

Syntax of inheritance

class BaseClass:
Body of base class
class DerivedClass(BaseClass):
Body of derived class

Derived class inherits features from the base class, adding new features to it. This results into re-usability of code.

When a subclass(derived class) inherits a base class, it inherits all the members of superclass(base class), such
as :Instance variables of the base class. Methods of the base class.
Types of Inheritance
Single Inheritance - When a class is inheriting a single class.

class X:
a = 10
b = 20.6
def method_x(self):
return 'class X’

class Y(X):
i = 30
def method_y(self):
return 'class Y’

ob_y = Y()
print('a = : ', ob_y.a)
print('b = : ', ob_y.b)
print(ob_y.method_x())
print('y = : ', ob_y.i)
print(ob_y.method_y())

a = : 10
b = : 20.6
class X
y = : 30
class Y
multiple inheritance which inherits the feature of two classes(inheriting more than one
class at a time)
class X:
a = 10
b = 20.6
def method_x(self):
return 'class X’
class Y:
i = 20
def method_y(self):
return 'class Y’

class Z(X,Y):
j = 30
def method_z(self):
return 'class Z’
Z ob_z = Z()
print('a = : ', ob_z.a)
print('b = : ', ob_z.b)
print(ob_z.method_x())
print('i = : ', ob_z.i)
print(ob_z.method_y())
print('j = : ', ob_z.j) print(ob_z.method_z())
a = : 10 b = : 20.6 class X i = : 20 class Y j = : 30 class Z
multilevel inheritance : When a class inherits the features of multiple classes(inheriting one class at a time), it is known
as multilevel inheritance

class X:
x1 = 10
def get_x1(self):
return self.x1
class Y(X):
y1 = 20
def get_y1(self):
return self.y1
class Z(Y):
z1 = 30
def get_z1(self):
return z1
ob = Z()
print('x1 = : ',ob.x1)
print('x1 = : ',ob.get_x1())
print('y1 = : ',ob.y1)
print('y1 = : ',ob.get_y1())
print('z1 = : ',ob.z1)
print('z1 = : ',ob.get_z1())

Output

x1 = : 10 x1 = : 10 y1 = : 20 y1 = : 20 z1 = : 30 z1 = : 30
class Base1:
Single Inheritance
pass
class Base2(Base1) :
pass

class Base1:
pass
class Base2(Base1) :
Multilevel Inheritance
pass
Class Base3(Base2):
pass

class Base1:
pass
class Base2: Multiple Inheritance
pass
class MultiDerived(Base1, Base2):
pass
The super() function in Python makes class inheritance more manageable and extensible. The function returns a
temporary object that allows reference to a parent class by the keyword super.

In Python, super() has two major use cases:


•Allows us to avoid using the base class name explicitly
•Working with Multiple Inheritance

class MyParentClass():
def __init__(self):
pass
class SubClass(MyParentClass):
def __init__(self):
super().__init__()
class A(object): class A(object):
def __init__(self): def __init__(self):
print(“Inside A inheritance”) print(‘Inside A inheritance’)

class B(object): class B(object):


def __init__(self): def __init__(self):
print(“Inside B inheritance”) print(‘Inside B inheritance’)

class C(A, B): class C(A, B):


def __init__(self): def __init__(self):
print(“Inside C inheritance”) print(‘Inside C inheritance’)
super().__init__() super().__init__()
B.__init__(self)

obj = C() obj = C()


Data abstraction in python

In most of the object-oriented languages access modifiers are used to limit the access to the variables and functions of a
class. 

Access modifiers is used to protect the data from unauthorized access as well as protecting it from getting manipulated.
When inheritance is implemented there is a huge risk for the data to get destroyed(manipulated) due to transfer of
unwanted data from the parent class to the child class. Therefore, it is very important to provide the right access
modifiers for different data members and member functions depending upon the requirements.

Most programming languages has three forms of access modifiers, which are Public, Protected and Private in a 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
behaviour of protected and private access specifiers.

A Class in Python has three types of access modifiers –

•Public Access Modifier


•Protected Access Modifier
•Private Access Modifier
Public : Any member can be accessed from outside the class. All members  are public by default. 
class std:
    def _ _init_ _(self, name, fee): 
           self.name=name 
            self.fee=fee

Protected : to make an instance variable protected is to add a prefix _ (single underscore) to it. This effectively prevents
it to be accessed, unless it is from within a sub-class. 
class std:
    def _ _init_ _(self, name, fee): 
           self._name=name 
            self._fee=fee
Private:  to make an instance variable private a  double underscore _ _ prefixed to a variable makes it private. These
members are only accessible from within the class. No outside  class Access is allowed.
class std:
    def _ _init_ _(self, name, fee): 
           self._ _name=name 
            self._ _ fee=fee
Protected
Protected variables and methods are very similar to private ones. You probably won't use
protected variables or methods very often, but it's still worth knowing what they are. A
variable that is protected can only be accessed by its own class and any classes derived
from it. That is more a topic for later, but just be aware that if you are using a class as the
basis of another one, protected variables may be a good option. Protected variables begin
with a single underscore.
# define parent class Company
class Company:
def __init__(self, name, proj):
self.name = name # name(name of company) is public
self._proj = proj # proj(current project) is protected

def show(self): # public function to show the details


print("The code of the company is = ",self.ccode)

class Emp(Company): # define child class Emp


def __init__(self, eName, sal, cName, proj):
# calling parent class constructor
Company.__init__(self, cName, proj)
self.name = eName # public member variable
self.__sal = sal # private member variable

def show_sal(self): # public function to show salary details


print("The salary of ",self.name," is ",self.__sal,)

c = Company("Stark Industries", "Mark 4") # creating instance of Company class


e = Emp("Steve", 9999999, c.name, c._proj) # creating instance of Employee class

print("Welcome to ", c.name)


print("Here",e.name,"is working on",e._proj)
# to show the value of __sal we have created a public function show_sal()
e.show_sal()
Polymorphism

Polymorphism is taken from the Greek words Poly (many) and morphism (forms). It
means that the same function name can be used for different types. This makes
programming more intuitive and easier.

Polymorphism is a very important concept in programming. It refers to the use of a single


type entity (method, operator or object) to represent different types in different scenarios.

We know that the + operator is used extensively in Python programs. But, it does not have
a single usage.
For integer data types, + operator is used to perform arithmetic addition operation.
num1 = 1
num2 = 2
print(num1+num2)

Similarly, for string data types, + operator is used to perform concatenation.


str1 = "Python"
str2 = "Programming"
print(str1+" "+str2)
example
len() being used for a string
print(len("geeks"))

len() being used for a list


print(len([10, 20, 30]))

print(len(["Python", "Java", "C"]))

print(len({"Name": "John", "Address": "Nepal"}))


•Polymorphism with class methods:

•A simple Python function


class Tomato():
     def type(self):
       print("Vegetable")
     def color(self):
•def add(x, y, z = 0):        print("Red")
class Apple():
• return x + y+z      def type(self):
       print("Fruit")
     def color(self):
       print("Red")
•print(add(2, 3))       
•print(add(2, 3, 4)) def func(obj):
       obj.type()
       obj.color()
        
obj_tomato = Tomato()
obj_apple = Apple()
func(obj_tomato)
func(obj_apple)
Here we have two types of polymorphism.
Method Overloading
Method Overriding

Method Overloading:
Method Overloading is the class having methods that are the same name with different arguments.
Arguments different will be based on a number of arguments and types of arguments.
It is used in a single class.
It is also used to write the code clarity as well as reduce complexity.

Method Overriding:
Method Overriding is the method having the same name with the same arguments.
It is implemented with inheritance also.
It mostly used for memory reducing processes.
Polymorphism with Inheritance
Polymorphism in python defines 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. Also, it is possible to modify a
method in a child class that it has inherited from the parent class.
This is mostly used in cases where the method inherited from the parent class doesn’t fit the child class. This
process of re-implementing a method in the child class is known as Method Overriding.

For example,

len("hello") # returns 5 as result


len([1,2,3,4,45,345,23,42]) # returns 8 as result
In this case the function len is polymorphic as it is taking string as input in the first case and is taking list as input
in the second case.
Python does not support method overloading, that is, it is not possible to define more than one
method with the same name in a class in python. This is because method arguments in python do
not have a type. A method accepting one argument can be called with an integer value, a string or
a double. Consider code sample below.

class OverloadDemo:   
          def method(self, a):
                  print(a)   
obj = OverloadDemo() 
obj.method(5) 
obj.method(‘sjc’) 
obj.method(10.2)

Same method works for 3 different data types. Thus, you cannot define two methods with the
same name and same number of arguments
Advantages of Method Overloading in Python.
•Normally methods are used to reduce complexity. Method overloading is even it reduce more complexity in the
program also improves the clarity of code.
•It is also used for reusability.

It has some disadvantages also when creating more confusion during the inheritance concepts.

class OverloadingDemo {
   methodOne(int a) {
Print(a)
}
   methodOne(String a) {

Print(a)

}
   methodOne(int a, String b) { }
   methodOne(double a) {}
}
when the arguments have different types but one might ask that overloading can also be performed when the
number of arguments are different. This is also not possible because python treats all the methods with the
same name as one method irrespective of the number of arguments. If there are more than one methods with
same name then last method definition will over write the other even if they have different number of
arguments, that is, python will only consider and the last method as if this is the only method present in the
class 

class OverloadDemo:  
def method(self):
print("No argument")  
def method(self, a):
print("One argument")
def method(self,a, b):
print("Two arguments")
obj = OverloadDemo()
obj.method(10)

This is because the last method over writes the other two methods and only one method is
present in the class which accepts two arguments but we supplied only 1 argument while
calling. If the number of arguments of the last method do not match the number of arguments
supplied while calling the method, it will be an error.
class OverloadDemo:
def default_arguments(self, a=‘sjc', b='the website’):
print(a)
print(b)  
obj = OverloadDemo()
obj.default_arguments('learning', 'python')
Operator Overloading
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.

For example
• + operator for different purposes.
print(1 + 2)
• concatenate two strings
print(“python"+"For")
• Product two numbers
print(3 * 4)
• Repeat the String
print("Geeks"*4)
class point:
def __init__(self,x,y):
self.x=x
self.y=x
p1=point(1,3)
p2=point(2,3)
p3=p1+p2
print(p3)

throws an error, because compiler don’t know how to add two objects. So we define a method for an
operator and that process is called operator overloading. 

We can overload all existing operators but we can’t create a new operator.

To perform operator overloading, Python provides some special function or magic function that is
automatically invoked when it is associated with that particular operator.

For example, when we use + operator, the magic method __add__ is automatically invoked in which the
operation for + operator is defined.
Overloading binary + operator in Python :
When we use an operator on user defined data types then automatically a special function or magic function
associated with that operator is invoked.
Changing the behavior of operator is as simple as changing the behavior of method or function.
You define methods in your class and operators work according to that behavior defined in methods.
# Python Program illustrate how
# to overload an binary + operator

class A:
def __init__(self, a):
self.a = a

# adding two objects


def __add__(self, o):
return self.a + o.a
ob1 = A(1)
ob2 = A(2)
ob3 = A(“python")
ob4 = A("For")
print(ob1 + ob2)
print(ob3 + ob4)
Python magic methods or special • Class function that begins with double underscore __ are
functions for operator overloading called special function
Binary Operators: • __init__() is eg for special function also known as magical
function

OPERATOR MAGIC METHOD


+ __add__(self, other)
– __sub__(self, other)
* __mul__(self, other)
/ __truediv__(self, other)
// __floordiv__(self, other)
% __mod__(self, other)
** __pow__(self, other)
Comparison Operators :

OPERATOR MAGIC METHOD


< __lt__(self, other)
> __gt__(self, other)
<= __le__(self, other)
>= __ge__(self, other)
== __eq__(self, other)
!= __ne__(self, other)
Assignment Operators :

OPERATOR MAGIC METHOD


-= __isub__(self, other)
+= __iadd__(self, other)
*= __imul__(self, other)
/= __idiv__(self, other)
//= __ifloordiv__(self, other)
%= __imod__(self, other)
**= __ipow__(self, other)
Module  
A module is a Python object with arbitrarily named attributes that you can bind and reference.
Simply, a module is a file consisting of Python code. A module can define functions, classes and variables. A
module can also include runnable code

Each python file is called a Module.

Modules can be either:


1. Built in
2. User-defined.
User-defined Modules

Create a Module
To create a module, create a Python file with a .py extension.

Call a Module
Modules created with a .py extension can be used in another Python source file,
using the import statement.
Built-in Modules
There are several built-in modules in Python, which you can import whenever you like.

Call a built-in Module


To call a built-in Module and use the function of that module write:
import moduleName #call a module
moduleName.function()#use module function
import math
print("The value of cosine is", math.cos(3))
print("The value of sine is", math.sin(3))
print("The value of tangent is", math.tan(3))
print("The value of pi is", math.pi)

Benefits of modules in Python


There are a couple of key benefits of creating and using a module in Python:
Structured Code
Code is logically organized by being grouped into one Python file which makes
development easier and less error-prone.
Code is easier to understand and use.
Reusability
Functionality defined in a single module can be easily reused by other parts of the
application. This eliminates the need to recreate duplicate code.
python package 
A python package is a collection of modules. Modules that are related to each other are mainly put in
the same package. When a module from an external package is required in a program, that package can
be imported and its modules can be put to use.

A package is a directory of Python modules that contains an additional __init__.py file, 


which distinguishes a package from a directory that is supposed to contain multiple Python scripts.
Packages can be nested to multiple depths if each corresponding directory contains its
own __init__.py file.
To create a package in Python, we need to follow these three simple steps:
1. Create a new folder named MyApp.
2. Inside MyApp, create a subfolder with the name 'mypackage'.
3. Create an empty __init__.py file in the mypackage folder.

First module
def SayHello(name):
print("Hello " + name)
return

Second module
def sum(x,y):
return x+y
def average(x,y):
return (x+y)/2
def power(x,y):
return x**y
Create test.py in the MyApp folder to test mypackage.

from mypackage import power, average, SayHello 


SayHello('john') 
x=power(3,2) 
print("power(3,2) : ", x) 

_init__.py
The package folder contains a special file called __init__.py, which stores the package's
content. It serves two purposes:
1.The Python interpreter recognizes a folder as the package if it contains __init__.py file.
2.__init__.py exposes specified resources from its modules to be imported.
3.An empty __init__.py file makes all functions from above modules available when this
package is imported. 
What’s Inheritance?
Inheritance models what is called an is a relationship. This means that when you
have a Derived class that inherits from a Base class, you created a relationship
where Derived is a specialized version of Base.

What’s Composition?
Composition is a concept that models a has a relationship. It enables creating
complex types by combining objects of other types

Class that references to one or more object of other classes as an instance variable

By using class name or by creating the object we can access the member of one
class inside another class

It enables creating complex types by combining object of different classes


when to use Inheritance and when to use Composition

Inheritance is used where a class wants to derive the nature of parent class and then
modify or extend the functionality of it. Inheritance will extend the functionality with
extra features allows overriding of methods

in the case of Composition, we can only use that class we can not modify or extend
the functionality of it. It will not provide extra features. Thus, when one needs to use
the class as it without any modification, the composition is recommended and when
one needs to change the behavior of the method in another class, then inheritance is
recommended.
Aggregation
Not to confuse, aggregation is a form of composition where objects are loosely coupled. There are not any
objects or classes owns another object. It just creates a reference. It means if you destroy the container, the
content still exists.

Aggregation vs composition

composition : if you delete the container object then all of its contents objects are also deleted.

Aggregation is a week form of composition. If you delete the container object contents objects can
live without container object.
lambda function 
A lambda function is a small anonymous function which returns an object.
The object returned by lambda is usually assigned to a variable or used as a part of other bigger functions.
Instead of the conventional def keyword used for creating functions, a lambda function is defined by using
the lambda keyword. The structure of lambda can be seen below:

The lambda function can have any number of arguments but there's always a single expression
after the : symbol. When this function is called, the expression is its return value.
square = lambda n : n*n
num = square(5)
print num

sub = lambda x, y : x-y
print(sub(5, 3))

it cannot substitute a function whose body may have conditionals, loops etc.

A lambda is much more readable than a full function since it can be written in-
line. Hence, it is a good practice to use lambdas when the function expression is
small.
The beauty of lambda functions lies in the fact that they return function objects
Static Methods
Static methods are methods that are related to a class but do not need to access any class-specific data.
There is no need to instantiate an instance because we can simply call this method. Static methods are
great for utility functions. They are totally self-contained and only work with data passed in as arguments.

This method is bound to the class but not to the object of the class.
This method can not access or modify class state.
This method is defined inside a class using @staticmethod decorator.
It does not receive any implicit first argument, neither self nor cls.
This method returns a static method of the function
Class Methods In Python
Class methods are methods that are related to a class and have access to all class-specific data. It uses
@classmethod, a built-in function decorator that gets evaluated after the function is defined. It returns
a class method function. It receives the cls parameter instead of self as the implicit first argument.

This method is also bound to the class but not to the object of the class.
This method can access the state of the class, therefore it can modify the class state that will be
applicable to all the instances.
This method is defined inside a class using @classmethod decorator.
It takes cls as a parameter that points to the class and not to the instance of the object.

You might also like