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]))
View Code

 

       归并排序:

 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]))
View Code

 

      二分查找:

 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))
View Code

   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
View Code

 

      队列是先进先出结构:

          如何实现队列;实现队列的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())
View Code

 

        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())
View Code

     链表:

      删除节点:

 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
View Code

 

 

      合并链表:

 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
View Code  

     二叉树:

      二叉树的镜像(反转):

 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
View Code

 

        层序遍历二叉树(广度优先):  

 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 []
View Code

     栈和队列:

      栈实现队列:

 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()
View Code

 

    堆:

      合并多个有序数组;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         
View Code    

    字符串:

      翻转字符串: 

 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
View Code

 

      判断一个数字是否是回文数:

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
View Code

 三.面向对象编程和设计模式

  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()
View Code

 

  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
View Code

   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})
View Code

 

  元类的使用:

 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))
View Code

 

   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()
View Code

 

   使用类编写装饰器:

 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)
View Code

 

   使用类砖石砌比较方便实现装饰器参数:

 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)
View Code

   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()
View Code

 

       构造模式:

        用来控制复杂对象的构造;

        创建和表示分离。比如你要买电脑,工厂模式直接给你需要的电脑,但是构造模式允许你自己定义电脑的配置,组装完成后给你

      原型模式:

        通过克隆原型来创建新的实例;

        可以使用相同的类型,通过修改部分属性来创建新的实例;

        用途:对于一些创建实例开销比较高的地方可以使用原型模式 

      单例模式:

        一个类创建出来的对象都是同一个;

        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)
View Code

 

      外观模式:

        简化复杂对象的访问问题;

      享元模式:

        通过对象复用(池)改善资源利用,比如连接池;

      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)
View Code

 

      观察者模式:对象发生改变的时候,观察者执行相应的操作

        发布订阅是一种最常用的实现方式;

        发布订阅用于解耦逻辑;

        可以通过回调方式实现,当发生事件时,调用相应的回调函数

 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)
View Code

 

      策略模式:针对不同规模使用不同的策略

        根据不同的输入采用不同的策略;

        比如买东西超过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)
View Code

   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()
View Code

 

效果

      封装成类:

 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)
View Code

 

  常用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()
View Code

 

  

 

       直接拼接:如果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()
View Code

 

         如何防范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))
View Code

 

      十进制->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))
View Code

 

      (需要自增id,需要一个计数器Redis incr)

       request->redis incr index->token->mysql->url

    如何设计秒杀系统:

      什么是秒杀系统?有没有用过?

      如何根据三要素设计?

      秒杀系统涉及到哪些后端组件?

      https://www.jianshu.com/p/d789ea15d060      

   

 

      

posted @ 2019-04-13 07:53  biu嘟  阅读(5493)  评论(0编辑  收藏  举报