UDN-企业互联网技术人气社区

板块导航

浏览  : 1420
回复  : 0

[面试技巧] Python面试必须要看的15个问题

[复制链接]
开花包的头像 楼主
发表于 2015-11-21 14:56:07 | 显示全部楼层 |阅读模式
  引言

  想找一份Python开发工作吗?那你很可能得证明自己知道如何使用Python。下面这些问题涉及了与Python相关的许多技能,问题的关注点主要是语言本身,不是某个特定的包或模块。每一个问题都可以扩充为一个教程,如果可能的话。某些问题甚至会涉及多个领域。

  我之前还没有出过和这些题目一样难的面试题,如果你能轻松地回答出来的话,赶紧去找份工作吧!

  问题1

  到底什么是Python?你可以在回答中与其他技术进行对比(也鼓励这样做)。

  答案

  下面是一些关键点:

  Python是一种解释型语言。这就是说,与C语言和C的衍生语言不同,Python代码在运行之前不需要编译。其他解释型语言还包括PHP和Ruby。

  Python是动态类型语言,指的是你在声明变量时,不需要说明变量的类型。你可以直接编写类似x=111和x="I'm a string"这样的代码,程序不会报错。

  Python非常适合面向对象的编程(OOP),因为它支持通过组合(composition)与继承(inheritance)的方式定义类(class)。Python中没有访问说明符(access specifier,类似C++中的public和private),这么设计的依据是“大家都是成年人了”。

  在Python语言中,函数是第一类对象(first-class objects)。这指的是它们可以被指定给变量,函数既能返回函数类型,也可以接受函数作为输入。类(class)也是第一类对象。

  Python代码编写快,但是运行速度比编译语言通常要慢。好在Python允许加入基于C语言编写的扩展,因此我们能够优化代码,消除瓶颈,这点通常是可以实现的。numpy就是一个很好地例子,它的运行速度真的非常快,因为很多算术运算其实并不是通过Python实现的。

  Python用途非常广泛——网络应用,自动化,科学建模,大数据应用,等等。它也常被用作“胶水语言”,帮助其他语言和组件改善运行状况。

  Python让困难的事情变得容易,因此程序员可以专注于算法和数据结构的设计,而不用处理底层的细节。

  为什么提这个问题:

  如果你应聘的是一个Python开发岗位,你就应该知道这是门什么样的语言,以及它为什么这么酷。以及它哪里不好。

  问题2

  补充缺失的代码

  1. def print_directory_contents(sPath):
  2.     """
  3.     这个函数接受文件夹的名称作为输入参数,
  4.     返回该文件夹中文件的路径,
  5.     以及其包含文件夹中文件的路径。

  6.     """
  7.     # 补充代码
复制代码


  答案

  1. def print_directory_contents(sPath):
  2.     import os                                       
  3.     for sChild in os.listdir(sPath):               
  4.         sChildPath = os.path.join(sPath,sChild)
  5.         if os.path.isdir(sChildPath):
  6.             print_directory_contents(sChildPath)
  7.         else:
  8.             print sChildPath
复制代码


  特别要注意以下几点:

  命名规范要统一。如果样本代码中能够看出命名规范,遵循其已有的规范。

  递归函数需要递归并终止。确保你明白其中的原理,否则你将面临无休无止的调用栈(callstack)。

  我们使用os模块与操作系统进行交互,同时做到交互方式是可以跨平台的。你可以把代码写成sChildPath = sPath + '/' + sChild,但是这个在Windows系统上会出错。

  熟悉基础模块是非常有价值的,但是别想破脑袋都背下来,记住Google是你工作中的良师益友。

  如果你不明白代码的预期功能,就大胆提问。

  坚持KISS原则!保持简单,不过脑子就能懂!

  为什么提这个问题:

  说明面试者对与操作系统交互的基础知识

  递归真是太好用啦

  问题3

  阅读下面的代码,写出A0,A1至An的最终值。

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


  答案

  1. A0 = {'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4}
  2. A1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  3. A2 = []
  4. A3 = [1, 3, 2, 5, 4]
  5. A4 = [1, 2, 3, 4, 5]
  6. A5 = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
  7. A6 = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]]
复制代码


  为什么提这个问题:

  列表解析(list comprehension)十分节约时间,对很多人来说也是一个大的学习障碍。

  如果你读懂了这些代码,就很可能可以写下正确地值。

  其中部分代码故意写的怪怪的。因为你共事的人之中也会有怪人。

  问题4

  Python和多线程(multi-threading)。这是个好主意码?列举一些让Python代码以并行方式运行的方法。

  答案

  Python并不支持真正意义上的多线程。Python中提供了多线程包,但是如果你想通过多线程提高代码的速度,使用多线程包并不是个好主意。Python中有一个被称为Global Interpreter Lock(GIL)的东西,它会确保任何时候你的多个线程中,只有一个被执行。线程的执行速度非常之快,会让你误以为线程是并行执行的,但是实际上都是轮流执行。经过GIL这一道关卡处理,会增加执行的开销。这意味着,如果你想提高代码的运行速度,使用threading包并不是一个很好的方法。

  不过还是有很多理由促使我们使用threading包的。如果你想同时执行一些任务,而且不考虑效率问题,那么使用这个包是完全没问题的,而且也很方便。但是大部分情况下,并不是这么一回事,你会希望把多线程的部分外包给操作系统完成(通过开启多个进程),或者是某些调用你的Python代码的外部程序(例如Spark或Hadoop),又或者是你的Python代码调用的其他代码(例如,你可以在Python中调用C函数,用于处理开销较大的多线程工作)。

  为什么提这个问题

  因为GIL就是个混账东西(A-hole)。很多人花费大量的时间,试图寻找自己多线程代码中的瓶颈,直到他们明白GIL的存在。

  问题5

  你如何管理不同版本的代码?

  答案:

  版本管理!被问到这个问题的时候,你应该要表现得很兴奋,甚至告诉他们你是如何使用Git(或是其他你最喜欢的工具)追踪自己和奶奶的书信往来。我偏向于使用Git作为版本控制系统(VCS),但还有其他的选择,比如subversion(SVN)。

  为什么提这个问题:

  因为没有版本控制的代码,就像没有杯子的咖啡。有时候我们需要写一些一次性的、可以随手扔掉的脚本,这种情况下不作版本控制没关系。但是如果你面对的是大量的代码,使用版本控制系统是有利的。版本控制能够帮你追踪谁对代码库做了什么操作;发现新引入了什么bug;管理你的软件的不同版本和发行版;在团队成员中分享源代码;部署及其他自动化处理。它能让你回滚到出现问题之前的版本,单凭这点就特别棒了。还有其他的好功能。怎么一个棒字了得!

  问题6

  下面代码会输出什么:

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

  5. f(2)
  6. f(3,[3,2,1])
  7. f(3)
复制代码


  答案:

  1. [0, 1]
  2. [3, 2, 1, 0, 1, 4]
  3. [0, 1, 0, 1, 4]
复制代码


  呃?

  第一个函数调用十分明显,for循环先后将0和1添加至了空列表l中。l是变量的名字,指向内存中存储的一个列表。第二个函数调用在一块新的内存中创建了新的列表。l这时指向了新生成的列表。之后再往新列表中添加0、1、2和4。很棒吧。第三个函数调用的结果就有些奇怪了。它使用了之前内存地址中存储的旧列表。这就是为什么它的前两个元素是0和1了。

  不明白的话就试着运行下面的代码吧:

  1. l_mem = []

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

  5. print l             # [0, 1]

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

  9. print l             # [3, 2, 1, 0, 1, 4]

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

  13. print l             # [0, 1, 0, 1, 4]
复制代码


  问题7

  “猴子补丁”(monkey patching)指的是什么?这种做法好吗?

  答案:

  “猴子补丁”就是指,在函数或对象已经定义之后,再去改变它们的行为。

  举个例子:

  import datetime

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

  大部分情况下,这是种很不好的做法 - 因为函数在代码库中的行为最好是都保持一致。打“猴子补丁”的原因可能是为了测试。mock包对实现这个目的很有帮助。

  为什么提这个问题?

  答对这个问题说明你对单元测试的方法有一定了解。你如果提到要避免“猴子补丁”,可以说明你不是那种喜欢花里胡哨代码的程序员(公司里就有这种人,跟他们共事真是糟糕透了),而是更注重可维护性。还记得KISS原则码?答对这个问题还说明你明白一些Python底层运作的方式,函数实际是如何存储、调用等等。

  另外:如果你没读过mock模块的话,真的值得花时间读一读。这个模块非常有用。

  问题8

  这两个参数是什么意思:*args,**kwargs?我们为什么要使用它们?

  答案

  如果我们不确定要往函数中传入多少个参数,或者我们想往函数中以列表和元组的形式传参数时,那就使要用*args;如果我们不知道要往函数中传入多少个关键词参数,或者想传入字典的值作为关键词参数时,那就要使用**kwargs。args和kwargs这两个标识符是约定俗成的用法,你当然还可以用*bob和**billy,但是这样就并不太妥。

  下面是具体的示例:

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

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

  5. f()
  6. f(1,2,3)                    # (1, 2, 3) {}
  7. f(1,2,3,"groovy")           # (1, 2, 3, 'groovy') {}
  8. f(a=1,b=2,c=3)              # () {'a': 1, 'c': 3, 'b': 2}
  9. f(a=1,b=2,c=3,zzz="hi")     # () {'a': 1, 'c': 3, 'b': 2, 'zzz': 'hi'}
  10. f(1,2,3,a=1,b=2,c=3)        # (1, 2, 3) {'a': 1, 'c': 3, 'b': 2}

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

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

  17. f2(1,2,3)                       # 1 2 (3,) {}
  18. f2(1,2,3,"groovy")              # 1 2 (3, 'groovy') {}
  19. f2(arg1=1,arg2=2,c=3)           # 1 2 () {'c': 3}
  20. f2(arg1=1,arg2=2,c=3,zzz="hi")  # 1 2 () {'c': 3, 'zzz': 'hi'}
  21. f2(1,2,3,a=1,b=2,c=3)           # 1 2 (3,) {'a': 1, 'c': 3, 'b': 2}

  22. f2(*l,**d)                   # 1 2 (3,) {'a': 7, 'c': 9, 'b': 8}
  23. f2(*t,**d)                   # 4 5 (6,) {'a': 7, 'c': 9, 'b': 8}
  24. f2(1,2,*t)                   # 1 2 (4, 5, 6) {}
  25. f2(1,1,q="winning",**d)      # 1 1 () {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
  26. f2(1,2,*t,q="winning",**d)   # 1 2 (4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
复制代码


  为什么提这个问题?

  有时候,我们需要往函数中传入未知个数的参数或关键词参数。有时候,我们也希望把参数或关键词参数储存起来,以备以后使用。有时候,仅仅是为了节省时间。

  问题9

  下面这些是什么意思:@classmethod, @staticmethod, @property?

  回答背景知识

  这些都是装饰器(decorator)。装饰器是一种特殊的函数,要么接受函数作为输入参数,并返回一个函数,要么接受一个类作为输入参数,并返回一个类。@标记是语法糖(syntactic sugar),可以让你以简单易读得方式装饰目标对象。

  1. @my_decorator
  2. def my_func(stuff):
  3.     do_things
  4. Is equivalent to

  5. def my_func(stuff):
  6.     do_things

  7. my_func = my_decorator(my_func)
复制代码


  你可以在本网站上找到介绍装饰器工作原理的教材。

  真正的答案

  1. @classmethod, @staticmethod和@property这三个装饰器的使用对象是在类中定义的函数。下面的例子展示了它们的用法和行为:

  2. class MyClass(object):
  3.     def __init__(self):
  4.         self._some_property = "properties are nice"
  5.         self._some_other_property = "VERY nice"
  6.     def normal_method(*args,**kwargs):
  7.         print "calling normal_method({0},{1})".format(args,kwargs)
  8.     @classmethod
  9.     def class_method(*args,**kwargs):
  10.         print "calling class_method({0},{1})".format(args,kwargs)
  11.     @staticmethod
  12.     def static_method(*args,**kwargs):
  13.         print "calling static_method({0},{1})".format(args,kwargs)
  14.     @property
  15.     def some_property(self,*args,**kwargs):
  16.         print "calling some_property getter({0},{1},{2})".format(self,args,kwargs)
  17.         return self._some_property
  18.     @some_property.setter
  19.     def some_property(self,*args,**kwargs):
  20.         print "calling some_property setter({0},{1},{2})".format(self,args,kwargs)
  21.         self._some_property = args[0]
  22.     @property
  23.     def some_other_property(self,*args,**kwargs):
  24.         print "calling some_other_property getter({0},{1},{2})".format(self,args,kwargs)
  25.         return self._some_other_property

  26. o = MyClass()
  27. # 未装饰的方法还是正常的行为方式,需要当前的类实例(self)作为第一个参数。

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

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

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

  34. # 类方法的第一个参数永远是该类

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

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

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

  41. # 静态方法(static method)中除了你调用时传入的参数以外,没有其他的参数。

  42. o.static_method
  43. # <function static_method at 0x7fdd25375848>

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

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

  48. # @property是实现getter和setter方法的一种方式。直接调用它们是错误的。
  49. # “只读”属性可以通过只定义getter方法,不定义setter方法实现。

  50. o.some_property
  51. # 调用some_property的getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{})
  52. # 'properties are nice'
  53. # “属性”是很好的功能

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

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

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

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

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

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

  76. o.some_other_property
  77. # calling some_other_property getter(<__main__.MyClass object at 0x7fb2b7077890>,(),{})
复制代码


  问题10

  阅读下面的代码,它的输出结果是什么?

  1. class A(object):
  2.     def go(self):
  3.         print "go A go!"
  4.     def stop(self):
  5.         print "stop A stop!"
  6.     def pause(self):
  7.         raise Exception("Not Implemented")

  8. class B(A):
  9.     def go(self):
  10.         super(B, self).go()
  11.         print "go B go!"

  12. class C(A):
  13.     def go(self):
  14.         super(C, self).go()
  15.         print "go C go!"
  16.     def stop(self):
  17.         super(C, self).stop()
  18.         print "stop C stop!"

  19. class D(B,C):
  20.     def go(self):
  21.         super(D, self).go()
  22.         print "go D go!"
  23.     def stop(self):
  24.         super(D, self).stop()
  25.         print "stop D stop!"
  26.     def pause(self):
  27.         print "wait D wait!"

  28. class E(B,C): pass

  29. a = A()
  30. b = B()
  31. c = C()
  32. d = D()
  33. e = E()

  34. # 说明下列代码的输出结果

  35. a.go()
  36. b.go()
  37. c.go()
  38. d.go()
  39. e.go()

  40. a.stop()
  41. b.stop()
  42. c.stop()
  43. d.stop()
  44. e.stop()

  45. a.pause()
  46. b.pause()
  47. c.pause()
  48. d.pause()
  49. e.pause()
复制代码


  答案

  输出结果以注释的形式表示:

  1. a.go()
  2. # go A go!

  3. b.go()
  4. # go A go!
  5. # go B go!

  6. c.go()
  7. # go A go!
  8. # go C go!

  9. d.go()
  10. # go A go!
  11. # go C go!
  12. # go B go!
  13. # go D go!

  14. e.go()
  15. # go A go!
  16. # go C go!
  17. # go B go!

  18. a.stop()
  19. # stop A stop!

  20. b.stop()
  21. # stop A stop!

  22. c.stop()
  23. # stop A stop!
  24. # stop C stop!

  25. d.stop()
  26. # stop A stop!
  27. # stop C stop!
  28. # stop D stop!

  29. e.stop()
  30. # stop A stop!

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

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

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

  37. d.pause()
  38. # wait D wait!

  39. e.pause()
  40. # ...Exception: Not Implemented
复制代码


  为什么提这个问题?

  因为面向对象的编程真的真的很重要。不骗你。答对这道问题说明你理解了继承和Python中super函数的用法。

  问题11

  阅读下面的代码,它的输出结果是什么?

  1. class Node(object):
  2.     def __init__(self,sName):
  3.         self._lChildren = []
  4.         self.sName = sName
  5.     def __repr__(self):
  6.         return "<Node '{}'>".format(self.sName)
  7.     def append(self,*args,**kwargs):
  8.         self._lChildren.append(*args,**kwargs)
  9.     def print_all_1(self):
  10.         print self
  11.         for oChild in self._lChildren:
  12.             oChild.print_all_1()
  13.     def print_all_2(self):
  14.         def gen(o):
  15.             lAll = [o,]
  16.             while lAll:
  17.                 oNext = lAll.pop(0)
  18.                 lAll.extend(oNext._lChildren)
  19.                 yield oNext
  20.         for oNode in gen(self):
  21.             print oNode

  22. oRoot = Node("root")
  23. oChild1 = Node("child1")
  24. oChild2 = Node("child2")
  25. oChild3 = Node("child3")
  26. oChild4 = Node("child4")
  27. oChild5 = Node("child5")
  28. oChild6 = Node("child6")
  29. oChild7 = Node("child7")
  30. oChild8 = Node("child8")
  31. oChild9 = Node("child9")
  32. oChild10 = Node("child10")

  33. oRoot.append(oChild1)
  34. oRoot.append(oChild2)
  35. oRoot.append(oChild3)
  36. oChild1.append(oChild4)
  37. oChild1.append(oChild5)
  38. oChild2.append(oChild6)
  39. oChild4.append(oChild7)
  40. oChild3.append(oChild8)
  41. oChild3.append(oChild9)
  42. oChild6.append(oChild10)

  43. # 说明下面代码的输出结果

  44. oRoot.print_all_1()
  45. oRoot.print_all_2()
复制代码


  答案

  oRoot.print_all_1()会打印下面的结果:

  1. <Node 'root'>
  2. <Node 'child1'>
  3. <Node 'child4'>
  4. <Node 'child7'>
  5. <Node 'child5'>
  6. <Node 'child2'>
  7. <Node 'child6'>
  8. <Node 'child10'>
  9. <Node 'child3'>
  10. <Node 'child8'>
  11. <Node 'child9'>
复制代码


  oRoot.print_all_1()会打印下面的结果:

  1. <Node 'root'>
  2. <Node 'child1'>
  3. <Node 'child2'>
  4. <Node 'child3'>
  5. <Node 'child4'>
  6. <Node 'child5'>
  7. <Node 'child6'>
  8. <Node 'child8'>
  9. <Node 'child9'>
  10. <Node 'child7'>
  11. <Node 'child10'>
复制代码


  为什么提这个问题?

  因为对象的精髓就在于组合(composition)与对象构造(object construction)。对象需要有组合成分构成,而且得以某种方式初始化。这里也涉及到递归和生成器(generator)的使用。

  生成器是很棒的数据类型。你可以只通过构造一个很长的列表,然后打印列表的内容,就可以取得与print_all_2类似的功能。生成器还有一个好处,就是不用占据很多内存。

  有一点还值得指出,就是print_all_1会以深度优先(depth-first)的方式遍历树(tree),而print_all_2则是宽度优先(width-first)。有时候,一种遍历方式比另一种更合适。但这要看你的应用的具体情况。

  问题12

  简要描述Python的垃圾回收机制(garbage collection)。

  答案

  这里能说的很多。你应该提到下面几个主要的点:

  Python在内存中存储了每个对象的引用计数(reference count)。如果计数值变成0,那么相应的对象就会小时,分配给该对象的内存就会释放出来用作他用。

  偶尔也会出现引用循环(reference cycle)。垃圾回收器会定时寻找这个循环,并将其回收。举个例子,假设有两个对象o1和o2,而且符合o1.x == o2和o2.x == o1这两个条件。如果o1和o2没有其他代码引用,那么它们就不应该继续存在。但它们的引用计数都是1。

  Python中使用了某些启发式算法(heuristics)来加速垃圾回收。例如,越晚创建的对象更有可能被回收。对象被创建之后,垃圾回收器会分配它们所属的代(generation)。每个对象都会被分配一个代,而被分配更年轻代的对象是优先被处理的。

  问题13

  将下面的函数按照执行效率高低排序。它们都接受由0至1之间的数字构成的列表作为输入。这个列表可以很长。一个输入列表的示例如下:[random.random() for i in range(100000)]。你如何证明自己的答案是正确的。

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

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

  9. def f3(lIn):
  10.     l1 = [i*i for i in lIn]
  11.     l2 = sorted(l1)
  12.     return [i for i in l1 if i<(0.5*0.5)]
复制代码


  答案

  按执行效率从高到低排列:f2、f1和f3。要证明这个答案是对的,你应该知道如何分析自己代码的性能。Python中有一个很好的程序分析包,可以满足这个需求。

  1. import cProfile
  2. lIn = [random.random() for i in range(100000)]
  3. cProfile.run('f1(lIn)')
  4. cProfile.run('f2(lIn)')
  5. cProfile.run('f3(lIn)')
复制代码


  为了向大家进行完整地说明,下面我们给出上述分析代码的输出结果:

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

  3.    Ordered by: standard name

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


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

  11.    Ordered by: standard name

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


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

  19.    Ordered by: standard name

  20.    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  21.         1    0.016    0.016    0.054    0.054 <stdin>:1(f3)
  22.         1    0.001    0.001    0.055    0.055 <string>:1(<module>)
  23.         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  24.         1    0.038    0.038    0.038    0.038 {sorted}
复制代码


  为什么提这个问题?

  定位并避免代码瓶颈是非常有价值的技能。想要编写许多高效的代码,最终都要回答常识上来——在上面的例子中,如果列表较小的话,很明显是先进行排序更快,因此如果你可以在排序前先进行筛选,那通常都是比较好的做法。其他不显而易见的问题仍然可以通过恰当的工具来定位。因此了解这些工具是有好处的。

  问题14

  你有过失败的经历吗?

  错误的答案

  我从来没有失败过!

  为什么提这个问题?

  恰当地回答这个问题说明你用于承认错误,为自己的错误负责,并且能够从错误中学习。如果你想变得对别人有帮助的话,所有这些都是特别重要的。如果你真的是个完人,那就太糟了,回答这个问题的时候你可能都有点创意了。

  问题15

  你有实施过个人项目吗?

  真的?

  如果做过个人项目,这说明从更新自己的技能水平方面来看,你愿意比最低要求付出更多的努力。如果你有维护的个人项目,工作之外也坚持编码,那么你的雇主就更可能把你视作为会增值的资产。即使他们不问这个问题,我也认为谈谈这个话题很有帮助。

  结语

  我给出的这些问题时,有意涉及了多个领域。而且答案也是特意写的较为啰嗦。在编程面试中,你需要展示你对语言的理解,如果你能简要地说清楚,那请务必那样做。我尽量在答案中提供了足够的信息,即使是你之前从来没有了解过这些领域,你也可以从答案中学到些东西。我希望本文能够帮助你找到满意的工作。

  加油!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部