Python Interview Questions

C

Chris Angelico

The theorist understands that a chisel and a screwdriver were intended
for different purposes, but the pragmatist gets the paint can open.

A good tool can always be used in ways its inventor never intended -
and it will function as its user expects.

$ some_program | egrep --color=always '(ERROR|^)'

will highlight the word ERROR in red anywhere it appears in the
program's output, while maintaining all other lines without color. Not
normal use of grep, to be sure, but quite functional.

A tuple may have been intended to be a record, a struct, whatever, but
it is what it is, and I'll use one any time it's the best tool for the
job. Maybe its immutability is critical; or maybe it's just the most
convenient syntax and all I care about is that it be iterable.

But when I'm explaining grep to someone, I'll describe it as a filter
that keeps only some lines from the original, and when I describe a
tuple, I'll point out that it's immutable and (potentially) hashable.
The obvious first, the unobvious later.

ChrisA
 
M

Mark Lawrence

The theorist understands that a chisel and a screwdriver were intended
for different purposes, but the pragmatist gets the paint can open.

To throw a chiseldriver into the works, IIRC a tuple is way faster to
create but accessing a list is much faster. The obvious snag is that
may have been Python 2.7 whereas 3.3 is completely different. Sorry but
I'm currently wearing my XXXL size Lazy Bone Idle Hat so have no figures
to back my probably incorrect memory up, anyone know anything about this?
 
I

Ian Kelly

To throw a chiseldriver into the works, IIRC a tuple is way faster to create
but accessing a list is much faster. The obvious snag is that may have been
Python 2.7 whereas 3.3 is completely different. Sorry but I'm currently
wearing my XXXL size Lazy Bone Idle Hat so have no figures to back my
probably incorrect memory up, anyone know anything about this?

It's not been my experience with Python 2.7 that list access is faster
than tuple access. Tuples are as fast as or faster than lists, pretty
much universally. They seem to have closed the gap a bit in
Python 3.3, though, as the following timings show. For one-shot
construction, tuples seem to be more efficient for short sequences,
but then lists win for longer sequences, although not by much. Of
course, lists are always going to be much slower if you build them up
with appends and extends.

C:\>python -m timeit -s "x = range(10)" "tuple(x)"
1000000 loops, best of 3: 0.773 usec per loop

C:\>python -m timeit -s "x = range(10)" "list(x)"
1000000 loops, best of 3: 0.879 usec per loop

C:\>python -m timeit -s "x = range(100)" "tuple(x)"
100000 loops, best of 3: 2.88 usec per loop

C:\>python -m timeit -s "x = range(100)" "list(x)"
100000 loops, best of 3: 2.63 usec per loop

C:\>python -m timeit -s "x = range(1000)" "tuple(x)"
10000 loops, best of 3: 37.4 usec per loop

C:\>python -m timeit -s "x = range(1000)" "list(x)"
10000 loops, best of 3: 36.2 usec per loop

C:\>python -m timeit -s "x = range(10000)" "tuple(x)"
1000 loops, best of 3: 418 usec per loop

C:\>python -m timeit -s "x = range(10000)" "list(x)"
1000 loops, best of 3: 410 usec per loop


For iteration, tuples are consistently 7-8% faster.


C:\>python -m timeit -s "x = tuple(range(10))" "for i in x: pass"
1000000 loops, best of 3: 0.467 usec per loop

C:\>python -m timeit -s "x = list(range(10))" "for i in x: pass"
1000000 loops, best of 3: 0.498 usec per loop

C:\>python -m timeit -s "x = tuple(range(100))" "for i in x: pass"
100000 loops, best of 3: 3.31 usec per loop

C:\>python -m timeit -s "x = list(range(100))" "for i in x: pass"
100000 loops, best of 3: 3.56 usec per loop

C:\>python -m timeit -s "x = tuple(range(1000))" "for i in x: pass"
10000 loops, best of 3: 31.6 usec per loop

C:\>python -m timeit -s "x = list(range(1000))" "for i in x: pass"
10000 loops, best of 3: 34.3 usec per loop

C:\>python -m timeit -s "x = tuple(range(10000))" "for i in x: pass"
1000 loops, best of 3: 318 usec per loop

C:\>python -m timeit -s "x = list(range(10000))" "for i in x: pass"
1000 loops, best of 3: 341 usec per loop


For direct item access, tuples seem to be about 2-3% faster.


C:\>python -m timeit -s "import operator as o; x = tuple(range(10)); g
= o.itemgetter(*range(len(x)))" "g(x)"
1000000 loops, best of 3: 0.67 usec per loop

C:\>python -m timeit -s "import operator as o; x = list(range(10)); g
= o.itemgetter(*range(len(x)))" "g(x)"
1000000 loops, best of 3: 0.674 usec per loop

C:\>python -m timeit -s "import operator as o; x = tuple(range(100));
g = o.itemgetter(*range(len(x)))" "g(x)"
100000 loops, best of 3: 4.52 usec per loop

C:\>python -m timeit -s "import operator as o; x = list(range(100)); g
= o.itemgetter(*range(len(x)))" "g(x)"
100000 loops, best of 3: 4.65 usec per loop

C:\>python -m timeit -s "import operator as o; x = tuple(range(1000));
g = o.itemgetter(*range(len(x)))" "g(x)"
10000 loops, best of 3: 43.2 usec per loop

C:\>python -m timeit -s "import operator as o; x = list(range(1000));
g = o.itemgetter(*range(len(x)))" "g(x)"
10000 loops, best of 3: 43.7 usec per loop

C:\>python -m timeit -s "import operator as o; x =
tuple(range(10000)); g = o.itemgetter(*range(len(x)))" "g(x)"
1000 loops, best of 3: 422 usec per loop

C:\>python -m timeit -s "import operator as o; x = list(range(10000));
g = o.itemgetter(*range(len(x)))" "g(x)"
1000 loops, best of 3: 447 usec per loop
 
S

Steven D'Aprano

Er, no, it's inherently a blob of multiple text lines.

No, it's a list that looks like (taken from the doc string of the code I
referenced):

[('/usr/lib/.../base.py', 'get_response'),
('/home/songza/.../views.py', 'song_info'),
('/home/songza.../api.py', 'get_song'),
('/home/songza/.../api.py', 'api')]

[it doesn't really have ...'s in the paths; I just elided some text to
make it easier to read][/QUOTE]

I see. It wasn't clear from your earlier description that the items had
been post-processed from collections of raw log lines to fixed records.
But it doesn't actually change my analysis any. See below.

By the way, based on the sample data you show, your script is possibly
broken. You don't record either the line number that raises, or the
exception raised, so your script doesn't differentiate between different
errors that happen to occur with similar stack traces. (I say "possibly"
broken because I don't know what your requirements are. Maybe your
requirements are sufficiently wide that you don't care that distinct
failures are counted together.)

E.g. these three stack traces will probably generate the same fixed
record, even though the errors are distinct:

#1
Traceback (most recent call last):
File "./spam.py", line 20, in select
selection = func(a, b)
File "./spam.py", line 60, in func
return 1/x
ZeroDivisionError: float division


#2
Traceback (most recent call last):
File "./spam.py", line 20, in select
selection = func(a, b)
File "./spam.py", line 60, in func
return 1/x
TypeError: unsupported operand type(s) for /: 'int' and 'NoneType'


#3
Traceback (most recent call last):
File "./spam.py", line 20, in select
selection = func(a, b)
File "./spam.py", line 55, in func
y = 1/(a + b)
ZeroDivisionError: float division


Maybe that's okay for your application, but it strikes me as odd that you
do distinguish *some* distinct errors in the same function, but not
others.


No. Each entry in the list represents a source file and a function
name. They're all the same "shape". You could remove one or add
another one, or shuffle the order, and you would have something which
was syntactically correct and semantically meaningful (even if it didn't
represent an actual code path.

If you remove/add/shuffle lines in the stack, you no longer have the same
stack. Take the example you gave before:

stack1 = [('/usr/lib/.../base.py', 'get_response'),
('/home/songza/.../views.py', 'song_info'),
('/home/songza.../api.py', 'get_song'),
('/home/songza/.../api.py', 'api')
]

Here's a different stack trace, representing a different code path, which
as you say is syntactically correct and semantically meaningful:

stack2 = [('/home/songza/.../api.py', 'api'),
('/home/songza.../api.py', 'get_song'),
('/home/songza/.../views.py', 'song_info'),
('/usr/lib/.../base.py', 'get_response')
]

Since they are different stacks, they are treated as different keys:

data = {stack1: 11, stack2: 22}

Do you agree that this is what your application expects? Different stack
traces are different keys, associated with different values.

I claim this only makes sense if you treat the stacks as inherently
immutable. Never mind Python's limitation. Let's pretend we were running
this code under some other language, NeoPython, which allowed mutable
keys.

You claim that stacks are *inherently mutable*. So I should be able to do
this:

stack1.sort() # it's the *same stack*, all I've done is mutate it
print data[stack1]

and expect to see "11" printed. I am looking at the same key, right? So I
certainly don't expect to see the value associated with a completely
different key.

But wait a minute... after sorting, stack1 and stack2 now are equal. I
could just as easily expect to see "22" printed.

I thought we had just agreed that stack1 and stack2 are *different* keys.
Of course they are different. They represent different code paths. But
after sorting stack1, it looks exactly like stack2. It looks like a
different code path. It *lies* -- it no longer represents the code path
that it actually represents, instead it looks like a *different* code
path.

I then generate another stack:

stack3 = [('/home/songza/.../api.py', 'api'),
('/home/songza.../api.py', 'get_song'),
('/home/songza/.../views.py', 'song_info'),
('/usr/lib/.../base.py', 'get_response')
]

should data[stack3] return 11 (it has the same value as stack1) or 22 (it
has the same value as stack2)? Or possibly 33? Or raise KeyError?

Treating stacks in this context as mutable is *incoherent*. It is nice
and convenient to be able to build up a stack trace using a mutable list,
you won't get an argument from me about that, but that can only be
considered a temporary data structure used to build the data structure
you actually care about, which is fixed.

That brings it back to my question: your application is not a counter-
example to my question about using lists as keys, because your data is
not inherently list-like. It is inherently tuple-like, you just build it
using a temporary list. That's perfectly fine, by the way, I do the same
thing.

As you say, the order of the lines in the stack trace is significant. You
cannot expect to mutate the stack and move lines around and treat it as
the same stack. If you move the lines about, it represents a different
stack. That is fundamentally different from the normal use of a list,
where you do expect to be able to move lines about and still have it
count as "the same list".

I think we're going to have to let this be. You obviously have your
concept of what a tuple is and what a list is. I disagree.

I think a tuple is an immutable sequence of items, and a list is a
mutable sequence of items.

I don't
think either of us is right or wrong, we just have different ways of
thinking about things.

You come at it from a theoretical point of view.

I certainly do not. My position here is imminently practical. The
alternative, the mutability of keys, is simply incoherent.

You think of each type
as an embodiment of certain concepts ("it represents a fixed-length
heterogenous sequence"). Your thinking is driven by what each type was
intended to be used for.

Not even close. My thinking is driven by the things each data structure
needs to do. See below.

I come at it from a practical point of view. To me, each type is a
collection of methods. I have certain operations I need to perform. I
pick the type which offers those operations. If the set of operations I
need to perform (in this case, {append, hash}) don't exist in a single
type, I'm forced to use both types and convert from one to the other as
needed.

I don't see that as a problem. Converting from one type to another is
exactly the sort of thing I described in my earlier question.

In your application, you build up a collection of code lines that
represent a stack trace. Here's that example from your own documentation
again:

[('/usr/lib/.../base.py', 'get_response'),
('/home/songza/.../views.py', 'song_info'),
('/home/songza.../api.py', 'get_song'),
('/home/songza/.../api.py', 'api')]

What are the sorts of things I might meaningfully want to do with this
*complete* stack trace?

Add extra lines to it? No. If I needed to add extra lines, it wouldn't be
complete.

Delete lines? Certainly not, that would change the code path it claims to
represent to a code path it doesn't represent.

Sort the list? Reverse it? Heavens no.

If you look at the available list methods, *not one* of the mutating
methods is appropriate to a completed stack trace object. *None* of the
mutator list methods are appropriate once the stack trace object is
complete, and using them would be counter-productive.

If you believe different, then please tell me what mutations your code
actually performs after the stack trace object is completed. In the code
you showed, you throw the list away after turning it into a tuple.

If the object represents a "list of code lines", in the sense of a
mutable Python list rather than a mere sequence, then why don't you use
any list methods on it?

The append method is useful during construction, but that is all. After
the stack is complete, use of any mutator method would be a bug. In other
words, it ought to be immutable, and the use of a list ought to be buried
in the appropriate function as an internal implementation detail. The
public interface ought to be that of an immutable tuple of immutable
strings, because once you have finished building the object, it should
not be possible to mutate it.

This is hardly a theoretical viewpoint. The idea of treating data that
ought not be changed as immutable is borne out of bitter experience of
millions of man-hours tracking down hundreds of thousands of bugs.

(Admittedly not all of those bugs were *my* bugs. I'm talking the
collective experience of programmers over fifty years of coding.)
 
T

Terry Reedy

than tuple access. Tuples are as fast as or faster than lists, pretty
much universally. They seem to have closed the gap a bit in
Python 3.3, though, as the following timings show. For one-shot
construction, tuples seem to be more efficient for short sequences,
but then lists win for longer sequences, although not by much. Of
course, lists are always going to be much slower if you build them up
with appends and extends.

Interesting results. But what system (hardware, os). These sorts of
times tend to vary with the system.
 
R

Roy Smith

Steven D'Aprano said:
I see. It wasn't clear from your earlier description that the items had
been post-processed from collections of raw log lines to fixed records.

Well, I did provide the code that does this.
But it doesn't actually change my analysis any. See below.

By the way, based on the sample data you show, your script is possibly
broken. You don't record either the line number that raises, or the
exception raised, so your script doesn't differentiate between different
errors that happen to occur with similar stack traces.

You really might want to read the code I provided. Here's the reference
again:

https://bitbucket.org/roysmith/python-tools/src/4f8118d175ed/logs/traceba
ck_helper.py

The "header" referred to does indeed contain the exception raised. And
the line numbers are included. Here's a typical output stanza:

2012-11-19T00:00:15+00:00 web5 ˇ˛2012-11-19 00:00:15,831 [2712]:
songza-api IGPhwNU2SJ691cx8 4C0ABFA9-50A974E7-384995 W6D-HSO
173.145.137.54 songza.django.middleware ERROR process_exception() Path =
u'/api/1/station/1459775/next', Exception =
ValueError(u"<SequentialSongPicker: <Station 1459775: u'Old School
105.3'>>: no song ids for mp3",)
/home/songza/env/python/local/lib/python2.7/site-packages/django/core/han
dlers/base.py:111:get_response()
/home/songza/deploy/current/pyza/djapi/decorators.py:11:_wrapped_view_fun
c()
/home/songza/env/python/local/lib/python2.7/site-packages/django/views/de
corators/http.py:45:inner()
/home/songza/deploy/current/pyza/djapi/views.py:1659:station_next()
/home/songza/deploy/current/pyza/models/station.py:660:next_song()
/home/songza/deploy/current/pyza/lib/song_picker.py:327:pick()
I say "possibly" broken because I don't know what your requirements are.

Our requirements are to scan the logs of a production site and filter
down the gobs and gobs of output (we produced 70 GB of log files
yesterday) into something small enough that a human can see what the
most common failures were. The tool I wrote does that.

The rest of this conversation is just silly. It's turning into getting
hit on the head lessons.
 
R

Roy Smith

OK, I've just read back over the whole thread. I'm really struggling to
understand what point you're trying to make. I started out by saying:
Use a list when you need an ordered collection which is mutable (i.e.
can be altered after being created). Use a tuple when you need an
immutable list (such as for a dictionary key).

To which you obviously objected. So now you write:
I think a tuple is an immutable sequence of items, and a list is a
mutable sequence of items.

So how is that different from what I said? Is this whole argument
boiling down to your use of "immutable sequence" vs. my use of
"immutable list"?
 
I

Ian Kelly

You really might want to read the code I provided. Here's the reference
again:

https://bitbucket.org/roysmith/python-tools/src/4f8118d175ed/logs/traceba
ck_helper.py

The "header" referred to does indeed contain the exception raised. And
the line numbers are included. Here's a typical output stanza:

Yes, but the dict is still keyed on the traceback alone, and only the
first header for a particular traceback is stored. If two different
exceptions occur at the same line of code and sharing the same
traceback, the second exception would be counted as a second
occurrence of the first, effectively squashing any reporting of the
second exception.
 
T

Terry Reedy

Our requirements are to scan the logs of a production site and filter
down the gobs and gobs of output (we produced 70 GB of log files
yesterday) into something small enough that a human can see what the
most common failures were. The tool I wrote does that.

The rest of this conversation is just silly. It's turning into getting
hit on the head lessons.

I agree. In early Python, tuples were more different from lists than
they are today. They did not have any (public) methods. Today, they have
..index and .count methods, which make little sense from the 'tuple is a
record' viewpoint. The addition of those methods redefined tuples as
read-only (and therefore hashable) sequences.

From the collections.abc doc
'''
Sequence | Sized, Iterable, Container |
__getitem__ __contains__, __iter__, __reversed__, index, and count
....
class collections.abc.Sequence
class collections.abc.MutableSequence
ABCs for read-only and mutable sequences.
'''True
 
S

Steven D'Aprano

Well, I did provide the code that does this.

You did? When? [goes back and looks]

Oh, so you did. Oops.

By the way, your news client seems to be mangling long URLs, by splitting
them when they exceed the maximum line length. I didn't follow the link
you gave because it was mangled, and then forgot it even existed. Sorry
about that.


[...]
You really might want to read the code I provided. Here's the reference
again:

https://bitbucket.org/roysmith/python-tools/src/4f8118d175ed/logs/ traceba
ck_helper.py

And mangled again :)

The "header" referred to does indeed contain the exception raised. And
the line numbers are included. Here's a typical output stanza:
[snip]

Ian Kelly has picked up on what I'm trying to say. You might collect the
traceback in the "header", but it doesn't get used in the key, and each
time you find a repeated stack trace, you toss away whatever header you
just saw and keep the header you saw the first time.

header, stack = traceback_helper.extract_stack(lines)
signature = tuple(stack)
if signature in crashes:
count, header = crashes[signature]
crashes[signature] = (count + 1, header)
else:
crashes[signature] = (1, header)
[end quote]


In general, it is an unsafe assumption that the actual exception raised
will be the same just because the stack trace is the same. So as I said,
if you have two *distinct* failures occurring in the same function (not
even necessarily on the same line), your code appears to treat them as
the same error. That seems odd to me, but if you have a good reason for
doing it that way, so be it.
 
S

Steven D'Aprano

OK, I've just read back over the whole thread. I'm really struggling to
understand what point you're trying to make. I started out by saying:


To which you obviously objected. So now you write:


So how is that different from what I said? Is this whole argument
boiling down to your use of "immutable sequence" vs. my use of
"immutable list"?

Sheesh, of course not. Give me some credit.

I gave some examples of when somebody might use lists, tuples, sets and
dicts. Apparently I forgot a couple, and you responded with a sarcastic
comment about the "One True Church Of Pythonic Orthodoxy And Theoretical
Correctness" and gave a couple of additional examples.

Although I didn't come out and *explicitly* say "I agree" to your
examples, I actually did, with one proviso: your example of using an
"immutable list" as dict key. So I asked a question about that *specific*
use-case:

Under what sort of circumstances would somebody want to take a mutable
list of data, say a list of email addresses, freeze it into a known state,
and use that frozen state as a key in a dict? What would be the point?
Even if there was some meaningful reason to look up "this list of 12000
email addresses" as a single key, it is going to get out of sync with the
actual mutable list.
[end quote]

Your reply was to give your stack trace script as an example. That's a
fine example as a use-case for a temporary list, and I've done similar
things dozens, hundreds of times myself. As I said:

Sure, I have built a collection of items as a list, because lists are
mutable, then frozen it into a tuple, and *thrown the list away*, then
used the tuple as a key. But that's not the same thing, the intent is
different. In my case, the data was never intended to be a list, it was
always intended to be a fixed record-like collection, the use of list was
as a temporary data structure used for construction. A bit like the idiom
of ''.join(some_list).
[end quote]

To me, this sounds *exactly* like your use-case: your data, stack traces,
represent a little chunk of immutable data that you build up a line at a
time using a temporary list first, just like I wrote. And I said so.
There's no sign in either your code or your description that the stack
traces get treated as mutable objects in any way once you have finished
building them a line at a time. So your real world, practical, "in the
trenches" example matches my experience: you build a *fixed data record*
using a *temporary list*, throw the list away, and then never mutate that
data record again.

So why are we disagreeing? Like many such discussions on the Internet,
this one has rambled a bit, and I've misunderstood some of your code
(sorry), and you seem to have misunderstood the question I am asking.
Maybe my explanation was not clear enough, in which case, sorry again.

I'm asking about the case where one might want the key to remain mutable
even after it is used as a key, but can't because Python won't let you.
There's no sign that your stack trace example is such an example.

As I earlier said:

But I can't think of any meaningful, non-contrived example where I might
want an actual mutable list of values as a dict key.
[end quote]


and I still can't.
 
P

Prasad, Ramit

Roy said:
OK, I've just read back over the whole thread. I'm really struggling to
understand what point you're trying to make. I started out by saying:


To which you obviously objected. So now you write:


So how is that different from what I said? Is this whole argument
boiling down to your use of "immutable sequence" vs. my use of
"immutable list"?


'''
Roy:
Usea list when you need an ordered collection which is mutable (i.e.
can be altered after being created). Use a tuple when you need an
immutable list (such as for a dictionary key).
Steven:
I keep hearing about this last one, but I wonder... who *actually* does
this? I've created many, many lists over the years -- lists of names,
lists of phone numbers, lists of directory search paths, all sorts of
things. I've never needed to use one as a dictionary key.
'''

To me this is more of a question than an argument. Now moving
on to your specific example.

'''
def extract_stack(lines):
"in traceback_helper module "
header = lines[0]
stack = []
for line in lines:
m =frame_pattern.match(line)
if not m:
continue
frame = (m.group('path'), m.group('function'))
stack.append(frame)
# [Convert to tuple and return after finished building stack.]
return (header, stack)
[...]
def main(args):
crashes = {}
[...]
for line in open(log_file):
if does_not_look_like_a_stack_dump(line):
continue
lines = traceback_helper.unfold(line)
header, stack = traceback_helper.extract_stack(lines)
signature = tuple(stack)
if signature in crashes:
count, header = crashes[signature]
crashes[signature] = (count + 1, header)
else:

crashes[signature] = (1, header)
'''

Seems to me that Steven is suggesting that stack (after being built)
should converted to a tuple before being returned, because a "stack"
for any unique exception should be unique and immutable. You do this
anyway; you just do it before putting it into a dictionary rather
than before returning it.

Same net effect (as long as you do not modify `stack` later), so no
real argument.


~Ramit


This email is confidential and subject to importantdisclaimers and
conditions including on offers for the purchase or sale of
securities, accuracy and completeness of information, viruses,
confidentiality, legal privilege, and legal entity disclaimers,
available at http://www.jpmorgan.com/pages/disclosures/email.
 
R

Roy Smith

Steven D'Aprano said:
By the way, your news client seems to be mangling long URLs, by splitting
them when they exceed the maximum line length.

Hmmm. So it did. My bad.
 
R

Roy Smith

Steven D'Aprano said:
I'm asking about the case where one might want the key to remain mutable
even after it is used as a key, but can't because Python won't let you.

Ah. Now I see what you're getting at. Thank you.

Well, I will admit that it probably doesn't make sense to mutate an
object after it's put into a dict (or at least mutate it in a way which
changes it's hash value and/or whether it compares equal to the original
object). If you did (assuming lists were allowed as keys):

l = [1, 2, 3]
d = {l: "spam"}
l.append(4)
print d[l]

I'm not sure what I would expect to print. It's not too hard to
experiment, though. All you need do is:

class HashableList(list):
def __hash__(self):
return hash(tuple(self))

and python is then happy to let you use a list as a key. I just played
around with this a bit off-line. I think I got the results I was
expecting, but since I'm not sure what I was expecting, that's hard to
say.

However, you didn't ask if it made sense to mutate an object after using
it as a key. You asked if it made sense to let the object remain
mutable after using it as a key. That's a harder question.

Let's say I had lots of of lists I wanted to use a dictionary keys. As
it stands now, I have to convert them to tuples, which means copying all
the data. For a lot of data, that's inefficient.

Wouldn't it be nice (or at least, more efficient) if I could just use
the original lists as keys directly, without the extra copy? I would
have to understand that even though they are mutable, interesting (and
perhaps, unwanted) things if I actually mutated them. But, we're all
consenting adults here. If I'm willing to accept responsibility for the
consequences of my actions in return for the efficiency gain, why
shouldn't I be allowed to?

I guess the answer is, that I am allowed to. I just need to do the
HashableList deal, shown above (no broken URL required to read the code).
 
E

elisha.java

I have used Python for a couple of projects last year and
I found it extremely useful. I could write two middle size
projects in 2-3 months (part time). Right now I am a bit
rusty and trying to catch up again with Python.

I am now appearing for Job Interviews these days and I am
wondering if anybody of you appeared for a Python
Interview. Can you please share the questions you were
asked. That will be great help to me.

While I haven't interviewed precisely for Python, I've been
on the other (interviewing) end and can offer a few of the
sorts of things I ask. I don't expect perfect answers to
all of them, but they show me a range of what the
interviewee knows. I try and give a scattershot of
questions from the following areas to try and narrow down
where they fall in terms of pythonability, and then grill
more deeply around the edges that I find.

Basic Python:
=============
- do they know a tuple/list/dict when they see it?

- when to use list vs. tuple vs. dict. vs. set

- can they use list comprehensions (and know when not to
abuse them? :)

- can they use tuple unpacking for assignment?

- string building...do they use "+=" or do they build a list
and use .join() to recombine them efficiently

- truth-value testing questions and observations (do they
write "if x == True" or do they just write "if x")

- basic file-processing (iterating over a file's lines)

- basic understanding of exception handling

Broader Basic Python:
=====================
- questions about the standard library ("do you know if
there's a standard library for doing X?", or "in which
library would you find [common functionality Y]?") Most
of these are related to the more common libraries such as
os/os.path/sys/re/itertools

- questions about iterators/generators

- questions about map/reduce/sum/etc family of functions

- questions about "special" methods (__<foo>__)

More Advanced Python:
=====================
- can they manipulate functions as first-class objects
(Python makes it easy, but do they know how)

- more detailed questions about the std. libraries (such as
datetime/email/csv/zipfile/networking/optparse/unittest)

- questions about testing (unittests/doctests)

- questions about docstrings vs. comments, and the "Why" of
them

- more detailed questions about regular expressions

- questions about mutability

- keyword/list parameters and unpacked kwd args

- questions about popular 3rd-party toolkits (BeautifulSoup,
pyparsing...mostly if they know about them and when to use
them, not so much about implementation details)

- questions about monkey-patching

- questions about PDB

- questions about properties vs. getters/setters

- questions about classmethods

- questions about scope/name-resolution

- use of lambda

Python History:
===============
- decorators added in which version?

- "batteries included" SQL-capible DB in which version?

- the difference between "class Foo" and "class Foo(object)"

- questions from "import this" about pythonic code

Python Resources:
=================
- what do they know about various Python web frameworks
(knowing a few names is usually good enough, though
knowledge about the frameworks is a nice plus) such as
Django, TurboGears, Zope, etc.

- what do they know about various Python GUI frameworks and
the pros/cons of them (tkinter, wx, pykde, etc)

- where do they go with Python related questions (c.l.p,
google, google-groups, etc)

Other Process-releated things:
==============================
- do they use revision control
(RCS/CVS/Subversion/Mercurial/Git...anything but VSS) and
know how to use it well

- do they write automated tests for their code

Touchy-feely things:
====================
- tabs vs. spaces, and their reasoning

- reason for choosing Python

- choice of editor/IDE

Good luck with your interviewing and hope this helped,

-tkc

I appreciate all these. I thought I knew Python!
 
R

Roy Smith

Somebody, whose identity has been lost in three-deep quoting, said:
We have a standard list of about 2 dozen screening questions we use that
cover a broad but shallow swath of CS, Unix, and Python. I'm not going
to share the exact questions, but here's some of the Python topics we
cover:

The ramifications of string immutability.

How default function arguments work, especially how they interact with
mutable objects.

How booleans and various ways of testing for equality work.

A question about subclassing a built-in type.

List comprehensions vs. generator expressions.

We don't expect everybody to get every question, but it gives us a quick
first cut to evaluate applicants before we decide to bring them in for
an interview or not.

We also want to see that you understand some basic computer science. If
nothing else, you need to understand, what O(n) means, and be able to
give some examples of Python code which exhibit various orders of
complexity.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top