pythonpropertiesunwrap

How do you unwrap a python property to get attributes from the getter?


From "outside", how can I access attributes in a property's getter function whether by unwrapping it or some other way?

In a python property, its __get__ function seems to be a wrapper of a wrapper of a wrapper ...

Using inspect.unwrap on the __get__ function returns another wrapper, not the getter. In fact, what unwrap returns seems to be the exact same object and id as what was unwrapped. See the MRE and its output below.

Yes, I know that in simple cases I can get the attribute through classinstance.__class__.propertygetterfunction._privateattribute

In my real situation, not the MRE below, the getter had been dynamically defined and is referenced only in synthesizing the property. Once created, there are no other references to the getter besides what is buried in the property.

What is the path from aclass.aproperty.__get__ to the getter and its attributes?

In the following MRE example, the getter contains one attribute containing how may times the getter has been called, and another containing the property's internal value.

ref: https://medium.com/@genexu/extracting-the-wrapped-function-from-a-decorator-on-python-call-stack-2ee2e48cdd8e

from inspect import unwrap

class propholder: 
     def __init__(self):
         self.__class__.propgetter._usecount=0 
 
     def propgetter(self):
         self.__class__.propgetter._usecount=self.__class__.propgetter._usecount +1 
         print(f"getcount= {self.__class__.propgetter._usecount}")
         return self.__class__.propgetter._pvalue # set by the setter
     
     def propsetter(self, v): 
         self.__class__.propgetter._pvalue=v
  
     # function to delete _age attribute 
     def propdeleter(self): 
         del self.__class__.propgetter
         del self.__class__.propsetter
         del self.__class__.propdeleter
     
     aprop = property(propgetter, propsetter, propdeleter)  
  
ap = propholder() 

for i in [10,11,12]:
    ap.aprop=i
    print(f" ap.aprop={ap.aprop} ")

prop_get=ap.__class__.aprop.__get__
u=unwrap(prop_get)
print( f"\n prop_get=ap.__class__.aprop.__get__ = {prop_get} of type {prop_get.__class__} at {id(prop_get)} ")
print( f"  u=unwrap(prop_get) = {u} of type {u} at {id(u)} ")
un=u
for i in range(2,6):
   u=unwrap(u)
   print( f"  u=unwrap(u) = {u} of type {u} at {id(u)} ")

Results:


getcount= 1
 ap.aprop=10 
getcount= 2
 ap.aprop=11 
getcount= 3
 ap.aprop=12 

 prop_get=ap.__class__.aprop.__get__ = <method-wrapper '__get__' of property object at 0x72b6af94d940> of type <class 'method-wrapper'> at 126128955449232 
  u=unwrap(prop_get) = <method-wrapper '__get__' of property object at 0x72b6af94d940> of type <method-wrapper '__get__' of property object at 0x72b6af94d940> at 126128955449232 
  u=unwrap(u) = <method-wrapper '__get__' of property object at 0x72b6af94d940> of type <method-wrapper '__get__' of property object at 0x72b6af94d940> at 126128955449232 
  u=unwrap(u) = <method-wrapper '__get__' of property object at 0x72b6af94d940> of type <method-wrapper '__get__' of property object at 0x72b6af94d940> at 126128955449232 
  u=unwrap(u) = <method-wrapper '__get__' of property object at 0x72b6af94d940> of type <method-wrapper '__get__' of property object at 0x72b6af94d940> at 126128955449232 
  u=unwrap(u) = <method-wrapper '__get__' of property object at 0x72b6af94d940> of type <method-wrapper '__get__' of property object at 0x72b6af94d940> at 126128955449232 


Solution

  • ap.__class__.aprop.__get__ is a method of ap.__class__.aprop, so when you access the __get__ attribute of ap.__class__.aprop you invoke the MethodType descriptor, which stores the object that the method is bound to as the __self__ attribute. In this case, the object it's bound to is ap.__class__.aprop, a property descriptor, which stores the getter function in the fget attribute.

    So in your demo code, the getter function can be obtained from ap.__class__.aprop.__get__ with:

    ap.__class__.aprop.__get__.__self__.fget
    

    And its private attribute can be accessed with:

    ap.__class__.aprop.__get__.__self__.fget._usecount
    

    Demo: https://ideone.com/FWQdnH

    Note that inspect.unwrap does nothing more than following the chain of __wrapped__ attributes stored by the functools.update_wrapper function or any wrapper function following the same protocol. It does not magically unwrap functions stored in any other attributes.