3

Using py3, I have an object that uses the @property decorator

class O(object):
    def __init__(self):
        self._a = None

    @property
    def a(self):
        return 1

accessing the attribute a via __dict__ (with _a) doesn't seem to return the property decorated value but the initialized value None

o = O()
print(o.a, o.__dict__['_a'])
>>> 1, None

Is there a generic way to make this work? I mostly need this for

def __str__(self):
    return ' '.join('{}: {}'.format(key, val) for key, val in self.__dict__.items())
1
  • The property belongs to the class, not the instance. Anyway, it isn't the value _a that is decorated, it is the method a Commented Nov 22, 2017 at 11:01

3 Answers 3

9

Of course self.__dict__["_a"] will return self._a (well actually it's the other way round - self._a will return self.__dict__["_a"] - but anyway), not self.a. The only thing the property is doing here is to automatically invoke it's getter (your a(self) function) so you don't have to type the parens, otherwise it's just a plain method call.

If you want something that works with properties too, you'll have to get those manually from dir(self.__class__) and getattr(self.__class__, name), ie:

def __str__(self):
    # py2
    attribs = self.__dict__.items()
    # py3
    # attribs = list(self.__dict__.items())

    for name in dir(self.__class__):
        obj = getattr(self.__class__, name)
        if isinstance(obj, property):
           val = obj.__get__(self, self.__class__)
           attribs.append((name, val))

    return ' '.join('{}: {}'.format(key, val) for key, val in attribs)

Note that this won't prevent _a to appears in attribs - if you want to avoid this you'll also have to filter out protected names from the attribs list (all protected names, since you ask for something generic):

def __str__(self):
    attribs = [(k, v) for k, v in self.__dict__.items() if not k.startswith("_")]

    for name in dir(self.__class__):
        # a protected property is somewhat uncommon but
        # let's stay consistent with plain attribs
        if name.startswith("_"):
            continue  
        obj = getattr(self.__class__, name)
        if isinstance(obj, property):
           val = obj.__get__(self, self.__class__)
           attribs.append((name, val))

    return ' '.join('{}: {}'.format(key, val) for key, val in attribs)

Also note that this won't handle other computed attributes (property is just one generic implementation of the descriptor protocol). At this point, your best bet for something that's still as generic as possible but that can be customised if needed is to implement the above as a mixin class with a couple hooks for specialization:

class PropStrMixin(object):

    # add other descriptor types you want to include in the 
    # attribs list
    _COMPUTED_ATTRIBUTES_CLASSES = [property,] 

    def _get_attr_list(self):
        attribs = [(k, v) for k, v in self.__dict__.items() if not k.startswith("_")]

        for name in dir(self.__class__):
            # a protected property is somewhat uncommon but
            # let's stay consistent with plain attribs
            if name.startswith("_"):
                continue  
            obj = getattr(self.__class__, name)
            if isinstance(obj, *self._COMPUTED_ATTRIBUTES_CLASSES):
               val = obj.__get__(self, self.__class__)
               attribs.append((name, val))
        return attribs 

    def __str__(self):
        attribs = self._get_attr_list()
        return ' '.join('{}: {}'.format(key, val) for key, val in attribs)


class YouClass(SomeParent, PropStrMixin):
    # here you can add to _COMPUTED_ATTRIBUTES_CLASSES
    _COMPUTED_ATTRIBUTES_CLASSES = PropStrMixin + [SomeCustomDescriptor]) 
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. that is a lot more information than I hoped for. great answer
3

Property is basically a "computed attribute". In general, the property's value is not stored anywhere, it is computed on demand. That's why you cannot find it in the __dict__.

@property decorator replaces the class method by a descriptor object which then calls the original method as its getter. This happens at the class level.

The lookup for o.a starts at the instance. It does not exist there, the class is checked in the next step. O.a exists and is a descriptor (because it has special methods for the descriptor protocol), so the descriptor's getter is called and the returned value is used.

(EDITED) There is not a general way to dump the name:value pairs for the descriptors. Classes including the bases must be inspected, this part is not difficult. However retrieving the values is equivalent to a function call and may have unexpected and undesirable side-effects. For a different perspective I'd like to quote a comment by bruno desthuilliers here: "property get should not have unwanted side effects (if it does then there's an obvious design error)".

4 Comments

using dir and getattr takes care of the inspection (including the whole inheritance hierarchy), and a property get should not have unwanted side effects (if it does then there's an obvious design error)
@brunodesthuilliers Thank you for your feedback. Tomorrow I will update my answer regarding the class inspection. With dir it is much easier that I thought. However I still see the risk of side-effects as real. For example anything communicating with hardware might produce timeouts, OSErrors and similar.
I do understand your concerns about side effects but once again a property should not in any way have such kind of possible failures - if it does then it should be a plain method.
@brunodesthuilliers I have updated my answer to include your view for visitors not reading the comments. I find the topic interesting. Here at SO I found only this: stackoverflow.com/questions/4739597/getter-with-side-effect . If lazy initialization is fine, I can imagine that accessing a getter would trigger initialization of a program subsystem normally disabled and for that reason not configured properly. A missing value error is likely in such situation.
0

You can also update self._a as getter since the return of the getter should always reflect what self._a is stored:

class O(object):
    def __init__(self):
        self._a = self.a

    @property
    def a(self):
        self._a = 1
        return self._a

A bit redundant, maybe, but setting self._a = None initially is useless in this case.

In case you need a setter

This would also be compatible given remove the first line in getter:

@a.setter
def a(self, value):
    self._a = value

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.