Python后端技术栈

Happiness is a way of travel. Not a destination.

幸福是一种旅行方式。 不是目的地。

1.导航
1.1Python 语言基础
1.语言特点

2.语法基础

3.高级特性

1.2算法与数据结构
1.常用算法和数据结构

2.分析时间和空间复杂度

3.实现常见数据结构和算法

1.3编程范式
1.面向对象编程

2.常用设计模式

3.函数式编程

1.4操作系统
1.常用 Linux

2.进程和线程

3.内存管理

1.5网络编程
1.常用协议 TCP、IP、HTTP

2.Socket 编程基础

3.Python 并发库

1.6数据库
1.MySQL 数据库、索引优化

2.关系型和 NoSQL 的使用场景

3.Redis 缓存(常用的数据类型以及使用场景,底层实现了解会更好)

1.7Python Web框架
1.常用框架的对比,使用 Restful

2.WSGI 原理

3.Web 安全的问题

1.8系统设计
1.设计原则,如何分析

2.后端系统常用的组件(缓存、数据库、消息队列等等)

3.技术选型和实现(短网址服务、Feed 流系统)

1.9技术之外的软实力
1.学习能力

2.业务理解能力,沟通交流能力

3.心态

2.0小扩展-STAR模型
在生活中描述一件事情或者是在面试中描述项目经验,如何让我们的语言更加有条理,逻辑性?可以采用如下的模型进行梳理。

情境(situation)

什么情况下发生的

任务(task)

你是如何明确你的任务的

行动(action)

采取了什么样的行动

结果(result)

结果怎么样?学到了什么?

2.技术栈详解
2.1 Python 语言基础
2.1.1 Python 语言特性
Python 是动态强类型语言,很多人都误认为是弱类型语言,其实是错误的。

动态是指在运行期确定类型(静态则是在编译期确定类型)。强类型指的是在没有强制类型转化前,不允许两种不同类型的变量相互操作(也就是不会发生隐式类型转换)。

2.1.2 Python 作为后端语言的优缺点
为什么使用 Python ?

答:它是一门胶水语言,轮子多,应用广泛;语言灵活,生产力高,是一些创业公司以及外包项目节省时间的首选语言。但是在使用的时候,需要考虑到性能的问题,代码维护问题,以及2和3版本的兼容问题。

2.1.3 Python 其他的重要知识点
鸭子类型简单介绍

曾有一个生动的例子来描述鸭子类型:当看到一只鸟,如果它走起来像鸭子、叫起来像鸭子,游泳的时候也像鸭子,那么我们就称这只鸟是鸭子。它的关注点在对象的行为,而不是类型。比如一些常用对象 file、StringIO、socket 都支持 read/write 方法,我们可以看做类似的对象 file like object;再举个例子,在 Python 中实现了 iter 魔法方法的对象都可以用 for 迭代。

什么是 monkey patch ?

答:所谓的 monkey patch 就是运行时的属性替换。比如我们常用的一个并发库 gevent ,需要将内置的 socket 修改为非阻塞。我们在使用的时候,用到如下的代码:

from gevent import monkey

打补丁,让gevent框架识别耗时操作,比如:time.sleep,网络请求延时
monkey.patch_all()
什么是自省?

答:自省就是在运行时判断一个对象的类型的能力。我们可以通过 type、id和 isinstance 等方法获取对象类型的信息。Inspect 模块提供了更多获取对象信息的函数。

什么是列表和字典推导式?

列表生成式:

my_list = [i for i in range(10) if i % 2 == 0]
字典推导式:

dict1 = {k: v for k, v in zip(a, b)}
特殊的情况:将列表推导式的方括号修改为小括号的时候,会返回一个生成器。

2.1.4 Python 之禅
The Zen of Python 便是著名的Python 之禅,它是由 Tim Peters 编写的关于 Python 编程的准则,我们可以使用下面的代码进行查看:

import this
我们在编程的过程中,如果拿不准的时候可以进行一个参考。

2.2 Python2 和 Python3 的差异
2.2.1 Python2/3 差异
Python3 中做了一些改进,我们需要了解。比如 print 成为了函数;还有编码的问题,Python3 中不再有 Unicode 对象,默认 str 就是 Unicode;除法也有所变化,比如 Python3 除法返回的是浮点数。

Python2 里面是没有类型申明的,Python3 中我们可以添加一个类型注解(type hint),帮助 IDE 实现类型提示以及类型检查(mypy)。Python3 中优化的 super() 方便直接调用父类函数。Python3 中还有一些高级的解包操作,如下面示例:

a, b, *rest = range(10)
上面会将0赋值给 a,将1赋值给 b,然后将剩下的赋值给 rest。

类型检查示例:

In [2]: def fuction(name: str): return ‘hello’ + name
In [3]: fuction(‘Ethan’)
Out[3]: ‘helloEthan’
Python3 限定关键字参数。也就是函数在传参的时候,我们可以通过关键字参数方式,指定参数名传参,避免参数太多时候搞混。

Python3 中重新抛出异常不会丢失栈信息,方便我们去排错(在 Python2 中如果在一个异常中 raise 一个异常,原来的异常就会丢失,Python3 中支持 raise from,保留异常栈信息)。

Python3 中一切都是返回迭代器,比如 range/zip/map/dict.value/etc. are all iterators。

2.2.2 Python3 新增
1.yield from 链接子生成器

2.asyncio 内置库, async、await 原生协程支持异步编程

3.新的内置库 enum(枚举),mock(单测时用),asyncio, ipaddress (处理ip地址)等等。

2.2.3 Python3 改进
1.生成的 pyc 文件统一放到 __pycache__文件夹下。

2.一些内置库的修改。如 urllib,selector(支持select、epoll等Linux底层的一些封装,方便我们统一做一些接口,实现异步IO) 等。

3.一些性能的优化,比如 dict。

2.2.4一些兼容2、3的工具

six 模块。

2to3 等工具转换代码。(脚本工具,将 Python2 转换为 Python3 代码)

__future__模块。在 Python2 中使用 Python3 的函数功能可参照如下代码:

from future import print_fuction
2.3 Python 函数
2.3.1 Python 如何传递参数?
答:Python 其实不是引用传递也不是值传递,而是共享传参(函数形参获得实参中各个引用的副本)。简单的理解一下:

我们在每一次传递参数的时候,形参和实参都指向同一个对象,这样就叫做对象传递,既不是拷贝了一个值,也不是直接去操作这块内存,但是它的结果有两个。对于可变对象来说,我们直接去修改它,对于不可变对象来说,表现就好像 copy 了一个值,然后去修改新的值。

不可变对象好像是传值,可变对象好像是传引用。但是实际是不同的。

2.3.2 Python 可变/不可变对象
1.可变对象:bool、int、float、tuple、str

2.不可变对象:list、set、dict

可变对象作为默认参数的时候,注意默认参数只计算一次。

2.3.3 Python 中 *args 和 **kwargs
函数传递中,他们处理可变参数。如果使用 *args那么会将所有的参数打包成一个 tuple 对象。 **kwargs 则是将所有的关键字参数打包成一个 dict 对象。

2.4 Python 异常机制
2.4.1什么是 Python 的异常?
答:异常就是一种错误处理机制。所有的异常都继承自 BaseException 。举几个和系统相关的异常:SystemExit、KeyboardInterrupt、GeneratorExit(生成器退出的异常)。还有一个异常的基类就是 Exception。

2.4.2使用异常的常见场景
答:网络请求(超时、连接错误);资源访问(权限问题、资源不存在);代码逻辑(越界访问、KeyError等)。

2.4.3处理异常
try:
# 可能会抛出异常的代码
except (Exception1, Exception2) as e:
# 异常处理代码
else:
# 异常没有发生时代码逻辑
finally:
# 无论异常有没有发生都会执行的代码,一般处理资源的关闭和释放。
2.4.4如何自定义异常
1.继承自 Exception 实现自定义异常(想想为什么不是 BaseException)

可以通过查看异常的等级信息,发现如果继承自顶级父类,那么一些常用的异常也没有了,自己需要定义的异常就太多太多,耗费时间。

2.可以给异常加上一些附加信息。

3.通常都是处理一些和业务相关的特定异常(raise MyException)

2.5 Python 性能分析与优化
Python 作为一门脚本语言来说,它的性能一直被诟病。并且由于存在一个臭名昭著的 GIL 导致没有办法充分利用多核,这都限制了 Python 的性能。

2.5.1什么是 CPython GIL?
GIL (Global Interpreter Lock)

1.CPython 解释器的内存管理并不是线程安全的,存在多个线程时,有可能会出现同时修改同一对象,这样容易出现问题。

2.为了保护多线程情况下对 Python 对象的访问,CPython 使用了简单的锁机制避免多个线程同时执行字节码。

缺陷便是没有办法同时利用 CPU 的多核,只有一个线程执行字节码。对于 CPU 密集型的程序来说,影响会比较大。

2.5.2 GIL 的影响
限制了程序的多核执行。

1.同一个时间只能有一个线程执行字节码

2.CPU 密集型程序难以利用多核优势。

3.IO 期间会释放 GIL ,对 IO 密集型程序影响不大。

2.5.3如何规避 GIL 的影响
1.CPU 密集型可以使用多进程 + 进程池的方式充分的利用多核。

2.IO 密集型可以使用多线程或者是协程。

3.使用 cython 扩展(将 Python 程序转化成 C 代码的一个扩展)。

2.5.4 GIL 的实现
CPython 中才会有 GIL ,其他的解释器是没有的。底层的代码逻辑是设置一个ticker,每执行多少个字节码的时候,去检查当前是否有全局解释器锁,如果有那么执行函数释放,让其他的线程去执行;如果没有,就重新获取锁。通俗一点就是每隔一段时间,就会尝试去释放当前线程的锁,让其他线程获取锁并去执行。

2.5.5为什么有了 GIL 之后,还要关注线程安全?
Python中什么操作才是原子的?一步到位执行完的。

1.一个操作如果是一个字节码指令可以完成的就是原子的。

2.原子的是可以保证线程安全的,非原子操作不是线程安全的。

3.使用 dis 操作来分析字节码。

import dis

def update_list(l):
l[0] = 1

dis.dis(update_list)
2.5.6如何剖析程序性能
使用各种 profile 工具(内置或第三方)

1.遵循二八定律,其实大部分的时间耗时在少量的代码上。

2.通过内置的 profile 和 cprofile 等工具衡量程序的运行时间。

3.对于 web 应用来说,使用 pyflame(uber开源) 的火焰图工具分析产品的性能。

2.5.7服务端性能优化措施
web应用一般语言不会成为瓶颈。可以采用如下的一些优化措施:

1.数据结构和算法优化。

2.数据库层:索引优化、慢查询消除、批量操作减少IO、NoSQL的使用。

3.网络IO:批量操作, pipline 操作减少 IO。

4.缓存:使用内存数据库 redis、memcached 等。以此抗一些并发比较高的请求。

5.使用异步的框架或者库如 asyncio 和 celery。

6.对于并发相关的一些请求使用 gevent 协程或者多线程。

2.6 Python 生成器与协程
2.6.1什么是生成器
Generator

1.生成器就是可以生成值的函数。

2.当一个函数里有了 yield 关键字就成了生成器。

3.生成器可以挂起执行并且保持当前执行的状态。

2.6.2基于生成器的协程
Python3 之前没有原生协程,只有基于生成器的协程。

1.pep 342(Coroutines via Enhanced Generators)增强生成器功能。

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

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

示例:

def coro():
# yield 关键字在 = 右边作为表达式,可以被send值
hello = yield ‘hello’
yield hello
c = coro()

输出 hello,这里调用next产出*个值 hello,之后函数暂停
print(next©)

再次调用 send 发送值,此时hello变量赋值为 world,然后yield 产出hello变量的值 world
print(c.send(‘world’))

之后协程结束,后续再send值会抛出异常StopIteration
2.6.3协程的注意点
1.协程需要使用 send(None) 或者 next(coroutine) 来 『预激』(prime) 才能启动。

2.在 yield 处协程会暂停执行。

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

4.可以通过 coroutine.send(value) 来给协程发送值,发送的值会赋值给 yield 表达式左边的变量 value=yield

5.协程执行完成之后(没有遇到下一个 yield 语句)会抛出 StopIteration 异常。

2.6.4协程装饰器
避免每次都要用 send 预激它。

from functools import wraps
def coroutine(func):
# 这样就不需要每次都用send(None)启动了
# 装饰器:向前执行到*个 yield 表达式,预激func
@wraps(func)
def primer(*args,**kwargs):
gen = func(*args,**kwargs)
next(gen)
return gen
return primer
2.6.5Python3 原生协程
Python3.5 引入 async/await 支持原生协程(native coroutine)

2.7单元测试
2.7.1什么是单元测试
Unit Testing

1.针对程序模块进行正确性检验。

2.一个函数,一个类进行验证。

3.自底向上保证程序正确性。

2.7.2为什么写单元测试
三无代码不可取(无文档、无注释、无单测)

1.保证代码逻辑的正确性(甚至有些采用测试驱动开发(TDD))

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

3.回归测试,防止改一处整个服务不可用。

2.7.3单元测试相关的库
1.nose/pytest 较为常用

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

3.coverage 统计测试覆盖率

如何设计测试用例:(等价类划分): 1.正常值功能测试。 2.边界值(比如*大*小,*左*右值) 3.异常值(比如None,空值,非法值)

2.8重点知识
2.8.1 Python 深拷贝与浅拷贝
浅拷贝:对于不可变对象相当于引用赋值;浅拷贝对于可变对象拷贝时只拷贝*层引用。

深拷贝:对于不可变对象同样相当于引用赋值;对于可变对象会逐层进行拷贝。

Python 中浅拷贝的方式:copy 模块的 copy 方法;对象本身的 copy 方法;工厂方法;切片(只对列表有效)。

工厂方法就是直接使用 list 等方法进行修改。 Python 中默认使用的就是浅拷贝方式。
Python技术交流群871458817 免费分享机器学习,爬虫,数据分析等,

2.8.2小结
1.不可变对象在赋值时会开辟新空间

2.可变对象在赋值时,修改一个引用的值,另一个引用也会发生改变。

3.深浅拷贝对不可变对象拷贝时,不开辟新的空间,相当于赋值操作。

4.浅拷贝在拷贝时,只拷贝顶层中的引用,如果元素是可变对象,并且被修改,那么拷贝的对象也会发生变化。

5.深拷贝在拷贝时,会逐层进行拷贝,直到所有的引用都是不可变对象为止。

6.Python 中有多种方式实现浅拷贝,copy 模块的 copy 函数,对象的 copy 函数,工厂方法,切片等。

7.大多数情况下,编写程序时,都是使用浅拷贝,除非有特定的需求。

8.浅拷贝的优点:拷贝速度快,占用空间少,拷贝效率高。
————————————————