1

The function definition below comes from the itertools recipes page (https://docs.python.org/2/library/itertools.html#recipes).

def random_product(*args, **kwds):
    "Random selection from itertools.product(*args, **kwds)"
    pools = map(tuple, args) * kwds.get('repeat', 1)
    return tuple(random.choice(pool) for pool in pools)

The function signature would seem to be more readable written with the "repeat" argument specified explicitly, e.g.

def random_product(*args, repeat=1)

But I'm guessing there is a reason why it is written using the general idiom. Can anyone explain?

4

In Python 2, you cannot put a keyword argument after *args, that's a syntax error:

>>> import sys
>>> sys.version_info
sys.version_info(major=2, minor=7, micro=8, releaselevel='final', serial=0)
>>> def random_product(*args, repeat=1):
  File "<stdin>", line 1
    def random_product(*args, repeat=1):
                                   ^
SyntaxError: invalid syntax

If you put it before the positional catch-all, then it captures the first positional argument:

>>> def random_product(repeat=1, *args): return repeat, args
... 
>>> random_product('foo', 'bar')
('foo', ('bar',))

In Python 2 then, your only option is to capture arbitrary keyword arguments with **kw (since that only ever captures actual keyword arguments, not positional arguments), and extract repeat from that.

Python 3 adjusted what is supported; there any keyword arguments specified after the *args catch-all are allowed and are seen as keyword-only:

>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=4, micro=1, releaselevel='final', serial=0)
>>> def random_product(*args, repeat=1):  return args, repeat
... 
>>> random_product('foo', 'bar')
(('foo', 'bar'), 1)
| improve this answer | |

Not the answer you're looking for? Browse other questions tagged or ask your own question.