15个重要Python面试题 测测你适不适合做Python?

简介

在找一个Python相关工作?很可能你要证明你知道怎么用Python工作。这里有一组和Python使用有关的面试题。关注Python语言本身,而不是框架或包。

这些试题是认真准备的,测试一下,如果你觉得答案很简单,去找份Python工作吧~

问题 1

Python到底是什么样的语言?你可以比较其他技术或者语言来回答你的问题。

回答

这里是一些关键点:Python是解释型语言。这意味着不像C和其他语言,Python运行前不需要编译。其他解释型语言包括PHP和Ruby。

  • Python是动态类型的,这意味着你不需要在声明变量时指定类型。你可以先定义x=111,然后 x=”I’m a string”,一点问题也不会有。
  • Python是面向对象语言,所有允许定义类并且可以继承和组合。Python没有访问访问标识如在C++中的public, private, 这就非常信任程序员的素质,相信每个程序员都是“成人”了~
  • 在Python中,函数是一等公民。这就意味着它们可以被赋值,从其他函数返回值,并且传递函数对象。类不是一等公民。
  • 写Python代码很快,但是跑起来会比编译型语言慢。幸运的是,Python允许使用C扩展写程序,所以瓶颈可以得到处理。Numpy库就是一个很好例子,因为很多代码不是Python直接写的,所以运行很快。
  • Python使用场景很多 – web应用开发,自动化,科学建模,大数据应用,等等。它也经常被看做“胶水”语言,使得不同语言间可以衔接上。
  • Python能够简化工作  ,使得程序员能够关心如何重写代码而不是详细看一遍底层实现。

python

为啥这题重要:

如果你在找份Python工作,你应该知道什么是Python,它为啥这么cool,还有它哪里不cool o.O

问题 2

填写缺失的代码:

def print_directory_contents(sPath):
    """
    This function takes the name of a directory 
    and prints out the paths files within that 
    directory as well as any files contained in 
    contained directories. 

    This function is similar to os.walk. Please don't
    use os.walk in your answer. We are interested in your 
    ability to work with nested structures. 
    """
    fill_this_in

答案:

def print_directory_contents(sPath):
    import os                                       
    for sChild in os.listdir(sPath):                
        sChildPath = os.path.join(sPath,sChild)
        if os.path.isdir(sChildPath):
            print_directory_contents(sChildPath)
        else:
            print(sChildPath)

关注点:

  • 变量命名规则要一致。如果已有一些潜在的命名规则,首先要遵守它。
  • 递归要注意有递归,而且有终止。清晰这一点才能保证不会永远调用下去没有终止。
  • 我们使用os模块来和操作系统跨平台交互。你可以写sChildPath = sPath + ‘/’ + sChild,但是这个代码在windows上是不工作的。
  • 熟悉各种基础包固然好,但是别忘了在工作中使用Google灵活解决问题。
  • 在你不清楚时,可以询问这个问题的真正应该实现什么。
  • KISS! Keep it Simple, Stupid!

为啥这题重要:

  • 展示基础的操作系统知识很重要
  • 递归很重要很有用

问题 3

读以下代码,写出下面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]

答案

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]]

为啥这题重要:

列表操作是很高效的技能,并且对许多人都需要一些学习时间。

  1. 如果你会读代码你也很可能会写
  2. 有些很奇怪的代码,有些很奇怪的人,实际生活就是这样

问题 4

Python和多线程,是不是个好主意?列举你觉得可以让Python代码并行运行的方法?

答案

Python实际上不允许多线程。它有一个threading包但是如果你想加快代码运行速度,或者想并行运行,这不是一个好主意。Python有一个机制叫全局解释器锁(GIL)。GIL保证每次只有一个线程在解释器中跑。一个线程获得GIL,之后再交给下一个线程。所以,看起来是多个线程在同时跑,但是实际上每个时刻只有CPU核在跑一个线程,没有真正利用多个CPU核跑多个线程。就是说,多个线程在抢着跑一个CPU核。

但是还是有使用threading包的情况的。比如你真的想跑一个可以线程间抢占的程序,看起来是并行的。或者有很长时间的IO等待线程,这个包就会有用。但是threading包不会使用多核去跑代码。

真正的多核多线程可以通过多进程,一些外部库如Spark和Hadoop,或者用Python代码去调用C方法等等来实现。

为啥这题重要:

因为GIL是一个大坑。许多人搞不明白为什么自己程序总是不能用多核并行,结果发现压根就是GIL的错。

问题 5

你怎么对你的代码进行跟踪,协同写代码?

答案:

版本控制! 只是关键,你应该很兴奋地告诉他们你怎用Git的。Git是我最喜欢的版本控制系统,但是也有其他的如svn等等。。

为啥这题重要:

因为没有版本控制的代码就像没有杯子的咖啡。当有大规模代码的时候,版本控制就显得更加重要。它可以跟踪代码,自动化检查,看代码修改历史,合并别人代码,协同工作,等等等等。。反正就是很棒。

问题 6

下面这段代码的输出是什么:

def f(x,l=[]):
    for i in range(x):
        l.append(i*i)
    print(l) 

f(2)
f(3,[3,2,1])
f(3)

答案

[0, 1]
[3, 2, 1, 0, 1, 4]
[0, 1, 0, 1, 4]

啊? 什么?

第一,第二行输出还行,第三行输出好奇怪,为什么?因为Python内存还是存了第一次残留的l值,所以它以0和1开头。

再试试下面这段:

l_mem = []

l = l_mem           # the first call
for i in range(2):
    l.append(i*i)

print(l)            # [0, 1]

l = [3,2,1]         # the second call
for i in range(3):
    l.append(i*i)

print(l)            # [3, 2, 1, 0, 1, 4]

l = l_mem           # the third call
for i in range(3):
    l.append(i*i)

print(l)            # [0, 1, 0, 1, 4]

问题 7

猴子补丁是什么?它是不是一个好主意?

Answer

猴子补丁是在一个函数或者对象已经存在的基础上,改变它的行为。比如:

import datetime
datetime.datetime.now = lambda: datetime.datetime(2012, 12, 12)

大多数时候这不是一个好主意 – 如果早就设计好,不是更好?一个可以使用猴子补丁的地方是做测试,著名的包mock,就是一个很好的例子。

为啥这题重要:

这题可以看出你了解一些单元测试的方法论。如果你对经常用猴子补丁反感,说明你是一个有责任感的崇尚可维护代码的好青年。还记得KISS原则吗?

PS: 读一点mock 代码真的很重要,如果你没读过的话。它很有用。

问题 8

*args, **kwargs是什么东西? 我们为什么会用它?

答案

我们用 *args 当我们不知道要有多少个参数传给函数,或者我们想把一个列表或者tuple存起来以后传给函数。我们用**kwargs当我们不知道有多少个关键字参数要传给函数,或者我们想把字典存起来以后传给函数。args 和 kwargs的名字是以前遗留下来的,你用*bob 和**billy也没关系但是这样不太好,嘿嘿。

这里是一个例子:

def f(*args,**kwargs): print(args, kwargs)

l = [1,2,3]
t = (4,5,6)
d = {'a':7,'b':8,'c':9}

f()
f(1,2,3)                    # (1, 2, 3) {}
f(1,2,3,"groovy")           # (1, 2, 3, 'groovy') {}
f(a=1,b=2,c=3)              # () {'a': 1, 'c': 3, 'b': 2}
f(a=1,b=2,c=3,zzz="hi")     # () {'a': 1, 'c': 3, 'b': 2, 'zzz': 'hi'}
f(1,2,3,a=1,b=2,c=3)        # (1, 2, 3) {'a': 1, 'c': 3, 'b': 2}

f(*l,**d)                   # (1, 2, 3) {'a': 7, 'c': 9, 'b': 8}
f(*t,**d)                   # (4, 5, 6) {'a': 7, 'c': 9, 'b': 8}
f(1,2,*t)                   # (1, 2, 4, 5, 6) {}
f(q="winning",**d)          # () {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
f(1,2,*t,q="winning",**d)   # (1, 2, 4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}

def f2(arg1,arg2,*args,**kwargs): print(arg1,arg2, args, kwargs)

f2(1,2,3)                       # 1 2 (3,) {}
f2(1,2,3,"groovy")              # 1 2 (3, 'groovy') {}
f2(arg1=1,arg2=2,c=3)           # 1 2 () {'c': 3}
f2(arg1=1,arg2=2,c=3,zzz="hi")  # 1 2 () {'c': 3, 'zzz': 'hi'}
f2(1,2,3,a=1,b=2,c=3)           # 1 2 (3,) {'a': 1, 'c': 3, 'b': 2}

f2(*l,**d)                   # 1 2 (3,) {'a': 7, 'c': 9, 'b': 8}
f2(*t,**d)                   # 4 5 (6,) {'a': 7, 'c': 9, 'b': 8}
f2(1,2,*t)                   # 1 2 (4, 5, 6) {}
f2(1,1,q="winning",**d)      # 1 1 () {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
f2(1,2,*t,q="winning",**d)   # 1 2 (4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8} 

为啥这题重要:

有时我们需要传参数给函数,但是有时候我们希望把参数都存起来,以后传给函数,有时,这只是一个节省时间的方法。

问题 9

@classmethod, @staticmethod, @property都是什么意思?

答案

修饰器的概念要懂。拿到一个函数,返回一个函数,或者拿到一个类,返回另一个类。

@my_decorator
def my_func(stuff):
    do_things

以上代码相当于:

def my_func(stuff):
    do_things

my_func = my_decorator(my_func)

你可以在这里找到修饰器的一般用法。

@classmethod, @staticmethod 和 @property修饰器是使用在类中的。 以下是她们如何使用的:

class MyClass(object):
    def __init__(self):
        self._some_property = "properties are nice"
        self._some_other_property = "VERY nice"
    def normal_method(*args,**kwargs):
        print("calling normal_method({0},{1})".format(args,kwargs))
    @classmethod
    def class_method(*args,**kwargs):
        print("calling class_method({0},{1})".format(args,kwargs))
    @staticmethod
    def static_method(*args,**kwargs):
        print("calling static_method({0},{1})".format(args,kwargs))
    @property
    def some_property(self,*args,**kwargs):
        print("calling some_property getter({0},{1},{2})".format(self,args,kwargs))
        return self._some_property
    @some_property.setter
    def some_property(self,*args,**kwargs):
        print("calling some_property setter({0},{1},{2})".format(self,args,kwargs))
        self._some_property = args[0]
    @property
    def some_other_property(self,*args,**kwargs):
        print("calling some_other_property getter({0},{1},{2})".format(self,args,kwargs))
        return self._some_other_property

o = MyClass()
# undecorated methods work like normal, they get the current instance (self) as the first argument

o.normal_method 
# <bound method MyClass.normal_method of <__main__.MyClass instance at 0x7fdd2537ea28>>

o.normal_method() 
# normal_method((<__main__.MyClass instance at 0x7fdd2537ea28>,),{})

o.normal_method(1,2,x=3,y=4) 
# normal_method((<__main__.MyClass instance at 0x7fdd2537ea28>, 1, 2),{'y': 4, 'x': 3})

# class methods always get the class as the first argument

o.class_method
# <bound method classobj.class_method of <class __main__.MyClass at 0x7fdd2536a390>>

o.class_method()
# class_method((<class __main__.MyClass at 0x7fdd2536a390>,),{})

o.class_method(1,2,x=3,y=4)
# class_method((<class __main__.MyClass at 0x7fdd2536a390>, 1, 2),{'y': 4, 'x': 3})

# static methods have no arguments except the ones you pass in when you call them

o.static_method
# <function static_method at 0x7fdd25375848>

o.static_method()
# static_method((),{})

o.static_method(1,2,x=3,y=4)
# static_method((1, 2),{'y': 4, 'x': 3})

# properties are a way of implementing getters and setters. It's an error to explicitly call them
# "read only" attributes can be specified by creating a getter without a setter (as in some_other_property)

o.some_property
# calling some_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{})
# 'properties are nice'

o.some_property()
# calling some_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{})
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: 'str' object is not callable

o.some_other_property
# calling some_other_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{})
# 'VERY nice'

# o.some_other_property()
# calling some_other_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{})
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: 'str' object is not callable

o.some_property = "groovy"
# calling some_property setter(<__main__.MyClass object at 0x7fb2b7077890>,('groovy',),{})

o.some_property
# calling some_property getter(<__main__.MyClass object at 0x7fb2b7077890>,(),{})
# 'groovy'

o.some_other_property = "very groovy"
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# AttributeError: can't set attribute

o.some_other_property
# calling some_other_property getter(<__main__.MyClass object at 0x7fb2b7077890>,(),{})
# 'VERY nice'

问题 10

考虑以下代码,输出是什么?

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()

答案

输出是以下代码的注释:

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

为啥这题重要:

因为面向对象编程十分重要。这个问题能检验你对继承的理解和super函数的使用。大多数时间解析顺序无关紧要,但是有时候在应用中是有用的。

问题 11

考虑以下代码,输出是什么?

class Node(object):
    def __init__(self,sName):
        self._lChildren = []
        self.sName = sName
    def __repr__(self):
        return "<Node '{}'>".format(self.sName)
    def append(self,*args,**kwargs):
        self._lChildren.append(*args,**kwargs)
    def print_all_1(self):
        print(self)
        for oChild in self._lChildren:
            oChild.print_all_1()
    def print_all_2(self):
        def gen(o):
            lAll = [o,]
            while lAll:
                oNext = lAll.pop(0)
                lAll.extend(oNext._lChildren)
                yield oNext
        for oNode in gen(self):
            print(oNode)

oRoot = Node("root")
oChild1 = Node("child1")
oChild2 = Node("child2")
oChild3 = Node("child3")
oChild4 = Node("child4")
oChild5 = Node("child5")
oChild6 = Node("child6")
oChild7 = Node("child7")
oChild8 = Node("child8")
oChild9 = Node("child9")
oChild10 = Node("child10")

oRoot.append(oChild1)
oRoot.append(oChild2)
oRoot.append(oChild3)
oChild1.append(oChild4)
oChild1.append(oChild5)
oChild2.append(oChild6)
oChild4.append(oChild7)
oChild3.append(oChild8)
oChild3.append(oChild9)
oChild6.append(oChild10)

# specify output from here onwards

oRoot.print_all_1()
oRoot.print_all_2()

答案

oRoot.print_all_1() 输出:

<Node 'root'>
<Node 'child1'>
<Node 'child4'>
<Node 'child7'>
<Node 'child5'>
<Node 'child2'>
<Node 'child6'>
<Node 'child10'>
<Node 'child3'>
<Node 'child8'>
<Node 'child9'>

oRoot.print_all_2() 输出:

<Node 'root'>
<Node 'child1'>
<Node 'child2'>
<Node 'child3'>
<Node 'child4'>
<Node 'child5'>
<Node 'child6'>
<Node 'child8'>
<Node 'child9'>
<Node 'child7'>
<Node 'child10'>

为啥这题重要:

因为对象的组装和结构很重要。对象是一系列对象的组装并且需要初始化。这也涉及到一些递归和生成器的使用。

生成器很棒。你也可以通过构造长列表来打印 print_all_2。但是一个生成器很好的特性是它不需要占用很多内存!

值得指出的是,print_all_1是深度优先的,而 print_all_2 是广度优先的。

问题 12

简介地描述下Python的垃圾回收机制。

答案

这里可以说许多东西,有几个重点你可以提到:

  • Python在内存中维护对象的引用次数。如果一个对象的引用次数变为0,垃圾回收机制会回收这个对象作为他用。
  • 有时候会有“引用循环”的事情发生。垃圾回收器定期检查回收内存。一个例子是,如果你有两个对象 o1 和 o2,并且o1.x == o2 and o2.x == o1. 如果 o1 和 o2 都没有被其他对象使用,那么它们都不应该存在。但是它们的应用次数都是1,垃圾回收不会起作用。
  • 一些启发算法可以用来加速垃圾回收。比如,最近创建的对象更可能是无用的。用创建时间来度量对象的生命时长,生命越长,越可能是更有用的对象。

CPython的说明文档中有相关解释。

问题 13

把下面三段程序更具高效性排序。程序的接收参数是一串元素是0到1的列表。列表会很长。一个输入例子是[random.random() for i in range(100000)]. 你怎么确定你的判断?

def f1(lIn):
    l1 = sorted(lIn)
    l2 = [i for i in l1 if i<0.5]
    return [i*i for i in l2]

def f2(lIn):
    l1 = [i for i in lIn if i<0.5]
    l2 = sorted(l1)
    return [i*i for i in l2]

def f3(lIn):
    l1 = [i*i for i in lIn]
    l2 = sorted(l1)
    return [i for i in l1 if i<(0.5*0.5)]

答案

最高效排行f2, f1, f3. 证明这个可以用Python的库profiling 包

import cProfile
lIn = [random.random() for i in range(100000)]
cProfile.run('f1(lIn)')
cProfile.run('f2(lIn)')
cProfile.run('f3(lIn)')

输出大致如下:

>>> cProfile.run('f1(lIn)')
         4 function calls in 0.045 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.009    0.009    0.044    0.044 <stdin>:1(f1)
        1    0.001    0.001    0.045    0.045 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.035    0.035    0.035    0.035 {sorted}


>>> cProfile.run('f2(lIn)')
         4 function calls in 0.024 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.008    0.008    0.023    0.023 <stdin>:1(f2)
        1    0.001    0.001    0.024    0.024 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.016    0.016    0.016    0.016 {sorted}


>>> cProfile.run('f3(lIn)')
         4 function calls in 0.055 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.016    0.016    0.054    0.054 <stdin>:1(f3)
        1    0.001    0.001    0.055    0.055 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.038    0.038    0.038    0.038 {sorted}

为啥这题重要?

定位和避免代码瓶颈有时候会很重要。会使用合理的工具也是很好的素质之一。

问题 14

你有挫败的经历吗?

错误答案

我从不失败!

为啥这题重要?

展示你有能力承认错误,为错误负责,并且吸取教训。

问题 15

你有个人项目吗?

真的吗?

这说明你真的很积极会主动提高自己的项目经验。

总结

这些问题涵盖了很多主题。答案也比较有针对性。在程序员面试中,你要表现出有深刻的见解并且,如果你能指出细节,那就尽量去指出。希望这篇博客对你找工作有帮助。

祝你找工作时所向披靡!

 

翻译自:15 Essential Python Interview Questions

发布者

David 9

邮箱:yanchao727@gmail.com 微信: david9ml

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注