047 Python面试知识点小结
一.Python基础
1.Python语言特性:
动态型(运行期确定类型,静态型是编译型确定类型),强类型(不发生隐式转换,弱类型,如PHP,JavaScript就会发生隐患式转换)
2.Python作为后端语言的优缺点:
优点:
胶水语言,轮子多,应用广泛;语言灵活,生产力高
缺点:
性能问题,代码维护问题,python2/3不兼容问题
3.鸭子类型:
“当一只鸟走起来像鸭子,游泳像鸭子,叫起来像鸭子,那么这只鸟就能被称为鸭子”
关注点在对象的行为,而不是类型;
如file,StringIO,socket都支持read/write方法(file类型)
定义了__iter__就可以用for迭代
4.monkey patch(猴子补丁):
就是运行时替换
4.1比如gevent需要修改内置的socket和select:
4.2自己写的猴子补丁:
5.is和==:
is是判断是否为同一个对象(id内存地址是否相同),==是判断值是否相同
6.列表,生成器,字典推导:
7.Python之禅:
Tim Peters编写的关于Python编程的规则;
import this查看,编程拿不准可以查看:
优美胜于丑陋(Python 以编写优美的代码为目标)
明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)
简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)
复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)
扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套)
间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)
可读性很重要(优美的代码是可读的)
即便假借特例的实用性之名,也不可违背这些规则(这些规则至高无上)
不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写 except:pass 风格的代码)
当存在多种可能,不要尝试去猜测
而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法)
虽然这并不容易,因为你不是 Python 之父(这里的 Dutch 是指 Guido )
做也许好过不做,但不假思索就动手还不如不做(动手之前要细思量)
如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然(方案测评标准)
命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)
8.Python2/3的差异:
Python3改进:
print改为函数,可以指定sep参数;
编码问题:Python3不再有Unicode对象,默认str就是Unicode
除法变化,Python3除号(/)返回浮点数,Python2向下取整,Python3向下取整为(//)
类型注解(type hint)。帮助IDE实现类型检查;
主要是提示作用,检查可以用mypy包
优化的super()函数方便直接调用父类函数;
高级解包操作:a,b,*rest=range(10)
限定关键字参数(Keyword only arguments),防止把数据搞混
Chained exceptions。Python3重新抛出异常不会丢失栈信息(有利于排错),raise from保留栈信息(raise NotImplementedError("haha") from OSError)
一切返回迭代器range,zip,map,dict.values,etc(节省内存)
生成的pyc文件统一放到__pyache__;
一些内置库的修改,如urllib,selector等;
性能优化等
Python新增:
yield from链接子生成器;
asyncio内置库,async/await原生协程支持异步编程;
新的内置库enum(枚举),mock(单测),asyncio(异步),ipaddress(处理ip地址),concurent.futures等
Python/2/3兼容工具:
six模块;2to3等工具转换代码;__future__
9. Python函数常考题:
Python如何传递参数:
传递值还是引用?都不是,唯一支持的参数传递是共享传参;
Call by Object(Call by Object Reference or Call by Sharing)
共享传参,函数形参获得实参种各个引用的副本
ll指向[1,2,3],l也指向[1,2,3],后又指向[],所以ll仍为[1,2,3],l为[]
默认参数只计算一次
Python可变,不可变对象:
不可变对象:bool/int/float.str/frozenset;
可变对象:list/set/dict
Python *args,**kwargs:
用来处理可变参数,*args会被打包成tuple,**kwargs会打包成dict
10.Python异常处理机制:
什么是Python的异常:
Python使用异常处理错误(有些语言使用错误码,如C)
BaseException(最低层异常)
SystemExit,Timeout(超时),KeyboradInterrupt(ctrl+c),GeneratorExit(生成器退出异常),继承Exception(继承BaseException)
使用异常常见场景:
网络超时(超时,资源错误);
资源问题(权限问题,资源不存在)
代码逻辑(越界访问,KeyError等)
如何处理:
try: #fun #可能抛出异常的代码块 except (Exception1,Exception2) as e: #可以捕获多个异常并处理 #异常处理的代码 else: #pass #异常没有发生的时候代码逻辑 finally: pass #无论异常有没有发生都会执行的代码,一般处理资源的关闭和释放
如何自定义异常,作用:
通过继承Exception实现定义异常(为什么不是BaseException,ctrl+c不能结束,BaseException是所有异常的基类,KeyboradInterrupt和SystemExit,GeneratorExit,Exception都继承于它,如果继承BaseException,可能捕获不到一些异常。)
给异常加一些附加信息;
处理一些业务相关的特定异常(raise MyException)
10.Python性能分析与优化,GIL常考题:
GIL(Global Interpreter Lock):全局解释器锁
Cpython解释器的内存管理并不是线程安全的;
保护多线程情况下对Python对象的访问;
Cpython使用简单的锁机制避免多个线程同时执行字节码。
GIL的影响:
限制了程序的多核执行:
同一个时间只能有一个线程执行字节码;
CPU密集程序难以利用多核优势;
IO期间会释放GIL,对IO密集程序影响不大
如何规避GIL影响:
CPU密集可以用多进程+进程池;
IO密集使用多线程/协程
cython扩展
Python中什么操作才是原子的?一步到位执行完:
一个操作如果一个字节码指令可以完成就是原子的;
原子的是可以保证线程安全的;
使用dis操作来分析字节码。
如何剖析程序性能:
使用profile工具(内置或第三方)
二八定律:大部分时间耗时在少量代码上;
内置的profile/cprofile等工具;
使用pyflame(uber开源)的火焰图工具
服务端性能优化措施:
web应用一般语言不会成为瓶颈:
数据结构与算法优化;
数据库层:索引优化,慢查询消除,批量操作减少IO,NoSql
网络IO:批量操作,pipeline操作减少IO
缓存:使用内存数据库 redis。memcached
异步:asyncio,celery
并发:gevent/多线程
11.生成器和协程:
生成器:
生成器就是可以生成值的函数;
当一个函数里有了yield关键字就成了生成器;
生成器可以挂起并保持当前执行状态
基于生成器的协程:
Python3之前没有原生的协程,只有基于生成器的协程
pep342增强生成器功能;
生成器可以通过yield暂停执行和产出数据;
同时支持end()向生成器发送数据和throw()向生成器抛异常
协程注意点:
协程使用send(None)或者next(coroutine)来预激(prime)才能启动;
在yield出协程会暂停执行;
单独的yield value会产出值给调用方;
可以通过coroutine.send(value)来给协程发送值,发送的值会赋值给yield表达式左边的变量value=yield
协程执行完成后(没有遇到下一个yield语句)会抛出StopIteration异常
避免每次都使用send预激它(from functool import wraps)@wraps装饰器向前执行一个yield表达式,预激
Python3.5引入async/await支持原生协程
12.Python单元测试:
什么是单元测试:
针对程序模块进行正确性检验;
一个函数,一个类进行验证;
自底向上保证程序正确性
为什么要写单元测试:
三无代码不可取(无文档,无注释,无单测)
保证代码逻辑的正确性(甚至有些采用测试驱动开发(TDD))
单测影响设计,易测的代码往往是高类聚低耦合的;
回归测试,防止一处修改整个服务不可用
单元测试库:
nose/pytest较为常用;
mock模块用来模拟替换网络请求等
coverage统计测试覆盖率
Python深拷贝和浅拷贝:
深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变。
浅拷贝相当于拷贝了对象的引用(在python中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用)
Python创建二维数组:
https://www.cnblogs.com/woshare/p/5823303.html
二.算法和数据结构:
1.常用的内置算法结构:
sorted;
dict/list/set/tuple....
collections中常用模块:
namedtuple:让tuple属性可读(赋予名字)
deque:双端队列(可以在队列前后操作,方便实现queue/statck)
Counter:实现计数的功能
OrderDict:可以记住key第一次进入的位置(有序的)
defaultdict():键不存在时不会报错(带有默认值的字典)
dict低层使用的hash表:
为了支持快速查找使用了哈希表作为低层结构;
哈希表平均查找复杂度为O(1);
CPython解释器使用二次探查解决哈希冲突问题(如何解决哈希冲突,哈希扩容)
list vs tuple:
都是线性结构,支持下标访问;
list是可变对象,tuple是保存的引用不可变(指的是没法替换掉这个对象,但是如果对像本身是一个可变对象,是可以修改这个引用指向的可变对象的);
list没法作为字典的key,tuple可以(可变对象不可hash)
LRUCache:
Least-Recently-Used替换最少使用的对象
缓存剔除策略,当缓存空间不够用的时候需要一种方式剔除key
常见的有LRU,LFU
LRU通过使用一个循环双端队列不断把最新访问的key放到表头实现
实现:
字典利用缓存,循环双端链表用来记录访问顺序:
利用Python内置的dict+collections.OrderdDict实现;
dict用来当作k/v键值对的缓存;
OrderedDict用来实现最近访问的key
实现代码:
1 from collections import OrderedDict 2 3 class LRUCathe: 4 def __init__(self,capacity=120): 5 self.od=OrderedDict() 6 self.capacity=capacity 7 8 def get(self,key): 9 if key in self.od: 10 val=self.od[key] 11 self.od.move_to_end(key) 12 return val 13 else: 14 return -1 15 # 更新k/v 16 def put(self,key,value): 17 if key in self.od: 18 del self.od[key] 19 #更新key到表头 20 self.od[key]=value 21 else: 22 self.od[key]=value 23 #判断当前容量是否满了 24 if len(self.od)>self.capacity: 25 self.od.popitem(last=False)
2.算法常考题:
排序,查找等。。。。
排序的稳定性:
相同大小的元素排序之后保持相对的位置不变,就是稳定的。稳定性对于排序一个复杂的结构,并且保持原有排序才有意义。
快排:(分治法)
选择基准分割数组为两个字数组,小于基准和大于基准的;
对两个子数组分别快排;
合并结果。
1 '''快速排序: 2 时间平均复杂度:O(nlog2n) 3 最好情况:O(nlog2n) 4 最坏:O(n^2) 5 空间复杂度:O(nlog2n) 6 排序方式:内部排序 7 稳定性:不稳定 8 ''' 9 def quicksort(arr): 10 if len(arr)<2: 11 return arr 12 13 pivot_index=0 14 pivot=arr[pivot_index] 15 less_pivot=[i for i in arr[pivot_index+1:] if i <=pivot] 16 great_pivot=[i for i in arr[pivot_index+1:] if i > pivot] 17 return quicksort(less_pivot)+[pivot]+quicksort(great_pivot) 18 print(quicksort([3,4,5,6]))
归并排序:
1 '''归并排序: 2 时间平均复杂度:O(nlog2n) 3 最好情况:O(nlog2n) 4 最坏:O(nlog2n) 5 空间复杂度:O(n) 6 排序方式:外部排序 7 稳定性:稳定 8 ''' 9 10 11 def mergeSort(arr): 12 import math 13 if len(arr)<2: 14 return arr 15 middle=math.floor(len(arr)/2) 16 left,right=arr[:middle],arr[middle:] 17 return merge(mergeSort(left),mergeSort(right)) 18 19 def merge(left,right): 20 result=[] 21 while left and right: 22 if left[0]<=right[0]: 23 result.append(left.pop(0)) 24 else: 25 result.append(right.pop(0)) 26 while left: 27 result.append(left.pop(0)) 28 while right: 29 result.append(right.pop(0)) 30 return result 31 if __name__=='__main__': 32 print(mergeSort([3,2,1,5,6,8,10,44,51,31,0,49]))
二分查找:
1 def binary_search(arr,val): 2 if not arr: 3 return -1 4 beg=0 5 end=len(arr)-1 6 while beg<=end: 7 mid=int((beg+end)/2) 8 if arr[mid]==val: 9 return mid 10 elif arr[mid]>val: 11 end=mid-1 12 else: 13 beg=mid+1 14 return -1 15 print(binary_search([1,3,5,7,0,12],12))
3.数据结构:
常考的数据结构:
数据结构链表,队列,栈,二叉树,堆
使用内置结构实现数据结构,比如内置的list/deque实现栈
leetcode或者《剑指offer》常见题
常见数据结构之链表:
链表有单链表,双链表,循环双端链表:
如何使用Python来表示链表结构;
实现链表常见操作,比如插入节点,反转链表,合并多个链表;
Leetcode练习常见链表题目
反转链表:(合并链表,求链表公共值)
1 # Definition for singly-linked list. 2 # class ListNode(object): 3 # def __init__(self, x): 4 # self.val = x 5 # self.next = None 6 7 class Solution(object): 8 def reverseList(self, head): 9 """ 10 :type head: ListNode 11 :rtype: ListNode 12 """ 13 pre=None 14 cur=head 15 while cur: 16 nextnode=cur.next 17 cur.next=pre 18 pre=cur 19 cur=nextnode 20 return pre
队列是先进先出结构:
如何实现队列;实现队列的append和pop操作,如何做到先进先出;使用Python的list或者collections.deque实现队列
栈是后进先出:
1 from collections import deque 2 3 class Stack(object): 4 def __init__(self): 5 self.item=deque() 6 7 def push(self,value): 8 self.item.append(value) 9 10 def pop(self): 11 return self.item.pop() 12 13 s=Stack() 14 s.push(1) 15 s.push(2) 16 print(s.pop()) 17 print(s.pop())
Python的dict和set低层实现都是hash表:
hash表的低层实现原理其实就是数组;
根据哈希表可以很快定位一个元素,平均查找为O(1);
不断加入元素会引起hash表从新开辟新空间,拷贝之前的元素到新空间
解决哈希冲突:
链接法:
元素key冲突之后使用一个链表填充相同key的元素;
开放寻址法:
冲突之后根据一定方式(二次探查)寻找下一个可用的槽
二叉树:
先序(根->左->右),中序(左->根->右),后序(左->右->根)
堆:
最大堆:对于每个非叶子节点V,V的值都比它的两个孩子大;
最大堆支持每次pop操作获取最大的元素,最小堆获取最小的元素;
常见问题:用堆来完成topk问题,从海量数据中寻找最大的k个
堆解决topk的问题:
获取大量元素,固定内存:
思路:1.先放入元素前k个建立一个最小堆;2.迭代剩余元素:如果当前元素小于栈顶元素,跳过该元素(肯定不是前k大),否则替换栈顶元素为当前元素,并从新调整堆
1 import heapq 2 3 class TopK: 4 def __init__(self,iterable,k): 5 self.minheap=[] 6 self.capacity=k 7 self.iterable=iterable 8 9 def push(self,val): 10 if len(self.minheap)>=self.capacity: 11 min_val=self.minheap[0] 12 if val<min_val: 13 pass 14 else: 15 #返回并替换pop堆顶最小值,插入新的值并调整堆 16 heapq.heapreplace(self.minheap,val) 17 else: 18 #前面k个元素直接放入minheap 19 heapq.heappush(self.minheap,val) 20 def get_topk(self): 21 for val in self.iterable: 22 self.push(val) 23 return self.minheap 24 25 tk=TopK([1,3,5,6,0,3,44,11,42,0,1,9999],4) 26 print(tk.get_topk())
链表:
删除节点:
1 # Definition for singly-linked list. 2 # class ListNode(object): 3 # def __init__(self, x): 4 # self.val = x 5 # self.next = None 6 7 class Solution(object): 8 def deleteNode(self, node): 9 """ 10 :type node: ListNode 11 :rtype: void Do not return anything, modify node in-place instead. 12 """ 13 nextnode=node.next 14 after_node=node.next.next 15 node.val=nextnode.val 16 node.next=after_node
合并链表:
1 # Definition for singly-linked list. 2 # class ListNode(object): 3 # def __init__(self, x): 4 # self.val = x 5 # self.next = None 6 7 class Solution(object): 8 def mergeTwoLists(self, l1, l2): 9 """ 10 :type l1: ListNode 11 :type l2: ListNode 12 :rtype: ListNode 13 """ 14 root=ListNode(None) 15 cur=root 16 while l1 and l2: 17 if l1.val<l2.val: 18 node=ListNode(l1.val) 19 l1=l1.next 20 else: 21 node=ListNode(l2.val) 22 l2=l2.next 23 cur.next=node 24 cur=node 25 #l1和l2可能还有剩余值 26 # if l1 is None: 27 # cur.next=l2 28 # else: 29 # cur.next=l1 30 cur.next = l1 or l2 31 return root.next
二叉树:
二叉树的镜像(反转):
1 # Definition for a binary tree node. 2 # class TreeNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.left = None 6 # self.right = None 7 8 class Solution: 9 def invertTree(self, root: TreeNode) -> TreeNode: 10 if root==None: 11 return root 12 else: 13 self.invertTree(root.left) 14 self.invertTree(root.right) 15 root.left,root.right = root.right,root.left 16 return root
层序遍历二叉树(广度优先):
1 # Definition for a binary tree node. 2 # class TreeNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.left = None 6 # self.right = None 7 8 class Solution: 9 def levelOrder(self, root: TreeNode) -> List[List[int]]: 10 if root: 11 res=[] 12 cur_nodes=[root] 13 next_nodes=[] 14 res.append([i.val for i in cur_nodes]) 15 while cur_nodes or next_nodes: 16 for node in cur_nodes: 17 if node.left: 18 next_nodes.append(node.left) 19 if node.right: 20 next_nodes.append(node.right) 21 if next_nodes: 22 res.append([i.val for i in next_nodes]) 23 cur_nodes=next_nodes 24 next_nodes=[] 25 return res 26 else: 27 return []
栈和队列:
栈实现队列:
1 from collections import deque 2 class Stack(object): 3 def __init__(self): 4 self.item=deque() 5 6 def pop(self): 7 return self.item.pop() 8 def push(self,val): 9 self.item.append(val) 10 def empty(self): 11 return len(self.item)==0 12 13 def top(self): 14 return self.item[-1] 15 16 class MyQueue: 17 def __init__(self): 18 """ 19 Initialize your data structure here. 20 """ 21 self.s1=Stack() 22 self.s2=Stack() 23 24 def push(self, x: int) -> None: 25 """ 26 Push element x to the back of queue. 27 """ 28 self.s1.push(x) 29 30 def pop(self) -> int: 31 """ 32 Removes the element from in front of queue and returns that element. 33 """ 34 if not self.s2.empty(): 35 return self.s2.pop() 36 while not self.s1.empty(): 37 val=self.s1.pop() 38 self.s2.push(val) 39 return self.s2.pop() 40 41 def peek(self) -> int: 42 """ 43 Get the front element. 44 """ 45 if not self.s2.empty(): 46 return self.s2.top() 47 while not self.s1.empty(): 48 val = self.s1.pop() 49 self.s2.push(val) 50 return self.s2.top() 51 52 def empty(self) -> bool: 53 """ 54 :return: 55 """ 56 return self.s1.empty() and self.s2.empty()
堆:
合并多个有序数组;TopK问题。
堆是完全二叉树,有最大堆和最小堆。
使用heapq实现堆的操作;
合并k个有序链表
1 # Definition for singly-linked list. 2 # class ListNode: 3 # def __init__(self, x): 4 # self.val = x 5 # self.next = None 6 from heapq import heapify,heappop 7 8 class Solution: 9 def mergeKLists(self, lists: List[ListNode]) -> ListNode: 10 h=[] 11 for node in lists: 12 while node: 13 h.append(node.val) 14 node=node.next 15 if not h: 16 return None 17 #构建一个最小堆 18 heapify(h) 19 #构建链表 20 root=ListNode(heappop(h)) 21 curnode=root 22 while h: 23 nextnode=ListNode(heappop(h)) 24 curnode.next=nextnode 25 curnode=nextnode 26 return root 27 28 29
字符串:
翻转字符串:
1 class Solution: 2 def reverseString(self, s: List[str]) -> None: 3 """ 4 Do not return anything, modify s in-place instead. 5 """ 6 beg=0 7 end=len(s)-1 8 while beg<end: 9 s[beg],s[end]=s[end],s[beg] 10 beg+=1
判断一个数字是否是回文数:
class Solution: def isPalindrome(self, x: int) -> bool: if x<0: return False s=str(x) beg,end=0,len(s)-1 while beg<end: if s[beg]==s[end]: beg+=1 end-=1 else: return False return True
三.面向对象编程和设计模式
1.什么是面向对象编程:
把对象作为基本单元,把对象抽象成类,包括成员和方法;
数据封装,继承,多态;
Python中使用类来实现。过程式编程(函数),OOP(类)
2.组合和继承:
组合是使用其他类的实例作为自己的一个属性;
子类继承父类的属性和方法(Is a关系);
优先使用组合保持代码简单。
3.类变量和实例变量的区别:
类变量是所有实例共享;
实例变量由实例单独享有,不同实例之间不影响;
当我们需要在一个类的不同实例之间共享变量的时候使用类变量。
1 class Person: 2 Coutry='China' 3 def __init__(self,name): 4 self.name=name 5 def print_name(self): 6 print(self.name) 7 p=Person('laoliu') 8 p2=Person('laowang') 9 p.print_name() 10 p2.print_name()
4.classmethod和staicmethod的区别:
都可以通过Class.method()的方式使用;
classmethod第一个参数是cls,可以引用类变量
staticmethod使用起来和普通函数一样,只不过放在类里去组织
注:classmethod是为了使用类变量,staticmethod是代码组织的需要,完全可以放在类之外
1 class Person: 2 Coutry='China' 3 def __init__(self,name): 4 self.name=name 5 @classmethod 6 def print_country(cls): 7 print(cls.Coutry) 8 @staticmethod 9 def join_name(first_name,last_name): 10 return last_name+first_name
5.元类:
元类是创建类的类。(元类允许我们控制类的生成,比如修改类的属性等);
使用type来定义类;
元类最常见的一个使用场景就是ORM框架
type:
1 #参数:类名,基类,属性 2 class Base(): 3 pass 4 class Child(Base): 5 pass 6 #等价定义,注意是tuple 7 SameChild=type('Child',(Base,),{}) 8 #加上方法 9 class ChildWithMethod(Base): 10 bar=True 11 12 def hello(self): 13 print('hello') 14 #等价 15 ChiildWithMethod=type('ChiildWithMethod',(Base,),{'bar':True,'hello':hello})
元类的使用:
1 #元类继承于type 2 class LowercaseMeta(type): 3 ''' 4 修改类的属性名称为小写的元类 5 ''' 6 def __new__(mcs,name,bases,attrs): 7 lower_attrs={} 8 for k,v in attrs.items(): 9 if not k.startswith('__'): 10 lower_attrs[k.lower()]=v 11 else: 12 lower_attrs[k]=v 13 return type.__new__(mcs,name,bases,lower_attrs) 14 class LowerCaseClass(metaclass=LowercaseMeta): 15 BAR=True 16 def HELLO(self): 17 print('hello') 18 print(dir(LowerCaseClass))
6.装饰器:
Python中一切皆对象,函数也可以当作参数来传递;
装饰器是接收函数作为参数,添加功能后返回一个新函数的函数(类);
Python中通过@使用装饰器(@是语法糖)
编写一个记录函数耗时的装饰器
1 #编写一个记录函数耗时的装饰器 2 import time 3 def log_time(func): 4 def _log(*args,**kwargs): 5 beg=time.time() 6 res=func(*args,**kwargs) 7 print('use time:{}'.format(time.time()-beg)) 8 return res 9 return _log 10 @log_time 11 def mysleep(): 12 time.sleep(1) 13 #相同 14 #mysleep=log_time(mysleep) 15 mysleep()
使用类编写装饰器:
1 class LogTime: 2 def __call__(self, func): 3 def _log(*args,**kwargs): 4 beg=time.time() 5 res=func(*args,**kwargs) 6 print('use time:{}'.format(time.time()-beg)) 7 return res 8 return _log 9 @LogTime() 10 def mysleep2(): 11 time.sleep(1)
使用类砖石砌比较方便实现装饰器参数:
1 class LogTime: 2 def __init__(self,user_int=False): 3 self.user_int=user_int 4 def __call__(self, func): 5 def _log(*args,**kwargs): 6 beg=time.time() 7 res=func(*args,**kwargs) 8 if self.user_int: 9 print('use time:{}'.format(time.time()-beg)) 10 else: 11 print('use time:{}'.format(int(time.time() - beg))) 12 return res 13 return _log 14 @LogTime(True) 15 def mysleep2(): 16 time.sleep(1)
7.设计模式:
创建型:
工厂模式:解决对象创建问题;
构造模式:控制复杂对象的创建;
原型模式:通过原型的克隆创建新的实例;
单例模式:一个类只能创建同一个对象;
对象池模式:预先分配同一个类型的一组实例;
惰性计算模式:延迟计算
工厂模式:
解决对象创建问题;
解决对象的创建和使用;
包括工厂方法和抽象工厂
1 class DogToy: 2 def speak(self): 3 print('wang wang') 4 5 class CatToy: 6 def speak(self): 7 print('miao miao') 8 def toy_factory(toy_type): 9 if toy_type=='dog': 10 return DogToy() 11 elif toy_type=='cat': 12 return CatToy()
构造模式:
用来控制复杂对象的构造;
创建和表示分离。比如你要买电脑,工厂模式直接给你需要的电脑,但是构造模式允许你自己定义电脑的配置,组装完成后给你
原型模式:
通过克隆原型来创建新的实例;
可以使用相同的类型,通过修改部分属性来创建新的实例;
用途:对于一些创建实例开销比较高的地方可以使用原型模式
单例模式:
一个类创建出来的对象都是同一个;
Python'的模块其实就是单例的,只会导入一次;
使用共享同一个实例的方式来创建单例模式
构建型:
桥代理组合适配器,享元回家装饰外观。
装饰器模式:
无需子类化就可以扩展对象功能;
代理模式:
把一个对象的操作代理到另一个对象;
前面实现的Stack/Queue,把操作代理到deque;
has-a组合关系;
还有就是实现一些安全相关的东西,比如一个比较裸露的接口,在校验层可以做一些校验。
适配器模式:
通过一个间接层适配统一接口;
相当于多功能冲电头,可以给多个不同手机冲电;
1 from abc import abstractmethod, ABCMeta 2 3 4 class Payment(metaclass=ABCMeta): 5 @abstractmethod 6 def pay(self, money): 7 raise NotImplementedError 8 9 10 class Alipay(Payment): 11 def pay(self, money): 12 print("支付宝支付%s元"%money) 13 14 15 class ApplePay(Payment): 16 def pay(self, money): 17 print("苹果支付%s元"%money) 18 19 #------待适配的类----- 20 class WeChatPay: 21 def fuqian(self,money): 22 print('微信支付%s元'%money) 23 24 #------类适配器------ 25 class RealWeChatPay(Payment,WeChatPay): 26 def pay(self, money): 27 return self.fuqian(money) 28 29 #-----对象适配器----- 30 class PayAdapter(Payment): 31 def __init__(self,payment): 32 self.payment=payment 33 def pay(self, money): 34 return self.payment.fuqian(money) 35 36 #RealWeChatPay().pay(100) 37 38 p=PayAdapter(WeChatPay()) 39 p.pay(200)
外观模式:
简化复杂对象的访问问题;
享元模式:
通过对象复用(池)改善资源利用,比如连接池;
MVC模式:
解耦展示逻辑和业务逻辑;
行为型:
访问者写好策略备忘录,观察模板迭代状态,命令中介解释责任链。
迭代器模式:通过统一的接口迭代对象
Python内置对迭代器模式的支持;
比如我们可以用for遍历各种Iterable的数据类型;
实现__next__和__iter__实现迭代器
1 class LinkedList: 2 class Node: 3 def __init__(self,item=None): 4 self.item=item 5 self.next=None 6 class LinkedListIterator: 7 def __init__(self,node): 8 self.node = node 9 #实现next方法,返回下一个元素 10 def __next__(self): 11 if self.node: 12 cur_node = self.node 13 self.node = cur_node.next 14 return cur_node.item 15 16 def __iter__(self): 17 return self 18 def __init__(self,iterable=None): 19 self.head = LinkedList.Node(0) 20 self.tail = self.head 21 self.extend(iterable) 22 23 #链表尾部追加元素 24 def append(self,obj): 25 s = LinkedList.Node(obj) 26 self.tail.next = s 27 self.tail = s 28 #链表自动增加长度 29 def extend(self,iterable): 30 for obj in iterable: 31 self.append(obj) 32 self.head.item += len(iterable) 33 34 def __iter__(self): 35 return self.LinkedListIterator(self.head.next) 36 37 def __len__(self): 38 return self.head.item 39 40 def __str__(self): 41 return '<<'+', '.join(map(str,self)) + '>>' 42 43 li = [i for i in range(100)] 44 lk = LinkedList(li) 45 print(lk)
观察者模式:对象发生改变的时候,观察者执行相应的操作
发布订阅是一种最常用的实现方式;
发布订阅用于解耦逻辑;
可以通过回调方式实现,当发生事件时,调用相应的回调函数
1 from abc import ABCMeta, abstractmethod 2 3 #抽象主题 4 class Oberserver(metaclass=ABCMeta): 5 @abstractmethod 6 def update(self): 7 pass 8 9 #具体主题 10 class Notice: 11 def __init__(self): 12 self.observers = [] 13 14 def attach(self,obs): 15 self.observers.append(obs) 16 17 def detach(self,obs): 18 self.observers.remove(obs) 19 20 def notify(self): 21 for obj in self.observers: 22 obj.update(self) 23 24 #抽象观察者 25 class ManagerNotice(Notice): 26 def __init__(self,company_info=None): 27 super().__init__() 28 self.__company_info = company_info 29 30 @property 31 def company_info(self): 32 return self.__company_info 33 34 @company_info.setter 35 def company_info(self,info): 36 self.__company_info = info 37 self.notify() 38 39 #具体观察者 40 class Manager(Oberserver): 41 def __init__(self): 42 self.company_info = None 43 def update(self,noti): 44 self.company_info = noti.company_info 45 46 #消息订阅-发送 47 notice = ManagerNotice() 48 49 alex=Manager() 50 tony=Manager() 51 52 notice.attach(alex) 53 notice.attach(tony) 54 notice.company_info="公司运行良好" 55 print(alex.company_info) 56 print(tony.company_info) 57 58 notice.company_info="公司将要上市" 59 print(alex.company_info) 60 print(tony.company_info) 61 62 notice.detach(tony) 63 notice.company_info="公司要破产了,赶快跑路" 64 print(alex.company_info) 65 print(tony.company_info)
策略模式:针对不同规模使用不同的策略
根据不同的输入采用不同的策略;
比如买东西超过10个打八折,超过20个打七折;
对外暴露统一的接口,内部采用不同的策略计算
8.函数式编程(推荐使用生成式):
把电脑运算视作数学上的函数计算(lambda计算)
高阶函数:map/reduce/filter;
无副作用,相同的参数调用始终产生同样的结果
print(list(map(lambda x:x*2,range(10)))) #求1到9和 reduce(lambda x,y:x+y,range(10)) #求10内偶数 filter(lambda x:x%2==0,range(10))
9.闭包:引用了外部自由变量的函数;自由变量:不在当前函数定义的变量;特性:自由变量会和闭包函数同时存在
绑定了外部作用域的变量的函数;
即使程序离开外部作用域,如果闭包仍然可见,绑定变量不会销毁;
每次运行外部函数都会重新组建闭包
1 from functools import wraps 2 3 def catche(func): #装饰器 4 store={} 5 @wraps(func) 6 def _(n): #闭包函数 7 if n in store: 8 return store[n] 9 else: 10 res=func(n) 11 srore[n]=res 12 return res 13 return _ 14 15 @catche 16 def f(n): 17 if n<=1: 18 return 1 19 else: 20 return f(n-1)+f(n)
10.Linux常考命令:
man:查看某个命令怎末用;
tldr:更简洁(pip install tldr)
文件:chown/chmod/chgrp
ls/rm/mv/touch/rename/ln(软硬连接)
locate/find/grep
编辑器 vi/nano
cat/head/tail查看文件
more/less交互式查看文件
ps查看进程,kill杀死,top/htop监控进程
free查看可用内存;
了解每一列含义,排查泄露
ipconfig/lsof/netstat/ssh/scp。tcpdump
useradd/usermod/grepadd/grepmod
11.线程和进程的区别:
进程是对运行程序的封装,是系统资源调度和分配的基本单位;
线程是进程的子任务,cou调度和分配的基本单位,实现进程内并发;
一个进程可以包含多个线程,线程依赖进程存在,并共享进程内存
12.线程安全:
Python中哪些是线程安全的:
一个操作可以在多线程环境中安全使用,获取正确的结果;
线程安全的操作好比线程是顺序执行而不是并发执行的;
一般如果涉及到写操作需要考虑如何让多个县城安全访问数据
线程同步的方式:
互斥量(锁):通过互斥机制防止多个线程同时访问公共资源;
信号量:控制同一时刻多个线程同时访问同一个资源的线程数
事件(信号):通过通知的方式保持多个线程同步
进程间通信:
管道/匿名管道/有名管道(pipe)
信号:比如用户ctrl+c产生SININT程序终止信号
消息队列
共享内存;
信号量;
套接字:最常用的
13.操作系统内存管理机制:
分页机制:操作系统为了高效管理内存,减少碎片
把逻辑地址和物理地址分离的内存分配管理方案:
程序的逻辑地址划分为固定大小的页;
物理地址划分为同样大小的帧;
通过页表对应逻辑地址和物理地址
分段机制:分段是为了满足代码的一些逻辑需求
数据共享,数据保护,动态链接等
通过段表实现逻辑地址和物理地址的映射关系;
每个段内部是内存分配,段和段之间是离散分配的
分页和分段的区别:
页是出于内存利用率的角度提出的离散分配机制;
段是出于用户角度,用于数据保护,数据隔离等用途的管理机制;
页的大小是固定的,操作系统决定,;段的大小不确定,用户程序决定
什么是虚拟内存:
通过把一部分暂时不用的内存信息放到硬盘上:
局部性原理,程序运行时候只有部分必要的信息装入内存;
内存中暂时不需要的内容放到硬盘上;
系统似乎提供了比实际内存大得多的容量,称之为虚拟内存
什么是内存抖动(颠簸):
频繁的页调度,进程中不断产生缺页中断;
置换一个页,又不断再次需要这个页;
运行程序太多了;页面替换策略不好。终止进程或者增加物理内存;
Python垃圾回收机制:https://blog.csdn.net/csucsgoat/article/details/81510313
引用计数为主(缺点:循环引用无法解决【两个对象互相引用之后引用计数无法归零】,del减少引用计数);
引用标记清除和分代回收解决引用计数的问题;
引用计数为主+标记清除和分代回收为辅
什么时候引用计数增加:
对象创建 a=1;
对象被引用:b=a;
对象作为参数传递func(a);
对象存储在容器中 l=[a];
什么时候引用计数会减少:
显示使用del a;
引用指向了别的对象 b=None;
离开了对象的作用域(比如函数执行结束);
从一个容器移除对象或者销毁容器
循环引用:
#a和b的引用计数都为1 a=[1] b=[1] #a和b的引用计数都为2 a.append(b) b.append(a) #a和b都销毁 但是还是1 del a del b
标记清除:
从根访问,如果不可达的点(独立的)就标记
分代回收:
分三代(0 1 2),每隔一定时间对每一代实行标记回收
四.网络协议TCP/UDP/HTTP
浏览器输入一个url中间经历的过程:
DNS查询->TCP握手->HTTP请求->反向代理nginx->uwsgi/gunicom->wep app响应->TCP挥手
TCP三次握手,四次挥手,状态转换:https://blog.csdn.net/ZWE7616175/article/details/80432486
TCP和UDP的区别: https://www.cnblogs.com/xiaomayizoe/p/5258754.html
TCP是面向连接的,可靠的,基于字节流的;
UDP是无连接,不可靠,面向报文的;
HTTP协议:
HTTP请求的组成:
状态行;
请求头;
消息主体(可能为空)
HTTP响应组成:
状态行;响应头;向应正文
HTTP请求头:https://www.cnblogs.com/honghong87/articles/6941436.html
HTTP常见状态码:https://blog.csdn.net/laishaohe/article/details/79052085
HTTP GET/POST的区别:https://www.cnblogs.com/logsharing/p/8448446.html
Restful语义上一个是获取,一个是创建;
Get是幂等的,POST是不幂等的;
GET请求参数放到url(明文),长度限制;POST放在请求体,更安全
什么是幂等的(指多次请求结果和请求一次结果一致):https://blog.csdn.net/qq_15037231/article/details/78051806
幂等方法是无论调用多少次得到相同结果的HTTP方法;
例如:a=4是幂等的,a+=4就是非幂等的;
幂等的方法客户端可以安全的从发请求
幂等:OPTIONS/GET/HEAD?PUT?DELETE;非幂等:POST/PATCH
安全的(是否会修改数据):OPTIONS/HEAD/GET;不安全的:PUT/POST/DELETE/PATCH
HTTP长连接:https://www.cnblogs.com/cswuyg/p/3653263.html
短连接:建立连接。。。数据传输。。关闭连接(连接的建立和关闭开销大)
长连接:Connection:Keep-alive。保持TCp连接不断开;
如何区分不同的HTTP请求:Content-Length(长度)|Transfer-Encoding(分段发送)
cookie和session的区别:
Session一般是服务器生成后交给客户端(通过url参数或cookie);
Cookie是实现session的一种机制,通过HTTP cookie字段实现;
Session通过服务器保存sessionid识别用户,cookie存储在客户端
网络编程:
TCP/UDP socket编程;HTTP编程:https://www.cnblogs.com/zengzy/p/5107516.html
使用socket发送HTTP请求:
。。。。
IO多路复用:操作系统提供的同时监听多个socket的机制
五种IO模型
常见的提升并发能力的方式:
多线程模型;多进程模型;
IO多路复用:
为了实现高并发需要一种机制并发处理多个socket;
Linux常见的是select/poll/epoll
select/poll/epoll的区别:https://www.cnblogs.com/Anker/p/3265058.html
Python并发网络库:
哪些并发网络库:
Tornado vs Gevent ns Asyncio:
Tornado并发网络库和同时也是一个web微框架;
Gevent绿色线程(greenlet)实现并发,猴子补丁修改内置socket;
Asyncio Python3内置的并发网络库,基于原生协程
Tornado:
适用于微服务,实现restful接口:
低层基于Linux多路复用;
可以通过协程或者回调实现异步编程;
不过生态不够完善,相应的异步框架比如ORM不完善
Gevent:
基于轻量级绿色线程实现并发;
需要注意monkey patch,gevent修改了内置的socket改为非阻塞;
配合gunicorn和gevent部署作为wsgi server
Asyncio:
基于协程实现的内置并发网络库:
Python3引入内置库,协程加事件循环;
生态不够完善,没有大规模生产环境检验;
目前应用不广泛,基于Aiohttp可以实现一些小的服务
五.数据库:
Mysql基础考点:
事务的原理,特性,事务并发控制;
常用的字段,含义的区别;
常用的数据库引擎之间的区别;
什么是事务:
事务是数据库并发控制的基本单位;
事务可以看作是一些列SQL语句的集合;
事务必须要么全部执行成功,要么全部执行失败(回滚),如转账操作。。。
事务的四个特性(ACID):
原子性:一个事务中的所有操作要不全部完成成功,要不全部失败;
一致性:事务开始和结束后数据完整性没有被破坏;
隔离性:允许多个事务同时对数据库修改和读写;
持久性:事务结束后,修改是永不丢失的
事务的并发控制:
如果不对事务进行并发控制,可能产生四种异常情况:
幻读:一个事务第二次查出现第一次没有结果;
非重复读:一个事务重复读两次得到的结果不同;
脏读:一个事务读取到另一个事务没有提交的修改;
丢失修改:并发写入造成其中一些修改丢失
为了解决并发控制异常,定义了4种事务隔离级别:
读未提交:别的事务可以读取到未提交改变;
读已提交:只能读已经提交的数据;
可重复读:同一个事务先后查询的结果一样(默认);
串行化:事务完全串行化串行,隔离级别最高,执行效率最低
如何解决高并发场景下,写辱数据库有数据重复问题:
使用数据库唯一索引;
使用队列异步写入;
使用redis等实现分布式锁
乐观锁和悲观锁:
悲观锁是先获取锁再进行操作。一锁二查三更新 select for update;
乐观锁先修改,更新的时候发现数据已经变了就回滚 check and set;
使需要根据响应速度,冲突频率,重试代价来判断使用哪一种
数据类型:。。。。int(10),表示最小显示十个字符宽
Innodb和MyISAM区别:
MyISAM不支持事务u,InnoDB支持事务;
MyISAM不支持外键,InnoDB支持外键;
MyISAM只支持表锁,InnoDB支持行锁和表锁;
MyISAM支持全文索引,InnoDB不支持;
。。。
Mysql索引原理及优化:
索引的原理,类型,结构;
创建索引的注意事项,使用原则;
如何排查和消除慢查询
什么是索引:
索引是数据表种一个或者多个列进行排序的数据结构,索引能够大幅提升检索速度(查找结构:线性,二分,二叉树。。。。);
创建,更新索引本身也会耗费空间和时间;
什么是B-Tree:
查找结构进化史:
线性查找:一个个找;实现简单;太慢;
二分查找:有序;简单;要求有序,插入特别慢;
HASH:查询块;占用空间;不太适合存储大规模数据;
二叉查找树:插入和查询很快(logn);无法存储大规模数据,复杂度退化(单边增长,成线性);
平衡树:解决bst退化的问题,树是平衡的;节点非常多的时候,依然树高很高;
多路查找树:一个父亲多个孩子节点(度);节点过多树高不会特别深;
多路平衡查找树:B-Tree(无法使用范围查找)
B-Tree:多路平衡查找树(每个节点最多m(m>=2)个孩子,称未m阶或者度);
叶节点具有相同的深度;
节点种的数据key从左到右是递增的;
B+Tree:
B+Tree是B-Tree的变形:
Mysql实际使用的B+Tree作为索引的数据结构;
只在叶子节点带有记录的指针(为什么?可以增加树的度)
叶子节点通过指针相连。为什么?实现范围查询
阶(度/孩子)越多越好吗?
操作系统内存分页,硬盘分块;
以磁盘块的大小去确定阶的大小,这样操作系统去读取和缓存数据就非常友好。
索引类型:
普通索引(create index);
唯一索引,索引列的值必循唯一(create unique index);
多列索引
主键索引:一个表只能有一个
全文索引:InnoDB不支持,倒排索引
什么时候创建索引:
经常用作查询条件的字段(WHERE条件);
进场用做表连接的字段;
经常出现在order by,group by之后的字段
创建索引需要注意的:
最佳实践:
非空字段 NOT NULL,Mysql很难对空值作查询优化;
区分度高,离散度大,作为索引的字段值尽量不要大量相同值;
索引的长度不要太长(比较耗费时间)
索引什么时候失效:
记忆口诀:模糊匹配,类型隐转,最左匹配;
以%开头的LIKE语句;
出现隐式类型转换(在Python这种动态语言查询中需要注意),无法匹配;
没有满足最左前缀匹配(为什么是最左前缀) (a,b,c) (1,2,3) (1,2,4),相当于元组,从左往右比较,如(a,b,c),(a,b),(a)能比较,但是(a,b,c)和(b,c)不能比较,第一个都不相同
聚集索引和非聚集索引:
聚集还是非聚集指得是B+Tree叶节点存的是指针还是数据记录;
MyISAM索引和数据分离,使用的是非聚集索引;
InnoDB数据文件就是索引文件,主键索引就是聚集索引
如何排查慢查询:
慢查询缺少索引,索引不合理或者业务代码实现导致:
slow_query_log_file开始并且查询慢查询日志;
通过explain排查索引问题;
调整数据修改索引;业务代码层限制不合理访问。
Mysql语句:
常用连接:
内连接:两个表都存在匹配时,才会返回匹配行;
外连接:返回一个表的行,即使另一个没有匹配;
全连接:只要某一个表存在匹配就返回
内连接:
将左表和右表能够关联的数据连接返回;
类似于求两个表的“交集”;
select * from A inner join B on a.id=b.id
外连接:
左连接:返回左表的所有记录,即使右表中没有任何满足匹配的记录;
右连接:返回右表的所有记录,即使左表中没有任何满足匹配的记录;
全连接:
只要某一个表存在匹配,就返回行;
类似于求两个表的“并集”;
到时Mysql不支持,可以用left join 和union,right join联合使用模拟
缓存及redis:
缓存的使用场景,为什么使用:
内存缓存(常见的有Redis,Memcached):
缓解关系数据库(常见的是Mysql)并发访问的压力:热点数据;
减少响应时间:内存IO速度比磁盘快;
提升吞吐量:Redis等内存数据库单机就可以支撑很大并发
redis和memcached的区别:
https://www.cnblogs.com/457248499-qq-com/p/7392653.html
resis常用数据类型及使用场景:
String(字符串):用来实现简单的KV键值对存储,比如计数器;
List(链表):实现双向链表,比如用户的关注,粉丝列表;
Hash(哈希表):用来存储彼此相关信息的键值对;
Set(集合):存储不重复元素,比如用户的关注者;
Sorted Set(有序集合):实时信息排行榜
Redis内置实现:使用C语言实现;
String:整数或者sds(Simple Dynamic String);
List:ziplist(压缩链表,通过一个连续的内存块实现list结构,其中的每个entry节点头部保存前后节点长度信息,实现双链表功能)或者double linked list;
Hash:ziplist或者hashtable(可以自己设置,大于某个值使用什么);
Set:intset或者hashtable;
SortedSet:skiplist跳跃表
时间空间复杂度。。。。
Sorted Set为了简化实现,使用skiplist而不是平衡树实现:
Redis有哪些持久化的方式:
快照方式:把数据快照放在磁盘二进制文件中,dump.rdb;
AOF(Append Only File):每一个写命令追加到appendonly.aof中;
可以修改Redis配置实现使用哪种方式
Redis的事务:
将多个请求打包,一次性,按序执行多个命令的机制;
Redis通过MULTI,EXEC,WATCH等命令实现事务功能;
Python redis-py,pipeline=conn.pipeline(transaction=True)
Redis实现分布式锁:
使用setnx实现加锁,可以同时通过expire添加超时时间;
锁的value值可以使用一个随机的uuid或者特定的命名;
释放锁的时候,通过uuid判断是否是该锁,是则执行delete释放锁
使用缓存的模式:
Cache Aside:同时更新缓存和数据库;
Read/Write Througr:先更新缓存,缓存负责同步更新数据库;
Write behind Caching:先更新缓存,缓存定期异步更新数据库。
(先更新数据库后更新缓存,并发写操作可能导致缓存读取的是脏数据,一般先更新数据库然后删除缓存)
缓存穿透的问题:
大量在缓存查询不到的数据的请求落到后端数据库,数据库压力增大
由于大量缓存查不到就去数据库取,数据库也没有要查的数据(爬虫id遍历,有可能大量的id没有)
解决:对于没查到的返回为None的数据也缓存;
插入数据的时候删除相应缓存,或者设置较短的超时时间
缓存击穿的问题:
某些非常热点的数据key过期,大量请求达到后端数据库;
热点数据key失效导致大量请求达到数据库增加数据库压力;
分布式锁:获取锁的线程从数据库拿数据更新缓存,其他线程等待
异步后台更新:后台任务针对过期的key自动刷新
缓存雪崩:
大量缓存不可用或者大量的缓存key同时失效(或重启缓存服务器),大量的请求直接打到数据库
解决:多级缓存:不同级别的key设置不同的超时时间;
随机超时:key的超时时间随机设置,防止同时超时;
架构层:提升系统的可用性。监控,报警完善
Mysql问题:https://blog.csdn.net/HeatDeath/article/details/79833462
Redis实现分布式锁:https://baijiahao.baidu.com/s?id=1623086259657780069&wfr=spider&for=pc
六.Python web
WSGI;常见Web框架:
什么是WSGI?为什么需要它?
Python Web Server Gateway Interface(pep3333)
解决Python Web Server乱象mod_python,CGI,FastCGI等
描述了Web Server(Gunicorn/uWSGI)如何与web框架(Flask/Django)交互,Web框架如何处理请求
def application(environ,start_response):
application就是WSGI app,一个可调用的对象
参数:
environ:一个包含WSGI环境信息的字典,由WSGI服务器提供,常见的key有PATH_INFO,QUERY_STRING等
start_response:生成WSGI响应的回调函数,接收两个参数,status和headers
函数返回响应体的迭代器
编写兼容WSGI的小应用(使用Python内置的WSGI server):
函数:
1 def myapp(environ,start_response): 2 status="200 OK" 3 headers=[('Content-Type','text/html;charset=utf8')] 4 start_response(status,headers) 5 #可迭代对象,字节 6 return [b'<h1>Hello World</h1>'] 7 8 if __name__=="__main__": 9 from wsgiref.simple_server import make_server 10 httpd=make_server('127.0.0.1',8888,myapp) 11 httpd.serve_forever()
效果
封装成类:
1 class Application(object): 2 3 def __init__(self,routers,**kwargs): 4 self.routers=routers 5 6 def __call__(self, environ, start_response): 7 try: 8 request=Request(environ) 9 callback,args=routers.match(request.path) 10 response=callback(request,*args) 11 except FileNotFoundError: 12 response=Response("<h1>Not Found</h1>",status=404) 13 start_response(response.status,response.headers.items()) 14 return iter(response)
常用Python Web框架对比:
Django VS Flask VS Tarnado
Django大而全,内置ORM,Admin等组件,第三方插件较多
Flask:微框架,插件机制,比较灵活
Tornado:异步支持的微框加和异步网络库
什么是MVC:Model(模型),View(视图),控制器(Controller)【解耦数据,展示和操作】
Model:负责业务对象和数据库的交互(ORM)
View:负责与用户的交互展示
Controller:接收请求参数调用模型和视图完成请求
什么是ORM(对象关系映射):提升开发效率和维护性
用于实现业务对象与数据表的字段映射
优势:代码更加面对对象,代码量更少,灵活性高,提升开发效率;
缺点:拼接对象比较耗时,有一定性能影响
一个Web框架的组成:
中间件:用于请求之前和请求之后做一些处理(比如记录日志等)
路由,表单验证,权限认证,ORM,视图函数,模板渲染,序列化
第三方插件:Redis连接,RESTful支持等
什么是Gunicorn:gunicorn -w 4 myapp:app(部署4个进程)
纯Python编写的高性能WSGI Server
pre-fork预先分配多个worker进程处理请求(master-slave)
多种worker支持:Sync/Async(Gevent)/Tornado/AsyncIO
Web安全:
SQL注入:
通过构造特殊的参数传入Web应用,导致后端执行了恶意的SQL
通常由于程序员未对输入进行过滤,直接动态拼接SQL产生
可以使用开源工具sqlmap,SQLninja检测
创建表并插入几条数据:
1 from hashlib import md5 2 def ha_md(s): 3 m=md5() 4 m.update('haha'.encode('utf-8')) 5 return m.hexdigest() 6 7 sql=""" 8 create table users( 9 id int PRIMARY KEY not null auto_increment, 10 name varchar(50) null, 11 email varchar(50) null, 12 password varchar(50) null 13 ); 14 insert into users(name,email,password) values('LYQ','lyq@haha.com','{0}'); 15 insert into users(name,email,password) values('LYQ2','lyq2@haha.com','{1}'); 16 insert into users(name,email,password) values('LYQ3','lyq3@haha.com','{2}'); 17 """.format(ha_md('123411'),ha_md('adade'),ha_md('467241')) 18 19 #sql注入演示代码 20 import os 21 import MySQLdb 22 db=MySQLdb.connect(host='localhost', 23 user='root', 24 passwd='112358', 25 db='test', 26 charset='utf8') 27 28 cur=db.cursor() 29 cur.execute(sql) 30 cur.close() 31 db.commit() 32 db.close()
直接拼接:如果password输入--(表示注释),只要name对了就能找到,修改为sql="select * from name=%s AND password=md5(%s)"就不会了
1 #sql注入演示代码 2 import MySQLdb 3 db=MySQLdb.connect(host='localhost', 4 user='root', 5 passwd='112358', 6 db='test', 7 charset='utf8') 8 9 cur=db.cursor() 10 name=input("请输入姓名:") 11 print("您1输入的姓名是:{0}".format(name)) 12 password=input("请输入密码:") 13 print("您输入的密码是:{0}".format(password)) 14 #直接拼接sql 15 sql="select * from name='"+name+"'"+" AND password=md5('"+password+"')" 16 cur.execute(sql) 17 cur.close() 18 db.commit() 19 db.close()
如何防范SQL注入:
web安全一大法则:永远不要相信用户的任何输入
对输入参数做好检查(类型和范围);过滤和转义特殊字符;
不要直接拼接sql,使用ORM可以大大降低sql注入的风险;
数据库层:做好权限管理配置i;不要明文存储敏感信息
XSS攻击:跨站脚本攻击
恶意用户将代码植入到提供给其他用户使用的页面中,未经转义的恶意代码输出到其他用户的浏览器被执行;
用户浏览页面的时候嵌入页面中的脚本(js)会被执行,攻击用户;
主要分为两类:反射型(非持久型),存储型(持久型)
如:博客评论:<script>alert("haha");</script>,如果没有转义js代码,访问会弹出haha;(存储型)
还有的可以手机用户的document.cookie发送到指定的服务器上
反射型如把脚本放在url参数里面,诱导用户去点击
xss危害:
盗用cookie,获取敏感信息;
利用用户私人账号执行一些违法行为,比如盗取个人或者商业资料,执行一些隐私操作;
甚至可以在一些访问量很大的网站上实现DDos攻击
如何访问xss攻击:
过滤(输入和参数)。对敏感标签<script><img> <a>等进行过滤。;
转义。对常见符号(“&”,“<”,“and”)转义(python3有个html.escape转义);
设置HttpOnly禁止浏览器访问和操作Document.cookie
CSRF:跨站请求伪造
利用网站对已认证用户的权限去执行未授权的命令的一种恶意攻击;
攻击者会盗用你的登录信息,以你的身份模拟发送请求;
web身份认证机制只能识别一个请求是否来自某个用户的浏览器,但是无法保证请求是用户自己或者批准发的
csrf:https://www.cnblogs.com/lovesong/p/5233195.html
csrf攻击具备的两个条件:
受害者已经登录到了目标网站并且没有退出(保持登录状态);
受害者访问攻击者链接或者吧表单
二者缺一不可
如何防范csrf:不要再任何GET请求中有任何数据修改操作
令牌操作(STP):再用户请求的表单中嵌入一个隐藏的csrf_token,服务端验证其是否与cookie中的一致(基于同源策略其他网站是无法获取cookie中的csrf_token)
黑客是拿不到你cookie中的csrftoken值的,前提是网站本身没有xss漏洞;
如果是js提交需要先从cookie获取csrf_token作为X-CSRFToken请求头提交
其他:检测来源HTTP Referer(容易被伪造);验证码方式(安全但是繁琐)
前后端分离和Restful:
什么是前后端分离:
后端只负责数据接口,不再渲染模板,前端获取数据并呈现
前后端分离的意义和方式:
前后端解耦,接口复用(前端和客户端公用接口),减少开发量;
各司其职,前后端同步开发,提升工作效率。定义好接口规范 ;
更有利于调试,测试和运维部署
什么是RESTfulL:
表现层状态转移,由HTTP协议的主要设计者Roy Fielding提出;
资源(使用URL指向的一个实体),表现层(资源的表现形式,比如图片,HTML文本等),状态转化(GET,POST,PUT,DELETE等动词来操作资源,实现资源状态的改变);
是一种资源为中心的web软件架构风格,可以用Ajax和RESTful web服务构建应用
设计的概念和准则:
所有事物抽象为资源,资源对应唯一的标识;
资源通过接口进行操作实现状态转移,操作本身是无状态的
什么是RESTful API:
RESTful风格的API接口:
通过HTTP GET/POST/DELETE 获取/新建/更新/删除资源
一般通过JSON格式返回数据;
一般web框架都有相应的插件支持RESTful API
如何设计RESTful API
GET http://[hostname]/api/users 检索用户列表.....
什么是https。。。http和https的区别。。。你了解对称加密和非对称加密码。。。
HTTPS的通信过程?
六.系统设计
什么是系统设计:
系统设计是一个定义系统架构,模块,接口和数据满足特定需求的过程;
比如设计一个短网址服务,评论服务,Feed流服务,抢红包系统;
微服务架构很多系统被按照业务拆分,需要单独设计一个系统服务
系统设计难点:中高级工程师必经之路
需要具备相关领域,算法经验,有一定的架构设计能力;
熟悉后端技术组件,比如消息队列,缓存,数据库,框架;
具备文档撰写,流程图绘制,架构设计,编码实现等综合能力
系统设计的要素:
使用场景和限制条件;
数据存储设计;
算法模块设计
按照三个元素来答:
什么场景和条件下使用;(比如短网址系统提供站内各种服务生成短网址;限制条件:用户估计有多少?至少要能支撑多少用户(服务)?估算并发qps:峰值qps是多少?平均qps是多少)
设计存储模块;
设计算法模块
数据存储设计:
短网址系统的存储设计?需要存储哪些字段?
如何设计短网址系统?
按照设计数据库,需要哪些字段,使用类型》数据增长规模;
数据库选型:是否需要持久化?使用关系型还是NoSQL;
如何优化?如何设计索引?是否可以使用缓存?
算法模块设计:
需要哪些接口?几口如何设计?
使用什么算法或者模型?
不同实现方式之间的优劣对比?如何取舍?
扩展:用户多了,qps高了如何处理?
数据存储多了不够存了如何处理?
故障如何处理?单点失败?多点失败,雪崩问题
短网址系统的设计和实现:
什么是短网址系统,包含哪些功能?
把一个长网址转成短网址的服务。
比如https://bitly.com。
转换之后网址的后缀不超过7位(字符或数字)
使用场景:
一个长网址转成短网址并存储;根据短网址还原长url;
要求短网址的后缀不超过7位(大小写字母和数字);
预估峰值插入请求数量级:数百;查询请求数量级:数千。
数据存储设计:
根据需求设计数据存储方式:
使用Mysql即可满足;
需要的字段有哪些:
id token(索引设计) url(原网址) created_time
短网址生成算法有哪些?对比优缺点
两个API:long2short_url,short2long_url
md5摘要算法:得到的是32位,可以阶段,取前7个字符,但是冲突/
类似于62进制的数字。二进制:0,1;十六进制0-9,a-f;十进制->62进制
十进制->二进制转换:
1 #不断取余,倒序输出 2 def mybin(num): 3 if num==0: 4 return 0 5 result=[] 6 while num: 7 num,rem=divmod(num,2) 8 result.append(str(rem)) 9 return ''.join(reversed(result)) 10 11 print(mybin(10))
十进制->16进制:递增序列算法
1 CHARS='abcdefghijklmnopqrstuvwxwzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' 2 def encode(num): 3 if num==0: 4 return CHARS[0] 5 res=[] 6 while num: 7 num,rem=divmod(num,len(CHARS)) 8 res.append(CHARS[rem]) 9 return ''.join(reversed(res)) 10 print(encode(61))
(需要自增id,需要一个计数器Redis incr)
request->redis incr index->token->mysql->url
如何设计秒杀系统:
什么是秒杀系统?有没有用过?
如何根据三要素设计?
秒杀系统涉及到哪些后端组件?
https://www.jianshu.com/p/d789ea15d060