所面临的挑战
作为一个粗略的数量级,吉尔斯·托马斯(联合创始人 PythonAnyw在这里)估计, 在1之间.8和4.全世界有300万Python开发人员.
那么,找到一个Python开发人员会有多难呢? 好吧, 如果我们的目标只是找到一个可以在简历上合理列出Python的人,那么这一点都不难. 但如果我们的目标是找到一个真正掌握了Python语言的细微差别和强大功能的Python大师, 那么,这无疑是一个巨大的挑战.
首先,我们需要一个高效的招聘流程,正如我们在文章中所描述的那样 在寻找精英少数-发现和雇用最好的开发人员在行业. 这样的过程可以用有针对性的问题和技术进行扩充, 比如这里提供的那些, 专门用于从大量具有一定Python经验的候选人中寻找Python大师.
蟒蛇大师或草丛中的蛇?

所以你已经找到了一个强大的Python 网络开发人员. 你如何确定他或她是, 事实上, 在你想要雇佣的前1%的精英候选人中? 然而,没有什么魔术或万无一失的技巧, 当然,你可以提出一些问题来帮助判断应聘者对语言知识的深度和复杂程度. 下面提供了这些问题的简要抽样.
不过,重要的是要记住,这些示例问题只是作为一个指导. 并不是每一位值得雇佣的“A”候选人都能正确回答所有问题, 也不能保证回答所有这些问题就能得到一个“A”候选人. 说到底,招聘既是一门科学,也是一门艺术.
杂草中的巨蟒…
当然,最好的开发人员不会浪费时间将那些可以在语言规范或API文档中轻松找到的内容提交到内存中, 任何编程语言都有一些任何专家都能掌握的关键特性和功能, 而且应该, 要精通. 下面是一些特定于python的例子:
问:为什么使用函数装饰器? 举个例子.
装饰器本质上是一个可调用的Python对象,用于修改或扩展函数或类定义. 装饰器的优点之一是,一个装饰器定义可以应用于多个函数(或类). 因此,许多需要大量样板(或者更糟的是冗余)的decorator都可以完成!)代码. 瓶例如,使用装饰器作为向网络应用程序添加新端点的机制. 装饰器的一些更常见的用法包括添加同步, 类型执行, 日志记录, 或类或函数的前置/后置条件.
Q:什么是λ表达式,列表推导式和生成器表达式? 它们的优点和合适的用途是什么?
Lambda表达式 创建单行匿名函数是一种速记技术吗. 他们的简单, 内联特性通常(尽管不总是)比正式函数声明更容易读和更简洁的代码. 另一方面, 它们简洁的内联特性, 通过定义, 这极大地限制了它们的能力和适用性. 匿名和内联, 在代码的多个位置使用相同λ函数的唯一方法是冗余地指定它.
列表理解 为创建列表提供简洁的语法. 列表推导式通常用于创建这样的列表:每个元素都是对另一个序列或可迭代对象的每个成员进行某些操作的结果. 它们还可以用来创建成员满足特定条件的那些元素的子序列. 在Python中,列表推导式提供了使用内置函数的另一种选择 map ()
和 filter ()
功能.
由于λ表达式和列表推导式的应用可能会重叠, 在什么时候、在什么地方使用和使用. 另一个. 有一点要记住, 虽然, 列表推导式的执行速度是否比类似的解决方案要快 map
和 λ
(一些快速测试产生了大约10%的性能差异). 这是因为调用λ函数会创建一个新的堆栈框架,而列表推导式中的表达式不需要这样做.
生成器表达式 在语法和功能上是否与列表推导式相似,但两者的操作方式和, 相应的, 当它们应该被使用时. 简而言之, 对生成器表达式或列表推导式进行迭代实质上也会做同样的事情, 但是列表推导式将首先在内存中创建整个列表,而生成器表达式将根据需要动态地创建条目. 因此,生成器表达式可以用于非常大(甚至无限)的序列和它们的惰性(i.e.值的生成可以提高性能和降低内存使用. 值得注意的是, 虽然, 标准的Python列表方法可以用于列表推导式的结果, 但不是直接在生成器表达式上.
Q:考虑以下两种初始化数组和结果数组的方法. 产生的数组有什么不同?为什么应该使用一种初始化方法. 另一个?
>>> # INITIALIZING AN ARRAY -- METHOD 1
...
>>> x = [[1,2,3,4]] * 3
>>> x
[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
>>>
>>>
>>> # INITIALIZING AN ARRAY -- METHOD 2
...
>>> y = [[1,2,3,4] for _ in range(3)]
>>> y
[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
>>>
>>> # WHICH METHOD SHOULD YOU USE AND WHY?
虽然这两种方法乍一看似乎产生了相同的结果, 两者之间存在着极其显著的差异. 方法2生产, 如你所料, 由3个元素组成的数组, 每个元素本身都是一个独立的4元素数组. 然而,在方法1中,数组的成员都指向同一个对象. 这很可能会导致如下所示的意想不到和不希望看到的行为.
>>> # MODIFYING THE x ARRAY FROM THE PRIOR CODE SNIPPET:
>>> x[0][3] = 99
>>> x
[[1, 2, 3, 99], [1, 2, 3, 99], [1, 2, 3, 99]]
>>> # UH-OH, DON’T THINK YOU WANTED THAT TO HAPPEN!
...
>>>
>>> # MODIFYING THE y ARRAY FROM THE PRIOR CODE SNIPPET:
>>> y[0][3] = 99
>>> y
[[1, 2, 3, 99], [1, 2, 3, 4], [1, 2, 3, 4]]
>>> # THAT’S MORE LIKE WHAT YOU EXPECTED!
...
Q:下面的第二个append()语句将输出什么?
>>> def append(列表=[]):
... #添加列表的长度
... 列表.追加(len(列表)
... 返回列表
...
>>> append(['a','b'])
['a', 'b', 2]
>>>
>>> append() # calling with no arg uses default 列表 value of []
[0]
>>>
>>> append() # but what happens when we AGAIN call append with no arg?
当函数参数的默认值是一个表达式时, 表达式只计算一次, 不是每次函数被调用. 因此, 一旦列表参数初始化为空数组, 后续不指定任何参数的追加调用将继续使用列表最初初始化时的相同数组. 因此,这将产生以下可能意想不到的行为:
>>> append() # first call with no arg uses default 列表 value of []
[0]
>>> append() # but then look what happens...
[0, 1]
>>> append() # successive calls keep extending the 相同 default 列表!
[0, 1, 2]
>>> append() # 等等, 等等, 等等...
[0, 1, 2, 3]
问:如何修改“append”方法的实现,以避免那里描述的不良行为?
下面是append方法的另一种实现,它是避免前面问题答案中描述的不良行为的几种方法之一:
>>> def append(列表=None):
... 如果列表为None:
列表= []
#添加列表的长度
... 列表.追加(len(列表)
... 返回列表
...
>>> append()
[0]
>>> append()
[0]
Q:如何用一行Python代码交换两个变量的值?
考虑这个简单的例子:
>>> x = 'X'
>>> y = 'Y'
在许多其他语言中,交换x和y的值需要你做以下操作:
>>> tmp = x
>>> x = y
>>> y = tmp
>>> x, y
('Y', 'X')
但在Python中, 这使得用一行代码进行交换成为可能(多亏了隐式元组打包和解打包),如下所示:
>>> x,y = y,x
>>> x,y
('Y', 'X')
问:下面最后一个语句将打印出什么?
>>> f列表= []
>>> for i in range(3):
... 弗利斯特雨.追加(λ:我)
...
>>> [f() for f in 弗利斯特雨] # what will this 打印 out?
在Python的任何闭包中,变量都是通过名称绑定的. 因此,上面这行代码将输出以下内容:
[2, 2, 2]
这大概不是上面代码的作者想要的!
A 解决方案 is to either create a separate 函数 or to pass the args by name; e.g.:
>>> f列表= []
>>> for i in range(3):
... 弗利斯特雨.添加(λ I = I: I)
...
>>> [f() for f in 弗利斯特雨]
[0, 1, 2]
Q: Python 2和Python 3之间的关键区别是什么?
尽管在这一点上Python 2被正式认为是遗留的, 它的使用仍然非常广泛,对于开发人员来说,识别Python 2和Python 3之间的区别是很重要的.
以下是开发者应该注意的一些关键区别:
- 文本和数据而不是Unicode和8位字符串. Python 3.0使用文本和(二进制)数据的概念,而不是Unicode字符串和8位字符串. 这样做的最大后果是Python 3中混合文本和数据的任何尝试.0将引发TypeError(以安全地组合这两个, 您必须解码字节或编码Unicode, 但你需要知道正确的编码, e.g. utf - 8)
- 这解决了naïve Python程序员长期存在的一个陷阱. 在Python中2, 如果字符串碰巧只包含7位(ASCII)字节,那么混合Unicode和8位数据就可以工作, 但如果它包含非ascii值,你会得到UnicodeDecodeError. 此外, 例外情况将发生在组合点, 而不是在非ascii字符被放入str对象时. 对于初学Python的程序员来说,这种行为经常引起困惑和恐慌.
- 打印功能. 的
打印
语句已被替换为 打印 ()
函数
- xrange——buh-bye.
xrange ()
不再存在(range ()
现在像 xrange ()
过去的行为,除了它适用于任意大小的值)
- API的变化:
邮政编码()
, map ()
和 filter ()
现在所有返回的都是迭代器而不是列表
dict.键()
, dict.项目()
和 dict.值()
现在返回“视图”而不是列表
dict.iterkeys ()
, dict.iteritems ()
和 dict.itervalues ()
不再支持
- 比较运算符. 排序比较运算符(
<
, <=
, >=
, >
)现在提出 TypeError
当操作数没有有意义的自然顺序时异常. 这种后果的一些例子包括:
- 表达式如
1 < ''
, 0 > None
or len <= len
不再有效
None < None
现在提出了一个 TypeError
而不是返回 假
- 对异构列表进行排序不再有意义——所有元素必须相互比较
关于Python 2和Python 3之间区别的更多细节可以参考 在这里.
Q: Python是解释的还是编译的?
正如 为什么有这么多蟒蛇?坦白地说,这是一个有点棘手的问题,因为它是畸形的. Python本身不过是一个接口定义(任何语言规范都是如此),它有多种实现. 相应的, the question of whether “Python” is interpreted or compiled does not apply to the Python language itself; rather, 它适用于Python规范的每个特定实现.
使这个问题的答案更加复杂的事实是, CPython(最常见的Python实现), 答案是“两者都有”. 具体来说,CPython首先编译代码,然后解释代码. 更准确地说,它不是预先编译为本机机器码,而是字节码. 机器码当然更快,但字节码更便于移植和安全. 然后在CPython的情况下解释字节码(或者在运行时解释并编译为优化的机器码,在 PyPy).
Q: CPython的其他实现有哪些? 什么时候以及为什么要使用它们?
一个比较突出的替代实现是 Jython,一个用Java编写的Python实现,利用Java虚拟机(JVM). 而CPython生成字节码在CPython VM上运行, Jython生成在JVM上运行的Java字节码.
另一个原因是 IronPython,用c#编写,目标是 .网络堆栈. IronPython运行在微软的公共语言运行时(CLR)上.
正如在 为什么有这么多蟒蛇?, 完全有可能在不接触Python的非cpython实现的情况下生存下来, 但换工作也有好处, 其中大部分依赖于你的技术堆栈.
另一个值得注意的替代实现是 PyPy 其主要特点包括:
- 速度. 由于它的即时(JIT)编译器,Python程序通常在PyPy上运行得更快.
- 内存使用情况. 大, 耗用内存的Python程序在PyPy中占用的空间可能比在CPython中要少.
- 兼容性. PyPy与现有python代码高度兼容. 它支持 cffi 并且可以运行流行的Python库,如 扭曲的 和 Django.
- 沙盒. PyPy提供了以完全安全的方式运行不受信任代码的能力.
- Stackless模式. PyPy默认情况下支持无堆叠模式, 为大规模并发提供微线程.
问:你用Python进行单元测试的方法是什么?
这个问题最基本的答案围绕着Python unittest 测试框架. 基本上, 如果考生在回答这个问题的时候没有提到unittest, 这应该是一个巨大的危险信号.
Unittest支持测试自动化, 共享测试的设置和关闭代码, 将测试聚合到集合中, 测试与报告框架的独立性. unittest模块提供了一些类,可以方便地为一组测试支持这些质量.
假设候选人确实提到了unittest(如果他们没有, 你可能只想马上结束面试!), you should also ask them to describe the key elements of the unittest framework; namely, 测试装置, 测试用例, 测试套件和测试运行程序.
unittest框架最近增加的一个功能是 模拟. Mock允许您用Mock对象替换被测试系统的部分,并断言如何使用它们. 模拟现在是Python标准库的一部分,可以作为unittest使用.Python 3中的模拟.3起.
模拟的价值和力量在 Python中Mocking入门. 如前所述在其中, 系统调用是模拟的主要候选对象:是否编写脚本弹出CD驱动器, 一个网络服务器,从/tmp中删除过时的缓存文件, 或者绑定到TCP端口的套接字服务器, 这些调用在单元测试上下文中都具有不希望出现的副作用. 类似的, 保持单元测试的效率和性能意味着在自动测试运行中尽可能多地保留“慢码”, 即文件系统和网络访问.
[注:这个问题是针对有Java经验的Python开发人员的.]
问:使用Python和Python编写代码时,需要牢记的关键区别是什么. Java?
免责声明# 1. Java和Python之间的差异有很多,这可能是一个值得单独(冗长)发表的主题. 下面是两种语言之间的一些关键差异的一个简单例子.
免责声明# 2. 这里的目的不是为了引发一场关于Python和Python的优点的宗教斗争. Java(尽管这可能很有趣!). 而, 这个问题实际上只是为了看看开发人员在多大程度上理解这两种语言之间的一些实际差异. 因此,下面的列表有意避免从编程效率的角度讨论Python相对于Java的可论证优势.
记住上面的两个免责声明, 下面是一些在Python和Python编写代码时需要记住的关键区别的示例. Java:
- 动态类型vs静态类型. 这两种语言之间最大的区别之一是,Java被限制为静态类型,而Python支持变量的动态类型.
- 静态vs. 类方法. Java中的静态方法不会转换为Python类方法.
- 在Python中, 调用类方法需要额外分配内存,而调用静态方法或函数则不需要.
- 在Java中,带点的名称(例如.g., foo.酒吧.方法)由编译器查找, 所以在运行时,你有多少个并不重要. 然而,在Python中,查找发生在运行时,因此“每个点都算数”。.
- 方法重载. 然而,Java需要明确指定多个具有不同签名的同名函数, 在Python中,如果调用者没有指定,也可以使用包含默认值的可选参数的单一函数来实现同样的功能.
- 单和. 双引号. 而单引号的使用与. 在Java中,双引号是有意义的, 它们可以在Python中互换使用(但是不能, 它不允许开始 相同 带双引号的字符串,并试图以单引号结束,反之亦然!).
- getter和setter(不是!). Getters 和 setters 在Python中 are superfluous; rather, 你应该使用内置的“property”(这就是它的用途!). 在Python中,getter和setter都是在浪费CPU和程序员的时间.
- 类是可选的. 而Java要求每个函数都定义在一个封闭类定义的上下文中, Python没有这样的要求.
- 缩进问题…… 在Python中. 这让许多Python编程新手感到困惑.
大局
Python的专业知识远远超出了该语言的技术细节. Python专家将对Python的优点和局限性有深入的理解和欣赏. 相应的, 以下是一些示例问题,可以帮助评估应聘者在这方面的专业技能:
问:Python特别适合做什么? 什么时候使用Python是项目的“正确选择”?
虽然好恶是因人而异的, 一个“称职的”开发人员会强调Python语言中通常被认为是有优势的特性(这也有助于回答Python“特别适合”的问题)。. 这个问题的一些更常见的正确答案包括:
- 易于使用和易于重构, 这要归功于Python语法的灵活性,这使得它对于快速原型设计特别有用.
- 更紧凑的代码, 再次感谢Python的语法, 以及大量功能丰富的Python库(与大多数Python语言实现一起免费发布).
- 一种动态类型和强类型语言, 提供了罕见的代码灵活性组合,同时避免了令人讨厌的隐式类型转换bug.
- 它是免费和开源的! 我们还需要多说吗?
关于什么时候使用Python是项目的“正确选择”的问题, 完整的答案还取决于与语言本身正交的许多问题, 比如先前的技术投资, 团队的技能组合, 等等. 尽管上面的问题暗示了对严格的技术答案的兴趣, 在面试中提出这些额外问题的开发者总是能够在我面前“获得更多分数”,因为这表明他们意识到了这一点, 和敏感, “更大的图景”(i.e.,不仅仅是使用的技术). 相反, Python永远是正确的选择,这是开发人员缺乏经验的明显标志.
问:Python语言的一些缺点是什么?
对于初学者来说, 如果你精通一门语言, 你知道它的缺点, 所以诸如“我没有什么不喜欢的”或“它没有缺点”这样的回答确实很能说明问题.
这个问题的两个最常见的有效答案(绝不是一个详尽的列表)是:
- 全局解释器锁(GIL). CPython(最常见的Python实现)不是完全线程安全的. 以支持多线程的Python程序, CPython提供了一个全局锁,当前线程必须持有该锁才能安全地访问Python对象. 作为一个结果, 不管有多少线程或处理器, 在任何给定的时间,只有一个线程被执行. 相比之下,值得注意的是PyPy实现 在本文前面讨论过 提供无堆叠模式,支持大规模并发的微线程.
- 执行速度. Python可能比编译语言慢,因为它是解释的. (好吧,. 看到我们的 前面的讨论中 关于这个主题.)
总结
本文提出的问题和技巧对于识别真正的Python开发大师非常有价值. 我们希望您在寻找Python软件开发人员中少数精英的过程中,能够发现它们是一个有用的基础,可以“分清是非”. 然而,重要的是要记住,这些只是作为工具,纳入你的整体招聘工具箱和战略的更大的背景.
和, 对于那些希望学习如何捕捉爬行动物的人来说,这篇指南读错了(对不起,伙计, 错误的蟒蛇!),我们建议你去看看佛罗里达州的野生动物基金会 Python的挑战.