Python recursively __getattribute__

A

Andreas Waldenburger

Hello,

I need to implement such behavior:

obj.attr1.attr2.attr3 --> obj.attr1__attr2__attr3
It looks like I have to override obj's class __getattribute__ and also
use python descriptors somehow.

Any help will be much appreciated.
http://stackoverflow.com/questions/4247036/python-recursively-getattribute

Why? No, really: Why?

In that link you say that you need to do this to support legacy code. I still don't see how this would be necessary. If you need to support legacy code, doesn't that mean that the solution you're asking for already exists?

I really think you should go into detail about why you need this. I'm certain that there's a better solution to your problem. ("Better" being one that is reasonably easy to implement and maintain.)

/W
 
R

Roman Dolgiy

Why? No, really: Why?

In that link you say that you need to do this to support legacy code. I still don't see how this would be necessary. If you need to support legacy code, doesn't that mean that the solution you're asking for already exists?

I really think you should go into detail about why you need this. I'm certain that there's a better solution to your problem. ("Better" being one that is reasonably easy to implement and maintain.)

/W

I have a django project.

obj is django-haystack's SearchResult instance, it contains a lot of
de-normalized data (user__name, user__address) from django model, and
I need to access it as result.user.name for compability reasons.
 
A

Andreas Waldenburger

I need to implement such behavior:
obj.attr1.attr2.attr3 --> obj.attr1__attr2__attr3
It looks like I have to override obj's class __getattribute__ and
also use python descriptors somehow.

Why? No, really: Why?

[...]

I have a django project.

obj is django-haystack's SearchResult instance, it contains a lot of
de-normalized data (user__name, user__address) from django model, and
I need to access it as result.user.name for compability reasons.

I don't know anything about django, so I may be babbling nonsense. Caveat emptor.

How about taking your "user__whatever" thingies and have a function emit customized result instances. For this you can just create a plain object subclass, say ResultElement. If these "user__whatever" thingies are strings (you haven't told), split them by "__" and create a new plain object for every level of attributes, attaching it to the previous level.

You can probably make your life easier if you use defaultdicts first and then translate these to your object hierarchy.

That's how I'd do it. I don't know if that helps you. If not, please provide more info.

/W
 
T

Terry Reedy

Hello,

I need to implement such behavior:

obj.attr1.attr2.attr3 --> obj.attr1__attr2__attr3

obj.attr1.attr2.attr3 is parsed as ((obj.attr1).attr2).attr3,
so this cannot work in general but only if attr1 and attr2 are known to
not be 'final' names.
It looks like I have to override obj's class __getattribute__ and also
use python descriptors somehow.

Any help will be much appreciated.
http://stackoverflow.com/questions/4247036/python-recursively-getattribute

The code posted there by THC4k depened on such knowledge, which you gave
there but not here.
 
R

Roman Dolgiy

obj.attr1.attr2.attr3 is parsed as ((obj.attr1).attr2).attr3,
so this cannot work in general but only if attr1 and attr2 are known to
not be 'final' names.



The code posted there by THC4k depened on such knowledge, which you gave
there but not here.

I need to support a lot of legacy code, with THC4k's approach I'll
have to modify project's existing code to use obj.attr1.val instead of
obj.attr1 but this is not suitable.
 
B

bruno.desthuilliers

I need to support a lot of legacy code, with THC4k's approach I'll
have to modify project's existing code to use obj.attr1.val instead of
obj.attr1 but this is not suitable.

You should probably re-read THC4k's answer. His code works just fine
AFAICT:

"""
# the proxy maps attribute access to another object
class GetattrProxy(object):
def __init__(self, proxied, prefix=None):
self.proxied = proxied
self.prefix = prefix

def __getattr__(self, key):
attr = (key if self.prefix is None else self.prefix + '__' +
key)
try:
# if the proxied object has the attr return it
return getattr(self.proxied, attr)
except AttributeError:
# else just return another proxy
return GetattrProxy(self.proxied, attr)


# the thing you want to wrap
class Target(object):
attr1__attr2__attr3 = 5
attr2 = "attr2"


t = Target()
proxy = GetattrProxy(t)

print "proxy.attr1.attr2.attr3 : '%s'" % proxy.attr1.attr2.attr3
print "proxy.attr2 : '%s'" % proxy.attr2
"""
 
R

Roman Dolgiy

Thanks to Andreas Waldenburger, THC4k (http://stackoverflow.com/
questions/4247036/python-recursively-getattribute) and others for
their tips. I was able to find solution:

class Null(object):
def __repr__(self):
return "<Null>"

def __str__(self):
return ''

def __nonzero__(self):
return False


class ResultAttrFactory(type):
_cache = {}

@classmethod
def prepare(cls, base, result):
dict_ = ResultAttr.__dict__.copy()
dict_.update({
'_ResultAttr__base': base,
'_ResultAttr__result': result})
return ('ResultAttr', (base,), dict_)

def __new__(cls, base, result):
if (base, result) in cls._cache:
type_ = cls._cache[(base, result)]
else:
type_ = super(ResultAttrFactory, cls).__new__(
cls, *cls.prepare(base, result))
cls._cache[(base, result)] = type_
return type_

def __init__(cls, base, result):
pass


class ResultAttr:
"""Should be used only with ResultAttrFactory"""
@staticmethod
def __new__(cls, arg1, name):
return cls.__base.__new__(cls, arg1)

def __init__(self, arg1, name):
self.__name = name
super(self.__class__, self).__init__(arg1)

def get_result_attr(self, name):
if self.__result.is_denorm_attr(name):
attr = getattr(self.__result, name, None)
else:
attr = getattr(self.__result, name)
return attr

def __getattr__(self, name):
lookup_name = "%s__%s" % (self.__name, name)
attr = self.get_result_attr(lookup_name)
if type(attr).__name__ == 'ResultAttr':
type_ = attr.__base
elif attr is None:
type_ = Null
else:
type_ = type(attr)
result_attr = ResultAttrFactory(
type_, self.__result)(attr, lookup_name)
return result_attr


class BaseResult(object):
""" ... def __init__(self, *args, **kwargs):
... self.x = 35
... self.y = 5
... self.y__a = 3
... self.y__b = 'hello'
... self.y__c__x = [1, 2, 3]
... super(Result, self, *args, **kwargs) Traceback (most recent call last):
AttributeError: 'Result' object has no attribute 'y__d' [1, 2, 3]
"""
def is_denorm_attr(self, name):
return bool([k for k in self.__dict__.keys() if "%s__" % name
in k])

def __getattribute__(self, name):
attr = super(BaseResult, self).__getattribute__(name)
if name in ('__dict__', 'is_denorm_attr'):
return attr
if self.is_denorm_attr(name):
return ResultAttrFactory(type(attr), self)(attr, name)
else:
return attr


if __name__ == '__main__':
import doctest
doctest.testmod()

https://gist.github.com/710977

The only problem is:
<Null>

Is there any way to raise AtrributeError here?

Also is there any negative side effects of such method?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,169
Messages
2,570,915
Members
47,456
Latest member
JavierWalp

Latest Threads

Top