-
Notifications
You must be signed in to change notification settings - Fork 8
/
python_oops_concept.py
346 lines (283 loc) · 8.64 KB
/
python_oops_concept.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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
class Sample:
full_name = "Arun Arunisto" #This is class attribute
def __init__(self, name): #Initializing/creating instance attribute
self.name = name #instance attribute
obj = Sample("Arun") #Instantiate means creating an object using class
#object can access class attribute and instance attribute like below
print(obj.name) #instance attribute
print(obj.full_name) #class attribute
#you can change attributes value dynamically
obj.name = "Arun Arunisto"
obj.full_name = "Arun"
print(obj.name)
print(obj.full_name)
#so the objects are mutable because it can change dynamically
#### Dunder method __str__ representation ###
class Person:
species = "Homosapiens"
def __init__(self, name, age):
self.name = name
self.age = age
#instance method
def details(self):
return f"Name : {self.name} | Age:{self.age}"
#Another instance method
def name_change(self, name):
self.name = name
#this will represent the object
def __str__(self):
return f"Name: {self.name}"
per1 = Person("Arun", 25)
print(per1.details())
per1.name_change("Arun Arunisto")
print(per1.details())
print(per1) #it will return a crytic type output shows where object saves in memory
#but you can set the print option of an object by creating a __str__() method
print(per1) #printing after the __str__() method
#these type of methods (__init__, __str__) are called dunder methods
#there are lots of dunder methods -> understanding the dunder methods is one of the key for mastering in oops
### Name Mangling ###
class Sample:
#name mangling is the process declaring methods/attributes public, non-public, or private
def __init__(self, name):
self.__name = name #private instance attributes
def __details(self): #private instance method
return self.__name
def __str__(self):
return self.__name
def __doc__(self):
return "Class to demonstrate Name Mangling"
obj = Sample("Aruunisto")
print(obj)
print(vars(obj))
print(vars(Sample))
#you can't access private/non-public attributes/methods
#but they're not strictly private you can access them by their mangled names
print(obj._Sample__name)
print(obj._Sample__details())
### Adding instance attributes methods dynamically ###
>>> class User:
pass
...
>>> arun = User()
>>> arun.name = "Arun Arunisto"
>>> arun.age = 25
>>> arun.__dict__
{'name': 'Arun Arunisto', 'age': 25}
>>> #adding instance attributes dynamically
>>> arun = User()
>>> arun.full_name = "arun arunisto"
>>> arun.age = 25
>>> arun.__dict__
{'full_name': 'arun arunisto', 'age': 25}
>>> #and adding methods dynamically
>>> def __init__(self, name, age):
... self.name = name
... self.age = age
...
...
>>> User.__init__ = __init__
>>> def method(self):
... return self.name
...
>>> User.method = method
>>> arun = User("Arun", 25)
>>> arun.method()
'Arun'
#### Property & Descriptor based attribute ####
from datetime import datetime
class AgeCalculator:
def __init__(self, year):
self.year = year
@property
def year(self):
return self._year
@year.setter
def year(self, value):
if len(str(value)) != 4 or not isinstance(value, int) or value <= 0:
raise ValueError("Not a valid year")
self._year = value
def age_calc(self):
cur_year = datetime.now().year
return int(cur_year) - self._year
obj = AgeCalculator(1996)
print(obj.age_cal()) #28
obj.year = 2006
print(obj.age_calc()) #18
##Error Cases
obj.year = 19 #this will raise an error
obj.year = 0000 #this will raise an error
obj.year = 19.96 #this will raise an error
obj.year = "1996" #this will also raise an error
"""
class : class is a blueprint for creating objects.
it defines the attributes (data) and methods
(function) that the objects will have
object: object is an instance of a class
"""
#sample
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def drive(self):
print(f"{self.brand} {self.model} is driving")
#creating objects of the class
car_1 = Car("Toyota", "Corolla")
car_2 = Car("Tesla", "Model S")
#calling methods on object
car_1.drive()
car_2.drive()
"""
Inheritance
Inheritance allows a class(sub class) to inherit
attributes and methods from another class (super class)
"""
class Employee_details:
def __init__(self, name, age):
self.name = name
self.age = age
def emp_details(self):
print("Employee name:",self.name)
print("Employee age:",self.age)
class Employee_designation(Employee_details):
def __init__(self, name, age, designation, salary):
Employee_details.__init__(self, name, age)
self.designation = designation
self.salary = salary
def emp_details_with_designation(self):
print("Employee name:",self.name)
print("Employee age:",self.age)
print("Employee designation:",self.designation)
print("Employee salary:",self.salary)
#calling subclass on object
emp_1 = Employee_designation("Arunisto", 25, "Developer", "$60k")
#accessing child method
emp_1.emp_details_with_designation()
#accessing parent class too
emp_1.emp_details()
print(emp_1.name)
"""
Encapsulation:
Encapsulation is the bundling data and methods on that single unit class
"""
class BankAccount:
def __init__(self, account_no, balance):
self.account_no = account_no
self.__balance = balance #private variable
def deposit_amount(self, amt):
self.__balance+=amt
def withdraw_amount(self, amt):
self.__balance-=amt
def get_balance(self):
return self.__balance
acc_1 = BankAccount("12345", 5000)
print(acc_1.get_balance())
acc_1.deposit_amount(100)
print(acc_1.get_balance())
print(acc_1._BankAccount__balance) #accessing private variable
"""
Data Abstraction:
It involves the process of hiding the implementation details of a system and only showing the essential features of the object.
"""
from math import pi
class Shape:
def __init__(self, val):
self.val = val
def get_area(self):
raise NotImplementedError("Not Accessible!")
def get_perimeter(self):
raise NotImplementedError("Not Accessible!")
class Circle(Shape):
#val represents radius here.
def __init__(self, val):
super().__init__(val)
def get_area(self):
return pi*self.val**2
def get_perimeter(self):
return 2*pi*self.val
class Square(Shape):
#val represents side here
def __init__(self, val):
super().__init__(val)
def get_area(self):
return 2*self.val
def get_perimeter(self):
return 4*self.val
circle = Circle(14)
print(circle.get_area())
square = Square(25)
print(square.get_perimeter())
shape = Shape(25)
print(shape.get_area()) #it will raise an error
"""
Abstract Base Classes:
Using `abc` module for abstract the python classes
"""
from math import pi
from abc import ABC, abstractmethod
class Shape(ABC):
def __init__(self, value):
self.value = value
@abstractmethod
def get_area(self):
pass
@abstractmethod
def get_perimeter(self):
pass
class Circle(Shape):
#here value referesd as radius
def __init__(self, value):
super().__init__(value)
def get_area(self):
return pi*self.value**2
def get_perimeter(self):
return 2*pi*self.value
"""
MRO - Method Resolution Order
It helps python to do the multiple inheritance
"""
#MRO - Test Case - 1
class A:
def __init__(self):
print("Class A")
class B(A):
def __init__(self):
super().__init__()
print("Class B")
class C(A):
def __init__(self):
super().__init__()
print("Class C")
class D(C, B):
def __init__(self):
super().__init__()
print("Class D")
# The MRO will be for this code
obj = D()
obj.__mro__
"""
Class D --> Class C --> Class B --> Class A --> object
"""
"""
MRO - Next approach changeing parent swap
"""
class A:
def __init__(self):
print("Class A")
class B(A):
def __init__(self):
super().__init__()
print("Class B")
class C(A):
def __init__(self):
super().__init__()
print("Class C")
class D(B, C):
def __init__(self):
super().__init__()
print("Class D")
# The MRO will be for this code
"""
Class D --> Class B --> Class C --> Class A --> object
"""