-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathclass.py
More file actions
240 lines (181 loc) · 9.47 KB
/
Copy pathclass.py
File metadata and controls
240 lines (181 loc) · 9.47 KB
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
# python supports OOP paradigm
# we use `class` keyword to create a class
# we can use `pass` keyword to define an empty class
class MyClassA:
"""
Optional docstring explaining MyClassA
"""
pass
print( 'MyClassA.__doc__ => ', MyClassA.__doc__ )
# in python class, we have attributes and methods
# attribute stores some data while method references a function
# python class has two kind of attributes, class attributes and instance attribute
# class attributes are shared across all the instances
class MyClassB:
# class attribute
classAttributeA = 'classAttrA'
# class attributes will be accessible on class
print( 'MyClassB.classAttributeA => ', MyClassB.classAttributeA )
# python class has three kind of methods, class methods, static methods and instance methods
class MyClassC:
# class attribute
classAttributeA = 'classAttrA'
# class method defintion, we use @classmethod decorator
# giving first argument is necessary event if we don't use it
@classmethod
def classMethodA( cls, name ):
return cls.classAttributeA + ':' + name
# static method defintion, we use @staticmethod decorator
@staticmethod
def staticMethodA( name ):
return name
# works: return MyClassC.classAttributeA + ':' + name
# @classmethod decorator passes class as the first argument implicitely
# hence class methods have access to the class they belong to
# these are useful to create factory function
print( 'MyClassC.classMethodA("John") => ', MyClassC.classMethodA("John") )
# @staticmethod decorator defines a statc method and do not pass class reference
# but you can use class variable to access static property, not recommended as class name might change
# these are useful for pure utility purposes
print( 'MyClassC.staticMethodA("John") => ', MyClassC.staticMethodA("John") )
# To create an object, we need to call class like a function
# an object contains attributes and methods defined by the class
# but we can add custom attributes and methods after object is created on that specific instance
instanceC_A = MyClassC()
print( 'instanceC_A => ', instanceC_A )
# all class attributes are accessible on object
# however class attributes are not defined on object, if python can not find instance attribute with the same name, it will find on its constructor class
# TODO: this is possible due to namespace: https://dzone.com/articles/python-class-attributes-vs-instance-attributes
print( 'instanceC_A.classAttributeA => ', instanceC_A.classAttributeA )
print( 'instanceC_A.classAttributeA is MyClassC.classAttributeA => ', instanceC_A.classAttributeA is MyClassC.classAttributeA )
# create instance property if doesn't exist and assign new value
instanceC_A.classAttributeA = 'instanceA'
print( 'After: instanceC_A.classAttributeA => ', instanceC_A.classAttributeA )
print( 'After: instanceC_A.classAttributeA is MyClassC.classAttributeA => ', instanceC_A.classAttributeA is MyClassC.classAttributeA )
# static and class methods are also accessible on object and they can be overriden in the same way attributes can be overridden
print('instanceC_A.classMethodA("Mike") => ', instanceC_A.classMethodA("Mike"))
print('instanceC_A.staticMethodA("Mike") => ', instanceC_A.staticMethodA("Mike"))
instanceC_A.classMethodA = lambda name: "instance " + name
instanceC_A.staticMethodA = lambda name: "instance " + name
print('After: instanceC_A.classMethodA("Mike") => ', instanceC_A.classMethodA("Mike"))
print('After: instanceC_A.staticMethodA("Mike") => ', instanceC_A.staticMethodA("Mike"))
# instance method is method defintion without any decorator
# this method is accessible on an instance of the class
# __init__ method is always called when instance of a class is created, passing all arguments passed by the user
# when any instance method is called, python implicitely passes object as the first argument, conventionally named as self
class MyClassD:
# also called as constructor method
# self is passed by python, represents the object which will be created
def __init__( self, firstName, lastName ):
# these are instance attributes because they are only accessible on the instance
# python does not support access modifiers on attributes or methods
self.firstName = firstName # add firstName attribute to the object and assign value
self.lastName = lastName # add lastName attribute to the object and assign value
# instance method
def getFullName( self ):
return self.firstName + " " + self.lastName
instanceD_A = MyClassD( 'John', 'Doe' )
print( 'instanceD_A.firstName, instanceD_A.lastName => ', instanceD_A.firstName, instanceD_A.lastName )
# call an instance method
print( "instanceD_A.getFullName() => ", instanceD_A.getFullName() )
# we can access class of the object using __class__ attribute
print( "instanceD_A.__class__ is MyClassD => ", instanceD_A.__class__ is MyClassD )
# we can see attributes of an object using __dict__ attributes
print("instanceD_A.__dict__ => ", instanceD_A.__dict__)
# attributes on an object can be deleted any type
del instanceD_A.firstName
# print( "After delete: instanceD_A.firstName => ", instanceD_A.firstName ) #AttributeError: 'MyClassD' object has no attribute 'firstName'
# having a constructor is not necessary, until unless we have some initialzation data
class MyClassE:
@staticmethod
def getGreetings():
return {'morning': 'Good Morning', 'hello': 'Hello World'}
# we can call another instance method of the object
# passing self to invoked method is not allowed, as python do that internally
def getMessage(self):
return self.getHelloWorld()
# having self is necessary, even though we are not using it
def getHelloWorld(self):
# we can call a static method or class method on object
# as it will look up on the class if instance method is not defined with that name
greetings = self.getGreetings()
return greetings['hello']
instanceE_A = MyClassE()
print( "instanceE_A.getMessage() => ", instanceE_A.getMessage() )
# python does not support access modifiers but using _ prefix is a conventional way to tell that the property or method is meant to be private
# we can use __ prefix to change the name of the attribute on final object. This makes little harder to access it on the object but not impossible. We still can reference them from inside using the same attribute name.
class MyClassG:
def __init__(self):
self._firstName = 'John'
self.__lastName = 'Doe'
def getFullName(self):
return self._firstName + ' ' + self.__lastName
instanceG_A = MyClassG()
print('instanceG_A.__dict__', instanceG_A.__dict__)
print('instanceG_A.__getFullName()', instanceG_A.getFullName())
# getters and setters in python
# in the below example, we are safely setting the name
# but since we can not allow user to set name directory as name has to be broken down it parts, user needs to use `setName` and `getName` methods
class MyClassH:
def __init__(self, name):
self.setName(name)
def setName(self, name):
nameParts = name.split(" ")
self.firstName = nameParts[0]
self.lastName = nameParts[-1]
def getName(self):
return "{} {}".format(self.firstName, self.lastName)
instanceH_A = MyClassH("John M. Doe")
print('instanceH_A.getName()', instanceH_A.getName())
instanceH_A.setName("Jenna Doe")
print('After: instanceH_A.getName()', instanceH_A.getName())
# python provides `property` function which creates a property object that calls `getter` method when a attribute is accessed, `setter` method when attribute is assigned a value and `deleter` method when attribute is deleted
# this property object is class attribute of type <class 'property'>, but when accessed from an instance invokes the methods registered on it depending on type of operation
# unless we are trying to perform, get, set or delete operation, method arguments in property function are optional
class MyClassI:
def __init__(self, name):
self.setName(name)
# getter method
def getName(self):
print("MyClassI.getName() called", end=" , ")
return "{} {}".format(self.firstName, self.lastName)
# setter method
def setName(self, name):
print("MyClassI.setName() called", end=" , ")
nameParts = name.split(" ")
self.firstName = nameParts[0]
self.lastName = nameParts[-1]
# deleter method
def delName(self):
print("MyClassI.getName() delName", end=" , ")
del self.firstName
del self.lastName
# property attribute
name = property(getName, setName, delName, "docstring")
instanceI_A = MyClassI("John M. Doe")
print('instanceI_A.name', instanceI_A.name)
instanceI_A.name = "Jenna Doe"
print('After: instanceI_A.name', instanceI_A.name)
del instanceI_A.name
# @property decorator makes it easy to write
# all method name should be same as attribute name
class MyClassJ:
def __init__(self, name):
self.name = name
@property # property must be defined on getter
def name(self):
return "{} {}".format(self.firstName, self.lastName)
@name.setter
def name(self, name):
nameParts = name.split(" ")
self.firstName = nameParts[0]
self.lastName = nameParts[-1]
@name.deleter
def name(self):
del self.firstName
del self.lastName
instanceJ_A = MyClassJ("John M. Doe")
print('instanceJ_A.name', instanceJ_A.name)
instanceJ_A.name = "Jenna F. Doe"
print('After: instanceJ_A.name', instanceJ_A.name)
del instanceJ_A.name