1. The default initializer gotcha
Suppose you are in an interview setting and you are being handed a piece of paper with the following Python code written on it:
def foo(bar=[])
No explanations, no nothing, just the obvious question: what is wrong with the code above?
… nothing in particular. Yes, nothing is wrong with the code above, it is legal Python code (apart from missing the ending “:” and that no function body is given). Who am I to judge the particular use case – after all, a bug is defined as code that does not run the way the programmer expected it to.
But suppose we add more code to that function definition:
def foo(bar=[]): bar.append(1) return bar
Now things are getting messier. What would a “normal” Python programmer expect to be the result of calling foo() with no arguments for 3 times in a row?
foo() # [1] foo() # [1] foo() # [1]
Nope. The real result is more similar to:
foo() # [1] foo() # [1, 1] foo() # [1, 1, 1]
How is this even possible? The issue is with how default initializers for function arguments are handled in Python and this is completely different from C/C++. The scope of the default initializer is not the function body but rather the global scope (something similar to the static variables in C/C++). And static variables are good for caching and keeping state. But yes, I believe we had enough of this.
2. String operations in Python
So the interviewer walks in and says: you have this string, S; please replace the 3rd character in the string with “A”.
The smart ass you points out that the string might be empty or not have enough characters. Well, good, ignore these. Some code gets written:
def repl(S): if len(S) >= 3: S[2] = 'A' # yep, the index is from 0 return S
The interviewer looks carefully and then says he’s done with the interview, wishes you a good day and that you’ll hear from HR soon. (What do you think you’re going to hear? The job offer? … I may not want to work for that type of company, if an offer is being extended!)
Back to the Python ins and outs: the strings in Python are immutable. This means the assignment above is no legal Python code. One may create a new string, though:
def repl(S): if len(S) >= 3: return S[:2] + 'A' + S[3:] else: return S
3. Late binding
So you get the following code during the interview:
F = [ lambda x : i * x for i in range(5) ]
If you didn’t get out of the room yet, you’re already a winner. Let’s analyse a bit what the code is supposed to do:
F is a list, a list containing (if you allow me) function pointers;
There are 5 function pointers in the list;
Each function takes exactly one argument (x in the code above) and is supposed to return x times i (i should be 0 for the first function, 1 for the next, 4 for the last).
So you expect the following behaviour:
F[0](2) # 0 F[1](2) # 2 ... F[4](2) # 8
Unfortunately this is not true. i is actually evaluated when F[x] is called and the last known value is 4. This results in a truly unexpected behaviour:
F[0](2) # 8 F[1](2) # 8 ... F[4](2) # 8
Honestly, I’m pretty sure very few experienced Python developers may be able to answer this one correctly. Asking such question during an interview will most likely make prospective employees hate your company dearly. Nevertheless, I am done for today.
Thank you for your read!