-
Notifications
You must be signed in to change notification settings - Fork 7
/
oop05 (inheritance and subclasses).py
249 lines (204 loc) · 8.66 KB
/
oop05 (inheritance and subclasses).py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# oop5
# inheritance and subclasses.
# inheritance allows us to inherit attributes and methods from a parent class.
# this is useful because we can create subclasses and get all the functionalitiey of our parent class,
# and then we can overwrite or add completely new functionality without affecting the parent class.
# now lets create different types of employees.
# lets say we wanted to create developers and managers.
# here we need to use subclasses.
class Employee:
raise_amount=1.04
def __init__(self,first,last,pay):
self.first=first
self.last=last
self.pay=pay
self.email=first+last+"@gmail.com"
def fullname(self):
return f"{self.first} {self.last}"
def apply_raise(self):
self.pay=int(self.pay*self.raise_amount)
# making a subclass
class Developer(Employee):# here we are using the parent class name in the brackets,
pass # to specify which parent class' functionality we want to inherit.
# here our subclass dont have any code of its own.
# but this subclass will have all the attributes methods of our Employee class.
dev_1=Employee("ahammad","shawki",200)
dev_2=Employee("cristiano","ronaldo",400)
print(dev_1.email)
print(dev_2.email)
# it will also work if we create our object in Developer subclass
dev_1=Developer("ahammad","shawki",200)
dev_2=Developer("cristiano","ronaldo",400)
print(dev_1.email)
print(dev_2.email)
# whats happen here?
# when we instantiated our Developers it first looked in our Developers subclass for constructer.
# and its not going to find it in our developer class because it's currently empty.
# so what python is going to do then is walk up a chain of inharitance until it finds what it is looking for.
# this chain called "method resoulution order"
# we can visulize that by help function.
#print(help(Developer))
# here we can see that-
"""Method resolution order:
Developer
Employee
builtins.object"""
# so when we create a new developer object it first look in our developer subclass for constructer.
# if it didn't find it there then search in the Employee parent class.
# if it didn't find it there also the last place that it would have looked is this bulitins.object class.
# every class in python inherits from this base bulitins.object class.
# we can also know by help method that our subclass has inherited all the variable and methods from the parent class for free.
# now we want to customize our subclass.
# and we are going to change the raise_amount for our developers.
# but first lets see what happens when we apply raise_amount function to our current developers.
print(dev_1.pay)
dev_1.apply_raise()
print(dev_1.pay)
# lets say we want our developers to raise amount of 1.10
# we can do this by-
class Developer2(Employee):
raise_amount=1.10
dev_3=Developer2("sergio","ramos",500)
print(dev_3.pay)
dev_3.apply_raise()
print(dev_3.pay)
# now python using our developer subclass raise amount instead of parent class raise amount.
# actually by changing the raise_amount and our subclass dont effect on any of our employee parent class.
# so we can make this kinds of changes of our subclasses without worrying about breaking anything in the parent class.
dev_4=Employee("gerath","bale",800)
print(dev_4.pay)
dev_4.apply_raise()
print(dev_4.pay)
# sometimes we need to initiate our subclasses with more information than our parent class can handle.
# lets say we want to add an extra attribute for our developers which is their main programming language.
# but currently our parent class doesn't contain that attribute.
# so to pass an additional attribute for our developers subclass, we need to give the subclass its own constructer.
# so we can do this-
class Developer3(Employee):
def __init__(self,first,last,pay,prog_lang):
super().__init__(first,last,pay)# here we don't need to write all the code like self.pay=pay etc.
self.prog_lang=prog_lang
# instead of doing that we will let our parent class to handle first, last and pay attribute.
# we will let developer to set the prog_lang attribute.
# so in order to let our parentclass to handle previous attribute, we can write-
# super().__init__(first,last,pay)
# here super is a function which allows us to do this.
# in the brackets after init we dont have to write "self" as our first arguement.
# now we have handled the prog_lang just like old tecnic.
dev_5=Developer3("luca","modrich",300,"ruby")
dev_6=Developer3("neymar","jr.",900,"java")
print(dev_5.email)
print(dev_5.prog_lang)
# lets make a new subclass called manager.
class Manager(Employee):
def __init__(self,first,last,pay,employees=None):# here we don't use empty list for our default employees value.
super().__init__(first,last,pay) # because we should not pass any mutable datatype like empty list or dictionary.
if employees is None: # instead of that we use None and do some extra coding to make sure that our code is error free.
self.employees=[]
else:
self.employees=employees
# add a employee
def add_emp(self, emp):
if emp not in self.employees:
self.employees.append(emp)
# remove a employee
def remove_emp(self, emp):
if emp in self.employees:
self.employees.remove(emp)
# print out the fullnames of employees
def print_emp(self):
for emp in self.employees:
print("-->",emp.fullname())
man_1=Manager("zinedin","zidane",100,[dev_1])
print(man_1.email)
man_1.add_emp(dev_2)
man_1.add_emp(dev_3)
man_1.remove_emp(dev_1)
man_1.print_emp()
# so now we know the importance of subclass?
# => here the code for all of our developers and managers is specific.
# => and they dont create problem with each other.
# => and we can inherit all the properties of parent class to our subclass by a single line of code.
# => so we are really getting reuse our code nicely here if we use subclasses.
# python has two buit_in function called isinstance and issubclass.
# is instance will tell us if an object is an instance/object of a class.
print(isinstance(man_1,Manager))
print(isinstance(man_1,Employee))
print(isinstance(man_1,Developer))
# here we need to enter two arguement.
# first one is the instance and the second is the class.
# is subclass will tell us if it is a subclass of a class.
print(issubclass(Developer,Employee))
print(issubclass(Manager,Employee))
print(issubclass(Manager,Developer))
# here we need to enter two arguement.
# first one is the subclass and the second is the parent class.
# there is also an important function called hasatter().
# it is used to see if a class or a object has certain properties or not.
# for example, if we want to see if Manager class has the add_emp() method or not, we can-
print(hasattr(Manager,"add_emp")) # NOTE: we have to pass the name of the property in string.
# we can also use this method with object instead of class,
print(hasattr(man_1,"remove_emp"))
# Extra tip:
# types of inheritance:
# single:
# when a inheritance involves one child class and one parent class only.
# multiple:
# when a inheritance involves more than one parent class.
# multilevel:
# the child class acts as a parant class for another parent class.
# hierarchical:
# it involvs multiple hybrid inheritance form the same parent class. it spreads like a tree.
# hybrid:
# it involves more than one type of inheritance.
# code:
# single:
class Parent:
def func1(self):
print("A function from the parent class")
class Child(Parent):
def func2(self):
print("A function from the child class")
# multiple:
class Parent1:
def func3(self):
print("A function from the parent1 class")
class Parent2:
def func4(self):
print("A function from the parent2 class")
class Child1(Parent1, Parent2):
def func5(self):
print("A function from the child1 class")
# multilevel:
class Parent3:
def func6(self):
print("A function from the parent3 class")
class Child2(Parent3):
def func7(self):
print("A function from the child2 class")
class Child3(Child2):
def func8(self):
print("A function from the child3 class which is a of child2 class")
# hierarchical(basic):
class Parent4:
def func6(self):
print("A function from the parent4 class")
class Child4(Parent4):
def func7(self):
print("A function from the child4 class")
class Child5(Parent4):
def func8(self):
print("A function from the child5 class")
# hierarchical(hybrid):
class Parent5:
def func9(self):
pass
class Child6(Parent5):
def func10(self):
pass
class Child7(Parent5):
def func11(self):
pass
class Child8(Child6,Child7):
def func12(self):
pass