Basic OOPS and Advanced Concepts#

[1]:
class Employee:
    number_of_leaves = 8

    def  __init__(self,name,salary,role):
        self.name = name
        self.salary = salary
        self.role = role

    def print_details(self):

        print(f"""
        NAME     :{self.name}
        SALARY   :{self.salary}
        ROLE     :{self.role}
        """)

    @classmethod
    def change_leaves(cls,new_leaves):
        cls.number_of_leaves = new_leaves

    @classmethod
    def instance_with_dash(cls,params_dash):
        return cls(*params_dash.split("-"))

    @staticmethod
    def print_good_string(string):
        print(f"this is a good string : {string}")




e1 = Employee("E1",45000,"t1")
e2 = Employee.instance_with_dash("E2-50000-t2")

e1.print_details()
e2.print_details()

print(e1.number_of_leaves, e2.number_of_leaves)

e1.number_of_leaves = 10
print(e1.number_of_leaves)

Employee.number_of_leaves = 20

print(e1.number_of_leaves, e2.number_of_leaves)

e2.number_of_leaves = 10
print(Employee.number_of_leaves, e2.number_of_leaves)

e1.print_good_string("good1")
Employee.print_good_string("good2")

        NAME     :E1
        SALARY   :45000
        ROLE     :t1


        NAME     :E2
        SALARY   :50000
        ROLE     :t2

8 8
10
10 20
20 10
this is a good string : good1
this is a good string : good2

Single Inheritance#

[2]:
class Player:
    def __init__(self,name,sports):
        self.name = name
        self.sports = sports

    def print_details(self):
        print(f"""
        NAME     :{self.name}
        SPORTS   :{self.sports}
        """)

without super()#

[3]:
class Programmer(Employee,Player):

    language ="C++"

    def print_language(self):
        print(self.language)

p1 = Programmer("e1",35000,"P1")
p1.print_details()
p1.print_language()

        NAME     :e1
        SALARY   :35000
        ROLE     :P1

C++
[4]:
# sequence matters

## this will throw error
class Programmer(Player,Employee):

    language ="C++"

    def print_language(self):
        print(self.language)

p1 = Programmer("e1",35000,"P1")
p1.print_details()
p1.print_language()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-a25debaa34a6> in <module>
      9         print(self.language)
     10
---> 11 p1 = Programmer("e1",35000,"P1")
     12 p1.print_details()
     13 p1.print_language()

TypeError: __init__() takes 3 positional arguments but 4 were given
[5]:
class Programmer(Player,Employee):

    language ="C++"

    def print_language(self):
        print(self.language)

p1 = Programmer("e1",["tennis","cricket"])
p1.print_details()
p1.print_language()

        NAME     :e1
        SPORTS   :['tennis', 'cricket']

C++

Heirarchy in descending order#

instance variable child > child class variable > parent instance variable > parent class variable

[6]:
class A:
    classvar1 = "this is a class vairable in class A"

    def __init__(self):
        self.classvar1 = "this is a instance variable in class A"

class B(A):
    classvar1 = "this is a class variable in class B"

    def __init__(self):
        self.classvar1 = "this is a instance variable in class B"



b = B()

b.classvar1

[6]:
'this is a instance variable in class B'
[7]:
class A:
    classvar1 = "this is a class vairable in class A"

    def __init__(self):
        self.classvar1 = "this is a instance variable in class A"

class B(A):
    classvar1 = "this is a class variable in class B"

#     def __init__(self):
#         self.classvar1 = "this is a instance variable in class B"



b = B()

b.classvar1

[7]:
'this is a instance variable in class A'
[8]:
class A:
    classvar1 = "this is a class vairable in class A"

#     def __init__(self):
#         self.classvar1 = "this is a instance variable in class A"

class B(A):
    classvar1 = "this is a class variable in class B"

#     def __init__(self):
#         self.classvar1 = "this is a instance variable in class B"



b = B()

b.classvar1

[8]:
'this is a class variable in class B'
[9]:
class A:
    classvar1 = "this is a class vairable in class A"

#     def __init__(self):
#         self.classvar1 = "this is a instance variable in class A"

class B(A):
    pass
#     classvar1 = "this is a class variable in class B"

#     def __init__(self):
#         self.classvar1 = "this is a instance variable in class B"



b = B()

b.classvar1

[9]:
'this is a class vairable in class A'

super()#

[10]:
class A:
    classvar1 = "this is a class vairable in class A"

    def __init__(self):
        self.var1 = "this is a instance variable in class A"

class B(A):
    classvar1 = "this is a class variable in class B"

    def __init__(self):
        super().__init__()
        self.var1 = "this is a instance variable in class B" ## overridden by child



b = B()

b.var1

[10]:
'this is a instance variable in class B'
[11]:
class A:
    classvar1 = "this is a class vairable in class A"

    def __init__(self):
        self.var1 = "this is a instance variable in class A"

class B(A):
    classvar1 = "this is a class variable in class B"

    def __init__(self):
        self.var1 = "this is a instance variable in class B"
        super().__init__() ## overridden by parent


b = B()

b.var1

[11]:
'this is a instance variable in class A'

Diamond shape problem#

-----------> A <-----------
|                         |
|                         |
|                         |
B                         C
^                         ^
|                         |
|                         |
|                         |
------------`D`------------
[12]:
class A:
    def func(self):
        print("CLASS A")
class B(A):
    def func(self):
        print("CLASS B")
class C(A):
    def func(self):
        print("CLASS C")
class D(B,C):
    pass

a = A()
b = B()
c = C()
d = D()

d.func()
CLASS B
[13]:

class A: def func(self): print("CLASS A") class B(A): def func(self): print("CLASS B") class C(A): def func(self): print("CLASS C") class D(C,B): pass a = A() b = B() c = C() d = D() d.func()
CLASS C

Operator overloading and dunder methods(underscore methods)#

https://docs.python.org/3/library/operator.html

[14]:
class Employee:
    number_of_leaves = 8

    def  __init__(self,name,salary,role):
        self.name = name
        self.salary = salary
        self.role = role

    def print_details(self):

        print(f"""
        NAME     :{self.name}
        SALARY   :{self.salary}
        ROLE     :{self.role}
        """)

    @classmethod
    def change_leaves(cls,new_leaves):
        cls.number_of_leaves = new_leaves

    @classmethod
    def instance_with_dash(cls,params_dash):
        return cls(*params_dash.split("-"))

    @staticmethod
    def print_good_string(string):
        print(f"this is a good string : {string}")

e1 = Employee("E1",45000,"SDE1")
e2 = Employee("E2",40000,"SDE1")

print(e1+e2)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-42bd4dc47e1e> in <module>
     30 e2 = Employee("E2",40000,"SDE1")
     31
---> 32 print(e1+e2)

TypeError: unsupported operand type(s) for +: 'Employee' and 'Employee'
[15]:
class Employee:
    number_of_leaves = 8

    def  __init__(self,name,salary,role):
        self.name = name
        self.salary = salary
        self.role = role

    def print_details(self):

        print(f"""
        NAME     :{self.name}
        SALARY   :{self.salary}
        ROLE     :{self.role}
        """)

    @classmethod
    def change_leaves(cls,new_leaves):
        cls.number_of_leaves = new_leaves

    @classmethod
    def instance_with_dash(cls,params_dash):
        return cls(*params_dash.split("-"))

    @staticmethod
    def print_good_string(string):
        print(f"this is a good string : {string}")

    ## create a dunder method to work on add operation

    def __add__(self,other):

        return self.salary + other.salary

    def __truediv__(self,other):

        return self.salary / other.salary

e1 = Employee("E1",45000,"SDE1")
e2 = Employee("E2",40000,"SDE1")

print(e1+e2)
print(e1/e2)
85000
1.125
[16]:
dir(Employee)
[16]:
['__add__',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__truediv__',
 '__weakref__',
 'change_leaves',
 'instance_with_dash',
 'number_of_leaves',
 'print_details',
 'print_good_string']
[17]:
e1 ## here it is showing default
[17]:
<__main__.Employee at 0x7f747cba7370>
[18]:
class Employee:
    number_of_leaves = 8

    def  __init__(self,name,salary,role):
        self.name = name
        self.salary = salary
        self.role = role

    def print_details(self):

        print(f"""
        NAME     :{self.name}
        SALARY   :{self.salary}
        ROLE     :{self.role}
        """)

    @classmethod
    def change_leaves(cls,new_leaves):
        cls.number_of_leaves = new_leaves

    @classmethod
    def instance_with_dash(cls,params_dash):
        return cls(*params_dash.split("-"))

    @staticmethod
    def print_good_string(string):
        print(f"this is a good string : {string}")

    ## create a dunder method to work on add operation

    def __add__(self,other):

        return self.salary + other.salary

    def __truediv__(self,other):

        return self.salary / other.salary

    def __repr__(self):
        return f"""Employee('{self.name}',{self.salary},'{self.role}')"""

e1 = Employee("E1",45000,"SDE1")
e2 = Employee("E2",40000,"SDE1")

print(e1)
print(repr(e1)) ##overridden repr
Employee('E1',45000,'SDE1')
Employee('E1',45000,'SDE1')
[19]:
class Employee:
    number_of_leaves = 8

    def  __init__(self,name,salary,role):
        self.name = name
        self.salary = salary
        self.role = role

    def print_details(self):

        print(f"""
        NAME     :{self.name}
        SALARY   :{self.salary}
        ROLE     :{self.role}
        """)

    @classmethod
    def change_leaves(cls,new_leaves):
        cls.number_of_leaves = new_leaves

    @classmethod
    def instance_with_dash(cls,params_dash):
        return cls(*params_dash.split("-"))

    @staticmethod
    def print_good_string(string):
        print(f"this is a good string : {string}")

    ## create a dunder method to work on add operation

    def __add__(self,other):

        return self.salary + other.salary

    def __truediv__(self,other):

        return self.salary / other.salary

    def __repr__(self):
        return f"""Employee('{self.name}',{self.salary},'{self.role}')"""

    def __str__(self):
        return f"""
        NAME     :{self.name}
        SALARY   :{self.salary}
        ROLE     :{self.role}
        """

e1 = Employee("E1",45000,"SDE1")
e2 = Employee("E2",40000,"SDE1")

print(e1) ##overridden repr with str
print(str(e1))
print(repr(e1))

        NAME     :E1
        SALARY   :45000
        ROLE     :SDE1


        NAME     :E1
        SALARY   :45000
        ROLE     :SDE1

Employee('E1',45000,'SDE1')

Abstract Base Class#

[20]:
from abc import ABC,abstractmethod

class Shape(ABC):

    @abstractmethod
    def print_area(self):
        pass
[21]:
class Square(Shape):

    def __init__(self,side):
        self.side = side


s = Square(10)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-21-1c588ec6d471> in <module>
      5
      6
----> 7 s = Square(10)

TypeError: Can't instantiate abstract class Square with abstract methods print_area
[22]:
class Square(Shape):

    def __init__(self,side):
        self.side = side

    def print_area(self):

        print(self.side**2)


s = Square(10)
s.print_area()
100
[23]:
Shape() # cant create
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-23-c85743fd6946> in <module>
----> 1 Shape() # cant create

TypeError: Can't instantiate abstract class Shape with abstract methods print_area

setter and property decorator#

[24]:
class Subscriber:

    def __init__(self,fname,lname):
        self.fname = fname
        self.lname = lname
        self._email = f"{self.fname}.{self.lname}@faloola.com"

    def name(self):
        return f"{self.fname} {self.lname}"

    def email(self):
        return self._email
s = Subscriber("Nishant","Maheshwari")
print(s.name())
print(s.email())

s.lname = "Baheti"
print(s.name())
print(s.email()) ## email will not change
Nishant Maheshwari
Nishant.Maheshwari@faloola.com
Nishant Baheti
Nishant.Maheshwari@faloola.com
[25]:
class Subscriber:

    def __init__(self,fname,lname):
        self.fname = fname
        self.lname = lname
#         self.email = f"{self.fname}.{self.lname}@faloola.com"

    def name(self):
        return f"{self.fname} {self.lname}"

    @property
    def email(self):
        return f"{self.fname}.{self.lname}@faloola.com"

s = Subscriber("Nishant","Maheshwari")
print(s.name())
print(s.email())

s.lname = "Baheti"
print(s.name())
print(s.email()) ## email changed but set as property which is not callable
Nishant Maheshwari
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-8f6b2bf645ba> in <module>
     15 s = Subscriber("Nishant","Maheshwari")
     16 print(s.name())
---> 17 print(s.email())
     18
     19 s.lname = "Baheti"

TypeError: 'str' object is not callable
[26]:
class Subscriber:

    def __init__(self,fname,lname):
        self.fname = fname
        self.lname = lname
#         self.email = f"{self.fname}.{self.lname}@faloola.com"

    def name(self):
        return f"{self.fname} {self.lname}"

    @property
    def email(self):
        return f"{self.fname}.{self.lname}@faloola.com"

s = Subscriber("Nishant","Maheshwari")
print(s.name())
print(s.email)

s.lname = "Baheti"
print(s.name())
print(s.email) ## email changed
Nishant Maheshwari
Nishant.Maheshwari@faloola.com
Nishant Baheti
Nishant.Baheti@faloola.com
[27]:
class Subscriber:

    def __init__(self,fname,lname):
        self.fname = fname
        self.lname = lname
#         self.email = f"{self.fname}.{self.lname}@faloola.com"

    def name(self):
        return f"{self.fname} {self.lname}"

    @property
    def email(self):
        return f"{self.fname}.{self.lname}@faloola.com"

s = Subscriber("Nishant","Maheshwari")
print(s.name())
print(s.email)

s.lname = "Baheti"
print(s.name())
print(s.email) ## email changed

s.email = "foo.bar@faloola.com"
Nishant Maheshwari
Nishant.Maheshwari@faloola.com
Nishant Baheti
Nishant.Baheti@faloola.com
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-27-efecc42b7d81> in <module>
     21 print(s.email) ## email changed
     22
---> 23 s.email = "foo.bar@faloola.com"

AttributeError: can't set attribute
[28]:
class Subscriber:

    def __init__(self,fname,lname):
        self.fname = fname
        self.lname = lname

    def name(self):
        return f"{self.fname} {self.lname}"

    @property
    def email(self):
        return f"{self.fname}.{self.lname}@faloola.com"

    @email.setter
    def email(self,new_email):
        name = new_email.split("@")[0]
        name_l = name.split(".")
        self.fname =  name_l[0]
        self.lname = name_l[1]



s = Subscriber("Nishant","Maheshwari")
print(s.name())
print(s.email)

s.lname = "Baheti"
print(s.name())
print(s.email) ## email changed


s.email = "foo.bar@faloola.com"
print(s.name())
print(s.email)
Nishant Maheshwari
Nishant.Maheshwari@faloola.com
Nishant Baheti
Nishant.Baheti@faloola.com
foo bar
foo.bar@faloola.com
[29]:
class Subscriber:

    def __init__(self,fname,lname):
        self.fname = fname
        self.lname = lname

    def name(self):
        return f"{self.fname} {self.lname}"

    @property
    def email(self):
        if self.fname is None or self.lname is None:
            return "Email is not set"
        return f"{self.fname}.{self.lname}@faloola.com"

    @email.setter
    def email(self,new_email):
        name = new_email.split("@")[0]
        name_l = name.split(".")
        self.fname =  name_l[0]
        self.lname = name_l[1]

    @email.deleter
    def email(self):
        self.fname = None
        self.lname = None

s = Subscriber("Nishant","Maheshwari")
print(s.name())
print(s.email)

s.lname = "Baheti"
print(s.name())
print(s.email) ## email changed


s.email = "foo.bar@faloola.com"
print(s.name())
print(s.email)

del s.email
print(s.email)

Nishant Maheshwari
Nishant.Maheshwari@faloola.com
Nishant Baheti
Nishant.Baheti@faloola.com
foo bar
foo.bar@faloola.com
Email is not set

Object instrospection#

[30]:
type("hello")
[30]:
str
[32]:
id("hello")
[32]:
140138285539376
[33]:
dir("hello")
[33]:
['__add__',
 '__class__',
 '__contains__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getitem__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__iter__',
 '__le__',
 '__len__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rmod__',
 '__rmul__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'capitalize',
 'casefold',
 'center',
 'count',
 'encode',
 'endswith',
 'expandtabs',
 'find',
 'format',
 'format_map',
 'index',
 'isalnum',
 'isalpha',
 'isascii',
 'isdecimal',
 'isdigit',
 'isidentifier',
 'islower',
 'isnumeric',
 'isprintable',
 'isspace',
 'istitle',
 'isupper',
 'join',
 'ljust',
 'lower',
 'lstrip',
 'maketrans',
 'partition',
 'replace',
 'rfind',
 'rindex',
 'rjust',
 'rpartition',
 'rsplit',
 'rstrip',
 'split',
 'splitlines',
 'startswith',
 'strip',
 'swapcase',
 'title',
 'translate',
 'upper',
 'zfill']
[35]:
import inspect

print(inspect.getmembers("Hello"))
[('__add__', <method-wrapper '__add__' of str object at 0x7f747d454270>), ('__class__', <class 'str'>), ('__contains__', <method-wrapper '__contains__' of str object at 0x7f747d454270>), ('__delattr__', <method-wrapper '__delattr__' of str object at 0x7f747d454270>), ('__dir__', <built-in method __dir__ of str object at 0x7f747d454270>), ('__doc__', "str(object='') -> str\nstr(bytes_or_buffer[, encoding[, errors]]) -> str\n\nCreate a new string object from the given object. If encoding or\nerrors is specified, then the object must expose a data buffer\nthat will be decoded using the given encoding and error handler.\nOtherwise, returns the result of object.__str__() (if defined)\nor repr(object).\nencoding defaults to sys.getdefaultencoding().\nerrors defaults to 'strict'."), ('__eq__', <method-wrapper '__eq__' of str object at 0x7f747d454270>), ('__format__', <built-in method __format__ of str object at 0x7f747d454270>), ('__ge__', <method-wrapper '__ge__' of str object at 0x7f747d454270>), ('__getattribute__', <method-wrapper '__getattribute__' of str object at 0x7f747d454270>), ('__getitem__', <method-wrapper '__getitem__' of str object at 0x7f747d454270>), ('__getnewargs__', <built-in method __getnewargs__ of str object at 0x7f747d454270>), ('__gt__', <method-wrapper '__gt__' of str object at 0x7f747d454270>), ('__hash__', <method-wrapper '__hash__' of str object at 0x7f747d454270>), ('__init__', <method-wrapper '__init__' of str object at 0x7f747d454270>), ('__init_subclass__', <built-in method __init_subclass__ of type object at 0x557127ed5780>), ('__iter__', <method-wrapper '__iter__' of str object at 0x7f747d454270>), ('__le__', <method-wrapper '__le__' of str object at 0x7f747d454270>), ('__len__', <method-wrapper '__len__' of str object at 0x7f747d454270>), ('__lt__', <method-wrapper '__lt__' of str object at 0x7f747d454270>), ('__mod__', <method-wrapper '__mod__' of str object at 0x7f747d454270>), ('__mul__', <method-wrapper '__mul__' of str object at 0x7f747d454270>), ('__ne__', <method-wrapper '__ne__' of str object at 0x7f747d454270>), ('__new__', <built-in method __new__ of type object at 0x557127ed5780>), ('__reduce__', <built-in method __reduce__ of str object at 0x7f747d454270>), ('__reduce_ex__', <built-in method __reduce_ex__ of str object at 0x7f747d454270>), ('__repr__', <method-wrapper '__repr__' of str object at 0x7f747d454270>), ('__rmod__', <method-wrapper '__rmod__' of str object at 0x7f747d454270>), ('__rmul__', <method-wrapper '__rmul__' of str object at 0x7f747d454270>), ('__setattr__', <method-wrapper '__setattr__' of str object at 0x7f747d454270>), ('__sizeof__', <built-in method __sizeof__ of str object at 0x7f747d454270>), ('__str__', <method-wrapper '__str__' of str object at 0x7f747d454270>), ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x557127ed5780>), ('capitalize', <built-in method capitalize of str object at 0x7f747d454270>), ('casefold', <built-in method casefold of str object at 0x7f747d454270>), ('center', <built-in method center of str object at 0x7f747d454270>), ('count', <built-in method count of str object at 0x7f747d454270>), ('encode', <built-in method encode of str object at 0x7f747d454270>), ('endswith', <built-in method endswith of str object at 0x7f747d454270>), ('expandtabs', <built-in method expandtabs of str object at 0x7f747d454270>), ('find', <built-in method find of str object at 0x7f747d454270>), ('format', <built-in method format of str object at 0x7f747d454270>), ('format_map', <built-in method format_map of str object at 0x7f747d454270>), ('index', <built-in method index of str object at 0x7f747d454270>), ('isalnum', <built-in method isalnum of str object at 0x7f747d454270>), ('isalpha', <built-in method isalpha of str object at 0x7f747d454270>), ('isascii', <built-in method isascii of str object at 0x7f747d454270>), ('isdecimal', <built-in method isdecimal of str object at 0x7f747d454270>), ('isdigit', <built-in method isdigit of str object at 0x7f747d454270>), ('isidentifier', <built-in method isidentifier of str object at 0x7f747d454270>), ('islower', <built-in method islower of str object at 0x7f747d454270>), ('isnumeric', <built-in method isnumeric of str object at 0x7f747d454270>), ('isprintable', <built-in method isprintable of str object at 0x7f747d454270>), ('isspace', <built-in method isspace of str object at 0x7f747d454270>), ('istitle', <built-in method istitle of str object at 0x7f747d454270>), ('isupper', <built-in method isupper of str object at 0x7f747d454270>), ('join', <built-in method join of str object at 0x7f747d454270>), ('ljust', <built-in method ljust of str object at 0x7f747d454270>), ('lower', <built-in method lower of str object at 0x7f747d454270>), ('lstrip', <built-in method lstrip of str object at 0x7f747d454270>), ('maketrans', <built-in method maketrans of type object at 0x557127ed5780>), ('partition', <built-in method partition of str object at 0x7f747d454270>), ('replace', <built-in method replace of str object at 0x7f747d454270>), ('rfind', <built-in method rfind of str object at 0x7f747d454270>), ('rindex', <built-in method rindex of str object at 0x7f747d454270>), ('rjust', <built-in method rjust of str object at 0x7f747d454270>), ('rpartition', <built-in method rpartition of str object at 0x7f747d454270>), ('rsplit', <built-in method rsplit of str object at 0x7f747d454270>), ('rstrip', <built-in method rstrip of str object at 0x7f747d454270>), ('split', <built-in method split of str object at 0x7f747d454270>), ('splitlines', <built-in method splitlines of str object at 0x7f747d454270>), ('startswith', <built-in method startswith of str object at 0x7f747d454270>), ('strip', <built-in method strip of str object at 0x7f747d454270>), ('swapcase', <built-in method swapcase of str object at 0x7f747d454270>), ('title', <built-in method title of str object at 0x7f747d454270>), ('translate', <built-in method translate of str object at 0x7f747d454270>), ('upper', <built-in method upper of str object at 0x7f747d454270>), ('zfill', <built-in method zfill of str object at 0x7f747d454270>)]
[ ]: