Question

Is there a way to override a @property getter in runtime in Python?

I want my class to have a 'complex' getter, that runs some code and returns a value that can change. But, if for some reason I set that variable to something else, I want it to always return that value.

For example:

class Random(object):
    @property
    def random_number():
        return random.randint(0,10)


    @random_number.setter
    def random_number(value):
        # Overlord says that random must always return `value`
        (((I don't know that to do here)))


>>>r = Random()
>>>r.random_number
>>>(returns something between 0,10)

>>>r.random_number = 4 # Confirmed by dice roll
>>>r.random_number
>>>4 (always)

I know that I could to this with an instance var such as 'force_value', that I always check in my getter, but I would like to avoid that if possible.

Ideally, my setter function could do something like

self.random_number.getter = lambda:4

But I don't know how to make that work.

Was it helpful?

Solution 2

You can write your own, property-like class, but without the __set__-method. This way, setting the attribute creates an instance-attribute instead of calling a setter-method

import random

class property_getter(object):
    def __init__(self, getter):
        self.getter = getter

    def __get__(self, obj, cls):
        return self.getter(obj)

class Random(object):
    @property_getter
    def random_number(self):
        return random.randint(0,10)

x = Random()
print x.random_number
print x.random_number
x.random_number = 5
print x.random_number

OTHER TIPS

You cannot 'override' a property on the instance level because a property is a data descriptor (it has both __get__ and __set__ hooks).

This means your getter has to check for an instance attribute. That's not as painful as it sounds:

class Random(object):
    @property
    def random_number(self):
        if hasattr(self, '_override'):
            return self._override
        return random.randint(0,10)

    @random_number.setter
    def random_number(self, value):
        # Overlord says that random must always return `value`
        self._override = value

You can even add a deleter:

    @random_number.deleter
    def random_number(self):
        if hasattr(self, '_override'):
            del self._override

The alternative would be to create your own alternative descriptor class, one that doesn't implement a __setter__ hook, so that you can set an attribute directly on the instance and have it override the descriptor. But for your use-case, just using an explicit setter seems simpler.

You can replace a property at run time. Actually, even the act of defining the property and attaching it to the class is a run-time operation (nothing noteworthy happens before run time in Python). However, since a property is a descriptor and descriptors are part of the class, it isn't the right place to store per-instance data. You should still go with a separate variable (perhaps self._number, being None to indicate that none has been forced yet), provided you really have to implement this silly and confusing behavior.

You could define the source for your numbers as a function that you switch out using your property setter. This works because Python sports first-class functions, which means you can get a reference to them (without calling them) and pass them around like any other object:

from functools import partial
import random


class Random(object):
    def __init__(self):
        self.number_source = partial(random.randint, 0, 10)

    @property
    def random_number(self):
        return self.number_source()

    @random_number.setter
    def random_number(self, value):
        self.number_source = lambda: value


r = Random()
print r.random_number

r.random_number = 42
print r.random_number

Output:

<random int>
42

See the docs for functools.partial for the details on partial(random.randint, 0, 10). It basically creates a function, that when called, calls random.randint(0, 10) and returns the result.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top