on the basis that default arguments, when mutable, retain their values across successive function calls (this is not applicable to immutable default arguments), I can understand Oddity Number One (sorry, I'm making reference to code below).
But I am not able to explain the result in Oddity Number Two.
Can anyone enlighten that?
Hm, so if you do f2() + f2(), you get 4 times the element.
So it seems, the calls are done twice, intermediate values, then the result (2*2) is added.
Now if you assume that this is done + by +, left to right (operator precedence), the result - which is now a *new* object (+ should create a new list), will be added to the result of the third call (3), so you get 4+3==7.
Bilbo Baggins I suppose that in both functions, the default param values are retained between calls because the params are list objects which are stored on the heap as reference objects. The default param value will only be initialized once from subsequent function calls.
With oddity #1, it's as if it's calling:
print([1, 1, 1], [1, 1, 1], [1, 1, 1])
One might have expected:
print(, [1, 1], [1, 1, 1])
However, I'm assuming that the print function doesn't convert the list return values to string until all comma delimited function params are all invoked. Since lists are reference types, the first returned list will point to the same memory reference as the 2nd and 3rd return values.
With oddity #2, it's as if calling:
print( + [2, 2] + [2, 2, 2, 2])
I'm assuming the following inserts the 2nd list into the 1st list, which points to the same object reference:
 + [2, 2] becomes [2, 2, 2]
The 3rd call to f2() will likely then append and return [2, 2, 2] +  which is [2, 2, 2, 2].
The 2nd plus operator will then insert [2, 2, 2, 2] into [2, 2, 2], resulting in:
[2, 2, 2, 2, 2, 2, 2]
I hope this makes sense and helps.
this behaviour is not limited to function arguments, but can be extended to functions dealing with mutable globals (example link above) and generators dealing with mutable globals
I see, sort of undocumented C sequence point...
I tried also with (mutable) globals, obtaining exactly the same results.
And the result is different if I do (f+f)+f rather than f+(f+f)
David Carroll, I don't agree with your analysis of oddity 2 (or I get you wrong).
See this version:
l += 
a = f() # a = 
b = f() # a AND B = [1, 1] (same ref obj)
c = f()+f()
print(*map(id, (a, b, c)))
c has a different id now.
In the moment c is defined, f is called twice, then the reference to that list is kept for now. So 'both' lists are [1, 1].
But then, the lists are added, and for lists and the + operator it is defined that they create a new object.
So c will be [1, 1, 1, 1], and it will not change, no matter how often you call f.
So oddity 2 is really new object from (old object + old object) + old object, or [1, 1, 1, 1] + [1, 1, 1].