ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

个很好的 Python 面试题

个很好的 Python 面试题

需要注明的是, 本段文字所依据的原始文本标题包含5 Great这几个英文单词, 同时还要向@非乌龟 表示感谢, 因为他指出了我存在的疏忽之处, 也就是没有标明来源出处, 并且对他的仔细认真予以肯定, 希望大家在阅读文章的时候都能去看一下原文内容, 之所以这样说是因为每个人对内容的理解都是不一样的, 原汁原味的最有帮助, 我翻译很多文章的目的一是为了自己以后找资料方便;二是作为一个索引, 以后再看原文的时候, 能更加快捷。

把这句话的意思给讲清楚, 其目的最终就是想让大家能够去看一看那个原文内容。

问题一: 在下面给出的这段代码运行之后, 它的输出结果到底会是什么样子的呢? 请把你的答案讲出来并且给予相应的解释说明为要。

class Parent(object):

x = 1

class Child1(Parent):

pass

class Child2(Parent):

pass

print Parent.x, Child1.x, Child2.x

Child1.x = 2

print Parent.x, Child1.x, Child2.x

Parent.x = 3

print Parent.x, Child1.x, Child2.x

答案

以上代码的输出是:

1 1 1

1 2 1

3 2 3

让你感到困惑或者是感到惊奇的, 是关于最后一行输出结果是 3、2、3, 而不是 3、2、1, 为什么会发生这个情况, 这里存在一个疑问, 为什么改变了 .x 的值之后, .x 的值会发生变化, 可是同时, .x 的值却又看起来好像没有发生改变, 这到底是怎么回事。

这个答案的关键点在于, 在当前环境里面, 类变量在底层是被当作字典这种东西来处理的, 如果一个变量的名字没有在现在的这个类的字典里找到位置, 程序就会去搜索那些祖先类, 也就是像父类这样的东西, 一直到那个被引用到的变量名被找出来为止, 要是说这个被引用到的变量名, 既没有在自己的所在的类里面出现, 也没有在祖先类那边找到踪影, 那么就会引发一个异常。

所以, 把 x 设定成 1 放在父类的代码里面。这就会导致类变量 X, 在引用了这个类的时候, 值会变为 1。同时也会影响到这个类的任何子类。因为当类变量 X 引用子类时, 其值也是 1。这就能够解释清楚了。第一个 print 语句输出的是 三个 1。

在随后的情形下, 要是该类的任意一个子类把对应的这个值给进行重写操作了, 就好比说是我们执行了语句 .x = 2 , 那么, 这样的话, 该值就仅仅只是在子类的范围里面发生改变了而已。这也就是产生第二个 print 语句其输出结果是 1、2、1 的原因所在了。

在最后的时候, 假如这个值在父类这里发生了变动(举个例子来说, 如果我们运行语句 .x = 3), 那么这种变动会影响那些没有对这个值进行重新定义的子类之中的值(在这个例子里面, 受到影响的子类是)。这就是为什么第三个 print 输出的结果是 3 2 3。

第二个问题是, 下面这一段程序代码运行出来的结果会到底是什么样子呢?请你自己先给出一个明确的答案, 然后再说清楚具体原因在哪里?

def div1(x,y):

print("%s/%s = %s" % (x, y, x/y))

def div2(x,y):

print("%s//%s = %s" % (x, y, x//y))

div1(5,2)

div1(5.,2)

div2(5,2)

div2(5.,2.)

答案

这个实际的答案, 它取决于你用的是什么版本, 到底是用了数字 2, 还是用了数字 3。

在 3里头, 大家心里想着的那个输出结果是:

5/2 = 2.5

5.0/2 = 2.5

5//2 = 2

5.0//2.0 = 2.0

在 2这个位置里面, 可是情况还是那样的, 上面所列举的那些代码程序最终被执行之后所产生的结果显示出来会是这样的:

5/2 = 2

5.0/2 = 2.5

5//2 = 2

5.0//2.0 = 2.0

默认,如果两个操作数都是整数, 2 自动执行整型计算。结果,5/2 值为 2,然而 5./2 值为 ```2.5``。

大家要特别留意的是, 尽管情况是这样的, 但是你可以在编号为 2 的地方把这个行为给重载掉,比方说能够达成在编号为 3 的地方想要得到的那种同样一种结果, 具体的做法就是加上下面这一些导入内容。

from __future__ import division

还有一点是需要去重视的, 那就是那个叫做“双划线”的意思是用两个斜杠(//)来表示的操作符, 它会一直去做整除运算这一件事, 完全不去管那两个操作数的类型是什么样的, 正是因为有这种状况存在着的, 所以才会得出 5.0//2.0 这个表达式的值是 2.0 的结果来。

注是在第3个地方, 斜杠 (/) 这个操作符是用来做浮点数的除法运算的, 而双斜杠 (//) 这个操作符则是用来做整除运算的, 意思就是算出来的商是不包含任何余数的, 就比如说用10去除以3, 那么结果就直接等于3, 剩下的那个余数会被直接砍掉, 不去管它, 但是对于 (-7) 除以 3 这种情况来说, 计算出来的结果却是 -3, 这一点大家要清楚, 因为这个算法跟其他很多编程语言的做法是完全不一样的, 所以在用的时候需要格外留意一下, 因为其他的那些语言在做整除运算的时候, 一般是朝着数值0的方向去取商的值的。

在2这个情况里面呢, 斜线这种符号代表的就是整除的意思, 所以说这和前面提到的3里面的那种双斜线的操作符是相等的。

第三个大问号, 下面的这些程序代码最终会呈现出什么样的结果。

list = ['a', 'b', 'c', 'd', 'e']

print list[10:]

答案

上面的这些代码会显示出结果,然后这个事儿并不会引出一个问题。

就像大伙儿心里都明白的那样, 要是你非要动手去够一个列表里头根本就不存在的成员, 比如说去摸一下那个已经越界了的下标, 那这事儿最后肯定是要出岔子的, 会弄出一个报错来。

可是, 事情要是换成了另外一种情况, 也就是说, 你拿个切片工具要去划拉一下这个列表, 而这个切片的开始地方, 恰恰就是比列表里头真正有的那些成员数还要高那么一截子, 这种情况就完全不一样了, 它绝对不会让你碰到什么错误, 而是会把一个空荡荡的列表直接交到你手里。

有一个让人不太高兴的小毛病, 就是它会导致漏洞出现。而且这个问题很难去找到根源在哪里。主要是因为它在执行的时候并没有弹出错误信息来提示大家。

问题四, 请你说明以下这段代码的运行结果具体是什么, 并且你需要给出自己的答案同时对这个问题进行相关的解释说明。

def multipliers():

return [lambda x : i * x for i in range(4)]

print [m(2) for m in multipliers()]

你会去对那个表示出来的定义进行修改, 从而得到你想要的结果。

答案

以上代码的输出是

6, 6, 6, 6

(而不是

0, 2, 4, 6

)。

出现这种现象的根本原因, 是后期的绑定所带来的影响, 也就是说, 闭包里面的变量的数值, 是在内部函数被调用的那个时刻, 才去进行查找并确定的。

所以, 最终得到的结果是, 只要有任何一个通过括号返回的那个函数被执行并且被调用的时候在当下这一刻, 字母 i 那个变量的数值会从它当时所处的周围作用域里面去进行查找动作, 等到确实发生了任何那个返回的函数被调用这个情况的时候那个for循环肯定已经执行结束了, i 在那个循环结束以后留下的最后数值是数字3, 于是每一个被返回的函数里面的值全都是3, 然后有一个数值是2被传递进去了, 上面写的代码就会得到结果, 它们会返回数值6, 具体的计算例子就是三个乘法两个等于六。

另外还要说一下, 就像在那本叫作 The ’s Guide to 的书里面已经指出过的那样, 这里存在着一项比较常见的错误看法, 这个看法涉及到关于表达式的一些情况。由这样一个表达式所创建的函数并不是什么特别的函数, 它和使用一个普通的def关键字所创建出来的函数在展现出来的表现方面是完全一样的。

面对这个问题, 咱们现在有两种办法来处理它。

最最常见的处理办法, 就是去弄一个闭包这个东西, 这个事是怎么做的, 是通过利用默认参数来把那个函数里的参数给立刻、马上绑在一起。打个比方啊:

def multipliers():

return [lambda x, i=i : i * x for i in range(4)]

还有一个其他的办法, 就是你可以去调用 dot函数, 这个函数的名字里面带着一个点。

from functools import partial

from operator import mul

def multipliers():

return [partial(mul, i) for i in range(4)]

问题五: 问的是以下这段代码执行之后的输出结果会是什么呢? 请给出你的答案并对这一情况进行解释说明。

def extendList(val, list=[]):

list.append(val)

return list

list1 = extendList(10)

list2 = extendList(123,[])

list3 = extendList('a')

print "list1 = %s" % list1

print "list2 = %s" % list2

print "list3 = %s" % list3

你会去对那个表示出来的定义进行修改, 从而得到你想要的结果。

上面说的那个程序在执行以后, 最后得到的结果是:

list1 = [10, 'a']

list2 = [123]

list3 = [10, 'a']

不少的人会产生一些错误的理解, 以为 list1 这个值应该是等于那个空括号里的状态, 同时认为 list3 的值也应当是等于相应的空状态, 这种想法背后的逻辑是觉得名称为 list 的那个变量所代表的参数, 会在每一次执行调用操作的时候, 自动地被设置到它原本设定的默认数值上面去。

尽管如此, 实际发生的状况是, 那个新的默认列表, 它仅仅只在函数被定义的时候, 才会被创建一次, 随后呢, 当这个函数被调用的时候, 如果没有被指定的列表参数提供数据的话, 那么它就会去使用同一个列表, 这就解释了为什么当函数被定义的时候, 这个表达式是用默认参数被计算出来的, 而不是在它被调用的时候才去计算。

所以, list1 和 list3 指的是同一个列表, 它们共同操作着这一份数据。而 list2 则是在操作一个它自己创建出来的、相互独立的列表, 这一步是通过把 List 参数设置成空列表, 并且以该空列表作为传值来完成这一操作的。

可以把函数的定义进行以下这种修改的, 但是, 在没有新的列表参数被指定到这个函数里面的时候, 它总是会从头开始去创建一个全新的列表的, 这种情况, 其实更有可能是大家一直以来的心里面所期待的那种正常 的。

def extendList(val, list=None):

if list is None:

list = []

list.append(val)

return list

通过使用这个经过改善而实现的代码, 最终得到的结果是:

list1 = [10]

list2 = [123]

list3 = ['a']

返回列表