pythondecoratordescriptor

Can you change a python descriptor's __get__ method at runtime?


All, As the title asks, is it possible to change the __get__ method of a Descriptor at run time. I'm in a situation where I've got a function that is being decorated and undecorated on the the fly at run time. I'd like the result of this function to be available as a attribute, similar to what the @property does. I researched that and found it's a descriptor, but it seems descriptors' __get__ method is read only.

class Test( object ):
    def __init__( self ):
        self._x = 10

    def get_x( self ):
        return self._x

    @property
    def x( self ):
        return self.get_x()

The above code does what I want, roughly, in that

  1. The value is set in the constructor
  2. I can decorate the get_x method to my heart's content
  3. instance.x returns the correct value

My issue is that I'd rather not have to create the get_x method since it's basically unnecessary. I just haven't been able to decorate the __get__ method of x as it is read-only.

Background

I'm writing a turn based strategy game, and I'm using decorators to implement persistent conditions. I'm able to implement these decorators effectively when I use test cases, but the issue is that to get the computed value then, you must use a function call, not an attribute access. This seems like an bad idea because getting values describing a unit would inconsistently use functions or attributes. I'd like to standardize on attributes if I can.


Solution

  • You can override the default "read-only" characteristic of a property's __get__ attribute using simple inheritance:

    class MyProperty( property ): pass
    
    class Test( object ):
        def __init__( self ):
            self._x = 10
    
        def get_x( self ):
            return self._x
    
        @MyProperty
        def x( self ):
            return self.get_x()
    
    test = Test()
    

    The problem now is that even if you redefine the __get__ attribute of your Text.x property, on test.x request python runtime will call MyProperty.__get__(Test.x, test, Test)

    So you could rewrite it only there like:

    MyProperty.__get__ = lambda self,instance,owner: "the x attribute"
    

    So good option here is to delegate call to some redefinable attribute like:

    MyProperty.__get__ = lambda self,instance,owner: self.get(instance,owner)
    

    From now on get attribute of your property in your full control. Also there is bad option to generate separate type for each property-like object. So in good case you could do something like:

    class MyProperty( property ):
        def __get__(self,instance,owner) :
            if not instance: return self
            else: return self.get(instance,owner)
    
    class Test( object ):
        def __init__( self ):
            self._x = 10
    
        def get_x( self ):
            return self._x
    
        @MyProperty
        def x( self ): pass
        
        @MyProperty
        def y(self): pass
    
        x.get = lambda self,clazz: self.get_x()
        y.get = lambda self,clazz: "the y property of " + clazz.__name__ + " object"
    
    >>> test = Test()
    >>> test.x
    10
    >>> test.y
    'the y property of Test object'