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;
}
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
|
show 1 more comment
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
if you want to see the result of individual functional call then usereturn [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
|
show 1 more comment
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
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
python python-3.x
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 usereturn [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
|
show 1 more comment
if you want to see the result of individual functional call then usereturn [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
|
show 1 more comment
2 Answers
2
active
oldest
votes
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.
1
Wow, 🤯. The best thing about this answer is that it is right.[]+f()+f()
gives[0, 0, 1]
andf()+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 intodis(lambda: f()+f()+f())
and the functionf
did get called twice beforeadd
is performed. Great answer btw.
– Henry Yik
2 days ago
2
@HenryYik it's because of howlist.__add__
is implemented. The firstf()
instantiates the list object, let's call itl1
. Thenl1.__add__(f())
is called, so,f()
needs to be evaluated first, which changes the reference that is shared withl1
. Thenl1.__add__(l2)
finishes, returning the new object.
– C.Nivs
2 days ago
|
show 3 more comments
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.
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
1
Wow, 🤯. The best thing about this answer is that it is right.[]+f()+f()
gives[0, 0, 1]
andf()+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 intodis(lambda: f()+f()+f())
and the functionf
did get called twice beforeadd
is performed. Great answer btw.
– Henry Yik
2 days ago
2
@HenryYik it's because of howlist.__add__
is implemented. The firstf()
instantiates the list object, let's call itl1
. Thenl1.__add__(f())
is called, so,f()
needs to be evaluated first, which changes the reference that is shared withl1
. Thenl1.__add__(l2)
finishes, returning the new object.
– C.Nivs
2 days ago
|
show 3 more comments
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.
1
Wow, 🤯. The best thing about this answer is that it is right.[]+f()+f()
gives[0, 0, 1]
andf()+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 intodis(lambda: f()+f()+f())
and the functionf
did get called twice beforeadd
is performed. Great answer btw.
– Henry Yik
2 days ago
2
@HenryYik it's because of howlist.__add__
is implemented. The firstf()
instantiates the list object, let's call itl1
. Thenl1.__add__(f())
is called, so,f()
needs to be evaluated first, which changes the reference that is shared withl1
. Thenl1.__add__(l2)
finishes, returning the new object.
– C.Nivs
2 days ago
|
show 3 more comments
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.
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.
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]
andf()+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 intodis(lambda: f()+f()+f())
and the functionf
did get called twice beforeadd
is performed. Great answer btw.
– Henry Yik
2 days ago
2
@HenryYik it's because of howlist.__add__
is implemented. The firstf()
instantiates the list object, let's call itl1
. Thenl1.__add__(f())
is called, so,f()
needs to be evaluated first, which changes the reference that is shared withl1
. Thenl1.__add__(l2)
finishes, returning the new object.
– C.Nivs
2 days ago
|
show 3 more comments
1
Wow, 🤯. The best thing about this answer is that it is right.[]+f()+f()
gives[0, 0, 1]
andf()+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 intodis(lambda: f()+f()+f())
and the functionf
did get called twice beforeadd
is performed. Great answer btw.
– Henry Yik
2 days ago
2
@HenryYik it's because of howlist.__add__
is implemented. The firstf()
instantiates the list object, let's call itl1
. Thenl1.__add__(f())
is called, so,f()
needs to be evaluated first, which changes the reference that is shared withl1
. Thenl1.__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
|
show 3 more comments
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.
add a comment |
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.
add a comment |
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.
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.
answered 2 days ago
EeveeEevee
37.8k8 gold badges69 silver badges112 bronze badges
37.8k8 gold badges69 silver badges112 bronze badges
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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