3

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.

UPDATE:

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 compatibility reasons.

UPDATE for THC4k's answer:

What if I have:

class Target(object):
    attr1 = 1
    attr1__attr2__attr3 = 5

>>> proxy.attr1
1
>>> proxy.attr1.attr2.attr3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'attr2'

Any help will be much appreciated.

5
  • 1
    Why do you need to do this? This is playing "magic games" with Python syntax, trying to make it do things very far from what it's meant to do. What happens if both obj.attr1__attr2 exists and obj.attr1 has an attribute attr2? The results are ambiguous. Commented Nov 22, 2010 at 15:38
  • I'm doing this to support legacy code. obj.attr1 will never have attr2 attribute so it isn't a problem. Commented Nov 22, 2010 at 15:40
  • 2
    This idea is just broken. I mean, really broken. Not only what @Glenn mentiones. It also takes deep hackery to implement, screws every introspection tool (and every developer trying to understand the code), and I doubt that it is worth the effort. Commented Nov 22, 2010 at 15:41
  • could you add a bit more context to your question? maybe there are other more appropiate ways to do it. Commented Nov 22, 2010 at 15:48
  • 1
    Saying obj.attr1.attr2 when both obj.attr1 and obj.attr1__attr2 exists is again ambiguous. Worse, what do you expect to happen if you say obj.attr1 when obj.attr1__attr2 exists, but obj.attr1 doesn't? It's impossible to make a.b raise an AttributeError and a.b.c not. This is inherently broken. Commented Nov 22, 2010 at 15:51

3 Answers 3

4

For the cases that you have list of the attribute names you can use itertools() function (in python-3.x functools.reduce()) and the getattr() built-in function :

Here is an example:

In [1]: class A:
   ...:     def __init__(self):
   ...:         self.a1 = B()
   ...:         

In [2]: class B:
   ...:     def __init__(self):
   ...:         self.b1 = C()
   ...:         

In [3]: class C:
   ...:     def __init__(self):
   ...:         self.c1 = 7
   ...:         

In [4]: from functools import reduce 
In [5]: reduce(getattr, [A(), 'a1', 'b1', 'c1'])
Out[5]: 7
Sign up to request clarification or add additional context in comments.

Comments

3

I hope you know what you're doing and this is not just a scheme to avoid fixing your existing code.

I think there are legitimate reasons to do this, after all I've done something similar in Lua to implement a wrapper around some C code without having to actually write code for every exposed function.

But you should at least separate the actual class from the proxy:

# 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


t = Target()
proxy = GetattrProxy(t)

print proxy.attr1.attr2.attr3

@katrielalex suggestion:

class GetattrProxy2(GetattrProxy):
    def __getattr__(self, key):
            attr = (key if self.prefix is None else self.prefix + '__' + key)
            proxy = GetattrProxy2(self.proxied, attr)

            # store val only if the proxied object has the attribute, 
            # this way we still get AttributeErrors on nonexisting items 
            if hasattr(self.proxied, attr):
                proxy.val = getattr(self.proxied, attr)
            return proxy

proxy = GetattrProxy2(t)
proxy.attr1.val # 1
proxy.attr1.attr2.attr3.val # 5
proxy.attr1.attr2.val # raise AttributeError

6 Comments

Great, but what if i have attr1 attribute in Target (have updated my question)
@t0ster: That is simply impossible, because nothing can be 1 and a proxy at the same time. proxy.attr1.attr2.attr3 will always mean (((proxy.attr1).attr2).attr3 which is evaluated to (((1).attr2).attr3 first. There is nothing you can do about this.
May be I can dynamically create GetattrProxy type that will be a subclass of attr1 (so it will emulate int, str, etc. behavior).
t0ster: For that you need to merge the GetattrProxy.__init__ and the actual type's __init__ methods ... for every type you're using ...
@THC, @t0ster: you can emulate this behaviour by adding a val attribute to the proxy class and returning GetattrProxy(self.proxied, attr, val=getattr(self.proxied, attr)) instead of just getattr(self.proxied, attr). You can then recover the data with .val on the final lookup.
|
0
class A:
    def __init__(self, a):
        self.a = a

# create objects with many a  atributes..        
a = A(A(A(A('a'))))


x = a
# as long as a has atribute a continue...
while hasattr(x, 'a'):
    print x
    x = x.a

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.