Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
flask 是轻量级的,并且可扩展性强,可定制性强 的框架。适用于开发小型的网站。开发大型的网站也行,因为flask提供了很多第三方的组件,我们把flask与第三方的组件结合起来,也可以搭建一个与Django类似的,集成了很多功能的一个框架。
一. 基本使用
1 2 3 4 5 6 7 8 9 | from flask import Flask app = Flask(__name__) @app .route( '/index' ) def index(): return 'index' if __name__ = = '__main__' : app.run() |
二、配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为: { 'DEBUG' : get_debug_flag(default = False ), 是否开启Debug模式 'TESTING' : False , 是否开启测试模式 'PROPAGATE_EXCEPTIONS' : None , 'PRESERVE_CONTEXT_ON_EXCEPTION' : None , 'SECRET_KEY' : None , 'PERMANENT_SESSION_LIFETIME' : timedelta(days = 31 ), 'USE_X_SENDFILE' : False , 'LOGGER_NAME' : None , 'LOGGER_HANDLER_POLICY' : 'always' , 'SERVER_NAME' : None , 'APPLICATION_ROOT' : None , 'SESSION_COOKIE_NAME' : 'session' , 'SESSION_COOKIE_DOMAIN' : None , 'SESSION_COOKIE_PATH' : None , 'SESSION_COOKIE_HTTPONLY' : True , 'SESSION_COOKIE_SECURE' : False , 'SESSION_REFRESH_EACH_REQUEST' : True , 'MAX_CONTENT_LENGTH' : None , 'SEND_FILE_MAX_AGE_DEFAULT' : timedelta(hours = 12 ), 'TRAP_BAD_REQUEST_ERRORS' : False , 'TRAP_HTTP_EXCEPTIONS' : False , 'EXPLAIN_TEMPLATE_LOADING' : False , 'PREFERRED_URL_SCHEME' : 'http' , 'JSON_AS_ASCII' : True , 'JSON_SORT_KEYS' : True , 'JSONIFY_PRETTYPRINT_REGULAR' : True , 'JSONIFY_MIMETYPE' : 'application/json' , 'TEMPLATES_AUTO_RELOAD' : None , } |
修改配置文件的方式:
1 2 3 4 5 6 7 | 方式一: app.config[ 'DEBUG' ] = True PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...) 方式二: app.config.from_object( "python类或类的路径" ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # 按照不同的情况 ;修改配置 app.config.from_object( 'settings.DevelopmentConfig' ) #settings.py文件 #基础的配置类 class Config( object ): DEBUG = False TESTING = False DATABASE_URI = 'sqlite://:memory:' #用于上线的配置类 class ProductionConfig(Config): DATABASE_URI = 'mysql://user@localhost/foo' #用于开发的配置类 class DevelopmentConfig(Config): DEBUG = True |
1 | PS: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为 True ,则就是instance_path目录 |
写一个配置文件的字符串怎么找到配置信息的原理
1 2 | # 'settings.DevelopmentConfig'通过切割,在importlib 加载模块,然后反射得到这个类的对象。dir(obj) 拿到这个类下面的所有属性和方法 # 再判断是大写的,这样就可以拿到这个配置下面的配置信息了。 |
原理的代码
1 2 3 4 5 6 7 8 9 10 11 12 | import importlib path = "settings.Foo" p,c = path.rsplit( '.' ,maxsplit = 1 ) m = importlib.import_module(p) cls = getattr (m,c) # 如果找到这个类? #dir(cls)找到这个类下面的所有属性的字符串,不包含__init__下面的对象属性 for key in dir ( cls ): if key.isupper(): print (key, getattr ( cls ,key)) #类.字符串 |
三、路由系统(带参数的装饰器)
1 2 3 4 | @app .route( '/index/<int:nid>' ,methods = [ 'GET' , 'POST' ]) def index(nid): print (nid) return "Index" |
- @app.route('/user/<username>')
- @app.route('/post/<int:post_id>')
- @app.route('/post/<float:post_id>')
- @app.route('/post/<path:path>')
- @app.route('/login', methods=['GET', 'POST'])
1 2 3 4 5 6 7 8 9 | DEFAULT_CONVERTERS = { 'default' : UnicodeConverter, 'string' : UnicodeConverter, 'any' : AnyConverter, 'path' : PathConverter, 'int' : IntegerConverter, 'float' : FloatConverter, 'uuid' : UUIDConverter, } |
路由系统原理(带参数的装饰器原理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | ''' 带参数的装饰器的原理 def route(self, rule, **options): def decorator(f): endpoint = options.pop("endpoint", None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator ''' ''' @app.route('/index', methods=["GET", "POST"]) 执行的过程: 1.先执行 decorator = app.route('/index', methods=["GET", "POST"]) 2. @decorator def index(): pass decorator(index) ''' |
路由系统里面的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @app .route和app.add_url_rule参数: rule, URL规则 view_func, 视图函数名称 defaults = None , 默认值,当URL中无参数,函数需要参数时,使用defaults = { 'k' : 'v' }为函数提供参数 endpoint = None , 名称,用于反向生成URL,即: url_for( '名称' ) methods = None , 允许的请求方式,如:[ "GET" , "POST" ] strict_slashes = None , 对URL最后的 / 符号是否严格要求, 如: @app .route( '/index' ,strict_slashes = False ), 访问 http: / / www.xx.com / index / 或 http: / / www.xx.com / index均可 @app .route( '/index' ,strict_slashes = True ) 仅访问 http: / / www.xx.com / index redirect_to = None , 重定向到指定地址 如: @app .route( '/index/<int:nid>' , redirect_to = '/home/<nid>' ) 或 def func(adapter, nid): return "/home/888" @app .route( '/index/<int:nid>' , redirect_to = func) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | subdomain = None , 子域名访问 from flask import Flask, views, url_for app = Flask(import_name = __name__) app.config[ 'SERVER_NAME' ] = 'wupeiqi.com:5000' @app .route( "/" , subdomain = "admin" ) def static_index(): """Flask supports static subdomains This is available at static.your-domain.tld""" return "static.your-domain.tld" @app .route( "/dynamic" , subdomain = "<username>" ) def username_index(username): """Dynamic subdomains are also supported Try going to user1.your-domain.tld/dynamic""" return username + ".your-domain.tld" if __name__ = = '__main__' : app.run() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #子域名 from flask import Blueprint public = Blueprint( 'public' , __name__) @public .route( '/' ) def home(): return 'hello flask' app.py: app = Flask(__name__) app.config[ 'SERVER_NAME' ] = 'example.com' from modules import public app.register_blueprint(public, subdomain = 'public' ) 现在可以通过public.example.com / 来访问public模块了。 |
自定制正则路由匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | from flask import Flask, views, url_for from werkzeug.routing import BaseConverter app = Flask(import_name = __name__) class RegexConverter(BaseConverter): """ 自定义URL匹配正则表达式 """ def __init__( self , map , regex): super (RegexConverter, self ).__init__( map ) self .regex = regex def to_python( self , value): """ 路由匹配时,匹配成功后传递给视图函数中参数的值 :param value: :return: """ return int (value) def to_url( self , value): """ 使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数 :param value: :return: """ val = super (RegexConverter, self ).to_url(value) return val # 添加到flask中 app.url_map.converters[ 'regex' ] = RegexConverter @app .route( '/index/<regex("\d+"):nid>' ) def index(nid): print (url_for( 'index' , nid = '888' )) return 'Index' if __name__ = = '__main__' : app.run() b. 自定制正则路由匹配 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | # -*- coding: utf-8 -*- # @Author: 曾辉 #1.定制类 from werkzeug.routing import BaseConverter from flask import Flask,request,url_for,session from flask_session import Session from flask_session import RedisSessionInterface import redis app = Flask(__name__) #在session数据存放在redis中, 存放的方式 和 Django 中session 是差不多的 , 把随机生成的特殊字符串放在cookies中, #下次访问那着这个字符串 找redis里面去取值。 app.config[ 'SESSION_TYPE' ] = 'redis' app.config[ 'SESSION_REDIS' ] = redis.Redis(host = '127.0.0.1' ,port = 6379 ,password = "zh4350697" ) Session(app) ''' 自定义url匹配正则表达式 ''' # BaseConverter 父类里面有 to_python ;to_url class MyurlRegex(BaseConverter): def __init__( self , map ,regex): super ().__init__( map ) self .regex = regex # def to_python(self, value): # return value # def to_url(self, value): # # value = super().to_url(value) # # return value #2.添加到转换器中 app.url_map.converters[ "reg" ] = MyurlRegex ''' 执行的过程: 1.用户发送请求 2.flask内部进行正则匹配 3.调用to_python(返回匹配正则表达的结果)的方法 4. to_python方法的返回值会交给视图函数作为参数传入 5. to_url 方法是反向生成url的时候调用的,返回的就是url ''' #3.使用自定义正则 @app .route( "/index/<reg('\d+'):nid>" ,methods = [ "GET" ]) def index(nid): #6 <class 'str'> # nid =int(nid) # print(nid) # #传入参数的反向生成url # url = url_for("index", nid=987) # print(url) # <SecureCookieSession {'xxx': 123}> ---> session session[ "xxx" ] = 123 print (session[ "xxx" ]) return "..." if __name__ = = "__main__" : # app.__call__ #self.wsgi_app(environ, start_response) app.run() |
四、请求和响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | from flask import Flask from flask import request from flask import render_template from flask import redirect from flask import make_response app = Flask(__name__) @app .route( '/login.html' , methods = [ 'GET' , "POST" ]) def login(): # 请求相关信息 # request.method 请求的方式 # request.args url的参数(GET请求的数据) # request.form form表单(以字典的形式),ajax (POST请求的数据) # request.values 返回GET和POST请求数据 # request.get_json() 接收json数据,并且返回 json反序列化 的数据 # request.cookies # request.headers # request.path /index ;是 不包含 ip 和 端口 # request.full_path # request.script_root # request.url 是全包含的; # request.base_url # request.url_root # request.host_url # request.host # request.files # obj = request.files['the_file_name'] # obj.save('/var/www/uploads/' + secure_filename(f.filename)) # 响应相关信息 # return "字符串" # return render_template('html模板路径',**{}) # return redirect('/index.html') # response = make_response(render_template('index.html')) # response是flask.wrappers.Response类型 # response.delete_cookie('key') # response.set_cookie('key', 'value') # response.headers['X-Something'] = 'A value' # return response return "内容" if __name__ = = '__main__' : app.run() |
五、Session
除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。
1 2 3 4 5 | # 设置session,是建立在cookies上的,存放到浏览器上的,因此要对其加密后在存放。 # 设置session值时的加密 # app.secret_key = "asdasdsad" # session["user"] = "123" |
1 2 3 | 设置:session[ 'username' ] = 'xxx' 删除:session.pop( 'username' , None ) |
flask-session组件,把session放入redis里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #!/usr/bin/env python # -*- coding:utf-8 -*- """ pip3 install redis pip3 install flask-session """ from flask import Flask, session, redirect from flask.ext.session import Session app = Flask(__name__) app.debug = True app.secret_key = 'asdfasdfasd' app.config[ 'SESSION_TYPE' ] = 'redis' from redis import Redis app.config[ 'SESSION_REDIS' ] = Redis(host = '192.168.0.94' ,port = '6379' ) Session(app) @app .route( '/login' ) def login(): session[ 'username' ] = 'alex' return redirect( '/index' ) @app .route( '/index' ) def index(): name = session[ 'username' ] return name if __name__ = = '__main__' : app.run() |
-闪现
- 基于session.pop 实现的,实现了 只能取一次的效果
-视图
-FBV
-CBV
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | from flask import Flask, redirect, request, url_for,views app = Flask(__name__) import functools def authwarppe(func): @functools .wraps(func) def innder( * args, * * kwargs): #执行原函数 ret = func( * args, * * kwargs) return ret return innder # CBV class UserView(views.MethodView): #限制访问的方式 methods = [ "GET" ] #批量的加装饰器 decorators = [authwarppe] def get( self , * args, * * kwargs): # self.dispatch_request return "get" def post( self , * args, * * kwargs): return "post" #路由与视图的关系 app.add_url_rule( "/user" , None ,UserView.as_view( "user" )) #反向生成url -- user #UserView.as_view -- view --- self.dispatch_request if __name__ = = "__main__" : app.run() |
六、模板
1、模板的使用
Flask使用的是Jinja2模板,所以其语法和Django无差别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | - 基本数据类型:可以执行python语法,如: dict .get() list [ 'xx' ] - 在模板中传入函数 - django,自动执行 {{func}} - flask,不自动执行 {{func()}} 必须自己加括号才会执行函数,不然返回的是函数名字 - 全局定义函数 @app .template_global() def sb(a1, a2): # 在模板里面调用的方式 {{sb(1,9)}} return a1 + a2 @app .template_filter() def db(a1, a2, a3): # 在模板里面调用的方式 {{ 1|db(2,3) }} return a1 + a2 + a3 - 模板继承 layout.html <!DOCTYPE html> <html lang = "zh-CN" > <head> <meta charset = "UTF-8" > <title>Title< / title> <meta name = "viewport" content = "width=device-width, initial-scale=1" > < / head> <body> <h1>模板< / h1> { % block content % }{ % endblock % } < / body> < / html> tpl.html { % extends "layout.html" % } { % block content % } {{users. 0 }} { % endblock % } - include { % include "form.html" % } form.html <form> asdfasdf asdfasdf asdf asdf < / form> - 宏 { % macro ccccc(name, type = 'text' , value = '') % } <h1>宏< / h1> < input type = "{{ type }}" name = "{{ name }}" value = "{{ value }}" > < input type = "submit" value = "提交" > { % endmacro % } {{ ccccc( 'n1' ) }} {{ ccccc( 'n2' ) }} #以后在模板可以多次执行; 每调用一次就 执行一次; - 安全 - 前端: {{u|safe}} - 后端: MarkUp( "asdf" ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #全局的模板渲染 #全局定义函数 @app .template_global() def add(x,y): # 使用{{add(3,7)}} return x * y @app .template_global() def template(): # 使用{{add(3,7)}} return Markup( '<input type="text">' ) # Markup 就相当于 Django 中的 make_safe @app .template_filter() def pow (a,b,c,d): # 使用{{6|pow(3,7,10)}} return a + b + c + d |
七、蓝图
蓝图用于为应用提供目录划分:
小型应用程序:示例
大型应用程序:示例
1 2 3 4 5 6 7 8 9 10 11 12 | from flask import Blueprint<br>ac = Blueprint( "ac" ,__name__)<br>其他: - 自定义的模板,静态文件 - 给某一类的Url添加前缀 app.register_blueprint(ac,url_prefix = '/api' ) - 给某一类的url添加before_request @ac .before_request def xxx(): print ( "ac.before_request" ) @ac .route( '/login' ) def login(): return render_template( "login.html" ) |
八、 特殊的装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 1. before_request 2. after_request 示例: from flask import Flask app = Flask(__name__) @app .before_request def x1(): print ( 'before:x1' ) return '滚' @app .before_request def xx1(): print ( 'before:xx1' ) @app .after_request def x2(response): print ( 'after:x2' ) return response @app .after_request def xx2(response): print ( 'after:xx2' ) return response @app .route( '/index' ) def index(): print ( 'index' ) return "Index" @app .route( '/order' ) def order(): print ( 'order' ) return "order" if __name__ = = '__main__' : app.run() 3. before_first_request from flask import Flask app = Flask(__name__) @app .before_first_request def x1(): print ( '123123' ) @app .route( '/index' ) def index(): print ( 'index' ) return "Index" @app .route( '/order' ) def order(): print ( 'order' ) return "order" if __name__ = = '__main__' : app.run() 4. template_global 5. template_filter 6. errorhandler 自定制报错 @app .errorhandler( 404 ) def not_found(arg): print (arg) return "没找到" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | #!/usr/bin/env python # -*- coding:utf-8 -*- from flask import Flask, Request, render_template app = Flask(__name__, template_folder = 'templates' ) app.debug = True @app .before_first_request def before_first_request1(): print ( 'before_first_request1' ) @app .before_first_request def before_first_request2(): print ( 'before_first_request2' ) @app .before_request def before_request1(): Request.nnn = 123 print ( 'before_request1' ) @app .before_request def before_request2(): print ( 'before_request2' ) @app .after_request def after_request1(response): print ( 'before_request1' , response) return response @app .after_request def after_request2(response): print ( 'before_request2' , response) return response @app .errorhandler( 404 ) def page_not_found(error): return 'This page does not exist' , 404 @app .template_global() def sb(a1, a2): return a1 + a2 @app .template_filter() def db(a1, a2, a3): return a1 + a2 + a3 @app .route( '/' ) def hello_world(): return render_template( 'hello.html' ) if __name__ = = '__main__' : app.run() |
九、中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | from flask import Flask app = Flask(__name__) @app .route( '/index' ) def index(): print ( 'index' ) return "Index" class Middleware( object ): def __init__( self ,old): self .old = old def __call__( self , * args, * * kwargs): print ( '请求前' ) ret = self .old( * args, * * kwargs) print ( '请求后' ) return ret # return self.wsgi_app(environ, start_response) if __name__ = = '__main__' : app.wsgi_app = Middleware(app.wsgi_app) app.run() app.__call__ #当请求进来 第三个参数自动的帮你加括号;run_simple(host, port, self, **options) # 对象() 执行 ---> 对象.__call__方法 (类里面的__call__); |
十、Flask插件
总结:
视图函数中使用:request/session/g/current_app
注意:请求上下文和应用上下文需要先放入Local中,才能获取到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from flask import Flask,current_app,request,session,g app = Flask(__name__) # 错误 # print(current_app.config) @app .route( '/index' ) def index(): # 正确 #在视图函数中才把ctx,app_ctx放入对应的local中,才能取到。 print (current_app.config) return "Index" if __name__ = = '__main__' : app.run() |
为什么要把上下文管理分成:
- 请求上下文: request_ctx (request/session)
- 应用上下文: app_ctx(app/g)
主要是因为在写离线脚本的时候,没有请求,因此没有请求上下文,只有应用上下文。所有为了方便,要拆开。
正常情况下,stack的值的栈只有一个ctx
__restage__ = {
123:{"stack":[ctx]}
}
但是在多app离线脚本的时候,stack的值的栈 可能有多个app1_ctx,app2_ctx。
离线脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #要应用到app上文管理 from crm import db,create_app app = create_app() app_ctx = app.app_context() with app_ctx: # with 对象 的时候就会自动的触发类的__enter__ 方法,然后执行下面的代码,最后执行__exit__ #__enter__是将app_ctx通过 LocalStack放入Local中, db.create_all() #会调用LocalStack 从Local中获取app,然后再从app中获取配置。 #__exit__ 是将当前的app_ctx对象从Local中移除掉 |
多app离线脚本;栈有多个app_ctx
1 2 3 4 5 6 7 8 9 10 11 12 13 | from crm import db,create_app app = create_app() app_ctx = app.app_context() app_ctx2 = app.app_context() with app_ctx: # with 对象 的时候就会自动的触发类的__enter__ 方法,然后执行下面的代码,最后执行__exit__ with app_ctx2: #__enter__是将app_ctx通过 LocalStack放入Local中, #里面就是先用的app_ctx2。因为是同时把app_ctx2和app_ctx放入栈中,后进先出。 db.create_all() #会调用LocalStack 从Local中获取app,然后再从app中获取配置。 #__exit__ 是将当前的app_ctx对象从Local中移除掉 |
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步