6

I'm new to Python so please excuse if I've glazed over something simple to do this.

I have an object like this:

class myObject(object):
    def __init__(self):
        self.attr1 = None
        self.attr2 = None

    @property
    def prop1(self):
        return foo.some_func(self.attr1)

And I instantiate it like this:

a = myObject()
a.attr1 = 'Apple'
a.attr2 = 'Banana'

And the method it's all wrapped in expects a return of dict, so I do this:

return a.__dict__

but prop1 is not included in the return. I understand why this is, it's not in the object's __dict__ because it only holds real attributes.

So the question is, how can I make my return, return something like this:

{'attr1': 'Apple', 'attr2': 'Banana', 'prop1': 'ModifiedApple'}

other than right before the return doing:

a.prop1_1 = a.prop1
Skinner927
  • 953
  • 2
  • 13
  • 25
  • 4
    Why not add a new method to your class that returns a copy of `self.__dict__` with the property value added? – Martijn Pieters Apr 01 '15 at 14:27
  • @MartijnPieters That might solve this specific case, but does not solve the generic case of finding the values of properties for arbitrary objects. – Nick Bailey Apr 01 '15 at 14:49
  • @NickBailey: APIs often use properties because retrieving the value might be somewhat costly, and calculating the value is postponed until you actually need it. You can auto-discover all `property` objects on the class if you really must, but *in general* it is not a good idea to poke at all the properties if you don't know what they are used for. – Martijn Pieters Apr 01 '15 at 14:51
  • certain attributes do not appear in the instance's `__dict__` . The property does appear in the class' `__dict__` though. Have a look at this http://stackoverflow.com/questions/14361256/whats-the-biggest-difference-between-dir-and-dict-in-python – Pynchia Apr 01 '15 at 14:55
  • @MartijnPieters. A fair point. But if you are working with a known class API , why are you using __dict__ at all? – Nick Bailey Apr 01 '15 at 14:55
  • @NickBailey: is that a rhetorical question or one directed at the OP? – Martijn Pieters Apr 01 '15 at 14:57
  • @MartijnPieters rhetorical mostly... sorry for being snarky. – Nick Bailey Apr 01 '15 at 15:03
  • @MartijnPieters how about you post that first comment as a response so I can give you the points. I had my head so deep down the rabbit hole I didn't even think of that. – Skinner927 Apr 01 '15 at 15:13
  • @NickBailey: no snark detected on my part, just trying to understand the correct context of your question there. :-) – Martijn Pieters Apr 01 '15 at 15:16
  • @Skinner927: posted and expanded version. – Martijn Pieters Apr 01 '15 at 15:17

2 Answers2

6

You should leave __dict__ be, only use it for attributes that live directly on the instance.

If you need to produce a dictionary with attribute names and values that includes the property, you could just add another property or method that produces a new dicitonary:

@property
def all_attributes(self):
    return dict(vars(self), prop1=self.prop1)

This could be automated with introspection to detect all property objects, but take into account that you use properties to avoid having to do the calculation up-front in the first place; triggering calculations for all properties may not be all that desirable.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Huge! I like the cleanliness of your solution! – Vrakfall Dec 13 '18 at 17:52
  • What if property is modified by a `@setter`? If I have `@property def uno(self): return self._uno` and `@uno.setter def uno(self, val): self._uno = val*10` your solution returns `{'_uno': 100}` instead of `{'uno': 100}` – leonard vertighel Apr 17 '20 at 10:46
  • @leonardvertighel: it returns **all** key-value pairs in the instance attributes mapping, yes. It'll return `uno` if you add it explicitly, and if you don't want to show `_uno` you need to filter it out explicitly: `return {**{k: v for k, v in vars(self) if k[:1] != '_'}, 'uno': self.uno}` filters out keys that start with an underscore. – Martijn Pieters Apr 17 '20 at 20:27
4

What you want is the inspect module.

a = myObject()
a.attr1 = 'Apple'
a.attr2 = 'Banana'
inspect.getmembers(a)

returns

[('attr1','Apple'),('attr2','Banana'),('prop1','ModifiedApple')...]

Note that getmembers will also return builtin members like __sizeof__, so you may need to apply a bit of filtering to the list.

If you want it as a dictionary you can always use

dict(inspect.getmembers(a))
Nick Bailey
  • 3,078
  • 2
  • 11
  • 13
  • Filtering the list returned from `inspect.getmembers` for properties might be a little complicated for all but the simplest classes, unless a specific convention was used for naming the class properties you want to find (like using the prefix 'prop' for all properties)... – Matt P Feb 27 '19 at 18:20