generator functions: why won't this work?

Mel mwilson at the-wire.com
Wed Apr 2 23:03:23 EDT 2008


zillow10 at googlemail.com wrote:
> On Apr 2, 3:57 pm, Mel <mwil... at the-wire.com> wrote:
>> zillo... at googlemail.com wrote:
>>
>> I'd just like to test my
>>
>>> understanding of this. Suppose I create the following generator
>>> object:
>>> g = getNextScalar(1, 2, (3, 4), 5)
>>> when the iterator reaches the tuple argument (3, 4) then, according to
>>> Steve and George, the * in *arg causes this tuple to be expanded into
>>> positional arguments, and it makes sense to do it this way. But what
>>> happens when getNextScalar(arg) is used instead?
>> Try it:
>>
>> Python 2.5.1 (r251:54863, Mar  7 2008, 04:10:12)
>> [GCC 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)] on linux2
>> Type "help", "copyright", "credits" or "license" for more information.
>>  >>> def a (arg):
>> ...   print arg
>> ...
>>  >>> def astar (*arg):
>> ...   print arg
>> ...
>>  >>> a(3,4)
>> Traceback (most recent call last):
>>    File "<stdin>", line 1, in <module>
>> TypeError: a() takes exactly 1 argument (2 given)
>>  >>> astar(3,4)
>> (3, 4)
>>  >>> a((3,4))
>> (3, 4)
>>  >>> astar((3,4))
>> ((3, 4),)
>>  >>>
>>
>>         Mel.
> 
> Well, I understand that (unless I missed the point you're trying to
> make). But with respect to the example I quoted:
> 
> def getNextScalar(*args):
>         for arg in args:
>                 if(isinstance(arg, tuple)):
>                         for f in getNextScalar(arg): # should use *arg
>                                 yield f
>                 else:
>                         yield arg
>  where the function is declared as def getNextScalar(*arg), but is
> called using getNextScalar(arg), with arg being a tuple: here the
> generator is being passed a single argument, so there's no TypeError
> as in your example. However, it fails - as I understand it - because
> the function keeps passing the same tuple (being unable to access the
> elements inside it) and goes into an infinite loop:
>>>> # works for this example, but not very useful:
>>>> g = getNextScalar(1, 2, 3, 4)
>>>> for i in g:
> 	print i
> 
> 
> 1
> 2
> 3
> 4
> 
> # third argument is a tuple:
>>>> g = getNextScalar(1, 2, (3, 4))
>>>> for i in g:
> 	print i
> 
> 
> 1
> 2
> 
> Traceback (most recent call last):
>   File "<pyshell#108>", line 1, in <module>
>     for i in g:
>   File "<pyshell#94>", line 4, in getNextScalar
>     for f in getNextScalar(arg):
>   File "<pyshell#94>", line 4, in getNextScalar
>     for f in getNextScalar(arg):
>   File "<pyshell#94>", line 4, in getNextScalar
>     for f in getNextScalar(arg):
> ...

Yeah.  I feel as though I haven't put the idea clearly.  When a 
function is defined as `def astar (a*)`, then the parameters that it's 
called with, however many they are, are packed up into one tuple. 
Illustrated by those print statements.

On the calling side, `a (*(3,4))` will unpack the (3,4) tuple and 
treat the contents as ordinary positional parameters of a.  The call 
`a(*(3,4))` and the call `a(3,4)` are completely equivalent as far as 
the function a can see.

This explanation is going nowhere fast.  Anyway, if you define 
getNextScalar to pack its positional arguments into a tuple that you 
can iterate over, THEN if you want to feed it a tuple of values to 
iterate over you have to star-unpack the tuple when you call.  The 
buggy behaviour is continually calling getNextScalar with a single 
positional parameter (3,4), packing that single parameter into a 
1-tuple ((3,4),) , iterating over the 1-tuple to extract (3,4) and 
calling getNextScalar again -- ad dump.

	Mel.



More information about the Python-list mailing list