class Test(io.BufferedIOBase): def __init__(self, f): self.f = f def __getattr__(self, name): print("getattr: searching for: ", name) #try: # v = getattr(self.f, name) #except Exception: # print(" found nothing!") # raise #print(" found: ", v) #return v #return 22 raise AttributeError(name) def __getattribute__(self, name): print("1.getattribute@self: searching for: ", name, flush=True) try: v = super().__getattribute__(name) except Exception as e: print(" 1.exception: ", e) print(" 1.found nothing!", flush=True) pass else: print(" 1.found: ", v, flush=True) return v print("2.getattribute@self.f: searching for: ", name, flush=True) #f = super().__getattribute__("f") f = object.__getattribute__(self, "f") try: v = getattr(f, name) except Exception as e: print(" 2.exception: ", e) print(" 2.found nothing!", flush=True) raise else: print(" 2.found: ", v, flush=True) return v print("ERROR") f1 = open(p, "rb") f2 = Test(f1) print("\nTest 01:") v = f2.closed print("result: ", v) print("\nTest 02:") v = f2.qwer print("result: ", v) Test 01: 1.getattribute@self: searching for: closed 1.getattribute@self: searching for: __IOBase_closed 1.exception: 'Test' object has no attribute '__IOBase_closed' 1.found nothing! 2.getattribute@self.f: searching for: __IOBase_closed 2.exception: '_io.BufferedReader' object has no attribute '__IOBase_closed' 2.found nothing! getattr: searching for: __IOBase_closed 1.found: False result: False Test 02: 1.getattribute@self: searching for: qwer 1.exception: 'Test' object has no attribute 'qwer' 1.found nothing! 2.getattribute@self.f: searching for: qwer 2.exception: '_io.BufferedReader' object has no attribute 'qwer' 2.found nothing! getattr: searching for: qwer --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) Cell In[532], line 49 47 print("result: ", v) 48 print("\nTest 02:") ---> 49 v = f2.qwer 50 print("result: ", v) Cell In[532], line 15, in Test.__getattr__(self, name) 6 print("getattr: searching for: ", name) 7 #try: 8 # v = getattr(self.f, name) 9 #except Exception: (...) 13 #return v 14 #return 22 ---> 15 raise AttributeError(name) AttributeError: qwer Following object_getattribute(), this is what I think happens: closed resolves to the non-data property from IOBase. The property is called before the previous __getattribute__ returns, looking up self.__closed, which is actually _IOBase__closed per name-mangling rules. Now the steps become strange. My __getattribute__ seems to be searching for __IOBase_closed (which doesn't exist), rather than _IOBase__closed. This I can attribute to mismatches between the Python and CPython code. My __getattribute__ fails and raises AttributeError, and then my __getattr__ fails and raises AttributeError. And yet somehow, the value of _IOBase__closed is returned. I can only explain this if Python calls another getattr* function further up the MRO. So now I'm stumped. Continue reading...