[Tutor] *args consumption

Kent Johnson kent37 at tds.net
Sun Mar 12 14:28:17 CET 2006


Smith wrote:
> But there is another use that I am looking at right now (as
encountered in the turtle module) where the arguments are analyzed in
the function and not passed along. In such cases it seems like the first
two lines of the function below are needed to get the passed arg out of
the tuple form that is created and into the form expected in the function.
> 
> ###
> def goto(*arg):
>     if len(arg) == 1:
>         arg = arg[0]
>     x, y = arg
>     print x, y
> goto(1,2)
> pt = 1,2
> goto(pt)
> ###
> 
> Without those first two lines, passing 'pt' as in the example results
> 
in a runtime error since arg = ((1, 2),) -- a tuple of length 1 --
cannot be unpacked into two items.
> 
> MY QUESTION: since the pass_along function will work with this
unpacking of length = 1 tuples and this is what you have to do anyway if
you are going to consume the tuple in a function (as in the goto
function above) is there a reason that this isn't automatically done for
star arguments? Am I missing some other usage where you wouldn't want to
unpack the *arg? If not, would the following "behind the scenes"
behavior be possible/preferred?

Danny has given some reasons why this is not useful standard behaviour. 
If this is a behaviour you need for many functions, you could create a 
decorator that provides it so you don't have to include the same 
boilerplate in each function.

Decorators are functions that accept a function as an argument and 
return a new function as a result. Here is a decorator that will unpack 
a tuple argument:

In [7]: def accept_tuple_or_coordinates(f):
    ...:     def unpacking_f(*args):
    ...:         if len(args) == 1:
    ...:             x, y = args[0]
    ...:         else:
    ...:             x, y = args
    ...:         return f(x, y)
    ...:     return unpacking_f
    ...:

This shows the typical structure of a decorator. It defines a new 
function that wraps its argument with some new functionality, and 
returns the new function.

Now to define a function of a point that can take either a single point 
or a pair of coordinates, just write a function of a pair of coordinates 
and decorate it:
In [8]: @accept_tuple_or_coordinates
    ...: def goto(x, y):
    ...:     print 'Going to', x, y
    ...:
    ...:

By prefixing the function definition with
   @accept_tuple_or_coordinates
the newly defined function is passed as the argument to the decorator, 
and the result is rebound to the name of the defined function. It is 
exactly as if you had written
   def goto(x, y):
     ...
   goto = accept_tuple_or_coordinates(goto)

In fact if you want to use decorators in Python 2.3 you have to use this 
method.

A decorator is reusable so you can have lots of functions like this:
In [9]: @accept_tuple_or_coordinates
    ...: def draw(x, y):
    ...:     print 'Drawing', x, y
    ...:
    ...:

Try it out:

In [10]: goto(1, 2)
Going to 1 2

In [11]: p = (3, 4)

In [12]: goto(p)
Going to 3 4

In [13]: draw(p)
Drawing 3 4

It works!

There are some subtleties to decorators that you can ignore but you may 
not want to. For example with this simple example the function name is 
changed by the decorator:

In [14]: draw
Out[14]: <function unpacking_f at 0x0101B8B0>

Docstrings are also lost. The solution is to copy these attributes in 
the decorator, like this:

def accept_tuple_or_coordinates(f):
     def unpacking_f(*args):
         if len(args) == 1:
             x, y = args[0]
         else:
             x, y = args
         return f(x, y)
     unpacking_f.__name__ = f.__name__
     unpacking_f.__dict__ = f.__dict__
     unpacking_f.__doc__ = f.__doc__
     return unpacking_f

Now the function name and docstring is preserved:

In [16]: @accept_tuple_or_coordinates
    ....: def draw(x, y):
    ....:     ''' Draws a single point at the given coordinates '''
    ....:     print 'Drawing', x, y
    ....:
    ....:

In [17]: draw
Out[17]: <function draw at 0x0101BBF0>

In [18]: draw.__doc__
Out[18]: ' Draws a single point at the given coordinates '

Kent



More information about the Tutor mailing list