Last active
December 11, 2023 12:30
-
-
Save orbingol/ab490b8de1dd80c1b2822a692b87ac3f to your computer and use it in GitHub Desktop.
Creating dynamic attributes in Python via overriding "__getattr__" and "__setattr__" magic methods
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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