附答案 | 最强Python面试题之Python进阶题第二弹
本文正在参与【[ 面霸养成记 ] 】 征文活动,一起来聊聊校招的那些事吧,牛客周边和百元京东卡等你来领~
写在之前
大家好呀,我是帅蛋。
这里是帅蛋的【最强Python面试题】系列,面试八股文都在这里呦!!!
周末两天不见,甚是想念,都有牛宝宝来催更了!我赶紧麻溜来更新 Python 进阶面试题第二弹!!
大家久等了!!!虽迟但到,一定要记得点赞收藏呀!!!
顺便提一句,我所有和面试相关的内容都会放在#帅蛋的面试空间# 中,大家可以关注下这个话题~
我会尽我最大的努力帮助到大家哒!!!
主要内容
这些面试题是我结合自己的经验整理的,主要就是下面这 5 个专题:
- Python 基础面试题
- Python 进阶
- Python 后台开发
- 爬虫
- 机器学习
已完成
Python 基础题
- 附答案 | 最强Python面试题之Python基础题(1)
- 附答案 | 最强Python面试题之Python基础题(2)
- 附答案 | 最强Python面试题之Python基础题(3)
- 附答案 | 最强Python面试题之Python基础题(4)
正在更新
Python 进阶题
对每道面试题我都会附带详细的答案,有些我觉得重要的内容会详细讲解,虽然是面试八股文,我还是希望大家不是只“知其然”,更得“知其所以然”。
关于更新频率,每天我会更新 10 道题左右,总共会有差不多 200 道。
无论是准备面试还是自己学习,这份面试题绝对值得你去看,去学习。
大家可以关注我,再关注我,使劲关注我,不要错过每天的更新~
以下是正文
Python 进阶面试题第二弹正式开始,大家一定要记得点赞收藏,一起加油!
1、什么是断言?应用场景?
assert断言——声明其布尔值必须为真判定,发生异常则为假。
info = {} info['name'] = 'egon' info['age'] = 18 # 用assert取代上述代码: assert ('name' in info) and ('age' in info)
设置一个断言目的就是要求必须实现某个条件。
2、有用过with statement吗?它的好处是什么?
with语句的作用是通过某种方式简化异常处理,它是所谓的上下文管理器的一种
用法举例如下:
with open('output.txt', 'w') as f: f.write('Hi there!')
当你要成对执行两个相关的操作的时候,这样就很方便,以上便是经典例子,with语句会在嵌套的代码执行之后,自动关闭文件。
这种做法的还有另一个优势就是,无论嵌套的代码是以何种方式结束的,它都关闭文件。
如果在嵌套的代码中发生异常,它能够在外部exception handler catch异常前关闭文件。
如果嵌套代码有return/continue/break语句,它同样能够关闭文件。
3、简述 Python 在异常处理中,else 和 finally 的作用分别是什么?
如果一个 Try - exception 中,没有发生异常,即 exception 没有执行,那么将会执行 else 语句的内容。反之,如果触发了 Try - exception(异常在 exception 中被定义),那么将会执行exception
中的内容,而不执行 else 中的内容。
如果 try 中的异常没有在 exception 中被指出,那么系统将会抛出 Traceback(默认错误代码),并且终止程序,接下来的所有代码都不会被执行,但如果有 Finally 关键字,则会在程序抛出 Traceback 之前(程序最后一口气的时候),执行 finally 中的语句。这个方法在某些必须要结束的操作中颇为有用,如释放文件句柄,或释放内存空间等。
4、map 函数和 reduce 函数?
(1) 从参数方面来讲:
map()包含两个参数,第一个参数是一个函数,第二个是序列(列表 或元组)。其中,函数(即 map 的第一个参数位置的函数)可以接收一个或多个参数。
reduce()第一个参数是函数,第二个是序列(列表或元组)。但是,其函数必须接收两个参数。
(2) 从对传进去的数值作用来讲:
map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次 。
reduce()是将传人的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用(累积计算)。
补充 Python 特殊函数
lambda 函数
lambda 是一个可以只用一行就能解决问题的函数,让我们先看下面的例子:
>>> def add(x): ... x += 1 ... return x ... >>> numbers = range(5) >>> list(numbers) [0, 1, 2, 3, 4] >>> new_numbers = [] >>> for i in numbers: ... new_numbers.append(add(i)) ... >>> new_numbers [1, 2, 3, 4, 5]
在上面的这个例子中,函数 add() 充当了一个中间角色,当然上面的例子也可以如下实现:
>>> new_numbers = [i+1 for i in numbers] >>> new_numbers [1, 2, 3, 4, 5]
首先我要说,上面的列表解析式其实是很好用的,但是我偏偏要用 lambda 这个函数代替 add(x) :
>>> lamb = lambda x: x+1 >>> new_numbers = [] >>> for i in numbers: ... new_numbers.append(lamb(i)) ... >>> new_numbers [1, 2, 3, 4, 5]
在这里的 lamb 就相当于 add(x) ,lamb = lambda x : x+1 就相当于 add(x) 里的代码块。下面再写几个应用 lambda 的小例子:
>>> lamb = lambda x,y : x + y >>> lamb(1,2) 3 >>> lamb1 = lambda x : x ** 2 >>> lamb1(5) 25
由上面的例子我们可以总结一下 lambda 函数的具体使用方法:lambda 后面直接跟变量,变脸后面是冒号,冒号后面是表达式,表达式的计算结果就是本函数的返回值。
在这里有一点需要提醒的是,虽然 lambda 函数可以接收任意多的参数并且返回单个表达式的值,但是 lambda 函数不能包含命令且包含的表达式不能超过一个。如果你需要更多复杂的东西,你应该去定义一个函数。
lambda 作为一个只有一行的函数,在你具体的编程实践中可以选择使用,虽然在性能上没什么提升,但是看着舒服呀。
map 函数
我们在上面讲 lambda 的时候用的例子,其实 map 也可以实现,请看下面的操作:
>>> numbers = [0,1,2,3,4] >>> map(add,numbers) [1, 2, 3, 4, 5] >>> map(lambda x: x + 1,numbers) [1, 2, 3, 4, 5]
map 是 Python 的一个内置函数,它的基本格式是:map(func, seq)。
func 是一个函数对象,seq 是一个序列对象,在执行的时候,seq 中的每个元素按照从左到右的顺序依次被取出来,塞到 func 函数里面,并将 func 的返回值依次存到一个列表里。
对于 map 要主要理解以下几个点就好了:
1.对可迭代的对象中的每一个元素,依次使用 fun 的方法(其实本质上就是一个 for 循环)。
2.将所有的结果返回一个 map 对象,这个对象是个迭代器。
我们接下来做一个简单的小题目:将两个列表中的对应项加起来,把结果返回在一个列表里,我们用 map 来做,如果你做完了,请往下看:
>>> list1 = [1,2,3,4] >>> list2 = [5,6,7,8] >>> list(map(lambda x,y: x + y,list1,list2)) [6, 8, 10, 12]
你看上面,是不是很简单?其实这个还看不出 map 的方便来,因为用 for 同样也不麻烦,要是你有这样的想法的话,那么请看下面:
>>> list1 = [1,2,3,4] >>> list2 = [5,6,7,8] >>> list3 = [9,10,11,12] >>> list(map(lambda x,y,z : x + y + z,list1,list2,list3)) [15, 18, 21, 24]
你看三个呢?是不是用 for 的话就稍显麻烦了?那么我们在想如果是 四个,五个乃至更多呢?这就显示出 map 的简洁优雅了,并且 map 还不和 lambda 一样对性能没有什么提高,map 在性能上的优势也是杠杠的。
filter 函数
filter 翻译过来的意思是 “过滤器”,在 Python 中,它也确实是起到的是过滤器的作用。这个解释起来略微麻烦,还是直接上代码的好,在代码中体会用法是我在所有的文章里一直在体现的:
>>> numbers = range(-4,4) >>> list(filter(lambda x: x > 0,numbers)) [1, 2, 3]
上面的例子其实和下面的代码是等价的:
>>> [x for x in numbers if x > 0] [1, 2, 3]
然后我们再来写一个例子体会一下:
>>> list(filter(lambda x: x != 'o','Rocky0429')) ['R', 'c', 'k', 'y', '0', '4', '2', '9']
reduce 函数
我在之前的文章中很多次都说过,我的代码都是用 Python3 版本的。在 Python3 中,reduce 函数被放到 functools 模块里,在 Python2 中还是在全局命名空间。
同样我先用一个例子来跑一下,我们来看看怎么用:
>>> reduce(lambda x,y: x+y,[1,2,3,4]) 10
reduce 函数的第一个参数是一个函数,第二个参数是序列类型的对象,将函数按照从左到右的顺序作用在序列上。如果你还不理解的话,我们下面可以对比一下它和 map 的区别:
>>> list1 = [1,2,3,4] >>> list2 = [5,6,7,8] >>> list(map(lambda x,y: x + y,list1,list2)) [6, 8, 10, 12]
对比上面的两个例子,就知道两者的区别,map 相当于是上下运算的,而 reduce 是从左到右逐个元素进行运算。
5、递归函数停止的条件?
递归的终止条件一般定义在递归函数内部,在递归调用前要做一个条件判断,根据判断的结果选择是继续调用自身,还是 return;返回终止递归。
终止的条件:
(1) 判断递归的次数是否达到某一限定值
(2) 判断运算的结果是否达到某个范围等,根据设计的目的来选择
6、回调函数,如何通信的?
回调函数是把函数的指针(地址)作为参数传递给另一个函数,将整个函数当作一个对象,赋值给调用的函数。
7、 __setattr__,__getattr__,__delattr__函数使用详解?
1.setattr(self,name,value):如果想要给 name 赋值的话,就需要调用这个方法。
2.getattr(self,name):如果 name 被访问且它又不存在,那么这个方法将被调用。
3.delattr(self,name):如果要删除 name 的话,这个方法就要被调用了。
下面我们用例子来演示一下:
>>> class Sample: ... def __getattr__(self,name): ... print('hello getattr') ... def __setattr__(self,name,value): ... print('hello setattr') ... self.__dict__[name] = value ...
上面的例子中类 Sample 只有两个方法,下面让我们实例化一下:
>>> s = Sample() >>> s.x hello getattr
s.x 这个实例属性本来是不存在的,但是由于类中有了 getattr(self,name) 方法,当发现属性 x 不存在于对象的 dict 中时,就调用了 getattr,也就是所谓的「拦截成员」。
>>> s.x = 7 hello setattr
同理,给对象的属性赋值的时候,调用了 setattr(self,name,value) 方法,这个方法中有 self.dict[name] = value,通过这个就将属性和数据存到了对象 dict 中。如果再调用这个属性的话,会成为下面这样:
>>> s.x 7
出现这种结果的原因是它已经存在于对象的 dict 中了。
看了上面的两个,你是不是觉得上面的方法很有魔力呢?这就是「黑魔法」,但是它具体有什么应用呢?我们接着来看:
class Rectangle: """ the width and length of Rectangle """ def __init__(self): self.width = 0 self.length = 0 def setSize(self,size): self.width, self.length = size def getSize(self): return self.width, self.length if __name__ == "__main__": r = Rectangle() r.width = 3 r.length = 4 print(r.getSize()) print(r.setSize((30,40))) print(r.width) print(r.length)
上面是我根据一个很有名的例子改编的,你可以先想一下结果,想完以后可以接着往下看:
(3, 4) 30 40
这段代码运行的结果如上面所示,作为一个强迫证的码农,对于这种可以改进的程序当然不能容忍。我们在上面介绍的特殊方法,我们一定要学以致用,虽然重新写的不一定比原来的好,但我们还是要尝试去用:
class NewRectangle: """ the width and length of Rectangle """ def __init__(self): self.width = 0 self.length = 0 def __setattr__(self, name, value): if name == 'size': self.width, self.length = value else: self.__dict__[name] = value def __getattr__(self, name): if name == 'size': return self.width, self.length else: return AttributeError if __name__ == "__main__": r = NewRectangle() r.width = 3 r.length = 4 print(r.size) r.size = 30,40 print(r.width) print(r.length)
我们来看一下运行的结果:
(3, 4) 30 40
我们可以看到,除了类的写法变了以外,调用的方式没有变,结果也没有变。
8、请描述抽象类和接口类的区别和联系?
(1) 抽象类
规定了一系列的方法,并规定了必须由继承类实现的方法。由于有抽象方法的存在,所以抽象类不能实例化。可以将抽象类理解为毛坯房,门窗、墙面的样式由你自己来定,所以抽象类与作为基类的普通类的区别在于约束性更强。
(2) 接口类
与抽象类很相似,表现在接口中定义的方法,必须由引用类实现,但他与抽象类的根本区别在于用途:与不同个体间沟通的规则(方法),你要进宿舍需要有钥匙,这个钥匙就是你与宿舍的接口,你的同室也有这个接口,所以他也能进入宿舍,你用手机通话,那么手机就是你与他人交流的接口。
(3) 区别和关联
接口是抽象类的变体,接口中所有的方法都是抽象的。而抽象类中可以有非抽象方法。抽象类是声明方法的存在而不去实现它的类。
接口可以继承,抽象类不行。
接口定义方法,没有实现的代码,而抽象类可以实现部分方法。
接口中基本数据类型为 static 而抽类象不是。
接口可以继承,抽象类不行。
可以在一个类中同时实现多个接口。
接口的使用方式通过 implements 关键字进行,抽象类则是通过继承 extends 关键字进行。
9、请描述方法重载与方法重写?
(1)方法重载
是在一个类里面,方法名字相同,而参数不同。返回类型呢?可以相同也可以不同。重载是让类以统一的方式处理不同类型数据的一种手段。
(2) 方法重写
子类不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
10、什么是 lambda 函数? 有什么好处?
lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的函数
1、lambda 函数比较轻便,即用即仍,很适合需要完成一项功能,但是此功能只在此一处使用,连名字都很随意的情况下
2、匿名函数,一般用来给 filter, map 这样的函数式编程服务
3、作为回调函数,传递给某些应用,比如消息处理
以上就是今天的内容,我是帅蛋,我们明天见~
❤️ 欢迎关注我,有问题,找帅蛋,我最看不得别人迷茫!
❤️ 如果你觉得有帮助,希望爱学习的你不要吝啬三连击哟[点赞 + 收藏 + 评论]~
#帅蛋的面试空间##秋招##面试八股文##Python##python面试#还有小小公众号 【编程文青李狗蛋】,聊聊迷茫吹吹牛皮~