Local variables persist across function calls

Discussion in 'Python' started by Dave, May 15, 2010.

  1. Dave

    Dave Guest

    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.
     
    Dave, May 15, 2010
    #1
    1. Advertising

  2. 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

    --
    blog at <url: http://alfps.wordpress.com>
     
    Alf P. Steinbach, May 15, 2010
    #2
    1. Advertising

  3. Dave

    Chris Rebert Guest

    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
     
    Chris Rebert, May 15, 2010
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Warren J. Hairston

    Persist a stack across PostBacks?

    Warren J. Hairston, May 11, 2004, in forum: ASP .Net
    Replies:
    1
    Views:
    370
    Jason DeFontes
    May 11, 2004
  2. Godwin Burby
    Replies:
    3
    Views:
    1,748
    Godwin Burby
    Mar 17, 2006
  3. Replies:
    8
    Views:
    314
    Scott David Daniels
    Nov 26, 2006
  4. Bob
    Replies:
    5
    Views:
    262
  5. John Davison
    Replies:
    1
    Views:
    97
    Richard Cornford
    Mar 6, 2007
Loading...

Share This Page