数据结构和算法
常见的数据结构: 数组,队列,栈,链表,堆,树,哈希表
队列先进先出;Queue单项队列(from queue import Queue),deque 双向队列(from collections import deque)
栈:后进先出;基本方法:push,pop,gettop,is_empty,size
链表:经常使用两个指针来解决问题
堆:常用来找到最大值或者最小值(构建大顶堆或者小顶堆);堆排序
树:二叉树:度的最大值为2,
完全二叉树:最下一层有右子树就必须有左子树,叶子节点只能出现在倒数2层或者1层
满二叉树,
哈夫曼树:出叶子节点外,度为2,
二叉查找树即二叉排序树左子树的所有节点都比根节点小,右子树的所有节点都比根节点大,
平衡二叉树左子树和右子树的高度差不大于1
哈希表:解决冲突的方法:
开放寻址法: 这种方法指定大小,当数据较少时速度很快,节省空间。
链地址法:通过链表,数据较大时可以忽略指针的空间,可动态扩展。
建立公共溢出区:把发生碰撞的放到一个特定的空间中,适合数据不大不小。
再哈希法:会浪费一些时间。
常见的算法
排序算法:冒泡排序;快速排序,直接插入排序;希尔排序,选择排序,二路归并排序,基数排序;计数排序
搜索算法:顺序搜索,折半查找,差值查找,跳跃查找
斐波拉茄数列
# 冒泡排序:两层循环,逐个比较
def bubble_sort(seq):
for i in range(len(seq) - 1):
for j in range(i + 1, len(seq)):
if seq[i] > seq[j]:
seq[i], seq[j] = seq[j], seq[i]
return seq
# 快速排序:找到基准值,大的放右边,小的放左面
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
# 插入排序:第一个值有序,让待排元素与有序数列从后向前比较,直到有序
def insert_sort(seq):
for index in range(1, len(seq)):
while (index - 1 >= 0 and seq[index - 1] > seq[index]):
seq[index], seq[index - 1] = seq[index - 1], seq[index]
index = index - 1
return seq
# 希尔排序:缩小增量排序,插入排序是希尔排序的特殊情况。
def shell_sort(seq):
gap = len(seq) // 2
while (gap > 0):
for i in range(gap, len(seq)):
while (i - gap >= 0 and seq[i - gap] > seq[i]):
seq[i - gap], seq[i] = seq[i], seq[i - gap]
i -= gap
gap //= 2
return seq
# 二路归并排序 分分合合
def merge_sort(seq):
if len(seq) < 2:
return seq
mid = len(seq) // 2
left, right = seq[0:mid], seq[mid:]
return merge(merge_sort(left), merge_sort(right))
def merge(left, right):
result = []
while left and right:
result.append(left.pop(0) if left[0] < right[0] else right.pop(0))
if left:
result.extend(left)
if right:
result.extend(right)
return result
# 选择排序,每次选择一个最小值进行排序
def select_sort(seq):
for i in range(len(seq) - 1):
minIndex = i
for j in range(i + 1, len(seq)):
if seq[minIndex] > seq[j]:
minIndex = j
seq[i], seq[minIndex] = seq[minIndex], seq[i]
return seq
# 基数排序:先按照某个元素排序,在按照次要元素排序,最终有序, 基于关键字的排序
def radix_sort(seq):
max_length = len(str(max(seq))) # 最大长度
for i in range(max_length):
ret = [[] for i in range(10)]
# 在按照第二位进行排序的时候需要在生成新的桶
for value in seq:
ret[(value//(10**i))%10].append(value)
seq = [i for item in ret for i in item]
return seq
# 计数排序:不进行比较,让他根据空列表下标回到有序状态
def count_sort(seq:list):
'''
最简单的一种,适用于最大值比较小,并且分布在一个有限的额区间
'''
max_value = max(seq)+1
count = [0]*max_value
for value in seq:
count[value] += 1
# 得到了count数组,然后将数组元素还原
ret = []
# for value,c in enumerate(count):
# if value:
# for _ in range(c):
# ret.append(value)
for i in range(len(count)):
while count[i] > 0:
ret.append(i)
count[i] -= 1
return ret
这种计数排序无法保证稳定性
# 稳定的计数排序:通过累加数组确定某个元素的下标值
def count_sort_pro(seq:list):
max_value = max(seq)+1
count = [0]*max_value
for value in seq:
count[value] += 1
# 得到了count数组,然后将数组元素还原
# 把得到的数组进行;累加,目的是找到位数
for i in range(1, len(count)):
count[i] += count[i-1]
# 现在的数组为累加数组
# 假设给的初始数组为[1,2,2,2,2,4,4,4]
# 得到的计数数组为[0,1,4,0,3]
# 得到的累加数组为[0,1,5,5,8]
# 反向填充数组
ret = [0 for i in range(len(seq))]
for i in range(len(seq)-1,-1,-1):
ret[count[seq[i]]-1] = seq[i]
count[seq[i]] -= 1
return ret
# 顺序搜索不说了
# 折半查找:又叫做二分查找,需要有序。
def binary_search(sorted_seq, target):
left = 0
right = len(sorted_seq) - 1
while (left <= right):
mid = (left + right) // 2
if sorted_seq[mid] == target:
return mid
if sorted_seq[mid] > target:
right = mid - 1
if sorted_seq[mid] < target:
left = mid + 1
return None
# 折半查找太傻了,每次都二分,其实可以根据target值的大小大体估计元素所在位置
# 就像在字典中找dear,你会靠前翻,而不是从中间去确认。
def insert_search(sorted_seq, target):
left = 0
right = len(sorted_seq) - 1
if target < sorted_seq[left] or target > sorted_seq[right]:
return None
while (left <= right):
if (sorted_seq[right] == sorted_seq[left]):
return None
mid = left + ((right - left) * (target -
sorted_seq[left])) // (sorted_seq[right] - sorted_seq[left])
if sorted_seq[mid] == target:
return mid
if sorted_seq[mid] > target:
right = mid - 1
if sorted_seq[mid] < target:
left = mid + 1
return None
# 把它想象成一个均匀增加的数列,于是根据相似定理有:
# (target-seq[left])/mid-left <=>(seq[right]-seq[left])/right-left
# 从零开始跳跃固定步数,当某值大于target时,target的返回就锁定在当前下标的前面,
# 当前下标减去步数的后面,没有就说明没有找到target
def jump_search(sorted_seq, target):
if target > len(sorted_seq):
return None
step = int(math.floor(math.sqrt(len(sorted_seq))))
base = 0
while sorted_seq[base] < target:
base += step
for i in range(base - step, base + 1):
if sorted_seq[i] == target:
return i
# 基于yield实现
def fab(n):
a, b, count = 0,1,0
while count < n:
yield b
a, b = b, a+b
count += 1
计算机网路
osi:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
tcp/ip:应用层,传输层,网络层,网络接口层
5层:应用层,传输层,网络层,数据链路层,物理层
HTTP:
http是一个以ascii码值进行传输,建立在tcp/ip协议之上的应用层规范
它是以请求/响应进行工作的,其中请求包括请求头请求体和状态行,状态行包括请求方法,请求地址和当前的http版本号。响应包括状态行,响应头和响应体,状态行包括数字的状态信息以及提示信息。因为http是状态的,当我们开发一些需要用户状态的接口时就需要用到会话追踪。
会话追踪,简单来说,会话追踪是一类用户和服务器之间保持状态的解决方案,当用户打开多个页面和可以判断是否为同一用户。
实现会话追踪的方式:url带参数,表单隐藏域,cookie,session。
其中cookie是服务器发送给客户端的一小部分数据,客户端请求时读取改数据并提交,客户端的每次请求都会返回cookie,cookie保存在客户端的方式有两种:一种是存放在内存中,称为临时cookie,当用户关闭标签时cookie对象就不存在了;还有一种放在磁盘上,称为永久cookie,在cookie的有效期内,当客户端在此访问网站时就会携带这一部分cookie。
session是服务器为每个用户开辟的独享的存储空间,当用户访问网站时,创建session对象并生成sessionID,服务器将session放到cookie中发送给客户端,因此,session的实现是依赖于cookie的。
CSRF:跨站请求伪造
虽然会话追踪可以判断是否为同一个用户,但是并不能确定请求是用户想要发起的,例如某攻击者发送了一个支付的链接给用户,用户点击后钱直接没了,所以要加入一些安全措施。
验证码,与用户进行交互来确定是来自于用户的操作,但是大量的验证信息会使网站不友好。csrf攻击能够成功的原因就是攻击者能够预测参数并构建出一个合法的请求,所以可以通过将参数进行加密的方式来预防攻击,另一个做法就是保持原有参数不变,增加一个Token参数,这个值必须要足够的随机,并且每次请求后都要重新赋值,确保参数的不可预测性。
keep-alive:如果客户端请求数较多,使用请求响应的方式就是多次的连接断开,造成资源的浪费,所以可以存在一个TCP连接,在一段时间内,一定的请求数,该链接可以复用。
HTTPS
HTTPS就是http的安全版,增加了tls来保证数据的安全性
TLS的工作原理:
客户端发送一条信息其中包含可供选择的加密算法,压缩算法,以及版本号
服务器向客户端发送选择的加密算法,压缩算法,版本号,CA证书(其中包含公钥)
客户端判断是否选择证书,若相信,生成一串伪随机数,通过公钥进行加密发送给服务器端
服务器端使用私钥就行解密,然后根据这串随机数得到对称加密秘钥
协商好对称加密秘钥之后,接下来的内容使用该秘钥进行加密传输
TCP
TCP提供了一种面向连接的,可靠的字节流服务
怎么保证可靠:通过确认重传,校验和,合理的分片和排序,滑动窗口实现流量控制,动态的改变窗口的大小来实现拥塞控制。
三次握手:首先客户端向服务器端发送SYN同步信号和序列号seqx,服务器收到后发送ack=1,ACKnumber=seqx+1,seqy,SYN,此时服务器处于半链接状态,然后客户端收到信息,发送确认信号与服务器进行连接
SYN攻击:SYN攻击指的是攻击者构造大量不存在的IP地址向服务器发送请求连接,服务器收到信号后向ip地址发送确认信号,由于ip地址不存在,导致服务器会一直重传,占用未连接队列,正确的SYN包被丢弃,让系统运行缓慢。
SYN攻击的解决方案:增加未连接队列的长度,缩短超时时间,syncookie技术(大体理解就是在发送完syn和ack后先不分配资源,计算出cookie值,这个cookie值作为synack的初始序列,然后得到客户端的ack后验证cookie在进行连接)
四次挥手:客户端传输完数据发送FIN,服务器端接收后返回ack,继续发送数据,发送完数据向客户端发送FIN,客户端接收信息后发送ack并进入等待状态time_wait,服务器端接收后断开连接。客户端等待的原因是:1。保证自己的ack确认信号被服务器端接收了,若服务器会超时重传,而客户端因断开连接接收不到数据造成服务器资源的浪费。2。2MSL是报文在网络上的最大存活时间,保证客户端断开连接后不会有报文去干扰服务器。
Keepalive当很长时间内客户端没有发送数据,服务器向它发送一个空的数据,如果收到回应,表示客户端没有关机,如果多次无回应,说明客户端可能异常关机了,应该断开连接。
滑动窗口 - 拥塞窗口:
UDP
同属于传输层协议,缺乏可靠性不能保证数据发送到接受党,也不能保证发送的数据是否有序,也没有确认重传的机制,面向无连接,报文有固定的长度。
IP
网络层协议,提供点对点的通信。
软件工程
24种设计模式
创建型:工程方法、抽象工厂,原型、单例、建造者
结构型:适配器、桥接、组合、外观、装饰器、享元、代理
行为型:解释器、模板方法、责任链、命令、迭代、中介者、备忘录、观察者、状态、策略、访问者
实现:<<<< 转载 >>>>
6大原则
单一职责原则:一个类只有单一的一种职责。
里氏替换原则:父类能出现的地方子类也可以出现,反之不行。
依赖倒置原则:通过抽象使各类或模块的实现彼此独立,互不影响
接口隔离原则:建立单一的接口,不要建立臃肿的接口,尽量细化。
迪米特法则:最少知道原则,对基类的了解越少越好
开闭原则:对扩展开放,对修改关闭。
Django的知识点
从输入网址到显示页面,发生了什么?
Django的内置组件有哪些?contenttypes组件的作用?
列举中间件的五种方法,以及中间件的使用场景?
FBV和CBV?CBV添加装饰器的方式?
Django的ORM的方法,以及写sql的方式?
F和Q,values和values_list?
F对象可以比较两个字段的值,或者更新某个字段的值,Q对象是将多个查询条件通过与或非的形式连接 起来,values得到的是一个是字典的格式{‘key’:value,},而values_list返回的是元组的形式(value,)
缓存的级别,设置方法?
celery怎么使用,用什么做消息队列?
request.META有什么?
里面包括请求头的信息,它是字典的形式, 对于常用的字段:remote_addr,可以通过request.META.get(’ HTTP_REMOTE_ADDR ')的方式获取全部大写,加上HTTP_,对于自定义的字段,直接大写,加上前缀HTTP_
什么是WSGI,uWSGI,uwsgi?
WSGI和uwsgi都是一种协议,其中WSGI规定了web服务器和web应用是如何通信的,而uwsgi则是一个实现了这两种协议的服务器,当然他也实现了ip协议等。
DRF知识点
问题
什么是restful规范?
1 尽量使用https协议
2 对于api接口尽量放在专有的域名下,或者通过url体现
3 版本,可以方便的进行版本的迭代
4 网络上的所有东西都是资源,所以网址中不能有动词,若是一个集合,应该使用复数形式
5 对于资源的具体操作通过HTTP动词实现,一般有get,put,post,patch,delete等
6 对于资源数目较多时,应该使用限制条件进行过滤,适当的分页。
7 状态码,200,300,403,404,500等
8 错误处理,要有对错误信息的描述
9 返回结果,针对不同的http动词应该有不同的返回结果
10 Hyperlink api,对于列表中的信息,每个对象都是可访问的。
DRF的源码流程?
从url开始,找到对应的视图类,进入视图类执行as_view方法,这个方法进行了重写,返回了一个免除csrf验证的view函数,这个函数没有重写,所以要从父类View中找,这个方法返回self.dispatch(),然后进入dispatch()方法, 在dispatch方法中显示对request对象进行封装,封装包括之前的request对象,多了parsers和authentications,这两个对象都是通过遍历类中的classes列表,然后实例化对象,所以现在parsers就是一个装有解析器类实例的列表,authentications也是一样,因为在访问时多次用到,所以放在一个变量中,无需多次实例化。然后在回到dispatch函数中,到下一部,将该类进行初始化,初始化的时候version, scheme = self.determine_version(),该该方法调用的是scheme的determine_version方法,而scheme就是版本类的实例,所以版本类应该返回一个元祖,一个是版本号,另一个是版本类。版本进行完就到了权限认证节流 ,performe_authentication, check_permission, check_throttle, 认证返回的是request.user, 其他两个都是遍历classes列表然后实例化对象,并执行核心函数。request.user是一个方法,他会通过反射机制看request中有没有_user,如果没有,就执行_authenticate方法,该方法和check_permission方法差不多,遍历认证实例,然后执行对应的核心函数。当使用request.data时,这个方法和request.user是一样的,首先通过反射的方式看_data,存在返回,不存在就执行load_data_and_file方法,这个方法会去找到parsers,找到对应的解析方式,然后调 用核心方法进行解析。
如何解决跨域问题?
一. JsonP:ajax支持jsonp格式,type:jsonp, 后端有支持jsonp的解析器就可以使用
二. 直接修改响应 : response = HttpResponse(json.dumps({"key": "value", "key2": "value"}))
response["Access-Control-Allow-Origin"] = "*"
response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"
response["Access-Control-Max-Age"] = "1000"
response["Access-Control-Allow-Headers"] = "*"
return response
三. 安装django-cors-headers模块,然后进行简单的配置 # 安装该模块,并将该模块注册到app中,然后在中间件最前面加上
'corsheaders.middleware.CorsMiddleware',
# 配置:https://github.com/adamchainz/django-cors-headers
四. 通过Nginx代理,在Nginx配置中,将发送的请求设置路由匹配,交给某个模块进行处理例如访问的是一个图片,而访问图片端口是8888,可以经过nginx,发送给fdfs_module模块处理或者代理到真正的服务器地址。
组件
认证
自定义的认证类需要重写authenticate()方法,返回一个元组(user,auth),或者抛出异常。
设置:在CBV中继承自APIView,为authentication_classes = [ My_authentication, ],未认证用户的设置{ 'UNAUTHENTICATED_USER':None},未认证的token设置{ 'UNAUTHENTICATED_TOKEN':None,}
全局设置:在settings中设置REST_FRAMEWORK= {'DEFAULF_AUTHENTICATION_CLASSES':'My_authentication'}
权限
自定义继承并重写has_permission,返回True or False
设置permission_classes = [My_permission,]
全局{'DEFAULT_PERMISSION_CLASSES':'My_permission'}
节流
自定义 1.1 若继承自SimpleThrottle,需要重写get_cache_key()方法,返回一个唯一标识,例如return request.META.get('REMOTE_ADDR') 1.2 若继承自Base_Throttle,需要重写allow_request()方法,返回True或者False 2. 设置访问频率可以通过再类中设置rate=‘3/m’3次每分,或者通过设置scope= ‘throttle_name’,然后在全局中设置scope的频率, 'DEFAULT_THROTTLE_RATES':{ 'throttle_name':'3/m', }
设置 throttle_classes = [My_Throttle,]
全局 {'DEFAULT_Thtottle_CLASSES':'My_throttle'}
版本
默认 URLVersion类,自定义至少重写determine_version()方法,根据reverse实现的。
全局'DEFAULT_VERSIONING_CLASS':'rest_framework.versioning.URLPathVersioning',
路由的使用路由使用,加上正则:url or re_path(r'^(?P[v1|v2]+)/order/$', OrderView.as_view())
序列化
自定义序列化类
# 类的继承
class Serializer(serializers.Serializer):
# 字段的定义: 与模型类相同,或者指定source,或者自定义。
username = serializers.CharFiled()
pwd = serializers.CharFiled(source = 'password') #
user_type = serializers.CharField(source = 'get_user_type_display') # Choice字段获取值
group = serializer.Charfield(source = 'group.title') # 外键
roles = serializers.SerializerMethodField() # 同时要定义函数get_字段
def get_roles(self,userinfo):# 传入的值是当前的模型类的实例
roles_obj = userinfo.roles.all()
ret = []
for item in roles_obj:
ret.append(
{'id':item.id, 'title':item.title }
)
return ret
# 特殊字段,view_name是详情页的视图namespace:app_name
# pk为默认值,路由中使用pk即可
# id通常为主键
group = serializers.HyperlinkedIdentityField(view_name='app:gp',lookup_url_kwarg='pk',lookup_field='id')
# 如果是外键属性则使用lookup_field='groud_id' ####
class Serializer(serializers.ModelSerializer):
class Meta:
model = UserInfo # 根据这个模型类,自动生成它的字段
# fields = '__all__'
fields = ['id','username', 'password', 'usertype', 'roles', 'group']
depth = 1 # 深度为1
序列化的使用
# 当有HyperLink字段时需要传入context字段,结果集many=True
ser = UserSerializer(instance=user,many=False,context={'request':request})
return Response(ser.data)
反序列化
反序列化
首先将前台数据reques.data传过来,得到一个序列化的实例
判断数据是否合法,在序列化类中执行校验(局部钩子和全局钩子)
通过校验:保存数据,执行序列化对象的save()方法,前提是实现序列化类的create()方法。
不通过:返回序列化对象的错误信息
在创建序列化对象的时候,在Fied中(required = True,max_length = 12 ,
# error_messages={'required':'这个字段必须','max_length':'太长了'})
# 这些是基本的校验规则。
ser = UserSerializer(data=request.data) # 不指定data会默认赋值给instance
#创建一个序列化对象,将data传进去
if ser.is_valid(): # 然后作判断,类似于form表单
# print(ser.validated_data['key'])
# print(ser.validated_data)
ser.save()
else:
print(ser.error_messages)
数据校验–钩子函数
# 局部钩子
def validate_name(self,value): # name是校验字段的名字,value是字段的额值
if 'j' in value.lower():
raise exceptions.ValidationError('名字中不能有j')
# raise exceptions.ValidationError({' 自定义键 ':' 自定义错误信息 '})
else:
return value
# 全局钩子 ,对于不需要入库的字段可以在全局钩子中,attrs.pop()出去,attrs中是入库字段。
def validate(self,attrs): # attrs保存所有字段的值
if attrs.get('pwd').lenght < 8:
raise exceptions.ValidationError('太短')
else:
return attrs
解析器
设置 :parse_classes = [JSONParse,FormParse]
使用:使用request.data时候就自动调用了解析器,到的解析后的数据。
分页
内置的分页器:PageNumberPagination , LimitOffsetPagination, CursorPagination,其中CursorPagination需要指定ordering = ‘id’。
使用:
roles = models.Role.objects.all()
pg = PageNumberPagination()
pg_roles = pg.paginate_queryset(queryset = roles,request = request,view=self)
ser = RolesSerializer(instance = pg_roles,many = True)
return pg.get_paginated_response(ser.data)
渲染器
渲染器使用不多:和其他组件的设置相同
路由
使用
from rest-framework.routes import Simpleroute
route = Simpleroute()
# route.register('prefix', 'viewset')
route.register('user', 'UserViewset')
# 会自动生成2个路由,一个包含list方法,另一个包含delete,update等方法
url('',include(route.urls))
视图
1. APIView:最常见的View,可定制性最高
2. GenericAPIVIew:
规范化了操作,但是没有简化操作,需要在视图中配置分页信息,序列化信息,以及要操作的序列:queryset。
使用self.get_serializer()获取序列化对象,self.get_queryset()获取结果集。
self.paginator来进行操作,所以没简化的意思是怎么做的还是要怎么做,之不同调用的api不同了:roles = self.get_queryset()
pg_roles = self.paginate_queryset(roles)
ser = self.get_serializer(pg_roles,mant=True) 3. GenericViewSet:
重写了as_view方法,需要在路由中需要传入字典,继承GenericAPIVIew。
4. ModelViewSet:
继承6个类,增, 删, 查, 查全部,改,GenericViewSet,定制化最低的类,但是对于简单的工作写起来方便,再使用自动生成路由美滋滋。
5. 各类增删查改的ViewSet
可以自定义类继承它们和GenericViewSet来实现定制化 的操作。