Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding __getattr__ to support dynamic nested attributes

Tags:

python

getattr

What is the best approach to take if you want to dynamically create and reference nested attributes?

I was writing a simple Flickr client, and wanted to match the documented API as closely as possible, without actually defining every method. For instance, to make a request to Flickr's flickr.people.getInfo API method:

flickr = Client()
data = flickr.people.getInfo(user_id='xxx')

In this case flickr.people.getInfo directly maps to the corresponding method in their API documentation. When called, people and getInfo are created as they are looked up, then the proper request to make is determined by the path to getInfo, which is people.getInfo. This is the approach I used:

class Attr(object):
    def __init__(self, client, name, parent):
        self._client = client
        self._name = name
        self._parent = parent

    def __getattr__(self, name):
        attr = Attr(self._client, name, self)
        setattr(self, name, attr)
        return attr

    def _get_path(self, path=None):
        if path:
            path = '.'.join((self._name, path))
        else:
            path = self._name
        if isinstance(self._parent, Attr):
            return self._parent._get_path(path)
        return path

    def __call__(self, *args, **kwargs):
        return self._client.execute_method(self._get_path(), *args, **kwargs)

class Client(object):
    def __getattr__(self, name):
        attr = Attr(self, name, None)
        setattr(self, name, attr)
        return attr

    def execute_method(self, method, *args, **kwargs):
        print method, args, kwargs

This works, but I'm curious if my approach to deal with nested attribute assignment/lookup can be improved, or if there are any errors lurking in wait, unbeknownst to me. In particular, I'm curious if there is a better way to figure out the "path" to a given attribute. For example, if I call Client().x.y.z(), x, y, z do not exist, and will be created one by one (as __getattr__ looks up a single attribute at a time). By the time z is called, I need to be able to discern that the path to z is x.y.z.

like image 231
zeekay Avatar asked Jun 27 '11 20:06

zeekay


People also ask

What is __ Getattr __?

__getattr__(self, name) Is an object method that is called if the object's properties are not found. This method should return the property value or throw AttributeError . Note that if the object property can be found through the normal mechanism, it will not be called.

What is Getattr Python?

Python getattr() function. Python getattr() function is used to get the value of an object's attribute and if no attribute of that object is found, default value is returned.


1 Answers

Thanks to Thomas K for pointing out that flipy already does this (and seems like a nice library for interacting with flickr). A cleaner approach:

class Method(object):
    def __init__(self, client, method_name):
        self.client = client
        self.method_name = method_name

    def __getattr__(self, key):
        return Method(self.client, '.'.join((self.method_name, key)))

    def __call__(self, **kwargs):
        print self.method_name, kwargs

class Client(object):
    def __getattr__(self, key):
        return Method(self, key)

Et voilà:

>>> c = Client()  
>>> c.some.method(x=1, y=2)
some.method {'y': 2, 'x': 1}
like image 65
zeekay Avatar answered Oct 23 '22 15:10

zeekay