#!/usr/bin/env python
# coding: utf-8
# # Inheritance
#
#
# - http://openbookproject.net/thinkcs/python/english3e/inheritance.html
# - https://www.python-course.eu/python3_inheritance.php
#
# - powerful feature that facilitates code reuse mimicking real-world phenomena
# - ability to define a new class (child) that is modified version of an existing class (parent)
# - can add new methods and properties to a class without modifying the existing class
# - some imitation(s) if inheritance:
# - can make programs difficult to read
# - when method is invoked, it is sometimes not clear where to find its definition esp. in a large project relevant code may be scattered among several modules
# - see better example (a hand of cards) in the text
# - syntax:
# ```
# class childClassName(parentClass1, baseClass2, ...):
# #code (attributes and methods)
# pass
# ```
# ## Single Inheritance
# - supported by almost all OOP langauges
#
# In[116]:
# by dafault all python class implicitly inherit from object base class
class A(object):
def __init__(self):
self.a = "A"
def printMe(self):
print("A's printMe called!")
print('a = {}'.format(self.a))
def sayHi(self):
print('{} says HI!'.format(self.a))
# In[117]:
obja = A()
obja.printMe()
obja.sayHi()
# In[118]:
# single inheritance
class B(A):
def __init__(self):
# must explictly invoke base classes constructors
# to inherit properties/attributes
A.__init__(self) # try commenting this out
self.b = 'B'
def update(self):
print("Attributes before modifaction: {} and {}".format(self.a, self.b))
self.a = 'AAA' #can modify inherited attributes
print("Attributes after modification: {} and {}".format(self.a, self.b))
# overrides inherited printMe
def printMe(self):
print("B's printMe called")
print('a = {}'.format(self.a))
# In[119]:
objb = B()
# shows that A's properties are inherited by B
objb.update()
# In[121]:
# object a's properties are independent from object b's properties
print("obja's property a = {}".format(obja.a))
print("objb's property a = {}".format(objb.a))
# In[122]:
# B inherits A's sayHi()
# what is the output of the following?
objb.sayHi()
# ## Overriding
# - child class can redefine method that are inherited from parent class with the same name
# - e.g., printMe() method in class B overrides A's printMe
# - A's printme can still be called
# - syntax
# ```
# ClassName.method(object)
# ```
# In[123]:
objb.printMe()
# In[124]:
A.printMe(obja)
# In[125]:
A.printMe(objb)
# In[130]:
# C inherits from B which inherits from A
class C(B):
def __init__(self):
B.__init__(self)
self.c = 'C'
def printMe(self):
print("C's printMe called:")
print("Attributes are {}, {}, {}".format(self.c, self.b, self.a))
# In[131]:
c1 = C()
c1.printMe()
# In[132]:
# sayHi() inherited from A
c1.sayHi()
# In[129]:
c1.update()
# ## Multiple Inheritance
# - Python allows a class to derive/inherit from multiple base classes
# - similar to C++
# - Java doesn't allow it (it's messy!)
#
# In[153]:
# not required to explictly inherit from object class
class D:
def __init__(self):
self.a = 'AAAAA'
self.d = 'D'
def scream(self):
print("D's scream() called:")
# class E inherits from class C and D
class E(C, D):
def __init__(self):
# the order in which the base constructors are called matters!
# same attributes of proior constructors are overridden by later constructors
# e.g., try switching D and C's construntor calls
D.__init__(self)
C.__init__(self)
self.e = 'E'
def printMe(self):
print("E's printMe called:")
print("Attributes are {}, {}, {}, {}, {}".format(self.e, self.d, self.c, self.b, self.a))
# In[154]:
e1 = E()
e1.printMe()
# In[155]:
e1.scream()
# In[156]:
e1.sayHi()
# ## abc module - Abstract Base Classes
# - allows to define ABCs with abstract methods @abstractmethod decorators
# In[ ]: