后端工程师的整理工作流程以一次web请求为例

这期间的每个流程需要进行掌握其中涉及的知识点以及相关技术栈

Python 开发面试梳理

  •  浏览器这里的前端相关
  •  负载均衡一般有哪些方式, 比如 nginx 之类的,
  •  web 框架可以选的 django 或者 flask 
  •  业务逻辑相关的具体实现涉及到编程范式, 设计模式等
  •  数据库相关sql或者nosql, 缓存的 redis 之类  

技术栈大纲

▓ Python 预言基础   -  语言特点 / 语法基础 / 高级特性

▓ Python函数常考 / 参数传递 / 不可变对象 / 可变参数

▓ 算法和数据结构 -   时间空间复杂度 / 实现常见的数据结构和算法

▓ 编程范式  -  面向对象编程 / 常用设计模式 / 函数式编程

▓ 操作系统  -  常用的 Linux 命令 / 进程  / 线程 / 内存管理

▓ 网络编程 - 常用协议 TCP, IP, HTTP /  Socket 编程基础 / Python 并发库

▓ 数据库  -  Mysql 索引优化 / 关系型和NoSQL 的使用场景

▓ Web 框架  -  常见的框架对比 / RESTful  / WSGI 原理 / Web 安全问题

▓ 系统设计  -  设计原则, 如何分析 / 后端常用的组件 (缓存, 数据库, 消息队列) / 技术选型和实现 (短网址服务 , Feed 流系统)

▓ 软实力  -  学习能力 / 业务理解能力 / 沟通交流能力 / 心态

python 基础问题

python 是静态还是动态 , 强类型还是弱类型 

动态强类型 

ps:

  动态 or 静态 ? 编译期还是运行期间确定类型

  强类型指不会发生隐式类型转换

python 作为后端预言的优缺点

优点 : 胶水预言, 轮子多, 应用广泛 ,预言灵活, 生产力高

缺点 : 性能问题, 代码维护问题, python 2 / 3 兼容问题

什么是鸭子类型

Python 开发面试梳理

关注点在对象的行为, 而不是类型

比如 file, StringIO, socket 对象都支持 read / wirte 方法 

再比如定义了 __iter__ 魔术方法的对象可以用 for 迭代 

什么是 monkey patch, 哪里用到了, 如何自己实现

所谓猴子补丁就是运行时的替换, 比如 gevent 库需要修改内置的 socket 

Python 开发面试梳理

简单的实现样例

Python 开发面试梳理

 什么是自省

Introspection 

 运行时判断一个对象的类型的能力

 python 一切皆对象, 用 type, id, isinstance 获取对象的信息 

 Inspect 模块提供了更多获取对象信息的函数

ps:

  id 获取其内存地址

    "is" 和 "==" 的区别?

     is 相当于调用 id 判断两个的内存地址是否一样

    == 则是判断值是否一致

Python 开发面试梳理

 什么是列表和字段推导 (语法糖)

比如 

Python 开发面试梳理

 一种快速生成 list / dict / set  的方式, 用来代替 map 或者 filter 等

Python 开发面试梳理

ps:

  换成 () 也可以返回生成器, 从而节省内存  

  Python 开发面试梳理

什么是深拷贝, 什么是浅拷贝

Python 开发面试梳理Python 开发面试梳理

浅拷贝共享地址, 比如引用

深拷贝不同享内存地址, 彼此独立. 互不影响

Python 开发面试梳理Python 开发面试梳理

 尽管深拷贝已经独立出来, 但是只能独立一层, 对于多层的可变对象的内部依旧是引用

可以使用 deepcopy 进行解决

ps:

  如何正确初始化一个 3 * 3 的二维数组

   Python 开发面试梳理\

什么是python 之禅

终端内 进行 this 的导入即可看到

Python 开发面试梳理

Python 2/3 的区别

print

print 成为函数

Python 开发面试梳理  Python 开发面试梳理

ps:

  py3 的 print 函数可以支持多个参数比 py2 的要更灵活的去控制 

  Python 开发面试梳理  

字符编码

Py3 不在有 Unicode 对象, 默认的 str 就是 Unicode

Python 开发面试梳理Python 开发面试梳理

除法变化

Py3 返回的是浮点数, Py2 会直接截断, 返回整数

Python 开发面试梳理 Python 开发面试梳理

 ps:

   想要 在py3 中实现 py2 的效果可以使用 //   

  Python 开发面试梳理

Python 3 的改进

类型注解

 type , hint 帮助 IDE 实现类型检查

Python 开发面试梳理

 类型注解只能进行提示无法做到校验

但是可以配合类似 mypy 之类的包进行真正的校验

优化 super() 

方便直接调用父类函数

Python 开发面试梳理

 py2 中需要传入参数 自己的类名以及self, 但是py3 中就不需要传入参数了

会方便很多, 是语法糖的一个简化操作

高级解包操作

Python 开发面试梳理

 常规操作在23中都可以进行

但是 py3 中有了更强的操作

Python 开发面试梳理

 可以用 * 带进行类似 *args 这样的操作进行便携获取

限定关键字参数

Python 开发面试梳理

存在多个不定参数的时候, 不想让顺序搞乱

可以使用关键字参数跳过顺序进行传参

关键字参数具备高于顺序位置传参的优先级

重新抛出异常不会丢失栈信息

py2 中如果在异常中再次抛出异常, 则之前的异常就会丢失

py3 中支持 raise from 保留之前异常, 这样有利于去排错

Python 开发面试梳理

一切返回迭代器

py2 中很多内置函数返回的就是实打实的列表 (range, zip, map. dict.values, etc,are all)

Python 开发面试梳理

如果数量比较大就比较麻烦, 但是 py3 返回的都是迭代器

因为是懒加载. 所以你不用他就不会生成列表去占用内存

Python 开发面试梳理

这个问题在 py 的解决方式是用 xrange 

因此这个问题在 py3 中不会出现之后. xrange 在 py3 中也被删除

如果需要变成列表则需要 list 强转一下

生成的pyc 文件统一放在 __pychche__ 中

一些内置库的修改

urlib , selector 等

性能优化等

Python3 新增

yield from

连接子生成器

asyncio 内置库

async / await 原生协程支持异步编程

新的内置库

enum, mock, asynic, ipaddress, concurrent.futures 等

Python 2 / 3 工具

  • six 模块
  • 2to3 等工具
  • __future__

Python函数常考 / 参数传递 / 不可变对象 / 可变参数

可变参数作为参数 , 不可变参数作为参数的区别

Python 开发面试梳理

Python 如何传递参数

python 传参并非 值传递 or 引用, 唯一支持的是 共享传参  (Call by Object / Call by sharing)

函数形参获得实参中各个引用的副本

Python 开发面试梳理

可变的对象直接在原来的基础上进行修改

Python 开发面试梳理

 不可变无法修改, 只能重新创建新的进行修改

 ps: 

  Python 开发面试梳理

 ps:

  类似题  

  Python 开发面试梳理

Python 可变参数作为默认参数

Python 开发面试梳理

默认参数只会计算一次

Python  *args, **kwargs

用来处理可变参数

*args 会打包成 tuple

 Python 开发面试梳理

*kargs 会打包成 dict

 Python 开发面试梳理

 混用

Python 开发面试梳理

配合解包传参

 Python 开发面试梳理

 Python 异常机制常考题

Python 开发面试梳理

  • BaseException 所有的异常都继承此异常
  • SystemExit / KeyboardInterrupt / GeneratorExit 控制系统相关的异常
  • Esception  其他的常见异常都继承此异常

使用异常的常见场景 

  • 网络请求, 超时, 连接错误
  • 资源访问, 权限, 资源不存在
  • 代码逻辑, 越界访问, KeyError 等

如何处理异常

Python 开发面试梳理

 如何自定义异常

继承 Exception 实现自定义异常, 并且加上一些附加信息, 用来处理一些业务相关的特定异常 (rause MyException)

Python 开发面试梳理

ps : 

  如果使用 BaseException 的话结束程序都是个问题, 因为控制 Ctrl + c 的 KeyboardInterrupt  异常也被捕获

  从而导致无法结束程序

Python 性能分析与优化, GIL 常考题

什么是 ,Cpytjon  GIL 

  •   Global Interpreter Lock 
  •   Cpython 解释器的内存管理不是线程安全的
  •   为了保护多线程下对 Python对象 的安全访问
  •   Cpython 使用简单的锁机制避免多线程直接执行

GIL 的影响

  • 限制了程序的多核执行
  • 同一个时间只能有一个线程执行字节码
  • CPU 密集程序难以利用多核心优势
  • IO 期间会释放GIL , 对于 IO 密集型程序影响不大

Python 开发面试梳理

如何规避 CIL 的影响

  • CPU密集可以使用多进程 + 进程池
  • IO 密集使用多线程 / 协程
  • cython 扩展 

GIL 的实现

Python 开发面试梳理

 GIL 试题

Python 开发面试梳理

 预测一下输出情况, 按照道理来说执行 5k 次, 每次 + 2 结果应该是 10000

Python 开发面试梳理

实际上多次执行可以看到偶尔会出现结果未保存就被覆盖的情况与预期不符

为什么有了GIL 还要关注线程安全 

python 中的原子操作

  •   一个字节码指令就可以完成的就是原子操作
  •   原子操作是可以保证线程安全的
  •   使用 dis 操作可以来分析字节码

dis 的使用

Python 开发面试梳理

 Python 开发面试梳理

 加锁解决线程安全问题

Python 开发面试梳理

 加锁会导致线程的性能下降, 但是保证了安全

如何剖析程序性能

使用各种 profile 工具 (内置或者第三方)

  • 二八定律, 大部分时间都耗时在少量的代码上
  • 内置的 profile / cprofile 等工具
  • 使用 pyflame (uber开源) 的火焰图工具

服务端性能优化措施

  • web 应用一般语言不会成为瓶颈
  • 数据结构与算法优化
  • 数据库层: 索引优化, 慢查询优化, 批量操作减少 IO, NoSQL
  • 网络 IO: 批量操作, pipeline 操作减少IO
  • 缓存: 使用内存数据库 redis / memcached 
  • 异步:  asyncio / celery
  • 并发: gevent / 多线程

Python 生成器 与 协程 

Generator 

  • 生成器就是可以生成值的函数
  • 当一个函数里有了 yield关键字就成了生成器
  • 生成器可以挂起执行兵器保持当前执行的状态

Python 开发面试梳理

基于生成器的协程

py3 之前是没有原生协程的

py2 是基于生成器来实现的协程

Python 开发面试梳理

Python 开发面试梳理

生成器可以通过 yield 暂停执行和产出数据

同时支持 send() 向生成器发送数据和 throw() 向生成器抛异常

Python 开发面试梳理

协程注意点

协程需要使用  send(None)  或者  next(coroutine)  来预激才能启动

在  yield  处协程会暂停执行

单独的  yield value  会产出值给调用方

可以通过  coroutine.send(value)  来给协程发送值

发送的值会赋值给 yield 表达式左边的变量  value = yield  

协程完成后(没有遇到下一个 yield语句) 会抛出  StopIteration  异常

协程装饰器

Python 开发面试梳理

 每次预激很麻烦, 装饰器在每次调用的时候先自动执行一个  next  方法从而进行预激

Python3 原生协程

在 3.5 版本的时候引入的 async / await 支持原生协程

Python 开发面试梳理

Python 单元测试

针对程序模块进行正确性的验证, 一个函数, 一个类进行验证

保证代码逻辑的正确性

单测影响设计, 易测的代码往往是高内聚低耦合的

回归测试, 防止改一处导致整个服务不可用

单元测试相关库

nose/pytest 较为常用

mock 模块用来模拟替换网络请求

coverage 统计测试覆盖率

测试实例

安装 

pip install pytest

待测函数

一个二分查找, 找到值返回值的位置, 找不到返回 -1 

Python 开发面试梳理

 测试用例编写

Python 开发面试梳理

 自动执行测试用例

pytest xxx.py 

正确执行时

Python 开发面试梳理

存在错误时

 Python 开发面试梳理

Python 内置的数据结构算法常考

Python 开发面试梳理

collections 模块相关的数据结构扩展

Python 开发面试梳理

 命名元祖 - 方便可读性

Python 开发面试梳理

 双端队列 - 方便前后存取

Python 开发面试梳理

 deque 可以很方便的实现 queue / stack 

Counter - 计数器

Python 开发面试梳理

 OrderedDict  - 有序字典

 Python 开发面试梳理

 OrderedDict 的 key 顺序是第一次插入的顺序, 可以用来实现 LRUCache 

DefaultDict  - 默认字典

带默认值的字段

Python 开发面试梳理

Pytnon dict 底层结构

dict 底层使用的是哈希表

哈希表的平均查找事件复杂度是 O(1)

CPython 解释器使用二次探查解决哈希冲突问题

常问的问题 哈希冲突和扩容

Python list / tuple 区别

都是线性结构, 支持下标访问

list 不能作为字典的 key, tuple 可以 (可变对象不可hash)

list 可变对象, tuple 保存的引用不可变 

Python 开发面试梳理

Python 开发面试梳理

什么是 LRUCache 

原理

Least-Recently-Used 替换掉最近最少使用的对象

缓存剔除策略, 当缓存空间不足的时候需要确定一种方式进行剔除key

常见的有 LRU , LFU 等

LRU 通过循环双端队列不断把最新访问的 key 放在表头实现

Python 开发面试梳理

实现

Python 开发面试梳理

# -*- coding: utf-8 -*-
from collections import OrderedDict


class LRUCache:
    
    def __init__(self, max_num=128):
        self.od = OrderedDict()
        self.max_num = max_num
    
    def get(self, k):  # 每次访问更新使用的 key
        if k in self.od:
            v = self.od[k]
            # 放在最尾部 (最右边), 表示最新使用过
            self.od.move_to_end(k)
            return v
        else:
            return -1
    
    def put(self, k, v):  # 更新 k/v
        if k in self.od:
            del self.od[k]
            self.od[k] = v  # 更新 key 到表头
        else:  # 插入
            self.od[k] = v
            # 判断容量
            if len(self.od) > self.max_num:
                # 满了删除最早的 key (最没人用的)
                self.od.popitem(last=False)

数据结构常考题

常见的数据结构 链表, 队列, 栈, 二叉树, 堆

使用内置的数据结构实现高级的数据结构, 比如内置的 list / deque 实现栈

Leetcode 或者 剑指offer 上的常见题

链表

Python 开发面试梳理

  • 如何使用 Python 来表示链表结构
  • 实现链表的常见操作, 比如插入节点, 反转链表, 合并多个链表等
  • Leetcode 常见的链表题目

删除连表的节点

Python 开发面试梳理

这道题要求的输入是被删除的节点而没有传入头结点, 因此按照正常来书

比如删除 5 我就找到 4  然后让 4 指向 要被删除的 5 的next 的1 就可以了

但是没有头结点只有 5 , 单链表没法往前找. 所以是没办法找到 4 

换个角度想 知道 5 那 1 是 5 的 next, 9 是 5 的next 的next, 即 5 往后的节点都是拿得到

因此可以考虑直接让 5 换成 1 然后跳过 1, 直接指向 9, 这样形式上就相当于删除了 5 

Python 开发面试梳理

如何反转链表

Python 开发面试梳理

原理来说就是由 一个 cur 定义当前节点, 然后 pre 进行往前指向

第一个节点的指向为空, 依次往后移动

把操作节点的后节点保存, 然后后指向属性改成前指向

再将当前操作节点保存为下一个操作节点的前指向之后进行后一个节点的重复操作

终止条件是当前节点的后指向不存在时到达结尾结束

# -*- coding: utf-8 -*-


class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None


def reverse_list(head):
    # 定义一个前指向的属性
    pre = None  # 第一个进来让往前直到 None
    cur = head
    # cur 会从 1 -> 2 -> 3 -> 4 -> None 的顺序移动
    while cur:
        nextnode = cur.next  # 保存下来后指向的 next 值
        cur.next = pre  # 将 pre 覆盖 next
        pre = cur  # 下一个的前指向为当前操作节点
        cur = nextnode  # 后移进行下一个的操作
    return pre


if __name__ == '__main__':
    # 1 - 2 - 3 - 4
    ln = ListNode(1)
    ln.next = ListNode(2)
    ln.next.next = ListNode(3)
    ln.next.next.next = ListNode(4)
    ln = reverse_list(ln)
    print(ln.val, ln.next.val, ln.next.next.val, ln.next.next.next.val)  # 4 3 2 1

合并两个有序列表

有两种方法可以实现, 一种是将 l1 插入到 l2 中

还有一种是创建一个 l3, 然后将 l1 l2 分别插入到 l3 中

以下为第二种方式, l1 和 l2 之间进行彼此比较从而决定谁来追加进入 l3, 每次被加入进去的指针往后移

同时 l3 每次追加后也要将指针后移, 最后将第一个节点之后的输出既可

# -*- coding: utf-8 -*-


class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None


def extend_list(l1, l2):
    if not l1 and not l2:
        return
    if not l1:
        return l2
    if not l2:
        return l1
    l3 = ListNode(0)
    # 头结点保存下来
    l3_head_node = l3
    while l1 and l2:
        if l1.val <= l2.val:
            l3.next = l1
            l1 = l1.next  # l1 后移
        else:
            l3.next = l2
            l2 = l2.next  # l2 后移
        l3 = l3.next  # l3 后移
    
    while l1 or l2:
        l3.next = l1 or l2
        if l1:
            l1 = l1.next
        if l2:
            l2 = l2.next
        l3 = l3.next
    
    return l3_head_node.next


if __name__ == '__main__':
    # 1 - 2 - 3 - 4
    ln1 = ListNode(1)
    ln1.next = ListNode(2)
    ln1.next.next = ListNode(3)
    ln1.next.next.next = ListNode(4)
    # 2 - 3 - 4 - 5
    ln2 = ListNode(2)
    ln2.next = ListNode(3)
    ln2.next.next = ListNode(4)
    ln2.next.next.next = ListNode(5)

    ret = extend_list(ln1, ln2)
    ln_list = []
    while ret:
        ln_list.append(ret.val)
        ret = ret.next
    print(ln_list)  # [1, 2, 2, 3, 3, 4, 4, 5]

队列

队列是先进先出的队列结构

  如何使用  Python 实现队列

  实现队列的 append 和 pop 操作,  如何做到先进先出

  使用 Python 的 list 或者 collection.deque 实现队列

代码实现

 Python 开发面试梳理

栈 ( stack ) 是后进先出的结构

  如何使用 Python 实现栈

  实现栈的 push 和 pop 操作, 如何做到后进先出

  同样可以用 Python list 或者 collection.deque 实现栈

代码实现

Python 开发面试梳理

 ps:

  如何用两个栈实现一个队列?

Python 开发面试梳理

from collections import deque


class Stack:
    def __init__(self):
        self.items = deque()
    
    def push(self, v):
        return self.items.append(v)
    
    def pop(self):
        return self.items.pop()
    
    def len(self):
        return len(self.items)


class Queue:
    def __init__(self, q1, q2):
        self.q1 = q1
        self.q2 = q2
    
    def append(self, v):
        return self.q1.push(v)
    
    def pop(self):
        while self.q1.len():
            self.q2.push(self.q1.pop())
        return self.q2.pop()


if __name__ == '__main__':
    q = Queue(Stack(), Stack())
    q.append(1)
    q.append(2)
    q.append(3)
    print(q.pop()) # 1
    print(q.pop()) # 2
    print(q.pop()) # 3
View Code

相关文章:

  • 2021-11-18
  • 2021-11-04
  • 2022-12-23
  • 2019-11-23
  • 2021-09-17
  • 2021-10-31
  • 2022-01-04
猜你喜欢
  • 2021-08-22
  • 2021-05-18
  • 2022-12-23
  • 2021-06-11
  • 2021-06-18
  • 2021-11-26
  • 2022-12-23
相关资源
相似解决方案