简体   繁体   中英

How to conditionally override a Python class property with a method

Is it possible to conditionally override a class property with a property method?

If I have this class which I can instantiate by passing in a dict:

def Foo(Object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    # pseudo code (this doesn't work)
    if not self.bar:
        @property
        def bar(self):
            return u"I have overridden foo's bar"

And I make this instance and set bar to '' or None :

my_foo = Foo(**{'bar':u''})

and then I call the bar property:

my_foo.bar

and get

u"I have overridden foo's bar"

I would like to have the property method bar returned, instead of the bar value that was passed in when the object was created.

Can I do that somehow? Any help would be awesome.

Python properties can't be overwritten, as doing so raises an AttributeError . However, you could try storing your override values and then looking them up when the property is executed:

def overridable(of):
    def nf(self):
        if hasattr(self, "_overrides") and of.__name__ in self._overrides:
            return self._overrides[of.__name__]
        return of(self)
    return nf


class Foo(object):
    _overrides = {}

    def __init__(self, **kwargs):
        for k, v in kwargs.iteritems():
            try:
                setattr(self, k, v)
            except AttributeError:
                self._overrides[k] = v

    @property
    @overridable
    def bar(self):
        return u'I have overridden foo\'s bar'


my_foo = Foo(bar=3)
print my_foo.bar

To override a property you have to act on the class and not on the instance, because their machinery, on the instance, gets called before __dict__ lookup and you end up with AttributeError s. Instead you can set a different property on the class.

But to do so you either have to modify your class every time you create an instance(which I bet you do not want), or you have to generate new classes dynamically.

For example:

class Foo(object):
    def __init__(self, val):
        self._val = val
    @property
    def val(self):
        return self._val

class SubType(Foo):
    def __new__(cls, val):
        if val % 2:
            #random condition to change the property
            subtype = type('SubFoo', (SubType,),
                           {'val': property((lambda self: self._val + 1))})
                return object.__new__(subtype)
            else:
                return object.__new__(cls)

And the results are:

>>> d = SubType(3)  #property changed
>>> d.val
4
>>> f = SubType(2)  #same property as super class
>>> f.val
2

I don't like much this kind of hacks. Probably the easier way of doing thing is calling a private method that computes the property value, for example:

class Foo(object):
    def __init__(self, val):
        self._val = val
    def _compute_val(self):
        return self._val
    @property
    def val(self):
        return self._compute_val()

class SubFoo(Foo):
    def _compute_val(self):
        if self._val % 2:
                return self._val + 1
        else:
                return self._val

Which yields the same results as before:

>>> d = SubFoo(3)
>>> d.val
4
>>> f = SubFoo(2)
>>> f.val
2

I believe this trick could be seen as an application of the Template Method design pattern, even though it is applied to properties.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM