What is python?

  • Python is an interpreted language. It does not need to be compiled before it is run.
  • Python is dynamic typed, which means that you don’t need to state the types of variables.
  • Python does not have access specifier like public, private.
  • In python, functions are first-class objects, which means that they can be assigned to variables, returned from other functions and passed into functions. Classes are also first-class object.
  • Writing python is fast but running it is often slower than compiled language.
  • Python can be used in many spheres like web application, automation, scientific modeling, big data application. It’s also used as “glue” code to get other languages and components to play nice.

Python and multi-threading. Is it a good idea? List some ways to get some Python code to run in a parallel way.

  • Python doesn’t allow multi-threading in the truest sense of world. It has a “multi-threading package” but if you want to multi-thread to speed your code up, then it is usually not a good idea to use it. Python has a construct called “Global Interpreter Lock(GIL)”. The GIL makes sure that only one of your 'threads' can execute at any one time. . A thread acquires the GIL, does a little work, then passes the GIL onto the next thread. This happens very quickly so to the human eye it may seem like your threads are executing in parallel, but they are really just taking turns using the same CPU core. All this GIL passing adds overhead to execution. This means that if you want to make your code run faster then using the threading package often isn't a good idea.
  • There are reasons to use Python's threading package. If you want to run some things simultaneously, and efficiency is not a concern, then it's totally fine and convenient. Or if you are running code that needs to wait for something (like some IO) then it could make a lot of sense. But the threading library wont let you use extra CPU cores.
  • 为什么有人说 Python 的多线程是鸡肋呢?

How do you keep track of different versions of your code ?

  • Version control! I use Git (or whatever is your favorite) to keep track of correspondence with Granny. Git is my preferred version control system, but there are others, for example subversion.

Describe Python's garbage collection mechanism in brief.

  • Python maintains a count of the number of references to each object in memory. If a reference count goes to zero then the associated object is no longer live and the memory allocated to that object can be freed up for something else.
  • Occasionally things called "reference cycles" happen. The garbage collector periodically looks for these and cleans them up. An example would be if you have two objects o1 and o2 such that o1.x == o2 and o2.x == o1. If o1 and o2 are not referenced by anything else then they shouldn't be live. But each of them has a reference count of 1.

What is the difference between range and xrange, how has this changed over time?

  • In python 2.x range() returns a list and xrange() returns an xrange object, which is kind of like an iterator and generates the numbers on demand.(Lazy Evaluation)

    In [1]: range(5)
    Out[1]: [0, 1, 2, 3, 4]
    
    In [2]: xrange(5)
    Out[2]: xrange(5)
    
    In [3]: print xrange.__doc__
    xrange([start,] stop[, step]) -> xrange object
    
    Like range(), but instead of returning a list, returns an object that
    generates the numbers in the range on demand.  For looping, this is 
    slightly faster than range() and more memory efficient.
    
  • In python 3.x xrange() has been removed and range() now works like xrange() and returns a range object.

How do you iterate over a list and pull element indices at the same time?

my_list = ['a', 'b', 'c']
list(enumerate(my_list))
[(0, 'a'), (1, 'b'), (2, 'c')]

my_list = ['a', 'b', 'c']
for i, char in enumerate(my_list):
    print i, char

Looking at the below code, write down the final values of A0, A1, ...An.

A0 = dict(zip(('a','b','c','d','e'),(1,2,3,4,5)))
A1 = range(10)
A2 = sorted([i for i in A1 if i in A0])
A3 = sorted([A0[s] for s in A0])
A4 = [i for i in A1 if i in A3]
A5 = {i:i*i for i in A1}
A6 = [[i,i*i] for i in A1]

Answer
A0 = {'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4}  # the order may vary
A1 = range(0, 10) # or [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] in python 2
A2 = []
A3 = [1, 3, 2, 5, 4]
A4 = [1, 2, 3, 4, 5]
A5 = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
A6 = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]]

What does this stuff mean: *args, **kwargs? And why would we use it?

  • Use *args when we aren't sure how many arguments are going to be passed to a function, or if we want to pass a stored list or tuple of arguments to a function.
  • **kwargs is used when we dont know how many keyword arguments will be passed to a function, or it can be used to pass the values of a dictionary as keyword arguments.
  • The identifiers args and kwargs are a convention, you could also use *bob and **billy but that would not be wise.

What do these mean to you: @classmethod, @staticmethod, @property?

  • These are decorators. A decorator is a special kind of function that either takes a function and returns a function, or takes a class and returns a class. The @ symbol is just syntactic sugar that allows you to decorate something in a way that's easy to read.

    @my_decorator
    def my_func(stuff):
      do_things
    

    Is equivalent to

    def my_func(stuff):
    do_things
    
    my_func = my_decorator(my_func)
    

Consider the following code, what will it output?

class A(object):
    def go(self):
        print("go A go!")
    def stop(self):
        print("stop A stop!")
    def pause(self):
        raise Exception("Not Implemented")

class B(A):
    def go(self):
        super(B, self).go()
        print("go B go!")

class C(A):
    def go(self):
        super(C, self).go()
        print("go C go!")
    def stop(self):
        super(C, self).stop()
        print("stop C stop!")

class D(B,C):
    def go(self):
        super(D, self).go()
        print("go D go!")
    def stop(self):
        super(D, self).stop()
        print("stop D stop!")
    def pause(self):
        print("wait D wait!")

class E(B,C): pass

a = A()
b = B()
c = C()
d = D()
e = E()

# specify output from here onwards

a.go()
b.go()
c.go()
d.go()
e.go()

a.stop()
b.stop()
c.stop()
d.stop()
e.stop()

a.pause()
b.pause()
c.pause()
d.pause()
e.pause()

Answer: The output is specified in the comments in the segment below:

a.go()
# go A go!

b.go()
# go A go!
# go B go!

c.go()
# go A go!
# go C go!

d.go()
# go A go!
# go C go!
# go B go!
# go D go!

e.go()
# go A go!
# go C go!
# go B go!

a.stop()
# stop A stop!

b.stop()
# stop A stop!

c.stop()
# stop A stop!
# stop C stop!

d.stop()
# stop A stop!
# stop C stop!
# stop D stop!

e.stop()
# stop A stop!

a.pause()
# ... Exception: Not Implemented

b.pause()
# ... Exception: Not Implemented

c.pause()
# ... Exception: Not Implemented

d.pause()
# wait D wait!

e.pause()
# ...Exception: Not Implemented