+ 1
variable scope in python (strange behavior??)
Hi, i'm new to this discussion page. I have a question solving a python challenge, but the answer is weird to me. def f(x=[]): x += [3] return sum(x) print(f()+f()+f()) answer = 18 I think the three function calls are independent, thus the result will be 9. But what i saw on my vscode debugger is that the variable x inside the second f call is initialized by [3]. x is not a global variable but how is it possible? Thank you in advance! ADDED) tracking result of x: first call: x=[] -> x=[3] second call: x=[3] -> x=[3,3] third call: x=[3,3] -> x=[3,3,3]
13 Respuestas
+ 3
BroFar Thank you for giving additional info. I noticed in your code that when an argument was initialized by a list object, the argument seems to point the same list object (not to point a new empty list []) until it is initialized again by other list.
Is this situation related to the list object itself?
+ 3
BroFar Great, thanks!
+ 2
This is an expansion on your code to kind of explain a bit as to how your answer got to 18 but mine will show you 3 15 and 45
https://code.sololearn.com/cMagnl8f1W3I/?ref=app
+ 2
Yes NeutrinoAnt
+ 2
NeutrinoAnt, I have recently written a little tutorial about these issues if you're interested:
https://code.sololearn.com/ce664AekBveN/?ref=app
+ 1
When a function is defined, every parameter that has a default value will create that object in the function's attribute __defaults__.
This happens only once, in the moment of definition.
Later, when you call the function and pass a value to the parameter that has a default value, the passed value will be taken.
If you don't pass anything, instead the parameter will receive the value from the defaults, which will be the same object from definition with every call.
Try this:
def f(x=[], y={}):
pass
print(f.__defaults__)
+ 1
HonFu That's amazing story! I've never thought about function's attribute, it contains some inner contents itself! Initializing process is the same as what i inferred but the attribute is surprising to me. Thanks!
+ I found it is related to the issue about 'mutable' object, which can change its content but not its own id. So interesting and misunderstandable concept i think.
+ 1
HonFu the code is not related to these issues... but so interesting👏👏👏
+ 1
NeutrinoAnt, in a way it is related, because it is another case where you have to know if your value is a new copy of something (an empty list) or a reference to an already existing object (a formerly created list in __defaults__.
But I suppose, the referencing part was relatively clear to you to begin with, right?
+ 1
HonFu Oh you are right, it has the common issues with reference and saving something in it. What i mainly focused on in your answer is the process for creating default values 'only once'.
+ 1
NeutrinoAnt, yeah, I think it helps you massively geting control of a language, when things start to 'fall together' in your head.
You gradually begin to see: Instead of being complex and multi-parted, if you really take a look, many things are really just one and the same thing.
Take a look at this for example:
def f(x=[]):
x.append(1)
f()
print(f.__defaults__)
f()
print(f.__defaults__)
f.__defaults__ = ([],)
f()
print(f.__defaults__)
Here at one point I just overwrite the defaults tuple like I'd do it in any other case, creating a new list.
And just as you would expect, now the list is suddenly shorter - because it's a new one.
+ 1
HonFu it looks interesting, why does this language allow very dangerous behaviors?
I wrote the expended code as below:
def f(x=[], y=[], z=[]):
x.append(1)
y.append(2)
z.append(3)
f.__defaults__ = ([-1], [-2], [-3], [-4])
f()
print(f.__defaults__)
f.__defaults__ = ([], [])
f() # errors: missing arguments x
print(f.__defaults__)
f.__defaults__ = ([],)
f() # errors: missing arguments x, y
print(f.__defaults__)
From above experiments, i found some interesting facts about initialization order.
The first print after f() results:
([-1], [-2, 1], [-3, 2], [-4, 3])
looks like the last three were input to x, y and z sequentially.
The second f() makes an error, but not about "missing argument z" but "missing argument x", inducing the initialization order is reversed! It becomes more plausible when the third f() makes an error "missing argument x and y".
+ 1
Ha, interesting experiment!
So the assignment to the parameters seems to be done backwards - for whatever reason.
Why did God/the Benevolent Dictator allow this?
Well, the whole 'philosophy' of the language is that people 'voluntarily' agree to follow certain rules and don't do risky stuff.
The typical line is 'we're consenting adults here'.
You have no real constants, no privacy in classes, no static types etc., so you just have to watch out yourself.
On the other hand, the types are strong, so you normally get an understandable error message rather than undefined behaviour. 🤷♂️