Concatenation of the result of a function with a mutable default argument“Least Astonishment” and the...

Why is there so little discussion / research on the philosophy of precision?

Who was president of the USA?

Anatomically Correct Whomping Willow

How would you identify when an object in a Lissajous orbit needs station keeping?

Numbers Decrease while Letters Increase

Is gzip atomic?

Did anyone try to find the little box that held Professor Moriarty and his wife after the Enterprise D crashed?

How many US airports have 4 or more parallel runways?

Does travel insurance for short flight delays exist?

French abbreviation for comparing two items ("vs")

Was there ever a treaty between 2 entities with significantly different translations to the detriment of one party?

Are there languages that inflect adverbs for gender

If all stars rotate, why was there a theory developed that requires non-rotating stars?

Position a tabular on the corner of a slide

What is a CirKle Word™?

Do they have Supervillain(s)?

Is there any way to keep a player from killing an NPC?

Are there any elected officials in the U.S. who are not legislators, judges, or constitutional officers?

Would the Republic of Ireland and Northern Ireland be interested in reuniting?

Can a Rogue PC teach an NPC to perform Sneak Attack?

Round towards zero

How to prevent clipped screen edges on my TV, HDMI-connected?

Are the players on the same team as the DM?

Almost uniform convergence implies convergence in measure



Concatenation of the result of a function with a mutable default argument


“Least Astonishment” and the Mutable Default ArgumentCalling a function of a module by using its name (a string)How to flush output of print function?Using global variables in a functionHow to make a chain of function decorators?“Least Astonishment” and the Mutable Default ArgumentHow do I concatenate two lists in Python?Argparse: Way to include default values in '--help'?Why is “1000000000000000 in range(1000000000000001)” so fast in Python 3?Warning about mutable default argument in PyCharm






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}







31















Suppose a function with a mutable default argument:



def f(l=[]):
l.append(len(l))
return l


If I run this:



def f(l=[]):
l.append(len(l))
return l
print(f()+["-"]+f()+["-"]+f()) # -> [0, '-', 0, 1, '-', 0, 1, 2]


Or this:



def f(l=[]):
l.append(len(l))
return l
print(f()+f()+f()) # -> [0, 1, 0, 1, 0, 1, 2]


Instead of the following one, which would be more logical:



print(f()+f()+f()) # -> [0, 0, 1, 0, 1, 2]


Why?










share|improve this question



























  • if you want to see the result of individual functional call then use return [l.copy()]

    – prashant rana
    2 days ago











  • Well the last funciont returns me this: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5]

    – Celius Stingher
    2 days ago











  • @CeliusStingher You have to redefine the function between uses.

    – Benoît Pilatte
    2 days ago











  • with - you will be able to distinguish between your data from each functional call , in second one you need to write a code to get the pattern

    – prashant rana
    2 days ago











  • Possible duplicate of "Least Astonishment" and the Mutable Default Argument

    – Goyo
    2 days ago


















31















Suppose a function with a mutable default argument:



def f(l=[]):
l.append(len(l))
return l


If I run this:



def f(l=[]):
l.append(len(l))
return l
print(f()+["-"]+f()+["-"]+f()) # -> [0, '-', 0, 1, '-', 0, 1, 2]


Or this:



def f(l=[]):
l.append(len(l))
return l
print(f()+f()+f()) # -> [0, 1, 0, 1, 0, 1, 2]


Instead of the following one, which would be more logical:



print(f()+f()+f()) # -> [0, 0, 1, 0, 1, 2]


Why?










share|improve this question



























  • if you want to see the result of individual functional call then use return [l.copy()]

    – prashant rana
    2 days ago











  • Well the last funciont returns me this: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5]

    – Celius Stingher
    2 days ago











  • @CeliusStingher You have to redefine the function between uses.

    – Benoît Pilatte
    2 days ago











  • with - you will be able to distinguish between your data from each functional call , in second one you need to write a code to get the pattern

    – prashant rana
    2 days ago











  • Possible duplicate of "Least Astonishment" and the Mutable Default Argument

    – Goyo
    2 days ago














31












31








31


2






Suppose a function with a mutable default argument:



def f(l=[]):
l.append(len(l))
return l


If I run this:



def f(l=[]):
l.append(len(l))
return l
print(f()+["-"]+f()+["-"]+f()) # -> [0, '-', 0, 1, '-', 0, 1, 2]


Or this:



def f(l=[]):
l.append(len(l))
return l
print(f()+f()+f()) # -> [0, 1, 0, 1, 0, 1, 2]


Instead of the following one, which would be more logical:



print(f()+f()+f()) # -> [0, 0, 1, 0, 1, 2]


Why?










share|improve this question
















Suppose a function with a mutable default argument:



def f(l=[]):
l.append(len(l))
return l


If I run this:



def f(l=[]):
l.append(len(l))
return l
print(f()+["-"]+f()+["-"]+f()) # -> [0, '-', 0, 1, '-', 0, 1, 2]


Or this:



def f(l=[]):
l.append(len(l))
return l
print(f()+f()+f()) # -> [0, 1, 0, 1, 0, 1, 2]


Instead of the following one, which would be more logical:



print(f()+f()+f()) # -> [0, 0, 1, 0, 1, 2]


Why?







python python-3.x






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited yesterday









double-beep

3,1746 gold badges19 silver badges33 bronze badges




3,1746 gold badges19 silver badges33 bronze badges










asked 2 days ago









Benoît PilatteBenoît Pilatte

1,6995 silver badges24 bronze badges




1,6995 silver badges24 bronze badges
















  • if you want to see the result of individual functional call then use return [l.copy()]

    – prashant rana
    2 days ago











  • Well the last funciont returns me this: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5]

    – Celius Stingher
    2 days ago











  • @CeliusStingher You have to redefine the function between uses.

    – Benoît Pilatte
    2 days ago











  • with - you will be able to distinguish between your data from each functional call , in second one you need to write a code to get the pattern

    – prashant rana
    2 days ago











  • Possible duplicate of "Least Astonishment" and the Mutable Default Argument

    – Goyo
    2 days ago



















  • if you want to see the result of individual functional call then use return [l.copy()]

    – prashant rana
    2 days ago











  • Well the last funciont returns me this: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5]

    – Celius Stingher
    2 days ago











  • @CeliusStingher You have to redefine the function between uses.

    – Benoît Pilatte
    2 days ago











  • with - you will be able to distinguish between your data from each functional call , in second one you need to write a code to get the pattern

    – prashant rana
    2 days ago











  • Possible duplicate of "Least Astonishment" and the Mutable Default Argument

    – Goyo
    2 days ago

















if you want to see the result of individual functional call then use return [l.copy()]

– prashant rana
2 days ago





if you want to see the result of individual functional call then use return [l.copy()]

– prashant rana
2 days ago













Well the last funciont returns me this: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5]

– Celius Stingher
2 days ago





Well the last funciont returns me this: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5]

– Celius Stingher
2 days ago













@CeliusStingher You have to redefine the function between uses.

– Benoît Pilatte
2 days ago





@CeliusStingher You have to redefine the function between uses.

– Benoît Pilatte
2 days ago













with - you will be able to distinguish between your data from each functional call , in second one you need to write a code to get the pattern

– prashant rana
2 days ago





with - you will be able to distinguish between your data from each functional call , in second one you need to write a code to get the pattern

– prashant rana
2 days ago













Possible duplicate of "Least Astonishment" and the Mutable Default Argument

– Goyo
2 days ago





Possible duplicate of "Least Astonishment" and the Mutable Default Argument

– Goyo
2 days ago












2 Answers
2






active

oldest

votes


















42















That's actually pretty interesting!



As we know, the list l in the function definition is initialized only once at the definition of this function, and for all invocations of this function, there will be exactly one copy of this list. Now, the function modifies this list, which means that multiple calls to this function will modify the exact same object multiple times. This is the first important part.



Now, consider the expression that adds these lists:



f()+f()+f()


According to the laws of operator precedence, this is equivalent to the following:



(f() + f()) + f()


...which is exactly the same as this:



temp1 = f() + f() # (1)
temp2 = temp1 + f() # (2)


This is the second important part.



Addition of lists produces a new object, without modifying any of its arguments. This is the third important part.



Now let's combine what we know together.



In line 1 above, the first call returns [0], as you'd expect. The second call returns [0, 1], as you'd expect. Oh, wait! The function will return the exact same object (not its copy!) over and over again, after modifying it! This means that the object that the first call returned has now changed to become [0, 1] as well! And that's why temp1 == [0, 1] + [0, 1].



The result of addition, however, is a completely new object, so [0, 1, 0, 1] + f() is the same as [0, 1, 0, 1] + [0, 1, 2]. Note that the second list is, again, exactly what you'd expect your function to return. The same thing happens when you add f() + ["-"]: this creates a new list object, so that any other calls to f won't interfere with it.



You can reproduce this by concatenating the results of two function calls:



>>> f() + f()
[0, 1, 0, 1]
>>> f() + f()
[0, 1, 2, 3, 0, 1, 2, 3]
>>> f() + f()
[0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]


Again, you can do all that because you're concatenating references to the same object.






share|improve this answer























  • 1





    Wow, 🤯. The best thing about this answer is that it is right. []+f()+f() gives [0, 0, 1] and f()+f()+[] gives [0, 1, 0, 1] !!!

    – Benoît Pilatte
    2 days ago








  • 5





    The new object returned by + is definitely the key here, it's so subtle

    – C.Nivs
    2 days ago






  • 3





    A good reminder for why a mutable default argument is a big no-no.

    – EliadL
    2 days ago






  • 2





    Looked into dis(lambda: f()+f()+f()) and the function f did get called twice before add is performed. Great answer btw.

    – Henry Yik
    2 days ago






  • 2





    @HenryYik it's because of how list.__add__ is implemented. The first f() instantiates the list object, let's call it l1. Then l1.__add__(f()) is called, so, f() needs to be evaluated first, which changes the reference that is shared with l1. Then l1.__add__(l2) finishes, returning the new object.

    – C.Nivs
    2 days ago



















2















Here's a way to think about it that might help it make sense:



A function is a data structure. You create one with a def block, much the same way as you create a type with a class block or you create a list with square brackets.



The most interesting part of that data structure is the code that gets run when the function is called, but the default arguments are also part of it! In fact, you can inspect both the code and the default arguments from Python, via attributes on the function:



>>> def foo(a=1): pass
...
>>> dir(foo)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', ...]
>>> foo.__code__
<code object foo at 0x7f114752a660, file "<stdin>", line 1>
>>> foo.__defaults__
(1,)


(A much nicer interface for this is inspect.signature, but all it does is examine those attributes.)



So the reason that this modifies the list:



def f(l=[]):
l.append(len(l))
return l


is exactly the same reason that this also modifies the list:



f = dict(l=[])
f['l'].append(len(f['l']))


In both cases, you're mutating a list that belongs to some parent structure, so the change will naturally be visible in the parent as well.





Note that this is a design decision that Python specifically made, and it's not inherently necessary in a language. JavaScript recently learned about default arguments, but it treats them as expressions to be re-evaluated anew on each call — essentially, each default argument is its own tiny function. The advantage is that JS doesn't have this gotcha, but the drawback is that you can't meaningfully inspect the defaults the way you can in Python.






share|improve this answer




























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f57593294%2fconcatenation-of-the-result-of-a-function-with-a-mutable-default-argument%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    42















    That's actually pretty interesting!



    As we know, the list l in the function definition is initialized only once at the definition of this function, and for all invocations of this function, there will be exactly one copy of this list. Now, the function modifies this list, which means that multiple calls to this function will modify the exact same object multiple times. This is the first important part.



    Now, consider the expression that adds these lists:



    f()+f()+f()


    According to the laws of operator precedence, this is equivalent to the following:



    (f() + f()) + f()


    ...which is exactly the same as this:



    temp1 = f() + f() # (1)
    temp2 = temp1 + f() # (2)


    This is the second important part.



    Addition of lists produces a new object, without modifying any of its arguments. This is the third important part.



    Now let's combine what we know together.



    In line 1 above, the first call returns [0], as you'd expect. The second call returns [0, 1], as you'd expect. Oh, wait! The function will return the exact same object (not its copy!) over and over again, after modifying it! This means that the object that the first call returned has now changed to become [0, 1] as well! And that's why temp1 == [0, 1] + [0, 1].



    The result of addition, however, is a completely new object, so [0, 1, 0, 1] + f() is the same as [0, 1, 0, 1] + [0, 1, 2]. Note that the second list is, again, exactly what you'd expect your function to return. The same thing happens when you add f() + ["-"]: this creates a new list object, so that any other calls to f won't interfere with it.



    You can reproduce this by concatenating the results of two function calls:



    >>> f() + f()
    [0, 1, 0, 1]
    >>> f() + f()
    [0, 1, 2, 3, 0, 1, 2, 3]
    >>> f() + f()
    [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]


    Again, you can do all that because you're concatenating references to the same object.






    share|improve this answer























    • 1





      Wow, 🤯. The best thing about this answer is that it is right. []+f()+f() gives [0, 0, 1] and f()+f()+[] gives [0, 1, 0, 1] !!!

      – Benoît Pilatte
      2 days ago








    • 5





      The new object returned by + is definitely the key here, it's so subtle

      – C.Nivs
      2 days ago






    • 3





      A good reminder for why a mutable default argument is a big no-no.

      – EliadL
      2 days ago






    • 2





      Looked into dis(lambda: f()+f()+f()) and the function f did get called twice before add is performed. Great answer btw.

      – Henry Yik
      2 days ago






    • 2





      @HenryYik it's because of how list.__add__ is implemented. The first f() instantiates the list object, let's call it l1. Then l1.__add__(f()) is called, so, f() needs to be evaluated first, which changes the reference that is shared with l1. Then l1.__add__(l2) finishes, returning the new object.

      – C.Nivs
      2 days ago
















    42















    That's actually pretty interesting!



    As we know, the list l in the function definition is initialized only once at the definition of this function, and for all invocations of this function, there will be exactly one copy of this list. Now, the function modifies this list, which means that multiple calls to this function will modify the exact same object multiple times. This is the first important part.



    Now, consider the expression that adds these lists:



    f()+f()+f()


    According to the laws of operator precedence, this is equivalent to the following:



    (f() + f()) + f()


    ...which is exactly the same as this:



    temp1 = f() + f() # (1)
    temp2 = temp1 + f() # (2)


    This is the second important part.



    Addition of lists produces a new object, without modifying any of its arguments. This is the third important part.



    Now let's combine what we know together.



    In line 1 above, the first call returns [0], as you'd expect. The second call returns [0, 1], as you'd expect. Oh, wait! The function will return the exact same object (not its copy!) over and over again, after modifying it! This means that the object that the first call returned has now changed to become [0, 1] as well! And that's why temp1 == [0, 1] + [0, 1].



    The result of addition, however, is a completely new object, so [0, 1, 0, 1] + f() is the same as [0, 1, 0, 1] + [0, 1, 2]. Note that the second list is, again, exactly what you'd expect your function to return. The same thing happens when you add f() + ["-"]: this creates a new list object, so that any other calls to f won't interfere with it.



    You can reproduce this by concatenating the results of two function calls:



    >>> f() + f()
    [0, 1, 0, 1]
    >>> f() + f()
    [0, 1, 2, 3, 0, 1, 2, 3]
    >>> f() + f()
    [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]


    Again, you can do all that because you're concatenating references to the same object.






    share|improve this answer























    • 1





      Wow, 🤯. The best thing about this answer is that it is right. []+f()+f() gives [0, 0, 1] and f()+f()+[] gives [0, 1, 0, 1] !!!

      – Benoît Pilatte
      2 days ago








    • 5





      The new object returned by + is definitely the key here, it's so subtle

      – C.Nivs
      2 days ago






    • 3





      A good reminder for why a mutable default argument is a big no-no.

      – EliadL
      2 days ago






    • 2





      Looked into dis(lambda: f()+f()+f()) and the function f did get called twice before add is performed. Great answer btw.

      – Henry Yik
      2 days ago






    • 2





      @HenryYik it's because of how list.__add__ is implemented. The first f() instantiates the list object, let's call it l1. Then l1.__add__(f()) is called, so, f() needs to be evaluated first, which changes the reference that is shared with l1. Then l1.__add__(l2) finishes, returning the new object.

      – C.Nivs
      2 days ago














    42














    42










    42









    That's actually pretty interesting!



    As we know, the list l in the function definition is initialized only once at the definition of this function, and for all invocations of this function, there will be exactly one copy of this list. Now, the function modifies this list, which means that multiple calls to this function will modify the exact same object multiple times. This is the first important part.



    Now, consider the expression that adds these lists:



    f()+f()+f()


    According to the laws of operator precedence, this is equivalent to the following:



    (f() + f()) + f()


    ...which is exactly the same as this:



    temp1 = f() + f() # (1)
    temp2 = temp1 + f() # (2)


    This is the second important part.



    Addition of lists produces a new object, without modifying any of its arguments. This is the third important part.



    Now let's combine what we know together.



    In line 1 above, the first call returns [0], as you'd expect. The second call returns [0, 1], as you'd expect. Oh, wait! The function will return the exact same object (not its copy!) over and over again, after modifying it! This means that the object that the first call returned has now changed to become [0, 1] as well! And that's why temp1 == [0, 1] + [0, 1].



    The result of addition, however, is a completely new object, so [0, 1, 0, 1] + f() is the same as [0, 1, 0, 1] + [0, 1, 2]. Note that the second list is, again, exactly what you'd expect your function to return. The same thing happens when you add f() + ["-"]: this creates a new list object, so that any other calls to f won't interfere with it.



    You can reproduce this by concatenating the results of two function calls:



    >>> f() + f()
    [0, 1, 0, 1]
    >>> f() + f()
    [0, 1, 2, 3, 0, 1, 2, 3]
    >>> f() + f()
    [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]


    Again, you can do all that because you're concatenating references to the same object.






    share|improve this answer















    That's actually pretty interesting!



    As we know, the list l in the function definition is initialized only once at the definition of this function, and for all invocations of this function, there will be exactly one copy of this list. Now, the function modifies this list, which means that multiple calls to this function will modify the exact same object multiple times. This is the first important part.



    Now, consider the expression that adds these lists:



    f()+f()+f()


    According to the laws of operator precedence, this is equivalent to the following:



    (f() + f()) + f()


    ...which is exactly the same as this:



    temp1 = f() + f() # (1)
    temp2 = temp1 + f() # (2)


    This is the second important part.



    Addition of lists produces a new object, without modifying any of its arguments. This is the third important part.



    Now let's combine what we know together.



    In line 1 above, the first call returns [0], as you'd expect. The second call returns [0, 1], as you'd expect. Oh, wait! The function will return the exact same object (not its copy!) over and over again, after modifying it! This means that the object that the first call returned has now changed to become [0, 1] as well! And that's why temp1 == [0, 1] + [0, 1].



    The result of addition, however, is a completely new object, so [0, 1, 0, 1] + f() is the same as [0, 1, 0, 1] + [0, 1, 2]. Note that the second list is, again, exactly what you'd expect your function to return. The same thing happens when you add f() + ["-"]: this creates a new list object, so that any other calls to f won't interfere with it.



    You can reproduce this by concatenating the results of two function calls:



    >>> f() + f()
    [0, 1, 0, 1]
    >>> f() + f()
    [0, 1, 2, 3, 0, 1, 2, 3]
    >>> f() + f()
    [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]


    Again, you can do all that because you're concatenating references to the same object.







    share|improve this answer














    share|improve this answer



    share|improve this answer








    edited 2 days ago

























    answered 2 days ago









    ForceBruForceBru

    24.9k9 gold badges39 silver badges62 bronze badges




    24.9k9 gold badges39 silver badges62 bronze badges











    • 1





      Wow, 🤯. The best thing about this answer is that it is right. []+f()+f() gives [0, 0, 1] and f()+f()+[] gives [0, 1, 0, 1] !!!

      – Benoît Pilatte
      2 days ago








    • 5





      The new object returned by + is definitely the key here, it's so subtle

      – C.Nivs
      2 days ago






    • 3





      A good reminder for why a mutable default argument is a big no-no.

      – EliadL
      2 days ago






    • 2





      Looked into dis(lambda: f()+f()+f()) and the function f did get called twice before add is performed. Great answer btw.

      – Henry Yik
      2 days ago






    • 2





      @HenryYik it's because of how list.__add__ is implemented. The first f() instantiates the list object, let's call it l1. Then l1.__add__(f()) is called, so, f() needs to be evaluated first, which changes the reference that is shared with l1. Then l1.__add__(l2) finishes, returning the new object.

      – C.Nivs
      2 days ago














    • 1





      Wow, 🤯. The best thing about this answer is that it is right. []+f()+f() gives [0, 0, 1] and f()+f()+[] gives [0, 1, 0, 1] !!!

      – Benoît Pilatte
      2 days ago








    • 5





      The new object returned by + is definitely the key here, it's so subtle

      – C.Nivs
      2 days ago






    • 3





      A good reminder for why a mutable default argument is a big no-no.

      – EliadL
      2 days ago






    • 2





      Looked into dis(lambda: f()+f()+f()) and the function f did get called twice before add is performed. Great answer btw.

      – Henry Yik
      2 days ago






    • 2





      @HenryYik it's because of how list.__add__ is implemented. The first f() instantiates the list object, let's call it l1. Then l1.__add__(f()) is called, so, f() needs to be evaluated first, which changes the reference that is shared with l1. Then l1.__add__(l2) finishes, returning the new object.

      – C.Nivs
      2 days ago








    1




    1





    Wow, 🤯. The best thing about this answer is that it is right. []+f()+f() gives [0, 0, 1] and f()+f()+[] gives [0, 1, 0, 1] !!!

    – Benoît Pilatte
    2 days ago







    Wow, 🤯. The best thing about this answer is that it is right. []+f()+f() gives [0, 0, 1] and f()+f()+[] gives [0, 1, 0, 1] !!!

    – Benoît Pilatte
    2 days ago






    5




    5





    The new object returned by + is definitely the key here, it's so subtle

    – C.Nivs
    2 days ago





    The new object returned by + is definitely the key here, it's so subtle

    – C.Nivs
    2 days ago




    3




    3





    A good reminder for why a mutable default argument is a big no-no.

    – EliadL
    2 days ago





    A good reminder for why a mutable default argument is a big no-no.

    – EliadL
    2 days ago




    2




    2





    Looked into dis(lambda: f()+f()+f()) and the function f did get called twice before add is performed. Great answer btw.

    – Henry Yik
    2 days ago





    Looked into dis(lambda: f()+f()+f()) and the function f did get called twice before add is performed. Great answer btw.

    – Henry Yik
    2 days ago




    2




    2





    @HenryYik it's because of how list.__add__ is implemented. The first f() instantiates the list object, let's call it l1. Then l1.__add__(f()) is called, so, f() needs to be evaluated first, which changes the reference that is shared with l1. Then l1.__add__(l2) finishes, returning the new object.

    – C.Nivs
    2 days ago





    @HenryYik it's because of how list.__add__ is implemented. The first f() instantiates the list object, let's call it l1. Then l1.__add__(f()) is called, so, f() needs to be evaluated first, which changes the reference that is shared with l1. Then l1.__add__(l2) finishes, returning the new object.

    – C.Nivs
    2 days ago













    2















    Here's a way to think about it that might help it make sense:



    A function is a data structure. You create one with a def block, much the same way as you create a type with a class block or you create a list with square brackets.



    The most interesting part of that data structure is the code that gets run when the function is called, but the default arguments are also part of it! In fact, you can inspect both the code and the default arguments from Python, via attributes on the function:



    >>> def foo(a=1): pass
    ...
    >>> dir(foo)
    ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', ...]
    >>> foo.__code__
    <code object foo at 0x7f114752a660, file "<stdin>", line 1>
    >>> foo.__defaults__
    (1,)


    (A much nicer interface for this is inspect.signature, but all it does is examine those attributes.)



    So the reason that this modifies the list:



    def f(l=[]):
    l.append(len(l))
    return l


    is exactly the same reason that this also modifies the list:



    f = dict(l=[])
    f['l'].append(len(f['l']))


    In both cases, you're mutating a list that belongs to some parent structure, so the change will naturally be visible in the parent as well.





    Note that this is a design decision that Python specifically made, and it's not inherently necessary in a language. JavaScript recently learned about default arguments, but it treats them as expressions to be re-evaluated anew on each call — essentially, each default argument is its own tiny function. The advantage is that JS doesn't have this gotcha, but the drawback is that you can't meaningfully inspect the defaults the way you can in Python.






    share|improve this answer






























      2















      Here's a way to think about it that might help it make sense:



      A function is a data structure. You create one with a def block, much the same way as you create a type with a class block or you create a list with square brackets.



      The most interesting part of that data structure is the code that gets run when the function is called, but the default arguments are also part of it! In fact, you can inspect both the code and the default arguments from Python, via attributes on the function:



      >>> def foo(a=1): pass
      ...
      >>> dir(foo)
      ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', ...]
      >>> foo.__code__
      <code object foo at 0x7f114752a660, file "<stdin>", line 1>
      >>> foo.__defaults__
      (1,)


      (A much nicer interface for this is inspect.signature, but all it does is examine those attributes.)



      So the reason that this modifies the list:



      def f(l=[]):
      l.append(len(l))
      return l


      is exactly the same reason that this also modifies the list:



      f = dict(l=[])
      f['l'].append(len(f['l']))


      In both cases, you're mutating a list that belongs to some parent structure, so the change will naturally be visible in the parent as well.





      Note that this is a design decision that Python specifically made, and it's not inherently necessary in a language. JavaScript recently learned about default arguments, but it treats them as expressions to be re-evaluated anew on each call — essentially, each default argument is its own tiny function. The advantage is that JS doesn't have this gotcha, but the drawback is that you can't meaningfully inspect the defaults the way you can in Python.






      share|improve this answer




























        2














        2










        2









        Here's a way to think about it that might help it make sense:



        A function is a data structure. You create one with a def block, much the same way as you create a type with a class block or you create a list with square brackets.



        The most interesting part of that data structure is the code that gets run when the function is called, but the default arguments are also part of it! In fact, you can inspect both the code and the default arguments from Python, via attributes on the function:



        >>> def foo(a=1): pass
        ...
        >>> dir(foo)
        ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', ...]
        >>> foo.__code__
        <code object foo at 0x7f114752a660, file "<stdin>", line 1>
        >>> foo.__defaults__
        (1,)


        (A much nicer interface for this is inspect.signature, but all it does is examine those attributes.)



        So the reason that this modifies the list:



        def f(l=[]):
        l.append(len(l))
        return l


        is exactly the same reason that this also modifies the list:



        f = dict(l=[])
        f['l'].append(len(f['l']))


        In both cases, you're mutating a list that belongs to some parent structure, so the change will naturally be visible in the parent as well.





        Note that this is a design decision that Python specifically made, and it's not inherently necessary in a language. JavaScript recently learned about default arguments, but it treats them as expressions to be re-evaluated anew on each call — essentially, each default argument is its own tiny function. The advantage is that JS doesn't have this gotcha, but the drawback is that you can't meaningfully inspect the defaults the way you can in Python.






        share|improve this answer













        Here's a way to think about it that might help it make sense:



        A function is a data structure. You create one with a def block, much the same way as you create a type with a class block or you create a list with square brackets.



        The most interesting part of that data structure is the code that gets run when the function is called, but the default arguments are also part of it! In fact, you can inspect both the code and the default arguments from Python, via attributes on the function:



        >>> def foo(a=1): pass
        ...
        >>> dir(foo)
        ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', ...]
        >>> foo.__code__
        <code object foo at 0x7f114752a660, file "<stdin>", line 1>
        >>> foo.__defaults__
        (1,)


        (A much nicer interface for this is inspect.signature, but all it does is examine those attributes.)



        So the reason that this modifies the list:



        def f(l=[]):
        l.append(len(l))
        return l


        is exactly the same reason that this also modifies the list:



        f = dict(l=[])
        f['l'].append(len(f['l']))


        In both cases, you're mutating a list that belongs to some parent structure, so the change will naturally be visible in the parent as well.





        Note that this is a design decision that Python specifically made, and it's not inherently necessary in a language. JavaScript recently learned about default arguments, but it treats them as expressions to be re-evaluated anew on each call — essentially, each default argument is its own tiny function. The advantage is that JS doesn't have this gotcha, but the drawback is that you can't meaningfully inspect the defaults the way you can in Python.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered 2 days ago









        EeveeEevee

        37.8k8 gold badges69 silver badges112 bronze badges




        37.8k8 gold badges69 silver badges112 bronze badges

































            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f57593294%2fconcatenation-of-the-result-of-a-function-with-a-mutable-default-argument%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Taj Mahal Inhaltsverzeichnis Aufbau | Geschichte | 350-Jahr-Feier | Heutige Bedeutung | Siehe auch |...

            Baia Sprie Cuprins Etimologie | Istorie | Demografie | Politică și administrație | Arii naturale...

            Nicolae Petrescu-Găină Cuprins Biografie | Opera | In memoriam | Varia | Controverse, incertitudini...