总结
一.Python base
(一)什么是面向对象
面向对象三大基本特征:封装/继承/多态
1.封装:
-
分而治之:将一个大的需求分解为许多类,每个类处理一个独立的功能
-
封装变化:变化的地方独立封装,避免影响其他类
-
高内聚:每个类完成一个变化点
-
低耦合:类与类的关联性和依赖度要低,一个类的变化尽可能少影响其他类
2.继承:一个代码复用的方式,作用是为了隔离客户端代码与功能的实现,就是抽象变化点用来用与做
3.多态:增强程序的扩展性,尽量在不影响原代码的基础上 增加新的功能
六大基本原则:开闭原则/单一职责/依赖倒置/组合复用/里氏替换/迪米特法则
1.开闭原则:对扩展开放,对修改关闭,增加新功能,不改变原有代码
2.单一职责:针对的问题是类与类的耦合性,每次修改这个类代码的原因只能有一个,不能因为其他类的问题修改此类
3.依赖倒置:通过把传统的依赖具象代码关系转变成抽象代码方式 就是客户端代码调用的类进项依赖使用抽象的组件 而不是直接依赖父类
4.里式替换:进一步要求父类尽可能不要存在态具体的功能,能抽象就抽象,任何的修改都完全依靠子类来补充和修改 也是开闭原则 对父类修改关系 对子类修改开放 也可以是说重写父类的方法
5.迪米特法则:又戏称"最少知道法则",说白了就是针对面向对象里的低耦合,它的本意指的就是类与类之间尽可能不要有太多的关联,当一个类需要产生变化时,其他的类尽量做到不产生改动.就是类与类交互时,在满足功能要求的基础上,传递的数据量越少越好。因为这样可能降低耦合度。
6.组合复用:就是指组合关系和集成关系都能满足业务要求是,有限使用组合关系
泛华/关联/依赖
泛华:子类与父类的关系:继承
关联:部分与整体的关系,功能的复用,变化影响一个类 就好比在A类中包含B类的成员
依赖:合作关系,就好比B类作为A类方法的参数,并不是A的成员 在关联关系的基础上进一步降低了耦合度
(二)数据类型
数字类型(int/float/bool/complex),字符串类型string,列表类型list,元组类型tuple,字典类型dict,集合类型set
(1)哪些数据类型可变,哪些数据类型不可变?
可变数据类型有:列表,字典,集合
不可变数据类型有:元组,字符串
(2)如何区分是否可变
从对象内存地址来说
可变数据类型:在内存地址不变的情况下,值可改变(列表和字典都是可变类型,但是字典中的key必须是不可变类型)
不可变数据类型:内存改变,值也跟着改变,(数字,字符串,布尔类型,元组都是不可变类型)
可以通过id()方法进行内存地址的检查
(3)list如何去重
1.利用set的自动去重功能
将列表转化为集合,在转化为列表,利用集合的自动去重功能,简单快速
缺点是如果用集合无法保证去重后的顺序
2.直观方法
先建立一个空列表,通过遍历原来的列表,在利用逻辑关系,not in来去重
3.可以通过列表中索引(index)的方法保证去重后的顺序不变
(4)字典如何排序,1.根据key,2.根据value
# 初始化字典
a = {"b": 5, "c": 2, "a": 4, "d": 1}
# 对字典按键(key)进行排序(默认由小到大)
test_1 = sorted(a.items(), key=lambda x: x[0])
# 输出结果
print(test_1)
# [('a', 4), ('b', 5), ('c', 2), ('d', 1)]
# 对字典按值(value)进行排序(默认由小到大)
test_2 = sorted(a.items(), key=lambda x: x[1])
# 输出结果
print(test_2)
# [('d', 1), ('c', 2), ('a', 4), ('b', 5)]
sorted(d.items(), key=lambda x: x[1]) 中 d.items() 为待排序的对象;key=lambda x: x[1] 为对前面的对象中的第二维数据(即value)的值进行排序。 key=lambda 变量:变量[维数] 。维数可以按照自己的需要进行设置。
维数以字符串来表示
# 将列表中的age由大到小排序
alist = [{'name':'a','age':20},{'name':'b','age':30},{'name':'c','age':25}]
b=sorted(alist,key=lambda x:x['age'],reverse=True)
print(b)
#[{'name': 'b', 'age': 30}, {'name': 'c', 'age': 25}, {'name': 'a', 'age': 20}]
(5)列表和元组的区别
-
列表可变序列,元组不可以变序列.
-
元组可以作为字典的键,列表不能
-
列表可以使用append(),extend(),insert(),remove(),pop()等方法实现添加和修改列表元素,而元组不能
-
列表可以使用切片访问和修改列表中的元素,元组通过切片只能访问元素,不支持修改
-
元组比列表的访问和处理速度块,所以只需要对其中的元素进行访问,而不进行修改时,建议使用元组
(三)迭代器,生成器
(1)迭代器,生成器是什么?
1.迭代器是一个更加抽象的概念,任何对象,如果他的类有next方法和iter方法返回自身.对于string,list,dict,tuple等这类容器对象,使用for循环遍历是很方便的.在后台for语句对容器对象调用了iter()函数,iter()是python的内置函数.iter()会返回一个定义了next()方法的迭代器对象,他在容器中逐个访问容器内元素,next()也是python的内置函数在没有后续元素时,next()会排除一个Stoplterration的异常.
2.生成器是创建迭代器的简单而强大的工具,只是在返回数据的时候需要使用yield语句.每次next()被调用时,生成器就会返回他脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)
(2)他们的区别?
生成器能做到迭代器能做的所有事,而且因为自动创建了 __ iter __ ()和next()方法,生成器显得特别简洁,而且生成器也是高效的,使用生成器表达式取代列表解析可以同时节省内存,除了创建和保持程序状态的自动生成,当生成器终结时,也会自动抛出Stoplterration的异常
(四)闭包.装饰器
闭包:其实就是在一个函数中嵌套另一个函数的定义。
创建闭包必须满足三点要求:
-
必须有一个内嵌函数
-
内嵌函数必须引用外部函数中的变量
-
外部函数的返回值必须是内嵌函数
闭包的作用:包括了外部函数的局部变量,这些局部变量在外部函数返回后也继续存在,并能被内部函数引用。
装饰器:其实是用闭包来实现的。
装饰器的作用:在不改变原先的函数值跟调用的方式,添加额外的功能
(五)推导式
(六)lamad表达式
二.网络编程
(一)并发.并行.进程.线程.协成基本概念
1.并发:同时处理多个任务,内核在任务间不断的切换达到好像多个任务被同时执行的效果,实际上每个时刻只有一个任务占用内核
2.并行:多个任务利用计算机多核资源在同时执行,此时多个任务间为并行关系
3.进程:是一个动态的过程描述,占有计算机运行资源,有一定的生命周期
4.线程:可以理解为进程的分支任务,也被称为轻量级的进程,可以使用计算机多核资源,是多资源编程方式,是系统内分配内核的最小单位
5.协程:微线程,是为非抢占式多任务产生子程序的计算机主键,协程允许不同入口点子在不同位置暂定或开始 就是说协程可以暂停执行的函数
6.程序:是一个可执行的文件,是静态的占有磁盘.
(二)(多)进程的几种创建方式
(1)fork创建进程 pid = os.fork() (2)multiprocessing创建进程 import multiprocessing as mp p = Process() p.start() p.join([timeout])
(3)进程池
(三)(多)线程的几种创建方式
(1)threading模块创建线程 from threading import Thread t = Thread() t.start() t.join([timeout])
(2)自定义线程类
(四)python多用多进程还是多线程,为什么
Python下推荐使用多进程!
1、先了解下GIL是什么?(全局解释器锁),为了数据安全所做的决定。
2、每个CPU在同一时间只能执行一个线程(在单核CPU下的多线程其实都只是并发,不是并行,并发和并行从宏观上来讲都是同时处理多路请求的概念。但并发和并行又有区别,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。)
3.在Python多线程下,每个线程的执行方式:
1、获取GIL
2、执行代码直到sleep或者是python虚拟机将其挂起。
3、释放GIL
可见,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。
每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。
那么是不是python的多线程就完全没用了呢?
在这里我们进行分类讨论:
1、CPU密集型代码(各种循环处理、计数等等),在这种情况下,由于计算工作多,会触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好。
2、IO密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。
所以就是CPU密集型的情况下 用进程 IO密集型的情况下用线程
请注意:多核多线程比单核多线程更差,原因是单核下多线程,每次释放GIL,唤醒的那个线程都能获取到GIL锁,所以能够无缝执行,但多核下,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低.
python下想要充分利用多核CPU,就用多进程”,原因是什么呢?
原因是:每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,所以在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。
(五)同步,互斥是什么,并且说明在python中如何体现
同步 : 同步是一种协作关系,为完成操作,多进程或者线程间形成一种协调,按照必要的步骤有序执行操作。
互斥 : 互斥是一种制约关系,当一个进程或者线程占有资源时会进行加锁处理,此时其他进程线程就无法操作该资源,直到解锁后才能操作。
死锁:是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
(六)如何避免死锁
通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率。
(七)谈谈你对http和https的理解
-
htttp:超文本传输协议,基于tcp/ip协议的应用层协议,无状态的协议,是客户端和服务端之间数据传输的格式规范,基于tcp的连接方式,80端口
-
https:是由ssl+http协议构建的可进行加密传输.身份验证的网络协议,更加安全,443端口
(八)osi七层模型,说出常用的协议以及位于哪一层
作用:网络通信工作流程标准化
种类复杂,逐渐演化更符合实际情况的四成模型(tcp/ip模型)
应用层:为用户提供所需要的各种服务(文件传输,数据格式化,代码转换,数据加密)(http,ftp,smtp)
传输层:为应用层提供端到端的通信功能,保证了数据包的顺序传送以及数据的完整性(tcp,udp)
网络层:为数据包选择路由(ip)
链路层:以二进制数据形式在物理媒体上传输数据(FDDI,PDN,SLIP)
应用层 HTTP,FTP,dns
表示层
会话层 DNS
传输层 tcp,udp
网络层 IP
数据链路层
物理层
(九)在浏览器输入url后会发生什么
域名解析 --> 发起3次握手 --> 建立TCP的3次握手 --> 建立TCP连接后发起http请求 --> 服务器响应http请求 --> 浏览器解析html代码 --> 并请求html代码中的资源 --> 四次挥手断开tcp连接 --> 浏览器对页面渲染呈现给用户
(十)tcp和udp的区别,适合在什么场景使用
-
流式套接字是以字节流方式传输数据,数据报套接字以数据报形式传输
-
tcp套接字会有粘包,udp套接字有消息边界不会粘包
-
tcp套接字保证消息的完整性,udp套接字则不能
-
tcp套接字依赖listen accept建立连接才能收发消息,udp套接字则不需要
-
tcp套接字使用send,recv收发消息,udp套接字使用sendto,recvfrom
tcp:用途广泛如传输文件,发送接收邮件,远程登录等 upd:聊天室,直播等
(十一)手写tcp客户端服务端用到的函数
(1)服务端:
-
socketfd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
-
socketfd.bind((ip,port))
-
socketfd.listen(3)
-
connfd,addr = socketfd.accept()
-
data = connfd.recv(n)
-
connfd.send(b'')
-
connfd.close()
-
socketfd.close()
(2)客户端:
-
socketfd = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
-
sockfd.bind((ip,port))
-
server_addr = (ip,port)
-
sockfd.connect(server_addr)
-
sockfd.send(data.encode())
-
sockfd.recv
-
sockfd.close()
(十二)简述三次握手和四次挥手的过程,为什么是4次挥手而不是3次挥手
(1)三次握手(建立连接)
-
一次握手:客户端通过向服务器端发送消息报文请求连接
-
二次握手:服务器端收到请求后,回复报文确定可以连接
-
三次握手:客户端收到回复,发送请求报文连接建立
举个打电话的例子:
A : 你好我是A,你听得到我在说话吗
B : 听到了,我是B,你听到我在说话吗
A : 嗯,听到了
建立连接,开始聊天!
(2)四次挥手(断开连接)
-
一次挥手:主动方发送报文请求断开连接
-
二次挥手:被动方收到请求后,立即回复,表示准别断开
-
三次挥手:被动方准备就绪,再次发送报文表示可以断开
-
四次挥手:主动方收到确定,发送最终报文完成断开
三次握手:
A:“喂,你听得到吗?”A->SYN_SEND
B:“我听得到呀,你听得到我吗?”应答与请求同时发出 B->SYN_RCVD | A->ESTABLISHED
A:“我能听到你,今天balabala……”B->ESTABLISHED
四次挥手:
A:“喂,我不说了。”A->FIN_WAIT1
B:“我知道了。等下,上一句还没说完。Balabala…..”B->CLOSE_WAIT | A->FIN_WAIT2
B:”好了,说完了,我也不说了。”B->LAST_ACK
A:”我知道了。”A->TIME_WAIT | B->CLOSED
A等待2MSL,保证B收到了消息,否则重说一次”我知道了”,A->CLOSED
(3)为什么是4次挥手而不是3次挥手
-
当主机A确认发送完数据且知道B已经接受完了,想要关闭发送数据口(当然确认信号还是可以发),就会发FIN给主机B。
-
主机B收到A发送的FIN,表示收到了,就会发送ACK回复。
-
==但这是B可能还在发送数据,没有想要关闭数据口的意思,所以FIN与ACK不是同时发送的,而是等到B数据发送完了,才会发送FIN给主机A。==
-
A收到B发来的FIN,知道B的数据也发送完了,回复ACK, A等待2MSL以后,没有收到B传来的任何消息,知道B已经收到自己的ACK了,A就关闭链接,B也关闭链接了。
(十三)cookie和session是什么,区别
-
Cookies是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器。IETF RFC 2965 HTTP State Management Mechanism 是通用cookie规范。网络服务器用HTTP头向客户端发送cookies,在客户终端,浏览器解析这些cookies并将它们保存为一个本地文件,它会自动将同一服务器的任何请求缚上这些cookies 。
-
session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。
Cookie | Session | |
---|---|---|
储存位置 | 客户端 | 服务器端 |
目的 | 跟踪会话,也可以保存用户偏好设置或者保存用户名密码等 | 跟踪会话 |
安全性 | 不安全 | 安全 |