[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