Python面试——Flask框架

159 阅读6分钟

简介

简单的总结和概括一下Flask框架基础的面试可能会出现的问题,不包含针对业务场景给出的解决方案,主要是方便用于回顾常用的知识点,文字性描述尽量精炼,减少长篇大论,提纲主要是结合cahtgpt给出的建议,对于部分遗漏或者可能暂时未涉及到的部分,没做处理。另外总结过程中感觉出现一些有用的资料地址,会整理到一起。有错误和纰漏,欢迎大家友好讨论!

提纲

Flask框架:

  • 基本概念、定位、适用的业务场景以及和其他Python Web框架对比有什么区别?(已完成)
  • 路由和视图(已完成)
  • 请求和响应(已完成)
  • 模板引擎(Jinja2)
  • 蓝图(blueprint)(已完成)
  • 请求上下文和会话(已完成)
  • Flask扩展 (已完成)
  • 中间件(已完成)
  • 错误处理和日志(已完成)
  • 部署(已完成)
  • REST API、Restful风格、Flask-Restful扩展(已完成)
  • 安全性
  • 性能优化(已完成)
  • 单元测试
  • 项目经验
  • 异步(线程池方式?celery异步任务框架)(根据工作岗位需求)

关于Flask

轻量的Python Web框架,提供最基本的工具,允许开发者自由的添加扩展和库。(和Django对比更加轻量,更加自由和灵活)

路由和视图

一般直接通过路由装饰器,添加对应的路由,和定义处理的方法,路径参数

@app.route("/index", methods=['GET'])
def index():
    pass

请求和响应

  • 通过request对象获取客户端发送的请求信息,请求头,请求表单,请求方法;
  • 响应对象,可通过redner_template返回对应模板下的html文件,jsonify返回json对象,字符串直接返回;(高版本Flask对象直接返回字典会自动序列化成json对象)

模板引擎(Jinja2)

(主要业务是前后端分离,暂时不涉及到前端页面的数据渲染)

蓝图(Blueprint)

蓝图可以将应用程序划分为更小、模块化组件的方式;用以组织和拆分路由和视图;提高代码的可维护性。

蓝图创建


from flask import Blueprint

bp = Blueprint("auth", __name__, url_prefix="/auth", template_folder="templates")

@bp.route("/login", methods=["GET"])
def login():
    pass

注册到APP

from flask import Flask
from . import auth

app = Flask(__name__)
app.register_blueprint(auth.bp)

蓝图嵌套

parent = Blueprint('parent', __name__, url_prefix='/parent')
child = Blueprint('child', __name__, url_prefix='/child')
parent.register_blueprint(child)
app.register_blueprint(parent)

请求上下文和会话

关于请求上下文和应用上下文有什么区别:

  • 请求上下文:表示当前处理的HTTP请求的上下文信息;(请求的方法、URL、头部信息、表单数据)
  • 应用上下文:表示应用程序的上下文信息;(应用配置、数据库连接、应用上下文是全局的,对于应用程序来说是唯一的,由Flask管理创建和销毁)

关于会话:

  • 会话是用于不同请求之间存储用户数据的机制,允许在不同页面之间的请求保持用户的状态;
  • 会话数据存放在客户端的Cookies中,通过签名保证数据的完整性和安全性;
  • 在Flask中使用会话,是要先配置一个密钥,通常是通过SECRET_KEY来设置,用以签名Cookie数据;
app.config.from_mapping(
        SECRET_KEY='66298503f1cce092aaf7a317c23c2480e615f3183536621f7644663e31541560',
        DATABASE=os.path.join(app.instance_path, "flasker.sqlite")
    )

快捷生成密钥:

python -c 'import secrets; print(secrets.token_hex())'

关于Flask框架请求的处理过程可参考:

dormousehole.readthedocs.io/en/latest/l…

Flask扩展

常用的Flask-SQLALChemy

自动通过已存在的数据库生成模型文件:

pip install flask-sqlacodegen

flask-sqlacodegen --flask --outfile <输出的文件名> <数据库连接 URI>

Mysql连接字符串:

dialect+driver://username:password@host:port/database

参考链接:

greyli.com/generate-fl… www.cnblogs.com/yoyoketang/…

flaks-sqlalchemy使用操作

设置Mysql连接字符串

SQLALCHEMY_DATABASE_URI = "dialect+driver://username:password@host:port/database"

app读取配置后,初始化连接

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
db.init_app(app)

模型定义

from flasker import db

class User(db.Model):
    __tablename__ = 'user'

    id = db.Column(db.String(255), primary_key=True)
    name = db.Column(db.String(255, 'utf8mb4_0900_ai_ci'), nullable=False, unique=True)
    password = db.Column(db.String(255))
    address = db.Column(db.String(255))

操作

# 查询
user = User.query.filter(User.name == username).first()
# 插入
user = User(id=str(uuid.uuid1()), name=username, password=generate_password_hash(password))
db.session.add(user)
db.session.commit()

Flask中间件

身份校验中间件(本质是通过装饰器校验目前登录用户的身份信息,如果没登录自动抛出异常然后处理)

第一种方式:

def login_required(view):
    @wraps(view)
    def wrap_view(*args, **kwargs):
        if g.user is None:
            abort(400)

        return view(*args, **kwargs)

    return wrap_view

@bp.route("/person-info", methods=["GET"])
@login_required
def person_blog_info():
    return f"<h1>This is {g.user.name} personal page.</h1>"

第二种方式:

class AuthenticationMiddleware:
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        print("start")
        response = self.app(environ, start_response)
        return response


app.wsgi_app = AuthenticationMiddleware(app.wsgi_app)

错误处理和日志

参考链接:

dormousehole.readthedocs.io/en/latest/e…

定义errorhandler错误处理,可指定处理对应状态码的错误。

@app.errorhandler(400)
def handle_exception(error):
    app.logger.error("用户尝试访问需要权限的页面。")
    return f"<h1 style='color: red;'> 用户未登录,无权访问....</h1>\n<a href='{url_for('auth.login')}'>请重新登录</a>"

日志处理器设置

def init_logger():

    if not os.path.isdir("logs"):
        os.makedirs("logs")

    handler = TimedRotatingFileHandler(
        filename="logs/flasker.log",
        backupCount=7
    )
    handler.setLevel(logging.DEBUG)

    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    handler.setFormatter(formatter)
    app.logger.addHandler(handler)

参考链接:

docs.python.org/3/library/l… dormousehole.readthedocs.io/en/latest/l…

部署

Flask是一个WSGI应用,需要一个WSGI服务器来运行,将传入的HTTP请求转换为标准的WSGI请求,传递到Python应用程序,然后将返回的WSGI响应转换为HTTP响应返回给客户端。

uWSGI服务器部署应用,Nginx作为代理服务器

uwsgi.ini

[uwsgi]
chdir=/opt/app/flaskdemo
module=main:app
home=/opt/app/venv
static-map=/static=/opt/app/flaskdemo/flasker/static
threads=8
http=0.0.0.0:8992
master=true
vacuum=true
thunder-lock=true
uid=root
gid=root
harakiri=30
post-buffering=4096
socket=%(chdir)/uwsgi/uwsgi.sock
stats=%(chdir)/uwsgi/uwsgi.status
pidfile=%(chdir)/uwsgi/uwsgi.pid
daemonize=%(chdir)/uwsgi/uwsgi.log

nginx.conf

server {
	listen 80;
	server_name 120.77.144.48;
	location / {
		include uwsgi_params;
		uwsgi_pass unix:/opt/app/flaskdemo/uwsgi/uwsgi.sock; 
	}
}

Restful 以及 flask-restful扩展

RESTful是一种设计风格,用于创建网络程序的API。 RESTful是一种通过URL标识资源,通过HTTP方法执行操作,使用标准的数据表述格式进行通信的方式。

flask-restful扩展使用,定义资源,解析传入参数,定义处理请求方式,绑定对应路由(blueprint)

# coding: utf-8
"""
    :date: 2023-11-8
    :author: linshukai
    :description: About Restful API Demo
"""
from werkzeug.security import check_password_hash, generate_password_hash
from flask_restful import Resource, reqparse, Api
from flask import Blueprint
from faker import Faker
from flasker.model import User
from flasker import db, app
import uuid

bp = Blueprint("rest", __name__, url_prefix="/rest")
rest_api = Api(bp)


class PersonResource(Resource):
    def get(self):
        get_parser = reqparse.RequestParser()
        get_parser.add_argument("name", type=str, location="args", required=True)

        args = get_parser.parse_args()
        fake = Faker()
        return {"code": 200, "data": {"address": fake.address(), "name": args.name}, "msg": "success"}

    def post(self):
        post_parser = reqparse.RequestParser()
        post_parser.add_argument("name", type=str, location="args", required=True)
        post_parser.add_argument("address", type=str, location="json", required=True)
        post_parser.add_argument("age", type=int, location="json", required=True)

        args = post_parser.parse_args()
        user = User(id=str(uuid.uuid1()), name=args.name, password=generate_password_hash("admin"),
                    address=args.address)
        try:
            db.session.add(user)
            db.session.commit()
        except Exception as e:
            db.session.rollback()
            app.logger.error(f"数据库错误:{e}")
            raise e

        return {"code": 200, "msg": "success"}


rest_api.add_resource(PersonResource, "/person")

参考链接:

flask-restful.readthedocs.io/en/latest/ juejin.cn/post/723895…

性能分析

通过werkzeug的组件进行分析

app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=(10,))

只显示耗时排序前十的调用耗时的方法

参考链接:

blog.csdn.net/lancegentry…