__getattr__ and others

Alex Martelli aleax at aleax.it
Wed Nov 13 07:27:25 EST 2002


Grzegorz Dostatni wrote:

> I want to create an object that (at runtime) is called with an arbitrary
> function with arbitrary number of arguments.  Haveing this call it will
> forward it through xml-rpc to the server, execute it and return the
> results.
> 
> I am missing something simple - here is what I have:
> 
>     def calling(self, method="", *args, **kwargs):
>         print "calling method ", method
>         print "With arguments", args, kwargs
> 
>     def __getattr__(self, name):
>         return lambda s=self, m=name, *args, **kwargs: apply(s.calling,
> (m, ) + args, kwargs
> 
> This works as expected for (o is the instance of the object)
> 
> 1. o.hello = OK. Returns the lambda function.
> 2. o.hello(keyword="Argument") = OK. that works.
> 3. o.hello(3, keyword="Argument") = This does not work.
> 4. o.hello(3) = This does not work again.
> 
> I have to do something like o.hello(o, "hello", 3) to get what I want.
> Does anyone have an idea how to make it more consistent with normal
> calling style?

I see it has already been pointed out to you that there are problems
with your lambda definition, when called with positional arguments.

The good news is that, in recent versions of Python, you don't need
the "default-arguments hack" to have a nested function know about
local variables of outer functions in which it's nested -- such a
"closure" is now entirely automatic.  Therefore, and without any
need for lambda's and apply's, you could just code:

    def __getattr__(self, name):
        def closure(*args, **kwds):
            return self.calling(name, *args, **kwds)
        return closure

*However* -- take care!  If you code such an unconditional __getattr__,
instances of your class WILL claim they have ANY special method Python
asks about -- consistent with the specification you give about an
*arbitrary* function, but not necessarily with what you really mean.

For example:

>>> class tricky:
...   def calling(self, method, *args, **kwds):
...     print 'calling', method, args, kwds
...   def __getattr__(self, name):
...     def closure(*args, **kwds):
...         return self.calling(name, *args, **kwds)
...     return closure
...
>>> t = tricky()
>>> print t
 calling __str__ () {}
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: __str__ returned non-string (type NoneType)
>>>

t claims it has a special-method __str__ (via that __getattr__, of
course), but it doesn't, really -- tricky.calling is not acceptable
in the role of special-method __str__ because it does not return
a string.  If you're SURE you want to forward special-method calls
to some xml-rpc server, OK -- but in this case the tricky class is
still not fully specs-compliant (it doesn't forward __getattr__,
nor __setattr__ and __delattr__, which are special-cased; nor
does it forward 'calling', because that's its own attribute).  In
the more likely case in which you want to except special-names from
your __getattr__ behavior, make sure its return statement is guarded
by an appropriate conditional, e.g.

    if name.startswith('__') and name.endswith('__'):
        raise AttributeError, name
    return closure

(Raising AttributeError is what __getattr__ must do in order to
communicate "no, I don't have this attribute").


Alex




More information about the Python-list mailing list