
2024.02-2024.071 python基础1 字典哈希冲突时底层是怎么解决的Python字典CPython采用开放地址法Open Addressing当发生哈希冲突时会通过一个名为 “perturb” 的扰动算法生成探测序列[伪随机探测]在探测表中依次向后查找直到找到空槽位来存储该键值对。3 进程间的通信方式线程协程执行单元内存模型数据通信传递业务数据同步控制传递信号/许可特性备注进程隔离默认不可见1.管道/队列拷贝序列化2.共享内存零拷贝映射3.Socket4. 消息中间件(Kafka)① 锁Lock② 事件Event③ 条件变量Condition④ 信号量Semaphore基于内核对象实现跨进程阻塞会触发系统调用开销最大。线程共享堆、全局可见1.共享变量直接读写2.队列Queue3.匿名管道os.pipe① 锁Lock② 事件Event③ 条件变量Condition④ 信号量Semaphore同一进程的锁基于用户态原子操作如pthread_mutex不涉及进程切换开销中等。协程单线程内共享协作式1.共享变量/闭包2.异步队列asyncio.Queue3.Future/Task 返回值① 异步锁asyncio.Lock② 异步事件asyncio.Event③ 异步条件asyncio.Condition④ 异步信号量asyncio.Semaphore纯用户态实现阻塞时不会让出操作系统CPU只是让出事件循环控制权开销最小。为了不在回答问题时翻车你可以用这个绝对严谨的判断标准如果A和B之间传递的东西是业务逻辑要处理的“原材料”比如用户名、订单号这叫通信。如果A和B之间传递的东西只是为了让对方“等一下”或“开始跑”的开关信号这叫同步。4 多个装饰器的执行顺序Python 多个装饰器的执行顺序需要严格区分定义阶段装饰阶段和调用阶段运行阶段两者的顺序完全相反。核心结论是装饰时从下往上调用时从上往下。outerinnerdeffunc():pass它实际上等价于funcouter(inner(func))定义/装饰阶段inner(func) 先执行先装饰然后将返回的结果传给 outer 再执行后装饰。所以应用顺序是由下往上。调用阶段当你执行 func() 时实际上执行的是 outer(inner(func))()。最外层的 outer 包装器会最先被触发然后才轮到 inner 包装器最后才执行原函数。所以运行时顺序是由上往下。5 怎么写一个装饰器6 装饰器可以有参数吗7 说一下python的垃圾回收Python 的垃圾回收GCGarbage Collection机制是一个以“引用计数”为主以“分代垃圾回收”为辅的混合策略。核心宗旨是自动管理内存当对象的引用计数归零时立即释放内存并额外通过追踪机制解决循环引用导致的泄漏问题。 第一层主力引用计数 (Reference Counting)这是 Python 最基础的 GC 机制工作方式极其简单直接规则每个 Python 对象内部都维护着一个数字ob_refcnt记录着有多少个引用指向它。动作每当新引用指向该对象时如b a计数1。每当引用失效时如del b或变量被重新赋值计数-1。回收当计数降到0时Python会立即注意是立即回收该对象占用的内存。优点即时的、确定性高没有任何延迟。致命缺点无法处理循环引用。比如两个对象互相持有对方的引用A.b B且B.a A即使外部没有变量指向它们了它们各自的引用计数依然为 1导致内存永远无法被回收。♻️ 第二层辅助分代垃圾回收 (Generational GC)为了解决循环引用问题Python 引入了gc模块实现了一个经典的“标记-清除 (Mark-Sweep”算法并采用分代 (Generational)策略来优化性能。1. 标记-清除 (Mark-Sweep) 原理当gc模块被触发或手动调用gc.collect()时标记 (Mark)从根对象如全局变量、栈帧出发逐个遍历将所有能访问到的对象标记为“存活”。清除 (Sweep)遍历所有对象如果某个对象没有被标记为“存活”说明它已经无法被程序访问即垃圾立刻回收它。注意这对循环引用有效因为循环引用的两个对象都不在“根”的引用链上不会被标记所以会被清除。2. 分代 (Generational) 优化原理“标记-清除”需要遍历全量内存耗时较长。根据“弱代假说”越年轻的对象死得越快Python 将对象分为三代 (Generation 0, 1, 2)代数命名特点触发频率0代新生代刚创建的对象。大多数对象很快会变成垃圾回收频率最高。最频繁1代中生代熬过 0 代回收仍未死亡的对象。频率适中2代老年代熬过 1 代回收的对象如长生命周期的全局变量。最不频繁阈值控制Python 维护着三个阈值例如默认(700, 10, 10)。当 0 代对象数量超过 700 时触发一次 0 代回收当 0 代回收次数超过 10 次触发 1 代回收以此类推。️ 与垃圾回收器的交互gc模块作为开发者你可以通过内置的gc模块来干预 GC 行为importgc# 1. 查看阈值print(gc.get_threshold())# 输出: (700, 10, 10)# 2. 手动强制回收通常用于处理大量循环引用的场景gc.collect()# 3. 禁用自动 GC用于提升某些性能敏感场景的速度风险极高务必手动管理gc.disable()⚠️ 特殊情况__del__方法与循环引用如果定义了__del__方法的对象陷入了循环引用Python无法回收该对象。因为 GC 无法确定是先回收对象还是先调用__del__方法会产生二义性。因此Python 会将此类对象放入gc.garbage列表你需要手动处理如强制打断循环。 一句话总结引用计数提供实时、无延迟的回收但搞不定循环引用。分代 GC专门打扫循环引用的战场通过分代策略把内存开销降到最低。两者结合构成了 Python 自动内存管理的完整闭环。在写高性能应用时如果创建了大量容灾对象列表、字典、类实例记得可以适时关注gc.collect()的开销。8 怎么查看有没有内存泄漏有什么第三方库Unicode 是“字符集”它为全球每一个字符分配了一个独一无二的编号码点解决了字符的“身份”问题而 UTF-8 是“编码规则”它定义了如何将这个编号转换成计算机能存储和传输的二进制字节序列。简单说Unicode 是字典查字符的编号UTF-8 是其中一种最常用的“压缩打包”方案将编号变成长度不等的字节它兼容 ASCII 且节省空间。9 如何排查内存泄漏python–杂识–22–web内存泄漏排查10 unicode、utf-8的区别11 生成器用于什么场景处理海量数据 / 超大文件最经典生成无限序列无穷数据流Web 服务中的流式响应边生成边传输12 进程、线程、协程的区别进程操作系统资源分配的基本单位拥有独立的虚拟地址空间和系统资源进程间默认内存隔离切换开销大由操作系统内核管理。线程操作系统CPU调度的基本单位隶属于进程并共享进程的堆内存和全局资源仅拥有独立的栈和寄存器由操作系统内核抢占式调度切换开销适中。协程用户态的轻量级执行流运行在线程之上通过协作式主动让出控制权await/yield实现并发切换仅涉及栈指针变更无需内核参与开销极小。13 python字典的键可以使用列表吗不可以。 因为字典的键必须是可哈希hashable的类型而列表是可变mutable的可变对象不能计算哈希值。15 python如何解决高并发问题Python高并发的核心策略就是对CPU密集型任务使用多进程对I/O密集型任务使用多线程或异步编程。并发方案核心机制适用场景优点缺点多线程 (Threading)共享内存受GIL限制I/O密集型网络爬虫、批量API调用、文件读写轻量、启动快、共享内存通信简单无法并行计算GIL是性能天花板多进程 (Multiprocessing)独立内存每个进程有自己的GILCPU密集型数值计算、图像/视频处理、机器学习突破GIL限制真正利用多核实现线性加速重资源、启动慢、进程间通信IPC开销大异步编程 (Asyncio)单线程协作式调度事件循环驱动高并发I/O密集型高并发Web服务、API网关、实时数据推送极轻量、支持海量并发连接、资源占用极低需要异步友好的库编程模型复杂有学习曲线16 讲一下跳表跳表Skip List是一种基于有序链表发展而来的数据结构。它通过**“空间换时间”的策略在底层有序链表之上随机建立多层索引**从而将查找效率从链表的O(n)提升至平均O(log n)。直观想象它就像一本带有“章节概览”和“目录”的厚书。底层是书的全部正文所有数据。上层索引是提炼出的“章节目录”和“小节标题”。查找时你先翻目录顶层发现章节不对快速跳到下一页目录再逐级往下找最后在正文底层精准定位。 跳表长什么样结构剖析跳表由多个层级Level组成每一层都是一个有序链表。Level 0 (最底层)包含所有的数据节点按照键Key的大小严格排序。Level 1 (第一层索引)包含部分数据节点约占总数的 1/2 或 1/4。Level 2 (第二层索引)包含更少的数据节点约占 Level 1 的 1/2 或 1/4。Head 节点每层都有一个头节点指向该层的第一个元素。⚙️ 核心操作是如何执行的操作执行流程通俗版平均时间复杂度查找 (Search)“先纵后横”。从最高层的头节点出发向右移动。如果右边的下一个节点大于目标值就向下降一层。重复此过程直到在底层找到精确匹配。O(log n)插入 (Insert)1. 执行查找定位到最底层该插入的位置。2.最关键通过随机算法决定新节点的**“高度”**拥有几层指针。3. 在涉及的每一层链表中将新节点插入到对应位置。O(log n)删除 (Delete)1. 查找目标节点。2. 在所有包含该节点的层级中将其指针摘除普通链表删除。O(log n) 核心机制解密为什么“随机”决定层数解决你之前的疑惑跳表最大的亮点也是它区别于平衡树的地方正是通过随机化来维护平衡。层数是怎么决定的抛硬币算法插入新节点时程序并不会手动指定它的高度而是运行一个简单的循环伪代码intrandomLevel(){intlevel1;// 假设概率 p 0.25 (Redis采用的值)while(random()plevelMAX_LEVEL){level;// 只要抽中就继续升层}returnlevel;}实际执行过程先把level设为 1必须存在于最底层。开始抽签如果运气好抽中前 25%升到第 2 层。继续抽签如果运气还是好再次抽中前 25%升到第 3 层。直到某次抽签运气不好抽中后 75%时停止level就是最终高度。 跳表 vs 其他数据结构横向对比对比维度跳表 (Skip List)平衡树 (红黑树)哈希表 (Hash Table)查找复杂度平均 O(log n)严格 O(log n)平均 O(1)实现难度极简单几十行代码极复杂需处理旋转变色中等有序遍历天然支持底层链表顺序支持中序遍历不支持数据无序范围查询极高效找到起点直接向后遍历较高效无法实现并发写入容易实现无锁Lock-Free极难实现无锁通常用粗粒度锁视实现而定内存占用稍大存储多层指针稍小只存左右孩子指针稍大负载因子 工业级经典应用让你知道它有多重要Redis 有序集合 (ZSET)当集合元素数量较多或成员为长字符串时Redis 底层采用“跳表 哈希表”的组合。哈希表负责 O(1) 查分数跳表负责高效的范围查找如ZRANGE、排名计算ZRANK以及按分数排序。LevelDB / RocksDB (LSM-Tree)在这些存储引擎的内存中MemTable使用跳表来维护有序的键值对以支撑极高速的写入和有序扫描。Lucene / Elasticsearch在倒排索引的合并和排序过程中利用跳表进行高效的多路归并。 一句话终极总结记下它面试够了跳表是一种用“随机抛硬币”代替“复杂旋转”来实现平衡的有序链表。它牺牲了严格的 O(log n) 确定性换取了极简的代码实现、天然的有序性以及卓越的并发性能。它是工程界在“平衡树”和“哈希表”之间找到的一个极其优雅的中间点。17 python2.7 与 python3 的字符串类型有哪些区别18 python3 多继承时如果继承的多个父类中都有同名的方法派生类实例化的对象调用同名的方法时会先调用父类中的哪一个顺序是怎样的Python 3 在多继承时方法调用顺序由C3 线性化算法计算出的方法解析顺序MROMethod Resolution Order决定。简单来说这个顺序遵循两个核心原则子类优先于父类查找从当前子类自身开始。保持父类声明顺序在继承列表中越靠前的父类优先级越高。MRO 的顺序可以通过类的__mro__属性或mro()方法查看。 方法解析顺序 (MRO) 示例我们通过一个例子来看 MRO 是如何工作的。classA:defgreet(self):print(Hello from A)classB:defgreet(self):print(Hello from B)classC(A,B):# C 先继承 A后继承 Bpass# 查看 C 的 MROprint(C.__mro__)# 输出: (class __main__.C, class __main__.A, class __main__.B, class object)# 创建实例并调用方法cC()c.greet()# 输出: Hello from A在这个例子中C的 MRO 是C - A - B - object。因为A在继承列表中排在B前面所以c.greet()会调用A的greet方法。如果将C的父类顺序调换为C(B, A)那么 MRO 会变为C - B - A - objectc.greet()调用的就会是B的greet方法。 总结Python 3 多继承的方法调用顺序由C3 算法计算出的MRO决定子类永远优先父类则按照你在定义子类时括号里的从左到右顺序决定优先级。19 同步是什么异步是什么同步Synchronous和异步Asynchronous从调用方的视角来看核心区别确实就是调用方是否需要等待被调用方的结果返回才能继续执行下去。 同步与异步的准确定义概念核心定义调用方视角通俗比喻同步 (Sync)发起调用后调用方必须阻塞等待直到被调用方执行完毕并返回结果才能继续执行下一步。主动等待发起了请求就站在原地等结果拿到结果才走。排队打饭你站在窗口前必须等阿姨把饭打好递给你你才能端着盘子离开。异步 (Async)发起调用后调用方不阻塞**立即返回继续执行自己的任务。被调用方的执行结果会通过回调、事件或Future等方式在未来某个时刻通知调用方。**不等待发起了请求转身就走等结果出来了有人会通知你或者你回头去查。扫码点餐你扫完码下好单发起调用就去找座位玩手机了。饭做好了系统会通过叫号或短信通知你来取。⚠️ 重要区分同步/异步 vs 阻塞/非阻塞这是两个最容易混淆的概念需要明确区分同步/异步关注的是消息通知的机制即被调用方如何把结果告诉调用方。是主动等还是回头通知阻塞/非阻塞关注的是调用方在等待结果时的状态即调用方在等待期间能不能干别的事。它们的关系如下同步阻塞调用方一直等且等待期间什么都干不了传统的input()读取键盘输入。同步非阻塞调用方一直等但在等待期间可以偶尔去干点别的比如轮询检查。异步阻塞调用方发起了异步调用但因为某些原因如没别的事可做还是被挂起了这种情况较少见。异步非阻塞调用方发起了异步调用后立即返回去干别的事结果好了再被通知这是最高效的模式。在绝大多数编程语境下我们讨论的高性能模型如asyncio指的都是异步非阻塞。 总结同步主动等待结果。异步不等待结果好了通知我。Python 中的同步模型对应的是多线程编程而异步模型对应的是协程asyncio。这也解释了为什么协程适合高并发——它在发起I/O请求后不等待立即去处理下一个任务从而大幅提升了资源利用率。2 flask框架1 orm解决了什么问题终结手写 SQL 的“体力活”提高开发效率自动适配不同的数据库方言解耦数据库依赖自动转换数据类型结果集映射: 直接在 Model 字段上定义 IntegerField() 或 DateTimeField()ORM 在存取时自动完成双向的类型转换让你拿到的就是纯正的 Python 对象。优雅处理表间关联关系关系导航关系型数据库的核心是外键和多表 Join但在面向对象中我们希望直接访问关联对象的属性。ORM 的价值ORM 提供了 ForeignKey、ManyToManyField 等抽象。你可以直接写出 book.author.name获取该书作者的名字ORM 会自动处理好背后的 JOIN 查询或懒加载Lazy Loading将复杂的 SQL 连接抽象为对象的属性访问极大简化了业务逻辑的编写。⚠️ 重点预警ORM 没解决什么问题虽然 ORM 很强但它没有解决反而有时会恶化高性能复杂查询的问题。N1 查询不当使用 ORM 关联如循环中访问外键属性会发出成百上千条查询把数据库“打崩”。复杂报表/聚合涉及多表联查、子查询、窗口函数时ORM 生成的 SQL 可能低效甚至错误。总结ORM 的核心是牺牲少量底层控制力换取极高的上层开发效率和维护性。对于简单的 CRUD 和业务系统它是神器对于复杂的海量数据报表通常还需要回归原生 SQL。因此成熟的开发策略通常是 “ORM 做 80% 的基础操作原生 SQL 做 20% 的复杂性能优化”。2 讲一下flask应用上下文和请求上下文的区别 Flask 上下文核心概念对照表对比维度应用全局上下文(Application Context)请求局部上下文(Request Context)核心目的提供应用进程级别的全局数据如配置、扩展实例。提供单次 HTTP 请求的局部数据如请求参数、会话。生命周期应用启动时创建应用关闭时销毁全程只有一份。请求到达时创建响应返回时销毁每次请求新建。是否跨请求共享是。所有请求共享同一个应用上下文对象。否。每个请求拥有独立且隔离的请求上下文。数据隔离范围进程级全局。A 请求修改会影响 B 请求需注意并发安全。请求级局部。A 请求的数据对 B 请求完全不可见。提供的主要对象current_app当前 Flask 应用实例。g注1request当前请求的 HTTP 数据。session当前请求的用户会话。典型使用场景1. 读取应用配置current_app.config[SECRET_KEY]2. 获取扩展实例current_app.extensions1. 读取请求参数request.args.get(name)2. 管理用户状态session[user_id] 1未激活时访问后果RuntimeError: Working outside of application contextRuntimeError: Working outside of request context手动创建方式with app.app_context():with app.test_request_context():⚠️ 特别修正与说明注1g对象的正确归类语义层面你应该理解的方式g属于请求级局部数据因为它生命周期严格等于单次请求不同请求的g完全隔离互不干扰。技术层面源码实现g对象存储在应用上下文内部_app_ctx_stack.top.g但它随着请求的到来被创建随着请求的结束被销毁。因此它的数据隔离范围是请求级而非应用级。应用级全局数据的正确存放方式如果你确实需要跨请求共享的全局数据如数据库连接池、静态配置请使用current_app.config配置字典current_app的自定义属性如current_app.db_pool单独的 Python 模块级变量需注意线程安全3 了解flask的debug方式吗app.run(debugTrue)开启 Flask 的调试模式总开关。它会启用自动重载器默认启用内置调试器默认在代码中抛出异常时显示详细的错误页面4 flask 如何高并发更换生产级 WSGI 服务器这是最基础的步骤。Flask 自带的开发服务器app.run()是单进程、阻塞式的。在生产环境必须换用 Gunicorn 或 uWSGI 这样的专业服务器它们支持多进程和多线程能显著提升并发处理能力。# -w 4 表示启动 4 个工作进程多进程gunicorn-w4-b127.0.0.1:5000 app:app# 启动 2 个工作进程每个进程里再开 4 个线程gunicorn-w2--threads4-b127.0.0.1:5000 app:app引入异步与协程对于 I/O 密集型应用这是提升性能的关键。一种方法是让 Gunicorn 使用 gevent 或 eventlet 这类基于协程的 worker 类型。它们通过“猴子补丁monkey patching”让同步代码异步化能在不大量修改代码的情况下处理大量并发连接。有优化案例显示通过 Gunicorn gevent 的组合Flask 应用的吞吐量可以提升至每秒 8 万次请求。# 使用 gevent 作为 worker 类-w 1 表示只启1个进程gunicorn-kgevent-w1--threads1-b127.0.0.1:5000 app:app应用层与架构优化使用缓存对频繁访问且不常变化的数据使用 Redis 或 Memcached 等缓存可以大幅减少数据库压力。数据库优化建立合适的索引并使用连接池来管理数据库连接。任务异步化将发送邮件、生成报表等耗时任务放入 Celery 等消息队列中异步执行避免阻塞 Web 请求。水平扩展在应用前部署 Nginx 等负载均衡器将请求分发到多个应用实例实现水平扩展。3 设计模式1 说一下外观模式与策略模式的区别python–设计模式–20–外观模式vs策略模式