Flask 框架
Django: 重武器,内部包含了非常多的组件,ORM,FORM,MODELform,缓存,中间件,信号 Flask: 短小精悍 内部没有包含太多组件,第三方组件丰富,能伸能缩,能大能小 Tornado: 异步非阻塞 (node.js) Bottle: 框架更小 企业级的应用很少使用 Web.py: 框架小
安装:
pip3 install flask
基本使用:
from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run()
配置文件:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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, } 方式一: app.config['DEBUG'] = True PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...) 方式二: app.config.from_pyfile("python文件名称") 如: settings.py DEBUG = True app.config.from_pyfile("settings.py") app.config.from_envvar("环境变量名称") 环境变量的值为python文件名称名称,内部调用from_pyfile方法 app.config.from_json("json文件名称") JSON文件名称,必须是json格式,因为内部会执行json.loads app.config.from_mapping({'DEBUG':True}) 字典格式 app.config.from_object("python类或类的路径") app.config.from_object('pro_flask.settings.TestingConfig') 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 class TestingConfig(Config): TESTING = True PS: 从sys.path中已经存在路径开始写 PS: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录
常使用的配置文件存放方式:
from flask import Flask app = Flask(__name__) app.config.from_object("settings.DevelopmentConfig") # 配置类路径
@app.route('/') def index(): return 'Hello World!' if __name__ == '__main__': app.run() 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 class TestingConfig(Config): TESTING = True
路由匹配:
方式一: @app.route('/login',methods=['GET','POST'],endpoint='l1') # 别名=l1 def login(): pass 方式二: app.add_url_rule('/index','in2',index, methods=['GET',"POST"]) # url 别名 视图函数 method方法
注: endpoint 不写的话使用视图函数名,endpiont 不可重复
路由系统
•@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'])
常用路由系统有以上五种
为视图加装装饰器:
FBV模式
def auth(func): def inner(*args, **kwargs): print('before') result = func(*args, **kwargs) print('after') return result return inner @app.route('/index.html', methods=['GET', 'POST'], endpoint='index') @auth def index(): return 'Index'
CBV模式
def auth(func): def inner(*args, **kwargs): print('before') result = func(*args, **kwargs) print('after') return result return inner class IndexView(views.MethodView): methods = ['GET'] decorators = [auth, ] def get(self): return 'Index.GET' def post(self): return 'Index.POST' app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint
@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) 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()
注:子域名访问:
项目中设置主域名和端口号:
app.config['SERVER_NAME'] = 'haha.com:5000' # 主域名 加 端口号
在host文件中设置 项目IP对应的域名:
127.0.0.1 www.haha.com # IP 对应 想要的哪些域名访问
127.0.0.1 lala.haha.com 127.0.0.1 hehe.haha.com
项目中设置 想要的域名访问的哪些视图函数
@app.route("/lala", subdomain="lala") # 域名为 lala.haha.com/lala 的url访问此视图
def lala_index(): return "lala.your-domain.tld" @app.route("/niu", subdomain="<name>") # 在host中,项目IP对应的子域名都可以访问 .haha.com/niu 的url访问此视图
def niu_index(name): # 子域名
return name + ".your-domain.tld" @app.route("/", subdomain="www") def www_index(): return "www.your-domain.tld"
自定制正则表达式路由匹配
from werkzeug.routing import BaseConverter class RegexConverter(BaseConverter): ''' 自定义URL匹配正则表达式类 ''' def __init__(self,map,regex): super().__init__(map) self.regex = regex # 要写上
def to_python(self, value): # 方法名固定写法
print('url匹配成功后传进来的值,做处理后再传进视图函数中>>>',value) # 123 return int(value) def to_url(self, value): # 方法名固定写法
print('使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数>>>',value) # abc val = super().to_url(value) return val app.url_map.converters['regex'] = RegexConverter # 添加进flask对象中 @app.route('/test/<regex("\d+"):nid>') def test(nid): print(url_for('test', nid='abc')) # url别名为test,若没有则使用视图函数名 ; nid 用于反向解析的参数 /test/abc
return 'Index'
模板:Flask使用的是Jinja2模板,所以其语法和Django无差异
自定义模板方法: 创建一个函数并通过参数的形式传入render_template
def func1(arg): return Markup("<input type='text' value='%s' />" % (arg,)) # flask自带防止xss攻击 所以在写入标签时 要使用Markup() 也可以在前端加 |safe
@app.route('/login',methods=['GET','POST'],endpoint='l1',subdomain="www") def login(): if request.method == "GET": return render_template('login.html',**{'func1':func1})
前端
{{ func1('哈哈哈') }}
请求与响应
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 # request.form # request.values # request.cookies # request.headers # request.path # 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 进行密钥签名要使用会话,你需要设置一个密钥。
-
设置:session['username'] = 'xxx'
- 删除:session.pop('username', None)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask, session, redirect, url_for, escape, request app = Flask(__name__) @app.route('/') def index(): if 'username' in session: return 'Logged in as %s' % escape(session['username']) return 'You are not logged in' @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': session['username'] = request.form['username'] return redirect(url_for('index')) return ''' <form action="" method="post"> <p><input type=text name=username> <p><input type=submit value=Login> </form> ''' @app.route('/logout') def logout(): # remove the username from the session if it's there session.pop('username', None) return redirect(url_for('index')) # set the secret key. keep this really secret: app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' 基本使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
pip3 install Flask-Session run.py from flask import Flask from flask import session from pro_flask.utils.session import MySessionInterface app = Flask(__name__) app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' app.session_interface = MySessionInterface() @app.route('/login.html', methods=['GET', "POST"]) def login(): print(session) session['user1'] = 'alex' session['user2'] = 'alex' del session['user2'] return "内容" if __name__ == '__main__': app.run() session.py #!/usr/bin/env python # -*- coding:utf-8 -*- import uuid import json from flask.sessions import SessionInterface from flask.sessions import SessionMixin from itsdangerous import Signer, BadSignature, want_bytes class MySession(dict, SessionMixin): def __init__(self, initial=None, sid=None): self.sid = sid self.initial = initial super(MySession, self).__init__(initial or ()) def __setitem__(self, key, value): super(MySession, self).__setitem__(key, value) def __getitem__(self, item): return super(MySession, self).__getitem__(item) def __delitem__(self, key): super(MySession, self).__delitem__(key) class MySessionInterface(SessionInterface): session_class = MySession container = {} def __init__(self): import redis self.redis = redis.Redis() def _generate_sid(self): return str(uuid.uuid4()) def _get_signer(self, app): if not app.secret_key: return None return Signer(app.secret_key, salt='flask-session', key_derivation='hmac') def open_session(self, app, request): """ 程序刚启动时执行,需要返回一个session对象 """ sid = request.cookies.get(app.session_cookie_name) if not sid: sid = self._generate_sid() return self.session_class(sid=sid) signer = self._get_signer(app) try: sid_as_bytes = signer.unsign(sid) sid = sid_as_bytes.decode() except BadSignature: sid = self._generate_sid() return self.session_class(sid=sid) # session保存在redis中 # val = self.redis.get(sid) # session保存在内存中 val = self.container.get(sid) if val is not None: try: data = json.loads(val) return self.session_class(data, sid=sid) except: return self.session_class(sid=sid) return self.session_class(sid=sid) def save_session(self, app, session, response): """ 程序结束前执行,可以保存session中所有的值 如: 保存到resit 写入到用户cookie """ domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) expires = self.get_expiration_time(app, session) val = json.dumps(dict(session)) # session保存在redis中 # self.redis.setex(name=session.sid, value=val, time=app.permanent_session_lifetime) # session保存在内存中 self.container.setdefault(session.sid, val) session_id = self._get_signer(app).sign(want_bytes(session.sid)) response.set_cookie(app.session_cookie_name, session_id, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure) 自定义Session
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/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
message 闪现
message 是基于session实现的用于保存数据的集合,
基于session所以不同用户是拿不到彼此message中的数据,
在message中获取的数据是一次性获取完的,再获取为空列表
from flask import Flask,flash,get_flashed_messages,session,redirect,request app = Flask(__name__) app.secret_key = 'lalala' # 闪现是基于session做的,根据用户的sessiong进行隔离 @app.route('/get') def get(): # 从摸个地方获取设置过的所有值,并清除 data = get_flashed_messages() # 一次性取出set进去的所有值 取出的是列表形式 若取不到值则为空列表 # data = get_flashed_messages(category_filter=["x1"]) # 一次性取出set进 x1 分类的所有值 print(data) # 再去取则为空[] return 'Hello' @app.route('/set') def set(): # 向某个地方设置值 # flash('abc') # 可以多次向其中赋值 flash('abc',category='x1') # 往x1分类中添加值 return 'Hello' if __name__ == '__main__': app.run()
请求的扩展: 类似于django中的中间件操作
@app.before_request() ===> 类似于 def process_request(): 请求到视图函数前,先做什么
可以基于此来做用户验证:
from flask import Flask,request,session,redirect app = Flask(__name__) app.secret_key = 'asdf' @app.before_request # 在执行相应的视图函数前,先执行before_request中的函数 def before1(): #print(request.url) # http://127.0.0.1:5000/login #print(request.path) # /login print('123') if request.path == '/login': # 增加白名单 return # 返回 None 表示通过验证 与 django中的中间件一样 if session.get('user_info'): return return redirect('/login') # session中无数据直接跳转到 login @app.route('/index') # 第一次访问index 无法访问 def index(): print('index') return 'index' @app.route('/login') def login(): print('login') session['user_info'] = 'haha' # 通过验证代码省略,通过验证后 session中设置值 用于验证 return 'login' if __name__ == '__main__': app.run()
before_request 与 after_request 加载运行过程: before 先加载先执行 after 先加载后执行
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask app = Flask(__name__) @app.before_request def before1(): print('before_1') # return 'aaa' @app.before_request def before2(): print('before_2') return @app.after_request def after1(response): print('after_1') return response @app.after_request def after2(response): print('after_2') return response @app.route('/index') def index(): print('index') return 'index' if __name__ == '__main__': app.run() ''' before 先加载先执行 after 先加载后执行 before_1 before_2 index after_2 after_1 若 before中有返回值 视图不执行 但after全部执行 与django旧版本类似 '''
before_first_request 只有第一次执行时起作用,用于初始化设置
@app.before_first_request def before_first(): print('before_first===>')
错误信息定制 (请求的错误捕捉并自定义)
from flask import Flask app = Flask(__name__) @app.errorhandler(404) # 对错误信息捕捉 def error_404(arg): return '404 错误信息处理' if __name__ == '__main__': app.run()
模板自定义函数
from flask import Flask,render_template,Markup app = Flask(__name__) @app.template_global() def global_nb(a): return Markup('<input type="text" value=%s>'%(a,)) @app.template_filter() def filter_nb(a,b,c): return a+b+c @app.route('/index') def index(): return render_template('login.html') if __name__ == '__main__': app.run()
前端 <div> {{global_nb('哈哈哈')}} </div> <hr> <div> {{'我'|filter_nb('和','你')}} </div>
Flask 中间件: 其实请求扩展中已经做到了中间件的功能 Flask中间件只作为了解
from flask import Flask app = Flask(__name__) @app.route('/index') def index(): print('index') return 'index' class Md(object): def __init__(self,old_wsgi_app): self.old_wsgi_app = old_wsgi_app def __call__(self, environ, start_response): print('===>开始前') ret = self.old_wsgi_app(environ, start_response) print('===>结束后') return ret if __name__ == '__main__': app.wsgi_app = Md(app.wsgi_app) app.run()
蓝图: blueprint
当项目需求越来越多,app.py中的函数越来越多,这时就需要把app.py中各方法进行拆分到不同py文件中,构造项目目录结构
我们可以自己进行构造项目目录结构
目录结构:
app.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from views import app if __name__ == '__main__': app.run()
view中的__init__.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask,render_template app = Flask(__name__) from . import index from . import user
view中的index.py 与 user.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from . import app from flask import render_template @app.route('/acc/index') def index(): print('index') return render_template('index.html')
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from . import app from flask import render_template @app.route('/user/user') def user(): print('user') return render_template('user.html')
但我们自己写的目录结构只是做了拆分,而且文件间又重复调用,并且功能太少
推荐使用 flask自带的 蓝图 来应用到项目中
简单应用目录(推荐使用)
目录结构:
app.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask_pro import app if __name__ == '__main__': app.run()
view中的__init__.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask app = Flask(__name__,template_folder='templates',static_folder='statics',static_url_path='/static') # 模板位置,静态文件位置,静态文件前缀 from .views.account import account from .views.blog import blog app.register_blueprint(account,url_prefix='/account') # 把各应用中的蓝图对象 注册到flask的app对象中 # url批量处理 类似于 django的路由分发 app.register_blueprint(blog)
view中的account.py 与 blog.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import render_template from flask import Blueprint account = Blueprint('account',__name__) @account.route('/login') def login(): print('login') return render_template('login.html')
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Blueprint from flask import render_template blog = Blueprint('blog',__name__,url_prefix='/blog') # 构造蓝图对象 # url_prefix='/blog' 给文件中的url进行批量处理 后缀加/blog @blog.route('/index',) def index(): print('index') return render_template('index.html')
大型项目应用目录
run.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask_pro import app if __name__ == '__main__': app.run()
flask_pro中的__init__.py文件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Flask from .web import web app = Flask(__name__) app.debug = True app.register_blueprint(web,url_prefix='/blog')
flask_pro中的web文件中的__init__.py文件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from flask import Blueprint web = Blueprint('web',__name__,template_folder='templates',static_folder='statics') from .views import index from .views import detail
flask_pro中的web文件中的index.py 与 detail.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from .. import web from flask import render_template @web.route('/index') def index(): print('index') return render_template('index.html')
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from .. import web from flask import render_template @web.route('/detail/<username>') def detail(username): print('detail'+username) return render_template('detail.html',**{'username':username})
Flask 上下文
单进程多线程 : threading.local() 对象 为每个线程开辟自己的内存空间,来保存自己的值,线程之间保持独立
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading local_values = threading.local() # 用于为每个线程开辟一块内存空间保存其独有的值 def func(num): local_values.name = num import time time.sleep(1) print(local_values.name, threading.current_thread().name) for i in range(20): th = threading.Thread(target=func, args=(i,), name='线程%s' % i) th.start()
为了使每个线程彼此有独立的储存空间,也可以使用线程的唯一标识(get_ident())来创建一个独立的空间,储存其独有的值
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading from _thread import get_ident class Local(): def __init__(self): self.storage = {} self.get_ident = get_ident def set(self,k,v): ident = self.get_ident() # 获取线程的唯一标识 origin = self.storage.get(ident) if not origin: origin = {k:v} else: origin[k] = v self.storage[ident] =origin def get(self,k): ident = self.get_ident() # 获取线程的唯一标识 origin = self.storage.get(ident) return origin[k] local_values = Local() def task(arg): local_values.set('name',arg) import time time.sleep(1) print(local_values.get('name'),threading.current_thread().name) for i in range(20): th = threading.Thread(target=task,args=(i,),name='%s 线程'%i) th.start()
单进程单线程(多协程): 单个线程下多个协程会共享资源 ,因此需要使用协程的唯一标(getcurrent())识来创建独立的空间
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import threading from greenlet import getcurrent as get_ident class Local(object): def __init__(self): self.storage = {} self.get_ident = get_ident def set(self,k,v): ident = self.get_ident() origin = self.storage.get(ident) if not origin: origin = {k:v} else: origin[k] = v self.storage[ident] = origin def get(self,k): ident = self.get_ident() origin = self.storage.get(ident) if not origin: return None return origin.get(k,None) local_values = Local() def task(num): local_values.set('name',num) import time time.sleep(1) print(local_values.get('name'), threading.current_thread().name) for i in range(20): th = threading.Thread(target=task, args=(i,),name='线程%s' % i) th.start()