Skip to content

Instantly share code, notes, and snippets.

@orbingol
Last active December 11, 2023 12:30
Show Gist options
  • Save orbingol/ab490b8de1dd80c1b2822a692b87ac3f to your computer and use it in GitHub Desktop.
Save orbingol/ab490b8de1dd80c1b2822a692b87ac3f to your computer and use it in GitHub Desktop.
Creating dynamic attributes in Python via overriding "__getattr__" and "__setattr__" magic methods
# Illustrates creating dynamic attributes in Python
#
# For implementation details of the dynamic attributes, please see the following methods:
# - GPSPosition.__getattr__
# - GPSPosition.__setattr__
#
class GPSPosition(object):
__slots__ = ('_coords', '_attribs', '_iter_index')
def __init__(self, coords, **kwargs):
self._coords = coords
self._attribs = kwargs.get('attribs', tuple())
def __str__(self):
return str(self._coords)
def __repr__(self):
return self.__str__()
def __iter__(self):
self._iter_index = 0
return self
def next(self):
return self.__next__()
def __next__(self):
try:
res = self._coords[self._iter_index]
except IndexError:
raise StopIteration
self._iter_index += 1
return res
def __len__(self):
return len(self._coords)
def __reversed__(self):
return reversed(self._coords)
def __getitem__(self, key):
return self._coords[key]
def __setitem__(self, key, value):
self._coords[key] = value
def __getattr__(self, name):
try:
# Try to get the attribute name from the attribute list
# 'index' function will return the index of the attribute in the '_attribs' list
idx = object.__getattribute__(self, '_attribs').index(name)
# Since we know the index value, it should be the same in the '_coords' list
return object.__getattribute__(self, '_coords')[idx]
except ValueError:
# 'index' function will throw a 'ValueError' exception if 'attr' is not in the list
raise AttributeError("'" + self.__class__.__name__ + "' object has no attribute '" + name + "'")
except AttributeError:
return object.__getattribute__(self, name)
def __setattr__(self, name, value):
# Check if the 'name' is in the '__slots__'
if name in object.__getattribute__(self, '__slots__'):
object.__setattr__(self, name, value)
else:
try:
# Try to get the attribute name from the attribute list
# 'index' function will return the index of the attribute in the '_attribs' list
idx = object.__getattribute__(self, '_attribs').index(name)
# Get the '_coords' list
temp = object.__getattribute__(self, '_coords')
# Update the '_coords' list
temp[idx] = value
# Update the class member in this instance
object.__setattr__(self, '_coords', temp)
except ValueError:
# 'index' function will throw a 'ValueError' exception if 'name' is not in the list
raise AttributeError("'" + self.__class__.__name__ + "' object has no attribute '" + name + "'")
@property
def coordinates(self):
return self._coords
@coordinates.setter
def coordinates(self, val):
if not isinstance(val, (list, tuple)):
raise TypeError("Input for 'coordinates' should be a list or tuple")
self._coords = list(val)
@property
def attributes(self):
return self._attribs
@attributes.setter
def attributes(self, val):
if not isinstance(val, (list, tuple)):
raise TypeError("Input for 'attributes' should be a list or tuple")
self._attribs = tuple(val)
# Example run
if __name__ == '__main__':
# Create a 3-D GPSPosition instance and set the coordinates & attributes
pos1 = GPSPosition([10, 21, 32], attribs=['x', 'y', 'z'])
# Print 'position'
print('pos1:', pos1)
# Get dynamic properties
print('x:', pos1.x) # will print 10
print('y:', pos1.y) # will print 21
print('z:', pos1.z) # will print 32
# Set dynamic properties
pos1.x = 12
print('updated x:', pos1.x) # will print 12
# Create a 4-D GPSPosition instance and set the coordinates & attributes
pos2 = GPSPosition([11, 13, 17, 19], attribs=['x', 'y', 'z', 'w'])
# Print 'position'
print('pos2:', pos2)
# Get dynamic properties
print('x:', pos2.x) # will print 11
print('y:', pos2.y) # will print 13
print('z:', pos2.z) # will print 17
print('w:', pos2.w) # will print 19
# Set dynamic properties
pos2.w = 23
print('updated w:', pos2.w) # will print 23
# Create a 2-D GPSPosition instance and set the coordinates & attributes
pos3 = GPSPosition([100, 200], attribs=['u', 'v'])
# Print 'position'
print('pos3:', pos3)
# Get dynamic properties
print('u:', pos3.u) # will print 100
print('v:', pos3.v) # will print 200
# Set dynamic properties
pos3.u = 150
pos3.v = 250
print('updated u:', pos3.u) # will print 150
print('updated v:', pos3.v) # will print 250
# Create a 2-D GPSPosition instance without the attributes
pos4 = GPSPosition([36, 42])
# Print 'position'
print('pos4:', pos4)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment