mklauber
mklauber

Reputation: 1144

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.

Upvotes: 3

Views: 1199

Answers (1)

Odomontois
Odomontois

Reputation: 16328

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'

Upvotes: 5

Related Questions