import sys import traceback class PropertyLoadDemmandNew(object): def __init__(self,attr,haveprop=False): self.attr = attr self._haveprop = haveprop @property def prop(self): try: return self._prop except AttributeError: requestloadondemmand = AttributeError("'{}' object has no attribute 'prop'".format(self.__class__.__name__)) if self._haveprop: setattr(requestloadondemmand,'load_for',self) raise requestloadondemmand def __getattr__(self,name): if name == "prop": _,request,_ = sys.exc_info() if getattr(request,'load_for',None) == self: self._prop = "prop data loaded" return self._prop # in addition this could be reduced to raise keyword only raise AttributeError("'{}' object has no attribute '{}'".format(self.__class__.__name__,name)) class PropertyLoadDemmandCurrent(object): def __init__(self,attr): self.attr = attr @property def prop(self): try: return self._prop except AttributeError: requestloadondemmand = AttributeError("'{}' object has no attribute 'prop'".format(self.__class__.__name__)) setattr(requestloadondemmand,'load_for',self) raise requestloadondemmand def __getattr__(self,name): if name == "prop": self._prop = "prop data loaded" return self._prop raise AttributeError("'{}' object has no attribute '{}'".format(self.__class__.__name__,name)) class PropertyLoadDemmandUnintended(object): def __init__(self,attr): self.attr = attr @property def prop(self): try: return self._prop except AttributeError: requestloadondemmand = AttributeError("'{}' object has no attribute 'prop'".format(self.__class__.__name__)) setattr(requestloadondemmand,'load_for',self) raise requestloadondemmand @property def prop2(self): return self.attr + self._compute_some_value() def __getattr__(self,name): if name == "prop": self._prop = "prop data loaded" return self._prop raise AttributeError("'{}' object has no attribute '{}'".format(self.__class__.__name__,name)) def _compute_some_value(self): return "computed complex value" + self.attr2 print("version 1 as working now") print("========================") test = PropertyLoadDemmandCurrent("hello") try: print(test.attr,test.prop) except: traceback.print_exc() # this would not report prop missing if prop.getter could use AttributeError exception to convey that it # wants prop to be loaded print("\nversion 2 AttributeError raised by prop.getter not cleared before __getattr__ call (haveprop=True)") print("==================================================================================================") test = PropertyLoadDemmandNew("hello",haveprop=True) try: print(test.attr,test.prop) except: traceback.print_exc() # this would insist that prop is not available as selected by haveprop parameter print("\nversion 2 AttributeError raised by prop.getter not cleared before __getattr__ call (haveprop=False)") print("===================================================================================================") test = PropertyLoadDemmandNew("hello",haveprop = False) try: print(test.attr,test.prop) except: traceback.print_exc() # the follwoint would properly report missing attr2 instead of prop2 print("\nexample of code called by getter triggering unintended attribute error (simplified)") print("===================================================================================") test = PropertyLoadDemmandUnintended("hello") try: print(test.attr,test.prop,test.prop2) except: traceback.print_exc()