__getattr__ and functions that don't exist

Ben Cartwright bencvt at gmail.com
Fri May 26 04:54:16 CEST 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.

Well, you could tweak __getattr__ as follows:

>>> class Foo:
...     def __getattr__(self, attr):
...         if attr.startswith('__'):
...             raise AttributeError
...         def intercepted(*args):
...             print "%s%s" % (attr, args)
...         return intercepted

But abandoning the whole idea is probably a good idea.  How is defining
a magic set_XXX method cleaner than just setting the attribute?  Python
is not C++/Java/C#.  Accessors and mutators for simple attributes are
overkill.  Keep it simple, you'll thank yourself for it later when
maintaining your code. :-)

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

Sure.  (Note, this is greatly simplified, but still somewhat complex.)
The Python interpreter does the following when you type in an
expression:

(1) evaluate the expression, store the result in temporary object
(2) attempt to access the object's __repr__ method
(3) if step 2 didn't raise an AttributeError, call the method, output
the result, and we're done
(4) if __getattr__ is defined for the object, call it with "__repr__"
as the argument
(5) if step 4 didn't raise an AttributeError, call the method, output
the result, and we're done
(6) repeat steps 2 through 5 for __str__
(7) as a last resort, output the default "<class __main__.Foo at
0xDEADBEEF>" string

In your case, the intepreter hit step 4.  f.__getattr__("__repr__")
returned the "intercepted" function, which was then called.  However,
the "interpreted" function returned None.  The interpreter was
expecting a string from __repr__, so it raised a TypeError.

Clear as mud, right?  Cutting out the __getattr__ trickery, here's a
simplified scenario (gets to step 3 from above):

  >>> class Bar(object):
  ... def __repr__(self):
  ...     return None
  ...
  >>> b = Bar()
  >>> b
  Traceback (most recent call last):
    File "<stdin>", line 1, in ?
  TypeError: __repr__ returned non-string (type NoneType)

Hope that helps!  One other small thing... please avoid top posting.

--Ben




More information about the Python-list mailing list