Overriding __getattr__ to support dynamic nested attributes
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 _开发者_Python百科_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
.
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}
精彩评论