__getattr__ and functions that don't exist

George Sakkis george.sakkis at gmail.com
Thu May 25 22:11:01 EDT 2006


Erik Johnson wrote:

> Thanks for your reply, Nick.  My first thought was "Ahhh, now I see. That's
> slick!", but after playing with this a bit...
>
> >>> class Foo:
> ...     def __getattr__(self, attr):
> ...         def intercepted(*args):
> ...             print "%s%s" % (attr, args)
> ...         return intercepted
> ...
> >>> f = Foo()
> >>> f
> __repr__()
> Traceback (most recent call last):
>   File "<stdin>", line 1, in ?
> TypeError: __repr__ returned non-string (type NoneType)
>
>
>     my thought is "Oooooh... that is some nasty voodoo there!"   Especially
> if one wants to also preserve the basic functionality of __getattr__ so that
> it still works to just get an attribute where no arguments were given.
>
>     I was thinking it would be clean to maintain an interface where you
> could call things like f.set_Spam('ham') and implement that as self.Spam =
> 'ham' without actually having to define all the set_XXX methods for all the
> different things I would want to set on my object (as opposed to just making
> an attribute assignment), but I am starting to think that is probably an
> idea I should just simply abandon.

You're right, you should probably abandon it because python is not
java; there's no reason to bloat your API with getters and setters
instead of the natural attribute access and assignment. Now if you've
spent a lot of time in the java camp and can't live without verbose
getters and setters, it's not hard to emulate them:

class Foo(object):
    def __getattr__(self, attr):
        if not (attr.startswith('get_') or attr.startswith('set_')):
            raise AttributeError
        name = attr[4:]
        if attr[0] == 'g':
            return lambda: getattr(self,name)
        else:
            return lambda value: setattr(self,name,value)

f = Foo()
f.set_bar(1)
print f.get_bar()
print f.bar


>     I guess I don't quite follow the error above though. Can you explain
> exactly what happens with just the evaluation of f?

Several thing happen:
1. When you give an expression in the shell, its value is computed and
then passed to the repr() function. The result of repr(f) is what's
printed.
2. repr(f) attempts to call f.__repr__()
3. __repr__ is not defined in class Foo or any of its (zero)
superclasses.
4. As a result, f.__getattr__('__repr__') is called instead and returns
the intercept local function.
5. intercept() is called with zero arguments (remember, intercept is
the result of f.__repr__)
6. intecept prints something but it doesn't return explicitly; thus it
returns None.
7. There's a rule that __repr__ (like __str__ and __unicode__) has to
return a string-type (I'm not sure where is this rule enforced, by the
classobj maybe ?). Since you returned None, an exception is raised.

Note that there is not an exception if
- you replace print with return in __getattr__, or
- Foo is a new-style class, i.e. extends object like this:
class Foo(object):
    # rest remain the same
The reason is that in step (3) above, __repr__ would be looked up in
Foo's superclass, object, and object.__repr__ would be called instead
of Foo.__getattr__. Try to use new-style classes in new code unless you
have a good reason not to.

> Thanks,
> -ej

HTH,
George


> "Nick Smallbone" <nick.smallbone at gmail.com> wrote in message
> news:1148596303.833301.15190 at g10g2000cwb.googlegroups.com...
> > Erik Johnson wrote:
> > > Maybe I just don't know the right special function, but what I am
> wanting to
> > > do is write something akin to a __getattr__ function so that when you
> try to
> > > call an object method that doesn't exist, it get's intercepted *along
> with
> > > it's argument*, in the same manner as __getattr__ intercepts attributes
> > > references for attributes that don't exist.
> > >
> > >
> > > This doesn't quite work:
> > >
> > > >>> class Foo:
> > > ...   def __getattr__(self, att_name, *args):
> > > ...     print "%s%s" % (att_name, str(tuple(*args)))
> > > ...
> > > >>> f = Foo()
> >
> > The problem is that the call to f.bar happens in two stages: the
> > interpreter calls getattr(f, "foo") to get a function, and then it
> > calls that function. When __getattr__ is called, you can't tell what
> > the parameters of the function call will be, or even if it'll be called
> > at all - someone could have run "print f.bar".
> >
> > Instead, you can make __getattr__ return a function. Then *that*
> > function will be called as f.bar, and you can print out its arguments
> > there:
> >
> > class Foo:
> >     def __getattr__(self, attr):
> >         def intercepted(*args):
> >             print "%s%s" % (attr, args)
> >         return intercepted
> >




More information about the Python-list mailing list