phoibos
phoibos

Reputation: 3979

Python: Making a read-only property accessible via **vars(some_class)

I often use the idiom '{var_name}'.format(**vars(some_class)).

However, when I use a property, I cannot use this to get the properties value.

Consider this program:

#!/usr/bin/env python

class Foo(object):
    def __init__(self):
        self._bar = None
        self.baz = 'baz here'

    @property
    def bar(self):
        if not self._bar:
            # calculate some value...
            self._bar = 'bar here'
        return self._bar

if __name__ == '__main__':
    foo = Foo()

    # works:
    print('{baz}'.format(**vars(foo)))

    # gives: KeyError: 'bar'
    print('{bar}'.format(**vars(foo)))

Question:

Is there a way to make a properties value accessible via **vars(some_class)?

Upvotes: 9

Views: 1651

Answers (5)

OrangeCube
OrangeCube

Reputation: 398

You can write it into __dict__ of your instance

class Article(object):
    def __init__(self):
        self.content = ""
        self.url = ""

    @property
    def title(self):
        return self.__dict__.get("title", "")

    @title.setter
    def title(self, title):
        self.__dict__["title"] = title

then:

>>> article = Article()
>>> article.title = "Awesome Title"
>>> vars(article)
{'content': '', 'url': '', 'title': 'Awesome Title'}

Upvotes: 0

Sven Marnach
Sven Marnach

Reputation: 601649

To do exactly what you asked for, you can write a class that translates item access to attribute access:

class WrapperDict(object):
    def __init__(self, obj):
        self.obj = obj
    def __getitem__(self, key):
        return getattr(self.obj, key)

Example:

>>> print('{bar}'.format_map(WrapperDict(Foo())))
bar here

Another rather hacky alternative would be to add

__getitem__ = object.__getattribute__

to the class Foo and then use the Foo instance directly:

>>> print('{bar}'.format_map(Foo()))
bar here

I think using attribute access notation is the better solution though.

Upvotes: 1

Bakuriu
Bakuriu

Reputation: 101959

Short answer: No, it's not possible to use .format(**vars(object)) to do what you want, since properties do not use __dict__ and from vars documentation:

vars(...)

vars([object]) -> dictionary

  • Without arguments, equivalent to locals().
  • With an argument, equivalent to object.__dict__.

However you can achieve what you want using different format specifiers, for example the attribute lookup:

In [2]: '{.bar}'.format(Foo())
Out[2]: 'bar here'

Note that you simply have to add a leading . (dot) to the names and you get exactly what you want.


Side note: instead of using .format(**vars(object)) you should use the format_map method:

In [6]: '{baz}'.format_map(vars(Foo()))
Out[6]: 'baz here'

Calling format_map with a dict argument is equivalent to calling format using the ** notation, but it is more efficient, since it doesn't have to do any kind of unpacking before calling the function.

Upvotes: 8

Robert Caspary
Robert Caspary

Reputation: 1624

If I understood you correct, something like this should work:

print( eval( 'foo.{bar}'.format( **dict( ( v, v ) for v in dir( foo ) ) ) ) )

But nevertheless this feels somehow "very bad".

Upvotes: 0

Tim Wakeham
Tim Wakeham

Reputation: 1039

Use . notation -

print('{0._bar}'.format(foo))

Upvotes: 1

Related Questions