经典考试题
考试题
一、列举你了解的Python较其他语言的优势(2分)
1、简单易学
2、开发速度快
3、拥有最成熟的程序包资源库(第三方库)
4 简单,优雅,明确
二、解释型和编译型语言的区别?并举例(2分)
编译型语言:在程序运行之前,有一个单独的编译过程,将程序翻译成机器语言,以后执行这个程序时,就不用再进行翻译了。C、C++ 全部编译一次执行
解释型语言:是在运行的时候将程序翻译成机器语言,所以运行速度相对于编译型语言要慢。JavaScript、VBScript、Perl、Python、Ruby、MATLAB 一边编译一边执行
Java先编译成字节码,然后再解释执行
三、列举Python常用数据类型并尽量多的写出其中的方法(2分)
- 字符串(str):
- 列表(list)
- y 返回 1 4 # exp(x) 返回e的x次幂(ex),如math.exp(1) 返回2.718281828459045 5 # fabs(x) 返回数字的绝对值,如math.fabs(-10) 返回10.0 6 # floor(x) 返回数字的下舍整数,如math.floor(4.9)返回 4 7 # log(x) 如math.log(math.e)返回1.0,math.log(100,10)返回2.0 8 # log10(x) 返回以10为基数的x的对数,如math.log10(100)返回 2.0 9 # max(x1, x2,...) 返回给定参数的最大值,参数可以为序列。 10 # min(x1, x2,...) 返回给定参数的最小值,参数可以为序列。 11 # modf(x) 返回x的整数部分与小数部分,两部分的数值符号与x相同,整数部分以浮点型表示。 12 # pow(x, y) x**y 运算后的值。 13 # round(x [,n]) 返回浮点数x的四舍五入值,如给出n值,则代表舍入到小数点后的位数。 14 # sqrt(x) 返回数字x的平方根,数字可以为负数,返回类型为实数,如math.sqrt(4)返回 2+0j " v:shapes="_x0000_s1028">数字(int、float)
- 字典(dict)
- 元祖(tuple)
索引、切片
- 集合(set)
四、列举Python2和Python3的区别?(5)
1.Python3对Unicode字符的原生支持
Python2中使用 ASCII 码作为默认编码方式导致string有两种类型str和unicode,Python3只支持unicode的string。python2和python3字节和字符对应关系为:
2.Python3采用的是绝对路径的方式进行import。
Python2中相对路径的import会导致标准库导入变得困难(想象一下,同一目录下有file.py,如何同时导入这个文件和标准库file)。Python3中这一点将被修改,如果还需要导入同一目录的文件必须使用绝对路径,否则只能使用相关导入的方式来进行导入。
3.Python2中存在老式类和新式类的区别,Python3统一采用新式类。新式类声明要求继承object,必须用新式类应用多重继承。
4. print语句被python3废弃,统一使用print函数
5. xrange函数被Python3废弃,统一使用range,Python3中range的机制也进行修改并提高了大数据集生成效率
https://www.cnblogs.com/kendrick/p/7478304.html
五、- 看代码写结果:(4分)
``` python
def func(name,users=[]):
users.append(name)
return users
v1 = func(1)
print(v1)
v2 = func(2,[])
print(v2)
v3 = func(3)
print(v3)
v4 = func(4,[])
print(v4)
结果:因为users默认的那个列表[],当users指向其他列表的时候,原来那个默认的列表并没有在内存中消失,没有在内存中消失是因为这个默认列表作为默认参数,这个列表的内存空间地址就确定了,函数对它也是有一个引用的,users对它也是一个引用,所以当users指向其他内容的时候,这个默认的[]也不会在内存中消失,大家记住吧,当默认参数为容器类的可变数据类型的时候,是有坑的。举个例子:a = 1,b=a,a=2,print(a),print(b)
[1]
[2]
[1, 3]
[4]
六、正则中的贪婪匹配是什么意思?请写出一个示例(2分)
趋向于最大长度的匹配就是贪婪匹配。匹配后用回溯算法返回最后一个*、+、{m,n}等
七、看代码写结果:(2分)
``` python
for i in range(10):
print(i)
print(i)
```
结果:0.1.2.3.4.5.6.7.8.9
I = 9 ,因为i指向了最后一个循环的值,所以打印的是9
八、- 看代码写结果:(6分)
``` python
data = [lambda:i for i in range(10)]
v1 = data[10]
print(v1)
v2 = data[1]
print(v2)
v3 = data[1]()
print(v3)
```
结果:
V1:报错,写错了吧
V2:lambda表达式
V3:9 #分析:因为i每次都被重新赋值了,赋值为每次循环的数字了。
九、列举面向对象三大特性(2分)
继承、单继承,多继承
封装、数据封装, 函数功能封装
多态 鸭子模型
十、列举面向对象中所有成员(2分)
变量 四种 实例变量
公有.私有
类变量
公有. 私有
方法 实例方法
静态方法
类方法
属性
十一、列举面向对象中所有你了解的特殊成员(4分)
__str__、__del__、__call__、__dict__、__getitem__、__getslice__、__iter__
十二、列举常用的模块并简述其所用(3分)
time:时间日期模块
random:随机元素
os:与操作系统交互的一个接口
sys:用于提供对python解释器的相关操作
json和pickle : 序列化
hashlib:加密
logging:日志模块
…
十三、- 看代码写结果(3分)
``` python
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
原因:对于父类中的静态变量,子类对象只能查看,不能修改,而child1.x = 2,是在外部对child1类添加了一个属性
打印一下:print(Child1.__dict__) print(Parent.__dict__)
十四、isinstance/issubclass/type的区别(3分)
type()判断某个对象是否是该类创建的,只看一层,如果是继承类,也不会考虑继承类的类型。.
Issubclass()判断该类是否是另一个类的派生类,也就是子类。参数为类
isinstance()判断某个对象是否是该类型的,这个类型可以是父类,也就是判断的时候也算上继承关系,参数为某个类的实例化对象。
十五、什么时候反射?并列举其相关所有方法(2分)
当需要通过字符串可以获取类的属性值的时候,可以使用。
hasattr(obj, name_str) 判断一个对象obj里是否有对应的name_str字符串方法的方法
getattr(obj, name_str) 根据字符串获取obj对象里的对应方法的内存地址
setattr(obj, name_str) 添加新属性
delattr(obj, name_str) 删除属性
十六、解释以下名词(10分)
名词:mac地址、ip地址、DHCP服务、路由器、交换机、子网掩码、网关、DNS、广播BS和CS架构
Mac地址:网络设备的唯一标识,身份证,全球唯一
IP地址:为每个使用网络的设备分配的一个逻辑地址,用来屏蔽物理地址的差异,并且起到管理网络的作用。
DHCP:动态主机配置协议,意思就是为每个接入网络的设备,自动分配给它一个IP地址,并起到管理IP地址的功能。
路由器:通过转发数据包来实现网络通信。
交换机:多端口网桥,其实就是将所有连接到交换机的电脑全部联通起来,使他们之间能够通过局域网直接进行通信。
子网掩码:计算IP地址段用的,判断两个IP地址是否是同一个子网。
网关:把关的人,关口,所有该网关内的计算机想要与外界进行通信都要通过网关,我们平常所说的网关是TCP/IP里的网关,其实就是一个网络通往其他网络的IP地址。
DNS:域名解析为IP地址
广播:大喊一声,全部人员都能听到,局域网通信基本都是广播形式的,最早期通过mac地址进行通信的电脑,就是以广播的形式进行通信的。
BS:浏览器与服务端
CS:客户端应用程序与服务端
十七、OSI 7层模型(3分)
十八、TCP 3次握手4次挥手(3分)
三次握手:
TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。
四次挥手:
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,然后客户端主动关闭,服务器被动关闭。服务端也可以主动关闭,一个流程。
客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
十九、默写一个socket客户端和socket服务端的基本通信,即:收发消息(4分)
Server:
import socket
sk = socket.socket()
sk.bind(('192.168.0.95',8898)) #把地址绑定到套接字
sk.listen() #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024) #接收客户端信息
ret = str(ret,encoding='utf-8')
print(ret) #打印客户端信息
msg = input('妹子说>>>')
msg = bytes(msg,encoding='utf-8')
conn.send(msg) #向客户端发送信息
conn.close() #关闭客户端套接字
sk.close() #关闭服务器套接字(可选)
client:
import socket
sk = socket.socket() # 创建客户套接字
sk.connect(('192.168.0.95',8898)) # 尝试连接服务器
msg = input('你问我>>>>')
msg = bytes(msg,encoding='utf-8')
sk.send(msg)
ret = sk.recv(1024) # 对话(发送/接收)
ret = str(ret,encoding='utf-8')
print(ret)
sk.close() # 关闭客户套接字
二十、解释什么是黏包以及如何解决(3分)
UDP没有粘包,TCP有,因为TCP是面向流的消息格式,粘包有两种:1.由于你设置的接收大小小于你收到的消息的大小,那么剩余的消息的部分会和下一次接收一次被接收到 2.快速连续发送两个很小的消息,两个小消息会被合到一起被一次接收拿到。本质上就是因为接收端不知道发送端发送消息的大小导致的。
解决办法:在发送消息之前,先将消息大小发送过去,接收端按照消息大小来接收。Struck模块
个人理解:
黏包:发送端将若干数据发送,在接受时数据黏在一起,在接受端的缓存区看就是后一个包黏在前一个数据包的尾部
本质原因是因为不知道数据包的大小
解决黏包:两种方法:1.发送两次数据第一次先将数据包的大小发送,然后第二次发送数据,接收方遍历接收
2.引入struck模块,也就是将数据答大小打包成一个四字节的报头信息,连同数据一起发送到接收端,接受端在接受时先将数据大小的信息提取出来然后循环接收
二十一、TCP和UDP区别(2分)
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文(数据包),尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
二十二、简述进程、线程、协程的区别?(3分)
1.进程是操作系统资源分配的最小单位,拥有独立的资源和地址空间,主要用于数据隔离
2.线程是CPU调度的单位
3.统一进程中的线程是资源共享的。
4.协程是用户级别的,程序之间的切换由用户自行处理,节省了CPU的调度时间。
二十三、什么是GIL锁以及作用(2分)
全局解释锁,每次只能一个线程获得cpu的使用权:为了线程安全,也就是为了解决多线程之间的数据完整性和状态同步而加的锁,因为我们知道线程之间的数据是共享的。
个人理解:
GIL锁是Python内部自带的全局解释锁,在同一时刻一个进程中只有一个线程被CPU调度
二十四、IO密集型操作时,为什么线程比进程更好?(3分)
在IO密集型的操作时,进程线程都不会太占用CPU,但是进程消耗的资源比较多。
二十五、什么是并发和并行?(2分)
并发:是伪并行,即看起来是同时运行。
并行:同时运行,只有具备多个cpu才能实现并行
二十六、使用两种方式编写多线程程序(4分)
方式一、
from threading import Thread
import time
class Sayhi(Thread):
def __init__(self,name):
super().__init__()
self.name=name
def run(self):
time.sleep(2)
print('%s say hello' % self.name)
if __name__ == '__main__':
t = Sayhi('妹子')
t.start()
print('主线程')
方式二、
from threading import Thread
import time
def sayhi(name):
time.sleep(2)
print('%s say hello' %name)
if __name__ == '__main__':
t=Thread(target=sayhi,args=('egon',))
t.start()
print('主线程')
二十七、在函数中如何获取 线程对象、线程唯一ID(2分)
threading.current_thread()
threading.current_thread().ident
二十八、threading.local的作用?(3分)
threading.local()这个方法的特点用来保存一个全局变量,但是这个全局变量只有在当前线程才能访问,如果你在开发多线程应用的时候 需要每个线程保存一个单独的数据供当前线程操作,可以考虑使用这个方法,简单有效。举例:每个子线程使用全局对象a,但每个线程定义的属性a.xx是该线程独有的,Python提供了 threading.local 类,将这个类实例化得到一个全局对象,但是不同的线程使用这个对象存储的数据其它线程不可见(本质上就是不同的线程使用这个对象时为其创建一个独立的字典)。
二十九、Python中为什么要使用线程池?如何使用线程池?(3分)
系统处理任务时,需要为每个请求创建和销毁对象。当有大量并发任务需要处理时,再使用传统的多线程就会造成大量的资源创建销毁导致服务器效率的下降。这时候,线程池就派上用场了。线程池技术为线程创建、销毁的开销问题和系统资源不足问题提供了很好的解决方案。
使用:
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
import os,time,random
def task(n):
print('%s is runing' %os.getpid())
time.sleep(random.randint(1,3))
return n**2
if __name__ == '__main__':
executor=ThreadPoolExecutor(max_workers=3)
# for i in range(11):
# future=executor.submit(task,i)
executor.map(task,range(1,12)) #map取代了for+submit
三十、什么是异步非阻塞?(2分)
举例:在银行排队办业务,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下,那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了
三十一、IO多路复用的作用?并列举实现机制以及区别?(3分)
I/O多路复用是用于提升效率,单个进程可以同时监听多个网络连接IO。举例:通过一种机制,可以监视多个文件描述符,一旦描述符就绪(读就绪和写就绪),能通知程序进行相应的读写操作,I/O多路复用避免阻塞在io上,原本为多进程或多线程来接收多个连接的消息变为单进程或单线程保存多个socket的状态后轮询处理
select是通过系统调用来监视一组由多个文件描述符组成的数组,通过调用select()返回结果,数组中就绪的文件描述符会被内核标记出来,然后进程就可以获得这些文件描述符,然后进行相应的读写操作。
每次调用select,都需要把fd集合由用户态拷贝到内核态,在fd多的时候开销会很大,
每次select都是线性遍历整个列表,当fd很大的时候,遍历的开销也很大
poll本质上与select基本相同,只不过监控的最大连接数上相较于select没有了限制,因为poll使用的数据结构是链表,而select使用的是数组,数组是要初始化长度大小的,且不能改变
Epoll:解决select和poll本身的缺陷
数组长度限制解决方案:fd上限是最大可以打开文件的数目,具体数目可以查看/proc/sys/fs/file-max。一般会和内存有关
需要每次轮询将数组全部拷贝到内核态解决方案:每次注册事件的时候,会把fd拷贝到内核态,而不是每次poll的时候拷贝,这样就保证每个fd只需要拷贝一次。
每次遍历都需要列表线性遍历解决方案:不再采用遍历的方案,给每个fd指定一个回调函数,fd就绪时,调用回调函数,这个回调函数会把fd加入到就绪的fd列表中,所以epoll只需要遍历就绪的list即可
三十二、greenlet和gevent模块的区别?(2分)
协程是一中多任务实现方式,它不需要多个进程或线程就可以实现多任务。
yield能实现协程,不过实现过程不易于理解,greenlet是在这方面做了改进,通过switch。
greenlet可以实现协程,不过每一次都要人为的去指向下一个该执行的协程,显得太过麻烦。python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent,gevent每次遇到io操作,需要耗时等待时,会自动跳到下一个协程继续执行
三十三、栈和队列的区别?(2分)
栈:先进后出
队列:先进先出