FAQ
I've been writing Python for a few years now, and tonight I ran into
something that I didn't understand. I'm hoping someone can explain
this to me. I'm writing a recursive function for generating
dictionaries with keys that consist of all permutations of a certain
set. Here's the function:

<code>
def make_perm(levels, result = {}, key = ''):
local = key
if levels == 1:
for i in ['a', 'b', 'c', 'd']:
result [local + i] = ''
else:
for i in ['a', 'b', 'c', 'd']:
make_perm(levels - 1, result, local + i)
return result
</code>

The first time I ran it, make_perm(2) does what I expected, returning
a dictionary of the form {'aa': '', 'ab':'', 'ac':'', ... }. But, if I
run make_perm(3) after that, I get a dictionary that combines the
contents of make_perm(2) with the values of make_perm(3), a dictionary
like {'aaa':'', 'aab':'', ...}. Running make_perm(2) again will return
the same result as make_perm(3) just did. It's like the local variable
is preserved across different calls to the same function. I don't
understand why this happens: "result" is not a global variable, and
accessing it outside the function generates a NameError, as it should.
After running make_perm once, printing the value of result inside the
function produces the last result returned on the first iteration.

If, however, I explicitly pass an empty dictionary into make_perm as
the second argument, the value of "result" is returned correctly. So I
have a solution to my problem, but I still don't understand why it
works. I've tried this on both Python 2.6 and Python 3.2, both in IDLE
and from the command line, and it behaves the same way. So it seems
like this is an intentional language feature, but I'm not sure exactly
why it works this way, or what's going on. Anyway, I'd appreciate it
if someone could explain this to me.

Search Discussions

  • Alf P. Steinbach at May 15, 2010 at 5:56 pm

    On 15.05.2010 19:18, * Dave:
    I've been writing Python for a few years now, and tonight I ran into
    something that I didn't understand. I'm hoping someone can explain
    this to me. I'm writing a recursive function for generating
    dictionaries with keys that consist of all permutations of a certain
    set. Here's the function:

    <code>
    def make_perm(levels, result = {}, key = ''):
    local = key
    if levels == 1:
    for i in ['a', 'b', 'c', 'd']:
    result [local + i] = ''
    else:
    for i in ['a', 'b', 'c', 'd']:
    make_perm(levels - 1, result, local + i)
    return result
    </code>
    I bet this is a FAQ, but I don't know where the FAQ is (probably at python.org?).

    The defaults for formal parameters are evaluated /once/, namely at function
    definition time, when the execution first passes through the definition.

    And what you're doing is to update that original default 'result' dictionary.

    To achieve the effect that you apparently want you can do


    def make_perm( levels, result = None, key = '' )
    if result is None: result = {} # Evaluated for each call.
    # Blah blah, the rest


    There are also other ways.


    Cheers & hth.,

    - Alf
  • Chris Rebert at May 15, 2010 at 6:01 pm

    On Sat, May 15, 2010 at 10:18 AM, Dave wrote:
    I've been writing Python for a few years now, and tonight I ran into
    something that I didn't understand. I'm hoping someone can explain
    this to me. I'm writing a recursive function for generating
    dictionaries with keys that consist of all permutations of a certain
    set. Here's the function:

    <code>
    def make_perm(levels, result = {}, key = ''):
    ? ? ? ?local = key
    ? ? ? ?if levels == 1:
    ? ? ? ? ? ? ? ?for i in ['a', 'b', 'c', 'd']:
    ? ? ? ? ? ? ? ? ? ? ? ?result [local + i] = ''
    ? ? ? ?else:
    ? ? ? ? ? ? ? ?for i in ['a', 'b', 'c', 'd']:
    ? ? ? ? ? ? ? ? ? ? ? ?make_perm(levels - 1, result, local + i)
    ? ? ? ?return result
    </code>

    The first time I ran it, make_perm(2) does what I expected, returning
    a dictionary of the form {'aa': '', 'ab':'', 'ac':'', ... }. But, if I
    run make_perm(3) after that, I get a dictionary that combines the
    contents of make_perm(2) with the values of make_perm(3), a dictionary
    like {'aaa':'', 'aab':'', ...}. Running make_perm(2) again will return
    the same result as make_perm(3) just did. It's like the local variable
    is preserved across different calls to the same function. I don't
    understand why this happens: "result" is not a global variable, and
    accessing it outside the function generates a NameError, as it should.
    After running make_perm once, printing the value of result inside the
    function produces the last result returned on the first iteration.

    If, however, I explicitly pass an empty dictionary into make_perm as
    the second argument, the value of "result" is returned correctly. So I
    have a solution to my problem, but I still don't understand why it
    works. I've tried this on both Python 2.6 and Python 3.2, both in IDLE
    and from the command line, and it behaves the same way. So it seems
    like this is an intentional language feature, but I'm not sure exactly
    why it works this way, or what's going on. Anyway, I'd appreciate it
    if someone could explain this to me.
    Default argument values are only evaluated *once*, at
    function-definition time, *not* every time the function is called. So
    whenever you call make_perm() without specifying the `result`
    argument, `result` will get *the same dictionary* as its value every
    time, and modifications to that dictionary will thus persist across
    calls.

    When a default argument value is of a mutable type such as a
    dictionary, the following idiom is used to get around the
    aforementioned behavior:

    def make_perm(levels, result = None, key = ''):
    if result is None:
    result = {}
    #rest same as before...

    This ensures `result` gets a fresh dictionary every time.

    See also the "Important warning" on
    http://docs.python.org/tutorial/controlflow.html#default-argument-values

    Cheers,
    Chris
    --
    I'm not a fan of this behavior either.
    http://blog.rebertia.com

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
grouppython-list @
categoriespython
postedMay 15, '10 at 5:18p
activeMay 15, '10 at 6:01p
posts3
users3
websitepython.org

People

Translate

site design / logo © 2022 Grokbase