python语言基础篇
1 手写一个装饰器
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计
# 需要参数的装饰器(需要工厂函数)
def log_decorator(level="INFO"): # 接收参数
def decorator(func): # 接收函数
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[{level}] 进入 {func.__name__}")
return func(*args, **kwargs)
return wrapper
return decorator
# 使用带参数的装饰器
@log_decorator(level="DEBUG")
def test():
pass
现在是三层结构(修饰器工厂+装饰器+wrapper)
- 第一层装饰器工厂:需要传递参数的情况
- 第二层装饰器:接收被装饰函数
- 第三层执行逻辑:执行切面逻辑
@wraps(func) :保留原始函数的名称、文档字符串等元数据,避免装饰后函数名变成wrapper
2 单例模式
单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
常见的单例模式写法:
1. 模块级单例(最简单)
# mysingleton.py
class My_Singleton(object):
def foo(self):
pass
my_singleton = My_Singleton()
# to use
from mysingleton import my_singleton
my_singleton.foo()
优点:天然线程安全,最简单
缺点:无法延迟初始化
2. 装饰器实现(推荐)
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Logger:
def __init__(self):
self.log_level = "INFO"
# 使用:
logger1 = Logger()
logger2 = Logger()
print(logger1 is logger2) # True
优点:简洁通用,支持参数传递
缺点:每个类需单独装饰
3. 重写 __new__方法(经典)
class Singleton(object):
def __new__(cls, *args, **kw):
if not hasattr(cls, '_instance'):
orig = super(Singleton, cls)
cls._instance = orig.__new__(cls, *args, **kw)
return cls._instance
class MyClass(Singleton):
a = 1
优点:直观易理解
缺点:需处理继承问题,__init__会被多次调用
3 GIL线程全局锁
线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,
为什么Python要有GIL?
-
简化内存管理:
- Python使用引用计数进行内存管理
- GIL避免了多线程同时修改引用计数的竞争条件
- 使CPython实现更简单、更稳定
-
保护C扩展:
- 许多Python库是用C编写的
- GIL简化了这些扩展的线程安全实现
4 协程
协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,它允许在单个线程内实现多个任务的协作式多任务处理。协程不是由操作系统内核管理,而是由程序员在代码中显式控制切换
核心特点:
- 用户态调度:切换由程序控制,无需操作系统介入
- 极低开销:切换成本远低于线程切换(约1/100)
- 协作式:协程主动让出控制权,而非被抢占
- 单线程并发:在单个线程内实现并发效果
核心的两个关键字,async和await
async用于声明一个函数为异步函数(也称为协程函数)await用于挂起当前协程的执行,等待一个可等待对象(awaitable)完成。
当遇到 await时,当前协程暂停执行,控制权返回给事件循环,等待可等待对象完成,等待期间不阻塞线程
import asyncio
async def task_one():
print("任务一开始")
await asyncio.sleep(1) # 挂起点1
print("任务一继续")
return "结果一"
async def task_two():
print("任务二开始")
await asyncio.sleep(0.5) # 挂起点2
print("任务二完成")
return "结果二"
async def main():
# 创建任务但不立即执行
t1 = asyncio.create_task(task_one())
t2 = asyncio.create_task(task_two())
# 等待两个任务完成
results = await asyncio.gather(t1, t2)
print(results)
# 启动事件循环
asyncio.run(main())
5 闭包原理
闭包(Closure)是函数式编程中的一个核心概念,在 Python 中指的是一个函数与其引用环境的组合。闭包是一个函数对象
闭包的三要素:
- 嵌套函数(内部函数定义在外部函数内)
- 内部函数引用了外部函数的变量
- 外部函数返回内部函数
def outer(x):
def inner(y):
return x + y
return inner
f = outer(10)
print(f(5)) # 输出 15
| 常见用途 |
|---|
| 函数工厂、装饰器、计数器、缓存 |
|---|
6 Python函数式编程
lambda函数
其实就是一个匿名函数
f = lambda x: x * x
print(f(4)) #**16**
def que(a, b, c):
return lambda x: a * x * x + b * x + c
# 第一种写法
f = que(-1, 1, 2) #-18
print(f(5))
# 第二种写法
print(que(-1, 1, 2)(5)) #-18
python中函数式编程支持:
filter 函数的功能相当于过滤器。调用一个布尔函数bool_func来迭代遍历每个seq中的元素;返回一个使bool_seq返回值为true的元素的序列。
>>>a = [1,2,3,4,5,6,7]
>>>b = filter(lambda x: x > 5, a)
>>>print(b)
>>>[6,7]
map函数是对一个序列的每个项依次执行函数,下面是对一个序列每个项都乘以2:
>>> a = map(lambda x:x*2,[1,2,3])
>>> list(a)
[2, 4, 6]
reduce函数是对一个序列的每个项迭代调用函数,下面是求3的阶乘:
>>> reduce(lambda x,y:x*y,range(1,4))
6
7 Python里的拷贝
引用和copy(),deepcopy()的区别
浅拷贝: 创建新对象,其内容是原对象的引用。
深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。
8 Python垃圾回收机制
Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。
1 引用计数
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。
优点:
- 简单
- 实时性
缺点:
- 维护引用计数消耗资源
- 循环引用
2 标记-清除机制
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
3 分代技术
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
举例: 当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。
操作系统(复杂)
1 select,poll和epoll
其实所有的I/O都是轮询的方法,只不过实现的层面不同罢了.
这个问题可能有点深入了,但相信能回答出这个问题是对I/O多路复用有很好的了解了.其中tornado使用的就是epoll的.
基本上select有3个缺点:
- 连接数受限
- 查找配对速度慢
- 数据由内核拷贝到用户态
poll改善了第一个缺点
epoll改了三个缺点.
关于epoll的: www.cnblogs.com/my_life/art…
数据库
1 事务
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 彻底理解数据库事务: www.hollischuang.com/archives/89…
2 数据库索引
推荐: zhuanlan.zhihu.com/p/113917726 聚集索引,非聚集索引,B-Tree,B+Tree,最左前缀原理
3 MyISAM和InnoDB
MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。
InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。
mysql 数据库引擎: www.cnblogs.com/0201zcr/p/5… MySQL存储引擎--MyISAM与InnoDB区别: segmentfault.com/a/119000000…
网络
1 三次握手
- 客户端通过向服务器端发送一个SYN来创建一个主动打开,作为三次握手的一部分。客户端把这段连接的序号设定为随机数 A。
- 服务器端应当为一个合法的SYN回送一个SYN/ACK。ACK 的确认码应为 A+1,SYN/ACK 包本身又有一个随机序号 B。
- 最后,客户端再发送一个ACK。当服务端受到这个ACK的时候,就完成了三路握手,并进入了连接创建状态。此时包序号被设定为收到的确认号 A+1,而响应则为 B+1。
2 四次挥手
注意: 中断连接端可以是客户端,也可以是服务器端. 下面仅以客户端断开连接举例, 反之亦然.
-
客户端发送一个数据分段, 其中的 FIN 标记设置为1. 客户端进入 FIN-WAIT 状态. 该状态下客户端只接收数据, 不再发送数据.
-
服务器接收到带有 FIN = 1 的数据分段, 发送带有 ACK = 1 的剩余数据分段, 确认收到客户端发来的 FIN 信息.
-
服务器等到所有数据传输结束, 向客户端发送一个带有 FIN = 1 的数据分段, 并进入 CLOSE-WAIT 状态, 等待客户端发来带有 ACK = 1 的确认报文.
-
客户端收到服务器发来带有 FIN = 1 的报文, 返回 ACK = 1 的报文确认, 为了防止服务器端未收到需要重发, 进入 TIME-WAIT 状态. 服务器接收到报文后关闭连接. 客户端等待 2MSL 后未收到回复, 则认为服务器成功关闭, 客户端关闭连接.