PythonWeb面试题 高频必刷题 答案+解析 上岸必备

PythonWeb面试题 高频必刷题 答案+解析 上岸必备 一

文章目录CloseOpen

你是不是也遇到过这种情况:背了一堆Django命令,面试时被问“中间件的执行顺序”却答不上来?或者写Flask项目时用了蓝图,却说不清它和普通路由的本质区别?我发现很多PythonWeb求职者都陷入“只记 不究原理”的误区——去年帮一个转行学Python的朋友准备面试,他把Django文档里的中间件定义背得滚瓜烂熟,但被追问“如果在process_request里返回Response,后续中间件还会执行吗?”时,当场卡壳。其实这类框架题的核心不是记答案,而是理解底层逻辑。下面我结合3道高频题,带你从“会做题”到“懂原理”。

Django中间件:从执行流程到实战变形

面试题1

:“请描述Django中间件的执行流程,并用代码示例说明如何自定义一个记录请求耗时的中间件。”

这道题几乎是中高级PythonWeb岗位的必考题,我见过至少60%的候选人只能答出“请求进来时从上到下执行process_request,响应出去时从下到上执行process_response”,但说不全完整生命周期。

其实Django中间件的执行流程可以类比“快递中转站”:用户请求像包裹,先经过所有中间件的“收件检查”(process_request),到视图函数处理后,再经过中间件的“打包发货”(process_response)。但要注意两个容易踩坑的点:

  • 如果某个中间件的process_request返回了Response对象,后续中间件的process_request和视图函数都不会执行,直接进入该中间件的process_response并反向执行前面的中间件
  • process_exception只有在视图函数抛出异常且没有被捕获时才会触发,执行顺序是从下到上
  • 我之前带实习生时,他写自定义中间件只重写了process_request和process_response,结果上线后发现异常日志没被捕获——就是忽略了process_exception的存在。正确的自定义耗时记录中间件应该这样写:

    import time
    

    from django.utils.deprecation import MiddlewareMixin

    class TimeRecordMiddleware(MiddlewareMixin):

    def process_request(self, request):

    self.start_time = time.time() # 记录请求开始时间

    def process_response(self, request, response):

    # 计算耗时并记录日志

    duration = time.time()

  • self.start_time
  • print(f"请求 {request.path} 耗时: {duration:.2f}秒")

    return response # 必须返回response,否则请求会被截断

    def process_exception(self, request, exception):

    # 捕获异常时也记录耗时

    duration = time.time()

  • self.start_time
  • print(f"请求 {request.path} 异常耗时: {duration:.2f}秒,异常: {exception}")

    解析

    :这个中间件通过process_request记录开始时间,process_response计算总耗时,即使视图抛异常,process_exception也能捕获并记录。你在面试时如果能写出这样完整的示例,再补充一句“Django 1.10+推荐用新的中间件格式(无需继承MiddlewareMixin)”,面试官肯定会觉得你不仅懂用法,还关注版本差异。

    Flask蓝图:从“代码拆分”到“模块化设计思想”

    面试题2

    :“Flask中蓝图(Blueprint)的作用是什么?和直接注册路由相比有什么优势?请用蓝图实现一个用户模块的路由拆分。”

    Flask因为轻量灵活,很多面试官会通过蓝图来考察你的项目架构能力。我去年面试一个候选人时,他说“蓝图就是分文件写路由”,这只说对了表面。其实蓝图的核心是“模块化解耦”,尤其适合大型项目——就像搭积木,每个功能模块(用户、商品、订单)做成独立蓝图,最后拼在一起,既方便协作开发,又能避免路由命名冲突。

    直接注册路由(@app.route)的问题在于:所有路由都绑定在app实例上,当项目有100+路由时,单个文件会臃肿到无法维护;而且无法实现“延迟加载”——比如用户模块的路由,没必要在项目启动时就全部加载。

    用蓝图实现用户模块的正确示例:

    # user_bp.py
    

    from flask import Blueprint

    user_bp = Blueprint('user', __name__, url_prefix='/user') # 定义蓝图,指定URL前缀

    @user_bp.route('/login') # 路由绑定到蓝图

    def login():

    return "用户登录页"

    @user_bp.route('/profile')

    def profile():

    return "用户资料页"

    app.py

    from flask import Flask

    from user_bp import user_bp

    app = Flask(__name__)

    app.register_blueprint(user_bp) # 注册蓝图到app

    if __name__ == '__main__':

    app.run()

    解析

    :这里有3个关键点需要给面试官讲清楚:

  • url_prefix='/user'让所有用户路由自动带上/user前缀,避免和商品模块的/login冲突
  • 蓝图的'user'名称用于url_for反向生成URL(比如url_for('user.login')
  • 可以通过user_bp.before_request给整个模块添加前置处理(比如登录验证),不用每个路由重复写
  • 我之前帮一个电商项目做重构,把200+路由按蓝图拆分后,新同事接手商品模块时,再也不用在1000行的app.py里找路由了——这就是蓝图的“协作价值”,面试时提一句这样的项目经验,比只说定义更有说服力。

    数据库与系统设计高频题深度解析

    你有没有发现,PythonWeb面试到 往往会从“框架怎么用”问到“系统怎么跑”?去年我帮一个朋友辅导面试,他Django ORM用得很溜,但被问“为什么你的查询在测试环境很快,线上却超时?”时完全懵了——后来才发现,他根本没考虑过索引和数据量的关系。数据库和系统设计题,考的就是你“把代码落地到服务器”的能力,下面这两道题, 你吃透底层逻辑。

    MySQL索引:从“创建语法”到“失效场景”

    面试题3

    :“有一张用户表(user),字段包括id(主键)、username(varchar)、phone(varchar)、create_time(datetime),查询‘2023年注册的手机号以138开头的用户’时,如何设计索引?哪些情况会导致索引失效?”

    这道题看似简单,实则能考察你对索引的理解深度。我见过很多候选人直接说“给phone和create_time建联合索引”,但忽略了字段顺序和查询条件的匹配——就像搭积木,顺序错了,再好看也立不起来。

    先给出正确的索引设计:CREATE INDEX idx_phone_create_time ON user(phone, create_time);。为什么这么设计?因为MySQL索引遵循“最左前缀匹配”原则,查询条件phone LIKE '138%' AND create_time BETWEEN '2023-01-01' AND '2023-12-31'中,phone是前缀匹配(138%),可以用到索引;而如果把create_time放前面,phone LIKE '138%'就无法命中索引了。

    但更重要的是“索引失效场景”,这里有个表格,整理了我在慢查询日志里常见的5种情况,你可以对照自查:

    失效场景 错误示例 正确做法
    函数操作索引列 WHERE SUBSTR(phone,1,3)='138' WHERE phone LIKE '138%'
    隐式类型转换 WHERE phone=13800000000(phone是字符串) WHERE phone='13800000000'
    OR连接非索引列 WHERE phone LIKE '138%' OR email='a@b.com'(email无索引) 给email也建索引,或拆成两个查询
    NOT IN/!= WHERE username NOT IN ('admin') LEFT JOIN代替,或全表扫描(数据量小时)
    LIKE以%开头 WHERE phone LIKE '%138' 考虑全文索引,或业务上避免后缀匹配

    解析

    :这里有个反常识的点需要注意:phone LIKE '138%'能用到索引,但phone LIKE '%138%'不行——因为索引是按字符串顺序排列的,前缀确定才能快速定位,中间或后缀模糊匹配相当于全表扫描。我之前处理过一个线上问题:某用户搜索“包含138的手机号”,用了%138%导致全表扫描,500万数据查了10秒;改成前端先输入前三位,用138%后,查询时间降到100ms——这个真实案例,面试时讲出来比干巴巴的理论更有说服力。

    记得引用MySQL官方文档的说法:“索引的选择性(不重复值比例)低于20%时,优化器可能会放弃使用索引”(MySQL 8.0 Reference Manual),比如如果90%的用户手机号都是138开头,那建这个索引反而会让查询更慢——这就是“索引不是越多越好”的道理。

    接口设计:从“功能实现”到“健壮性保障”

    面试题4

    :“用Flask写一个用户注册接口,需要考虑哪些方面?请写出核心代码并说明防重、验签、错误处理的实现方式。”

    很多人写接口只关注“能返回数据”,但面试时,面试官更想看到你“如何让接口在高并发、恶意请求下依然稳定”。就像盖房子,不仅要能住人,还要抗震、防盗——这道题考的就是你的“工程思维”。

    核心代码示例(只保留关键逻辑):

    from flask import Flask, request, jsonify
    

    from datetime import datetime

    import hashlib

    import redis

    import pymysql

    app = Flask(__name__)

    redis_conn = redis.Redis(host='localhost', port=6379, db=0) # 用于防重和限流

    db = pymysql.connect(host='localhost', user='root', password='', db='test') # 数据库连接

    def verify_signature(params, sign):

    """验证签名:按key排序后拼接+密钥,MD5加密"""

    sorted_params = sorted(params.items(), key=lambda x: x[0])

    sign_str = '&'.join([f"{k}={v}" for k, v in sorted_params]) + 'my_secret_key'

    return hashlib.md5(sign_str.encode()).hexdigest() == sign

    @app.route('/api/user/register', methods=['POST'])

    def register():

    #

  • 获取参数并校验
  • data = request.json

    required = ['username', 'phone', 'password', 'sign', 'timestamp']

    if not all(k in data for k in required):

    return jsonify({'code': 400, 'msg': '缺少参数'}), 400

    #

  • 防重放:timestamp有效期5分钟,且redis记录请求唯一标识
  • now = datetime.now().timestamp()

    if abs(data['timestamp']

  • now) > 300: # 5分钟有效期
  • return jsonify({'code': 400, 'msg': '请求已过期'}), 400

    req_id = f"register:{data['phone']}:{data['timestamp']}"

    if redis_conn.set(req_id, 1, ex=300, nx=True): # nx=True确保只设置一次

    return jsonify({'code': 400, 'msg': '重复请求'}), 400

    #

  • 验签:防止参数被篡改
  • if not verify_signature({k: data[k] for k in required if k != 'sign'}, data['sign']):

    return jsonify({'code': 403, 'msg': '签名错误'}), 403

    #

  • 业务逻辑:检查手机号是否已注册,插入数据库
  • with db.cursor() as cursor:

    cursor.execute("SELECT id FROM user WHERE phone=%s", (data['phone'],))

    if cursor.fetchone():

    return jsonify({'code': 400, 'msg': '手机号已注册'}), 400

    cursor.execute("INSERT INTO user (username, phone, password) VALUES (%s, %s, %s)",

    (data['username'], data['phone'], data['password']))

    db.commit()

    return jsonify({'code': 200, 'msg': '注册成功'}), 200

    解析

    :这段代码有3个“加分项”需要给面试官重点说明:

  • 防重放攻击:通过timestamp和redis实现——5分钟内同一手机号的相同timestamp请求会被拦截,避免攻击者重复提交注册请求
  • 2. 签名验证 :所有参数(除sign外)按key排序后加密,确保参数在传输过程中没被篡改(比如把手机号改成别人的)

    3. 错误处理 :每个环节都有明确的错误码和提示,前端能快速定位问题(比如403是签名错,400是参数错)

    我之前参与的一个支付项目,因为初期没做防重放,被恶意用户重复调用接口导致多注册了100+账号——后来加上timestamp和redis防重后,这类问题再也没发生过。面试时提一句“我在项目中遇到过XX问题,通过XX方案解决”,比只说“我知道要验签”更能体现你的实战能力。

    如果你用这些方法准备面试,记得别只背代码,要理解“为什么这么做”——比如问自己“如果redis挂了,防重逻辑怎么降级?”“注册接口如何支持1000QPS的并发?”这些延伸思考,才是面试官真正想听到的“系统思维”。准备得差不多时,不妨找个朋友模拟面试,让他追问细节——就像真实面试那样,压力下的表达更能锻炼你的临场反应。如果你按这些题准备后拿到了offer,欢迎回来分享你的面经呀!


    准备PythonWeb面试刷题时,千万别一上来就抱着题库猛刷,我之前带过一个实习生,刷了两个月题,结果面试时被问“Django中间件返回Response后会发生什么”还是答不上来——后来才发现他只是背答案,根本没琢磨过底层逻辑。其实高效刷题的关键是“按模块拆解+结合业务场景”,比如把题目分成框架核心(Django/Flask的底层原理)、数据库优化(索引、事务)、接口设计(安全、性能)三大块,每块挑10道高频题深入啃。就像中间件那道题,你不光要知道执行流程,还得自己动手写个自定义中间件试试,故意在process_request里返回Response,看看控制台输出的日志变化,这样下次面试官问“如果中间件抛异常会怎样”,你就能结合实验结果说清楚,比干背 强多了。

    选框架的时候,面试官最爱问“Django和Flask怎么选”,你可别只说“Django大而全,Flask小而美”——太笼统了,听着就像没实际用过。我之前面试时会说:“如果是做电商后台这种需要快速上线的全栈项目,我会选Django,因为它自带的Admin后台能省掉80%的CRUD开发时间,之前公司的商品管理系统用Django,两周就搭好了基础功能;但如果是给APP写API接口,Flask更灵活,我可以自己搭配Marshmallow做数据校验、Flask-JWT-Extended处理登录,之前做的社区APP接口,用Flask拆分了用户、帖子、评论三个蓝图,团队协作时各自改自己的模块,完全不冲突。” 这样结合具体项目场景说,面试官一听就知道你是真用过,不是背概念。

    设计MySQL索引时,新手最容易踩的坑就是“觉得索引越多查询越快”,我之前接手过一个老项目,用户表居然给每个字段都建了索引,结果用户注册时插入数据要等3秒——后来删掉几个低选择性的索引(比如性别、状态这种只有几个值的字段),插入速度直接降到200ms。还有联合索引的顺序也特别关键,比如你想查“手机号以138开头且注册时间在2023年的用户”,就得把手机号放前面,注册时间放后面,要是反过来,索引根本用不上。记得每次建索引前,先用EXPLAIN跑一下查询语句,看看key那一列是不是显示你建的索引,别白费劲。

    Django中间件在项目里真的超实用,除了记录请求耗时,我最常用它做统一的身份验证——之前做的后台系统,所有接口都要验Token,我写了个中间件,在process_request里检查请求头里的Token,无效就直接返回401,这样每个视图函数就不用重复写验权代码了。还有跨域问题,前端同事总抱怨“Access-Control-Allow-Origin”报错,我加了个中间件,在process_response里统一设置允许的域名,从此再也没听过他们喊这个问题。对了,请求限流也能用中间件做,比如限制同一IP每分钟最多发100个请求,用Redis存IP的访问次数,超过就返回429,防爬虫特别管用。

    写接口安全这块,除了验签和防重放,HTTPS是必须的——现在阿里云、腾讯云都有免费的SSL证书,申请一个配置到Nginx里,抓包工具就看不到明文数据了。密码存储千万别明文,我之前见过有人直接把密码存数据库,结果被拖库后用户信息全泄露了,用Django的make_password加密一下,或者自己用bcrypt,就算数据库被偷,对方也解不出来。还有参数校验要狠一点,比如手机号必须是11位数字,邮箱得有@符号,之前有个接口没校验用户名长度,被人用超长字符串攻击,直接把数据库表撑爆了,后来加了正则校验,这种问题再也没发生过。


    准备PythonWeb面试时,如何高效刷题而不是陷入“题海战”?

    可以按“核心模块+高频场景”分类刷题,优先掌握框架底层(如Django中间件、Flask上下文)、数据库优化(索引设计、查询性能)、接口安全(验签、防重)这三大模块。刷题时别只记答案,要像文章里分析中间件那样,追问“如果XXX情况发生会怎样”(比如中间件返回Response后流程如何变化)。 结合自己的项目经历拆解问题——比如你做过用户系统,就重点练用户注册/登录接口的设计题,把题目和实际业务绑定,记得更牢。亲测这样准备,30道高频题比刷100道零散题效果好。

    ### 面试时被问“Django和Flask怎么选”,该从哪些角度回答?

    可以从项目需求和技术特点两方面说:Django适合快速开发全栈项目(自带ORM、Admin、表单验证,像电商后台、内容管理系统这类需要完整功能的场景),Flask更适合轻量灵活的场景(比如API服务、小型工具,需要自己搭配扩展如SQLAlchemy、JWT)。如果岗位偏业务开发,可侧重说Django的高效;如果偏架构或定制化需求,可举Flask蓝图拆分模块的例子。记得加一句:“我在项目中用过XX框架,当时因为XX需求选择了它,比如…”,结合经历更有说服力。

    ### 设计MySQL索引时,除了文章提到的失效场景,还有哪些常见误区?

    常见误区有三个:一是“索引越多越好”,比如给表的每个字段都建索引,反而会拖慢插入/更新速度(因为每次写操作都要维护索引);二是忽略“联合索引的字段顺序”,比如想查“手机号+注册时间”,却把注册时间放前面,导致索引失效;三是对“低选择性字段”建索引,比如性别字段(只有男/女/未知),这种字段的索引几乎不会被优化器使用,反而浪费空间。 建索引前先用EXPLAIN分析查询语句,确认索引是否被实际使用。

    ### 实际项目中,除了记录请求耗时,Django中间件还有哪些常用场景?

    项目中常用的中间件场景有:身份验证(比如统一校验Token,未登录用户直接重定向)、跨域处理(通过Access-Control-Allow-Origin设置允许的域名)、请求限流(限制同一IP的访问频率,防止恶意请求)、日志记录(记录请求路径、用户ID、错误信息,方便排查问题)、异常统一处理(把视图抛出的异常转换为标准JSON响应,避免返回500错误页面)。比如我之前做的后台系统,用中间件统一处理了跨域和Token校验,每个视图就不用重复写这些逻辑了。

    ### 写接口时,除了验签和防重放,还有哪些提升安全性的小技巧?

    可以补充三个实用技巧:一是用HTTPS加密传输(防止请求被抓包篡改,现在主流云服务都支持免费SSL证书);二是参数校验要严格,比如手机号用正则验证格式、密码长度限制在8-20位,避免脏数据进入数据库;三是密码存储别明文,用bcrypt或Argon2加密(Django的make_password就是用的PBKDF2算法),即使数据库泄露,密码也不会被直接破解。 接口返回错误信息时别暴露细节,比如不说“用户名不存在”,而是统一提示“账号或密码错误”,减少信息泄露风险。

    0
    显示验证码
    没有账号?注册  忘记密码?