python面试

本文围绕Python展开,涵盖函数装饰器、lambda表达式、数组初始化等常见问题。介绍了Python 2和3的差异,如文本数据概念、打印功能等。还提及Python的解释编译情况、替代实现,以及Python与Java编码的区别,分析了Python的适用场景和缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问:为什么要使用函数装饰器?举个例子。

装饰器本质上是一个可调用的Python对象,用于修改或扩展函数或类定义。装饰器的优点之一是单个装饰器定义可以应用于多个功能(或类)。因此,装饰器可以实现很多,否则需要大量的样板(甚至更糟糕的冗余!)代码。 例如,Flask使用装饰器作为向Web应用程序添加新端点的机制。装饰器的一些更常见用途的示例包括向类或函数添加同步,类型实施,日志记录或前/后条件。

问:什么是lambda表达式,列表推导和生成器表达式?每个的优点和适当用途是什么?

Lambda表达式是一种用于创建单行匿名函数的简写技术。它们简单,内联的特性 - 尽管并非总是如此 - 导致代码比正式函数声明的替代方案更易读和简洁。另一方面,根据定义,他们简洁的内联性质极大地限制了他们的能力和适用性。作为匿名和内联,在代码中的多个位置使用相同lambda函数的唯一方法是冗余地指定它。

列表推导提供了创建列表的简明语法。列表推导通常用于制作列表,其中每个元素是应用于另一个序列或可迭代的每个成员的一些操作的结果。它们还可用于创建其成员满足特定条件的元素的子序列。在Python中,列表推导提供了使用内置函数map()和filter()函数的替代方法。

由于lambda表达式和列表推导的应用使用可能会重叠,因此对于何时何地使用其中一个与另一个相比,意见差别很大。有一点要记住,虽然是一个列表理解的执行速度比采用同类解决方案有所加快map和lambda(一些快速测试产生了大约10%的性能差异)。这是因为调用lambda函数会创建一个新的堆栈帧,而列表推导中的表达式将在不进行此操作的情况下进行计算。

生成器表达式在语法和功能上类似于列表推导,但两者的运行方式之间存在一些相当显着的差异,因此,应该使用每种方式。简而言之,迭代生成器表达式或列表推导将基本上做同样的事情,但列表推导将首先在内存中创建整个列表,而生成器表达式将根据需要动态创建项目。因此,生成器表达式可以用于非常大(甚至无限)的序列,并且它们的延迟(即按需)生成值会导致性能提高和内存使用率降低。但值得注意的是,标准Python列表方法可以用于列表推导的结果,但不能直接用于生成器表达式的结果。

问:考虑以下两种方法来初始化数组和将产生的数组。结果数组将如何不同?为什么要使用一种初始化方法而不是另一种?

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!


问:下面的第二个附录()声明将打印出什么?

def append(list=[]):
… # append the length of a list to the list
… list.append(len(list))
… return list

append([‘a’,‘b’])
[‘a’, ‘b’, 2]

append() # calling with no arg uses default list value of []
[0]

append() # but what happens when we AGAIN call append with no arg?
当函数参数的默认值是表达式时,表达式仅计算一次,而不是每次调用函数时。因此,一旦list参数初始化为空数组,后续调用append而不指定任何参数将继续使用最初初始化列表的同一数组。因此,这将产生以下(可能是意外的)行为:

append() # first call with no arg uses default list value of []
[0]

append() # but then look what happens…
[0, 1]

append() # successive calls keep extending the same default list!
[0, 1, 2]

append() # and so on, and so on, and so on…
[0, 1, 2, 3]
问:如何修改上一个问题中’append’方法的实现,以避免在那里描述的不良行为?

append方法的以下替代实现将是避免上一个问题的答案中描述的不良行为的多种方法之一:

def append(list=None):
… if list is None:
list = []

    # append the length of a list to the list

… list.append(len(list))
… return list

append()
[0]

append()
[0]
问:如何用一行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’)
问:下面的陈述将打印出什么?

flist = []
for i in range(3):
… flist.append(lambda: i)

[f() for f in flist] # what will this print out?
在Python的任何闭包中,变量都是按名称绑定的。因此,上面的代码行将打印出以下内容:

[2, 2, 2]
大概不是上面代码的作者想要的!

甲解决方法是创建一个单独的函数或通过按名称ARGS; 例如:

flist = []
for i in range(3):
… flist.append(lambda i = i : i)

[f() for f in flist]
[0, 1, 2]
问:Python 2和3之间的主要区别是什么?

虽然Python 2在这一点上被正式认为是遗留的,但它的使用仍然非常广泛,这对于开发人员识别Python 2和3之间的差异非常重要。

以下是开发人员应注意的一些主要差异:

文本和数据而不是Unicode和8位字符串。 Python 3.0使用文本和(二进制)数据的概念而不是Unicode字符串和8位字符串。最大的分歧是,在Python 3.0中混合文本和数据的任何尝试都会引发TypeError(要安全地组合两者,你必须解码字节或编码Unicode,但你需要知道正确的编码,例如UTF-8)
这解决了天真的Python程序员长期存在的陷阱。在Python 2中,如果字符串恰好只包含7位(ASCII)字节,则混合使用Unicode和8位数据,但如果包含非ASCII值,则会得到UnicodeDecodeError。此外,异常将发生在组合点,而不是在非ASCII字符放入str对象的位置。对于新手Python程序员来说,这种行为是混乱和惊慌的常见原因。
打印功能。 该print声明已被替换为print()函数
xrange - buh-bye。 xrange()不再存在(range()现在表现得像xrange()以前的行为,除了它适用于任意大小的值)
API更改:
zip(),map()而filter()现在都返回迭代器而不是列表
dict.keys(),dict.items()而dict.values()现在回到“意见”,而不是名单
dict.iterkeys(),dict.iteritems()而dict.itervalues()不再支持
比较运算符。 排序比较操作符(<,<=,>=,>)现在提出一个TypeError例外,当操作数没有意义的自然顺序。其中一些例子包括:
这样的表达式1 < ‘’,0 > None或者len <= len不再有效
None < None现在提出一个TypeError而不是返回False
对异构列表进行排序不再有意义 - 所有元素必须相互比较
有关Python 2和3之间差异的更多详细信息,请参见此处。

问:Python是解释还是编译?

如为什么有这么多蟒蛇?坦率地说,这是一个棘手的问题,因为它是畸形的。Python本身只不过是一个接口定义(对于任何语言规范都是如此),其中有多个实现。因此,解释或编译“Python”的问题不适用于Python语言本身; 相反,它适用于Python规范的每个特定实现。

这个问题的答案进一步复杂化的是,在CPython(最常见的Python实现)的情况下,答案实际上是“两者兼而有之”。具体来说,使用CPython,首先编译代码然后进行解释。更准确地说,它不是预编译为本机机器代码,而是预编译为字节码。虽然机器代码肯定更快,但字节码更加便携和安全。然后在CPython的情况下解释字节码(或者在PyPy的情况下,在运行时解释和编译为优化的机器代码)。

问:CPython有哪些替代实现?您何时以及为何使用它们?

其中一个更突出的替代实现是Jython,一个用Java编写的Python实现,它使用Java虚拟机(JVM)。当CPython生成在CPython VM上运行的字节码时,Jython会生成Java字节码以在JVM上运行。

另一个是IronPython,用C#编写并以.NET堆栈为目标。IronPython运行在Microsoft的公共语言运行时(CLR)上。

正如为什么有这么多蟒蛇也指出的那样?,完全有可能在没有接触过Python的非CPython实现的情况下生存,但是切换有很多好处,其中大部分依赖于你的技术堆栈。

另一个值得注意的替代实现是PyPy,其主要功能包括:

速度。 由于它的Just-in-Time(JIT)编译器,Python程序通常在PyPy上运行得更快。
内存使用情况。 对于PyPy来说,庞大的,需要大量内存的Python程序可能会比CPython中占用更少的空间。
兼容性。 PyPy与现有的python代码高度兼容。它支持cffi,可以运行像Twisted和Django这样流行的Python库。
沙箱。 PyPy提供了以完全安全的方式运行不受信任的代码的能力。
无堆叠模式。 PyPy默认支持无堆栈模式,为大规模并发提供微线程。
问:你在Python中进行单元测试的方法是什么?

对这个问题的最根本的答案主要围绕Python的单元测试的测试框架。基本上,如果候选人在回答这个问题时没有提及unittest,那应该是一个巨大的危险信号。

unittest支持测试自动化,共享测试的设置和关闭代码,将测试聚合到集合中,以及测试与报告框架的独立性。unittest模块提供的类可以轻松地为一组测试支持这些质量。

假设候选人确实提到了单元测试(如果他们不这样做,你可能只想在那时和那里结束访谈!),你也应该让他们描述单元测试框架的关键元素; 即测试夹具,测试用例,测试套件和测试运行器。

单元测试框架的最新增加是mock。mock允许您使用模拟对象替换测试中的系统部分,并对如何使用它们进行断言。mock现在是Python标准库的一部分,在Python 3.3之后以unittest.mock的形式提供。

模拟的价值和力量在“Python中的模拟简介”中得到了很好的解释。如其中所述,系统调用是模拟的主要候选者:是否编写脚本以弹出CD驱动器,从/ tmp移除过时缓存文件的Web服务器,或绑定到TCP端口的套接字服务器,这些调用都是不希望的单元测试中的副作用。同样,保持单元测试的高效性和高性能意味着在自动化测试运行中保留尽可能多的“慢速代码”,即文件系统和网络访问。

[注意:这个问题适用于在Java方面经验丰富的Python开发人员。]
问:在Python与Java编码时,要记住哪些关键区别?

免责声明#1。 Java和Python之间的差异很多,可能是一个值得自己(冗长)帖子的话题。以下是两种语言之间一些主要差异的简要示例。

免责声明#2。 这里的目的不是针对Python与Java的优点展开宗教斗争(尽可能多的乐趣!)。相反,问题实际上只是为了看看开发人员如何理解两种语言之间的一些实际差异。因此,下面的列表有意避免从编程生产力的角度讨论Python相对于Java的可论证的优势。

考虑到上述两个免责声明,下面是在使用Python与Java编码时要记住的一些关键差异的示例:

动态与静态类型。 这两种语言之间最大的区别之一是Java仅限于静态类型,而Python支持动态类型化变量。
静态与类方法。 Java中的静态方法不会转换为Python类方法。
在Python中,调用类方法涉及额外的内存分配,调用静态方法或函数则不会。
在Java中,编译器会查找虚线名称(例如,foo.bar.method),因此在运行时,它们中有多少并不重要。但是,在Python中,查找在运行时发生,因此“每个点计数”。
方法重载。 虽然Java要求使用不同签名明确指定多个同名函数,但在Python中使用单个函数可以实现相同的功能,如果未由调用者指定,则该函数包含具有默认值的可选参数。
单引号与双引号。 虽然单引号与双引号的使用在Java中具有重要意义,但它们可以在Python中互换使用(但不是,它不允许使用双引号开始使用相同的字符串并尝试以单引号结束它,或者反之亦然!)。
吸气剂和二传手(不是!)。 Python中的getter和setter是多余的; 相反,你应该使用内置的’property’(这就是它的用途!)。在Python中,getter和setter浪费了CPU和程序员的时间。
类是可选的。 Java要求在封闭类定义的上下文中定义每个函数,而Python没有这样的要求。
缩进很重要… 在Python中。这咬了很多新手Python程序员。
大图
Python的专业知识远远超出了语言的技术细节。 Python专家将深入理解和理解Python的优点及其局限性。因此,这里有一些示例问题可以帮助评估候选人的专业知识的这个方面:

问:Python特别适用于什么?何时使用Python是项目的“正确选择”?

虽然喜欢和不喜欢是非常个人化的,但是“值得他或她的盐”的开发人员将突出通常被认为是有利的Python语言的特征(这也有助于回答Python“特别好”的问题)。这个问题的一些更常见的有效答案包括:

由于Python语法的灵活性,易于使用和易于重构,这使其特别适用于快速原型设计。
更简洁的代码,再次感谢Python的语法,以及丰富的功能丰富的Python库(随大多数Python语言实现自由分发)。
一种动态类型和强类型语言,提供罕见的代码灵活性组合,同时避免讨厌的隐式类型转换错误。
它是免费和开源的! 需要我们说更多?
关于何时使用Python是项目的“正确选择”的问题,完整的答案还取决于与语言本身正交的一些问题,例如先前的技术投资,团队的技能组合等等。 。虽然上面提到的问题意味着对严格的技术答案的兴趣,但是在面试中提出这些额外问题的开发者总是会“得分更多”,因为它表明了对“更大的图景”的认识和敏感度。 (即,不仅仅是所采用的技术)。相反,Python始终是正确选择的响应是一个不成熟的开发人员的明确标志。

问:Python语言有哪些缺点?

对于初学者来说,如果你熟悉一门语言,就会知道它的缺点,所以回答诸如“没有什么我不喜欢它”或“它没有任何缺点”这些都是非常有说服力的。

这个问题的两个最常见的有效答案(绝不是详尽无遗的清单)是:

全球口译员锁(GIL)。 CPython(最常见的Python实现)不是完全线程安全的。为了支持多线程Python程序,CPython提供了一个全局锁,必须由当前线程保存才能安全地访问Python对象。因此,无论存在多少个线程或处理器,在任何给定时间只执行一个线程。相比之下,值得注意的是,本文前面讨论的PyPy实现提供了一种无堆栈模式,支持用于大规模并发的微线程。
执行速度。 Python可能比编译语言慢,因为它被解释。(好吧,有点。看看我们之前关于这个主题的讨论。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值