[Python-3000] pre-PEP: Default Argument Expressions

Jim Jewett jimjjewett at gmail.com
Thu Feb 15 17:48:02 CET 2007

On 2/13/07, Chris Rebert <cvrebert at gmail.com> wrote:
>      There are currently few, if any, known good uses of the current
>      behavior of mutable default arguments.

Then are there *any* good use cases for the proposed semantics?

Here are the use cases that I can remember seeing for mutable default arguments.

(1)  Not really (treated as) mutable.  ==> Doesn't care about the
mutability semantics.

    >>> def f(extra_settings={}) ...

usually doesn't modify or even store extra_settings; it just wants an
empty (and perhaps iterable) mapping.  (Sometimes, it doesn't even
need that, and is really just providing type information.)

(2)  Storing state between calls. ==> Keep the current semantics

We disagree on how useful this is, and how easily it can be replaced,
but agree that the use exists.

We also agree that it smells bad -- but the problem isn't mutable
arguments.  The problem is that the state variable really isn't
(intended as) a parameter at all, and it feels wrong to pretend that
it is.

In theory, we could fix this with something like C's static

    >>> def f():
    ...         once state_var={}

but in practice, there is some value in leaving it accessible, because of

(2a)  A test harness may wish to pass in its own state_var to get
extra information, or to avoid cluttering the production logs.

(3)  Collecting results ==> the code is buggy, don't encourage it.

    >>> def squares(data, results=[])
    ...         for e in data:
    ...                 results.append(e*e)

should instead be written as

    >>> def squares(data)
    ...         results=[]
    ...         for e in data:
    ...                 results.append(e*e)

to make it clear that results is newly constructed container.

(3b)  Adding stuff to a container ==> ???

I think this is the real motivation; I've done it myself.  But I
realized later that it was bad code.

For example, I may want a filter to return a list of candidates for
further processing.

    >>> def still_valid(data):
    ...         results = []
    ...         for e in data:
    ...                 if good_enough(e):
    ...                         results.append(e)

Hey, and maybe I have some other candidates already ...

    >>> candidates.extend(still_valid(data))

hmm ... but what if I don't usually have any previous candidates?
Couldn't I use a default argument?

    >>> def still_valid(data, results=[]) ...

And now I have buggy code.

The right answer isn't to force re-evaluation of []; it is to be clear
on when your functions will have side effects.  If you don't want call
sites littered with

    >>> candidates = []
    >>> candidates.extend(still_valid(data))

then write a helper, such as

    >>> def extra_candidates(data, known_candidates):
    ...         known_candidates.extend(still_valid(data))


More information about the Python-3000 mailing list