5 ms·
Couldn't agree more! One of my all time new python interview questions gets a surprisingly large number of developers. Given a function like: def append_o
by SEJeff 12y ago
Couldn't agree more! One of my all time new python interview questions gets a surprisingly large number of developers.
Given a function like:
def append_one(l=[]):
l.append(1)
return l
What does this return each time?
>>> append_one()
>>> append_one()
>>> append_one()
- ryangittins 12y agoThe l (lowercase L) and the 1 (one) look really similar. Could that be the cause of some confusion? Of course, the function name helps, but most developers have learned not to trust function names to be an accurate description of what the function does, especially in tricky interview questions. Still, I'd change this to something like: def append_five(l=[]): l.append(5) return l It tests the same thing (knowledge of how default parameters work), but without the confounding problem of similar-looking characters. Of course, syntax highlighting would help the applicant out. All of that being said, I still don't doubt that many developers don't know what they should about default parameters.
- trevor-e 12y agoI'm not a Python expert, but iirc from various blog posts the "l" variable does not get reset between function calls which will cause undesired behavior. So calling the function 3 times without argument would produce a list of size 1,2, and 3 with the third call rather than 3 lists of size 1. Can any Python guru's confirm?
- ryangittins 12y agoYes, that's correct. The default value is only interpreted once, when the `def` statement is called. After that point, it's completely mutable. You have to see Python functions as objects and default parameter values as object variables.
- Dylan16807 12y agoThe problem is not the existence of the object variables, but that they are in such an unfortunate place. The rest of the parameter list is declaring fresh local variables. It's inconsistent that the left side of the equals sign is per-invocation, and the right side is per-def.
- Genmutant 12y agoYes that is correct. The default value gets created when the function is interpreted ("compiled").
- deathanatos 12y ago> The default value gets created when the function is interpreted ("compiled"). No. The default value gets "created" (the expression is evaluated and stored) when the def statement is executed. Take the following example: In [1]: def foo(): ...: def append_five(l=[]): ...: l.append(5) ...: return l ...: return append_five ...: In [2]: a = foo() In [3]: b = foo() In [4]: a() Out[4]: [5] In [5]: b() Out[5]: [5] In [6]: _4 is _5 Out[6]: False We only wrote one function definition, but multiple lists are created. (They are created when the "def append_five" definition executes, during the execution of foo.)
- tmerr 12y agoI thought that was what he meant. Is there any sharp distinction between "interpreting" and "evaluating" in python that I am unaware of? I've always used the words more or less interchangeably. But now that I think about it that might be a little naive since I have no idea how it works under the hood
- gcr 12y agoThe parent wrote "compiled", which is certainly more incorrect than either "interpreted" or "evaluated."
- msellout 12y agoYou could say that interpreting is first parsing and second executing/evaluating. The parser tokenizes and does a small amount of optimization such as ignoring unassigned values.
- SEJeff 12y agoThe key is object mutability. A list type is mutable and a tuple type is immutable. If the candidate correctly deduces what will happen, I'll ask them to write a bug-free version, which looks like one of the below: def append_one(var=None): var = var or [] var.append(1) return var def append_one(var=None): if var is None: var = [] var.append(1) return var Mutability is a very subtle but very important concept to understand in python. Everyone who uses python for non-trivial code should know it well: https://docs.python.org/2/reference/datamodel.html https://docs.python.org/2/reference/datamodel.html
- mbell 12y ago> The key is object mutability. A list type is mutable and a tuple type is immutable. I don't think the question has much to do with mutability, it isn't surprising to me nor would I imagine most programmers that a list is mutable, that's very common. The surprising part of this question is that the default value of 'l' continues to exist outside the lexical scope of the function, the expected behavior is that the value of 'l' is initialized at function call time and is garbage collected after each call. As it sits, using default values in python is sort of like defining a global that only has a named reference inside the function block, which is very strange.
- pyre 12y agoIt has something to do with mutability, because if an object is immutable, the behavior of Python matches what the naive developer expects. It's only mutable objects that break those expectations. Don't even get into unexpected behavior in classes: In [1]: class A(object): ...: l = [] ...: In [2]: a, b = A(), A() In [3]: a.l.append("Something") In [4]: a.l Out[4]: ['Something'] In [5]: b.l Out[5]: ['Something'] In [6]: class B(object0: ...: KeyboardInterrupt In [6]: class B(object): ...: l = None ...: def __init__(self): ...: self.l = [] ...: In [7]: c, d = B(), B() In [8]: c.l.append("Something") In [9]: c.l, d.l Out[9]: (['Something'], [])
- 12y ago
- zo1 12y agoThe expression presented in the parameter list is only evaluated once, and that is when the method is defined. The confusion is that people assume the expression is evaluated every time the method is called.
- kyllo 12y agoThe confusion is that people assume the expression is evaluated every time the method is called. Because that's how it works in a lot of other languages, such as Ruby and Javascript.
- zo1 12y agoI doubt that's the only reason. I fell for it myself at first without ever seeing a line of Ruby. It initially feels intuitive, and that's why I think most fall for it.
- ollysb 12y agoI think it's because the arguments are bound when the function is called. It's just natural that you'd expect the default values to also be bound at the same time.
- sophacles 12y agoNot the op, but I'd accept the confusion response of: [[]] [[],[]] [[],[],[]] because the behavior is the same, whether or not they misread an 'l' as a 1.
- pyre 12y agoPython doesn't seem to agree with you :^) In [1]: a = [] In [2]: a.append(a) In [3]: a Out[3]: [[...]] In [4]: a[0] Out[4]: [[...]] In [5]: a[0][0] Out[5]: [[...]] In [6]: a[0][0][0] Out[6]: [[...]] In [7]: a[0][0][0][0] Out[7]: [[...]] In [8]: a.append(a) In [9]: a Out[9]: [[...], [...]] In [10]: a[0][1][0] is a Out[10]: True In [11]: id(a) Out[11]: 4547140064 In [12]: id(a[0][1][0]) Out[12]: 4547140064
- sophacles 12y agoYeah, the whole infinite loop thing. Wasn't fully thinking when I wrote my reply. Good catch.
- jmount 12y agoWow, that is really ugly semantics. Here are some notes of mine on how hard R works to avoid exposing this sort of aliasing/mutability issue to the user: http://www.win-vector.com/blog/2014/04/you-dont-need-to-understand-pointers-to-program-using-r/ http://www.win-vector.com/blog/2014/04/you-dont-need-to-unde...
- lilsunnybee 12y agoYeah i really don't understand why this is just assumed to be a common 'gotcha' to be recognized and avoided by every competent python programmer. What exactly does the python spec specify as the desired behavior here? If you have this 'broken stair' that everyone should just know to step over, shouldn't somebody actually fix the stair!? I know python is not unique in having warts like this, but it's pretty b.s. in general that unexpected behavior is just thought to be okay, especially in a language meant to be very accessible, and most especially since it's being used as a perfectly valid metric for disqualifying new python programmers from employment.
- hueving 12y agoIt's not broken when you understand that functions are objects and default parameters are just members of those objects. Each time the function is executed you get local vars that point to these object members. If one is a mutable type, any changes you make to it will then obviously persist.
- rmrfrmrf 12y agoAt what level would you test an interviewee with this kind of question: Python guru, Python expert, Python ninja, Python rockstar, or merely "is familiar with Python"? Your example is a very common gotcha that has been covered ad nauseam, but IMO it's still the kind of bug that would be caught immediately in code review and is very easily fixed.
- lilsunnybee 12y agoI thought we were past using trick questions like this anyways! Since, you know, it would be easy for an experienced yet anxious programmer to get tripped up on this, but someone who just browsed "python interview questions 101" to breeze on through. Also it selects against experienced multi-language developers, since language-specific quirks like this are not generally useful information to keep front-loaded, but are trivial to become re-familiar with in a work environment, or even gasp learn for the first time from a co-worker or helpful article. If the industry as a whole cared about evidence-based, non-superstitious, non-monoculture-reinforcing hiring practices, we'd realize that tripping people up and judging programming capability based on minutia is as unfair as it is self-defeating.
- SEJeff 12y agoI don't use this as a trick question. I ask them to describe mutable vs immutable objects and gotchas. Then I write this function and ask them to describe in excruciating detail what it does, why, and how. It is simply a easy way to gauge a candidate's proficiency with the language. It also helps if they know that this is a problem. You'd be shocked to know a lot of people on the market for jobs writing python don't get this question correct, but the smart ones often do when talking through it even if they didn't originally.
- yoo-interested 12y agoI think the idea is to see whether the interviewee is a kind of person who always googles and reads on "gotchas of language X" whenever he/she learns X.
- Retric 12y agoI would caution you not to interview on things you would not be happy to see in your code base. In my experence your much better off with people that look at odd syntax and say, "I don't know what that does" vs those who do.
- GhotiFish 12y agoWell, this is pulled from a list of common python errors. Using the default value in some capacity isn't that uncommon... Though maybe you were speaking to a more general case? for example, decoding a an obfuscated C file.
- mark-r 12y agoI've seen this trotted out time and time again, and at least in this simplified form it's a red herring. If you're going to mutate the argument, it doesn't make sense to give it a default value. If you're going to return a modified form of the input you need to make a copy of it. Doing both is simply absurd.
- pbh101 12y agoDisagree. Would say it's a decent violation of expectations for the same instance to be passed into every invocation. Of course, the counterargument is 'know your tools,' which I'm partial to, but the fact that this pops up is an indication it is counterintuitive.
- mark-r 12y agoI actually agree it's counterintuitive. But this particular example makes no sense, nobody should be writing real-world code that looks like this in the first place. Either modify the original or return a copy, don't try to do both.