实测 Jinja2 宏:import 与 from-import;call 块与带参数的 caller();签名里不能写 *args,体内用 varargs 与 kwargs;递归与默认值;作用域(import 默认读不到调用方变量);自动转义;500 行表格里宏调用约为直接写的 3.3 倍。
实测模板里的惰性对象:SQLAlchemy 懒加载关系在模板循环里造成 21 条 SELECT 而 selectinload 只需 2 条;Query 对象迭代几次就查询几次;生成器只能遍历一次;缺失变量与 Undefined 的各种行为(property 里的 AttributeError 被吞掉);StrictUndefined。
实测模板渲染异常:UndefinedError、TemplateSyntaxError(lineno、name)、TemplateNotFound、TypeError、include 里的错误都带着模板文件与行号;非测试模式都是 500;StrictUndefined;render_template 出错只有错误页,stream_template 出错会留下半截内容并中断传输(curl 退出码 18);debug 页面显示模板源码行。
实测 Jinja 模板缓存:编译结果缓存在 LRUCache(默认 400);auto_reload 开着每次命中多 stat 一次文件(1.74 对 0.32 µs);改文件后 auto_reload=False 看不到;cache_size=2 时轮流用 3 个模板全部未命中;TEMPLATES_AUTO_RELOAD 要在 jinja_env 创建前设;第一次请求是后续请求的 18 倍。
实测 Jinja2 的磁盘字节码缓存:300 个模板在新进程里首次渲染从约 1497 ms 降到 39.5 ms(快 37.9 倍);缓存目录 300 个文件约 2984 KiB;按源码校验和失效;Flask 里设 jinja_env.bytecode_cache;gunicorn 重启后头 60 个页面 337 ms → 49 ms。
实测模板里执行函数的代价(1000 行表格):全局函数约 1.41×、过滤器约 1.16×;宏与 include 比直接写贵 3.2×、2.1×;set 复用比重复表达式快 1.39×;模板里放 0.1 ms 的慢函数会让 200 行渲染慢约 206 倍。
实测 Flask 配置加载顺序:没有优先级表,后执行的覆盖先执行的(from_object → from_pyfile → from_file → from_prefixed_env → update → 赋值,PAGE_SIZE 10 → 50 → 7 → 200 → 1 → 2);只收大写键;instance 目录;from_prefixed_env 的 JSON 类型转换与 A__B 嵌套;get_namespace。
实测 config.from_object(模块名/模块/类/实例,只收大写)、from_pyfile(执行 Python 文件,不存在与语法错误)、from_envvar(环境变量里是文件路径)、from_file(json/toml)、from_mapping 与 update 的区别;从类加载时列表是共享的同一个对象。
实测环境变量配置:bool("false") 为 True 的陷阱;FLASK_* 被 flask 命令读取;没装 python-dotenv 时的提示;装了之后 .env 与 .flaskenv 同名时 .env 生效、真实环境变量优先;gunicorn 不读 .env,除非应用自己 load_dotenv()。
实测开发、测试、生产三套类配置:基类继承;生产缺 SECRET_KEY 时启动就 RuntimeError(fail fast);TESTING=True 让异常传出 test client;SESSION_COOKIE_SECURE 随环境变化;用 APP_ENV 与 flask --app 选配置;启动检查拦住生产环境的 DEBUG=True。
实测 Application Factory:模块级 app 的循环导入 ImportError 与绕法;工厂创建互相隔离的多个实例;扩展在模块级创建、init_app 绑定到每个 app;视图用 current_app;flask --app 与 gunicorn 找工厂的各种写法;create_app 每次约 305 µs。
实测 Flask 扩展的初始化时机:init_app 之前访问抛 RuntimeError;SQLAlchemy 与 Flask-Caching 在 init_app 时读一次配置、之后改无效,Flask-Login 用到时才读;一个扩展对象服务多个应用;工厂里先配置后 init_app;自己写扩展为什么要把状态放进 app.extensions。