I'm trying to create a circle class using the magic methods __getattr__ and __setattr__, and I seem to have my __getattr__ working, but when I implement __setattr__ (which should only allow the values for x and y to be set if the value is an int, and raise an AttributeError when the user tries to set the attributes area, circumference, and distance to circle), my __getattr__ throws the maximum recursion error. When I comment it out, the __getattr__ then works just fine.
from math import pi, hypot, sqrt ''' Circle class using __getattr__, and __setattr__ (rename circle2) ''' # __getattr__(self, name): Automatically called when the attribute name # is accessed and the object has no such attribute. # __setattr__(self, name, value): Automatically called when an attempt is made to bind the attribute name to value. class Circle: def __init__(self, x, y, r): self.x = x self.y = y self.r = r self.area = pi * self.r * self.r self.circumference = 2 * pi * self.r self.distance_to_origin = abs(sqrt((self.x - 0)*(self.x - 0) + (self.y - 0) * (self.y - 0)) - self.r) def __getattr__(self, name): if name in ["x", "y", "r", "area", "circumference", "distance_to_origin"]: print('__get if statement') # check getattr working return getattr(self, name) else: print('Not an attribute') return None ''' def __setattr__(self, name, value): print(name, value) if name in ['x', 'y']: if isinstance(value, int): print('we can set x,y') self.__dict__[name] = value else: # value isn't an int raise TypeError('Expected an int') elif name in ['area', 'circumference', 'distance_to_origin']: raise RuntimeError('Cannot set attribute') ''' if __name__ == '__main__': circle = Circle(x=3, y=4, r=5) # print(circle.x) print(circle.__getattr__('x')) # print(circle.y) print(circle.__getattr__('y')) # print(circle.r) print(circle.__getattr__('r')) # print(circle.area) print(circle.__getattr__('area')) # print(circle.circumference) print(circle.__getattr__('circumference')) # print(circle.distance_to_origin) print(circle.__getattr__('distance_to_origin')) # print(circle.test) ''' tests = [('circle.x = 12.3', "print('Setting circle.x to non-integer fails')"), ('circle.y = 23.4', "print('Setting circle.y to non-integer fails')"), ('circle.area = 23.4', "print('Setting circle.area fails')"), ('circle.circumference = 23.4', "print('Setting circle.circumference fails')"), ('circle.distance_to_origin = 23.4', "print('Setting circle.distance_to_origin fails')"), ('circle.z = 5.6', "print('Setting circle.z fails')"), ('print(circle.z)', "print('Printing circle.z fails')")] for test in tests: try: exec(test[0]) except: exec(test[1]) ''' With __setattr__ commented out, the testing code:
if __name__ == '__main__': circle = Circle(x=3, y=4, r=5) # print(circle.x) print(circle.__getattr__('x')) # print(circle.y) print(circle.__getattr__('y')) # print(circle.r) print(circle.__getattr__('r')) # print(circle.area) print(circle.__getattr__('area')) # print(circle.circumference) print(circle.__getattr__('circumference')) # print(circle.distance_to_origin) print(circle.__getattr__('distance_to_origin')) prints out:
__get if statement 3 __get if statement 4 __get if statement 5 __get if statement 78.53981633974483 __get if statement 31.41592653589793 __get if statement 0.0
if name == ...should almost certainly beif name in ..., andif value is int:should almost certainly beif isinstance(value, int):. Not a cause of a recursion error, but this is terrible code all around.getattrinside__getattr__without having set anything on the instance (so it's still not going to be an existing value) should cause recursion errors by itself.getattr(x, 'abc')goes through the exact same code path asx.abc. Ifx.abcdoesn't exist, the__getattr__is invoked to try to satisfy the missing attribute (so you know the attribute doesn't exist if you're in__getattr__in the first place). If the "can't find attribute handler" then turns around and, without modification, asks for the same attribute on itself, the end result is predictable. You can't just ask for it again without having set the value; the attribute still isn't there.__getattr__should only be called if I'm trying to access an attribute that doesn't exist? So if I were to docircle.testthen__getattr__would be run to try and find the attributetest?