Flask基础
开篇引入
1.django和flask的相同点和不同点?
共同点:都是基于wsgi的 不同点: (1) django是一个大而全的框架,提供了方便内置的框架,orm,admin,分页,form,model_form,缓存,信号等很多方便的组件, 我们只要在配置文件中一修改就可以使用。 (2) flask是一个短小精悍,可扩展性非常强,flask适合开发小型的网站,也可以开发大型网站,因为它给我们提供有许多第三方组件,我们就可以结合这些第三方组件集成一个像django一样拥有很多功能的web框架。可定制性非常强。 (3) flask和django最大的不同点:request/session flask是直接导入的,request在全局起作用。 django是依附于request参数,通过参数传导。 两个框架没有优劣之分,具体应用看实际需求。
2.什么是wsgi?
web服务网关接口,wsgi是一个协议,实现该写一个的模块: - wsgiref - werkzeug 实现协议的模块本质上就是socket服务端用于接收用户请求,并处理。 一般web框架基于wsgi实现,这样实现关注点分离,主要负责业务处理。
1 from wsgiref.simple_server import make_server 2 3 def run_server(environ, start_response): 4 start_response('200 OK', [('Content-Type', 'text/html')]) 5 return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ] 6 7 8 if __name__ == '__main__': 9 httpd = make_server('127.0.0.1', 8000, run_server) 10 httpd.serve_forever()
1 from werkzeug.wrappers import Response 2 from werkzeug.serving import run_simple 3 4 def run_server(environ, start_response): 5 response = Response('hello') 6 return response(environ, start_response) 7 8 if __name__ == '__main__': 9 run_simple('127.0.0.1', 8000, run_server)
from werkzeug.wrappers import Response from werkzeug.serving import run_simple class Flask(object): def __call__(self,environ, start_response): response = Response('hello') return response(environ, start_response) def run(self): run_simple('127.0.0.1', 8000, self) app = Flask() if __name__ == '__main__': app.run()
基础知识
安装:
pip3 install flask
特点: 短小精悍、可扩展强 的一个Web框架。
特色:上下文管理机制
wsgi:web service getway interface web服务网管接口
依赖wsgi:werkzurg
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple
def run(environ,start_response):
response = Response('hello')
return response(environ, start_response)
if __name__ == '__main__':
run_simple('localhost', 4000, run)
from werkzeug.wrappers import Request, Response @Request.application def hello(request): return Response('Hello World!') if __name__ == '__main__': from werkzeug.serving import run_simple run_simple('localhost', 4000, hello)
一、配置文件
1.使用:所有配置都在app.config中
- 方式一:app.config['DEBUG'] = True
PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...)
- 方式二:app.config.from_object('类的路径')
- 方式三:app.config.from_pyfile('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
2.实现原理
指定一个字符串(类的路径/文件路径),importlib-->getattr 找到这个类/模块把这个类/模块的所有字段(大写)一个一个获取,获取的时候判断isupper(),给一个路径'settings.Foo',可以找到类并获取其中大写的静态字段。
import importlib path = 'settings.Foo' p,c = path.rsplit('.',maxsplit=1) m = importlib.import_module(p) # m = __import__(p) cls = getattr(m,c) for key in dir(cls): if key.isupper(): print(key,getattr(cls,key))
'ENV': None, 'DEBUG': None, 是否开启DEBUG模式 'TESTING': False, 是否开启测试模式 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, })
二、路由系统
重点:基于装饰器实现
技术点:-functools.wraps(func):保留原函数的原信息
装饰器(带参数)
- methods=['GET','POST']
- endpoint:反向生成url
- url_for(endpoint)
自定义装饰器放下面
注意事项:
- - endpoint默认是函数名
- - 不要让endpoint重名,如果重名函数也一定要相同。
- - 加装饰器时要加functools.wraps(func) + functools.partial
# -*- coding: utf-8 -*- """ @Datetime: 2018/12/21 @Author: Zhang Yafei """ """1.装饰器""" from functools import wraps def auth(func): @wraps(func) # 伪装的更彻底 def inner(*args,**kwargs): print('前') ret = func(*args,**kwargs) print('后') return ret return inner @auth def index(): print('index') @auth def detail(): print('index') print(index.__name__) print(detail.__name__) """2.endpoint默认是函数名"""
路由设置的两种方式
方式一 @app.route('/xxx') def index(): return "index" 方式二 def index(): return "index" app.add_url_rule("/xxx",None,index)
-动态路由 /index/<int:nid> def index(nid): print(nid) return 'index'
1 rule, URL规则 2 view_func, 视图函数名称 3 endpoint=None, 名称,用于反向生成URL,即: url_for('名称') 4 methods=None, 允许的请求方式,如:["GET","POST"] 5 strict_slashes=None, 对URL最后的 / 符号是否严格要求, 6 redirect_to=None, 重定向到指定地址 7 8 defaults=None, 默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数 9 subdomain=None, 子域名访问
from flask import Flask,url_for app = Flask(__name__) # 步骤一:定制类 from werkzeug.routing import BaseConverter 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 # 步骤二:添加到转换器 app.url_map.converters['reg'] = RegexConverter """ 1.用户发送请求 2.flask内部进行正则匹配 3.调用to_python(正则匹配的结果)方法 4.to_python方法的返回值会交给视图的函数 """ # 步骤三:使用自定义正则 @app.route('/index/<reg("\d+"):nid>') def index(nid): print(nid,type(nid)) print(url_for('index',nid=987)) return 'index' if __name__ == '__main__': app.run()
@main.route('/data_list/<table_name>') def data_list(table_name): df = reader.enable_admins[table_name] page_obj = Pagination(df['data'].shape[0], request.args.get('p'),per_page_num=5) page_str = page_obj.page_str(base_url=url_for('main.data_list', table_name=table_name)) admin_class = df['admin_class'] data_list = df['data'][page_obj.start:page_obj.end] context = {'table_name':table_name, 'data_list':data_list, 'page_obj':page_obj, 'page_str':page_str, 'admin_class':admin_class, } return render_template('data_list.html', **context)
三、视图
视图:FBV和CBV
技术点:反射
视图函数中获取request或session 方式一:直接找LocalStack获取 from flask.globals import _request_ctx_stack print(_request_ctx_stack.top.request.method) 方式二:通过代理LocalProxy(小东北)获取 from flask import Flask,request print(request.method)
import functools from flask import Flask,views app = Flask(__name__) def wrapper(func): @functools.wraps(func) def inner(*args,**kwargs): return func(*args,**kwargs) return inner class UserView(views.MethodView): methods = ['GET'] decorators = [wrapper,] def get(self,*args,**kwargs): return 'GET' def post(self,*args,**kwargs): return 'POST' app.add_url_rule('/user',None,UserView.as_view('uuuu')) if __name__ == '__main__': app.run()
四、请求相关
技术点:面向对象的封装
request属性
# 请求相关信息 # request.method 请求方法 GET/POST # request.args GET请求参数 # request.form POST请求参数 # request.values GET和POST请求参数 # request.cookies cookie值 # 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))
print('headers:', request.headers) print('cookies:', request.cookies) print('path:', request.path) print('full_path:', request.full_path) print('url:', request.url) print('script_root:', request.script_root) print('base_url:', request.base_url) print('url_root:', request.url_root) print('host:', request.host) print('host_url:', request.host_url) headers: Host: 172.25.0.246:8999 Connection: keep-alive Content-Length: 37 Pragma: no-cache Cache-Control: no-cache User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 S afari/601.1 wechatdevtools/1.02.1901230 MicroMessenger/6.7.3 Language/zh_CN webview/ token/a46ac17280ff1fcb82b684b2084ee168 Origin: http://127.0.0.1:23672 Authorization: f3c4e30debf4030ace5c3cdf40e6332d#1 Content-Type: application/x-www-form-urlencoded Accept: */* Referer: https://servicewechat.com/wx49bec3712cb29bf5/devtools/page-frame.html Accept-Encoding: gzip, deflate cookies: {} path: /api/member/check-reg full_path: /api/member/check-reg? url: http://172.25.0.246:8999/api/member/check-reg script_root: base_url: http://172.25.0.246:8999/api/member/check-reg url_root: http://172.25.0.246:8999/ host: 172.25.0.246:8999 host_url: http://172.25.0.246:8999/
@main.route('/data_list/<table_name>') def data_list(table_name): df = reader.enable_admins[table_name] page_obj = Pagination(df['data'].shape[0], request.args.get('p'),per_page_num=5) page_str = page_obj.page_str(base_url=url_for('main.data_list', table_name=table_name)) admin_class = df['admin_class'] data_list = df['data'][page_obj.start:page_obj.end] context = {'table_name':table_name, 'data_list':data_list, 'page_obj':page_obj, 'page_str':page_str, 'admin_class':admin_class, } return render_template('data_list.html', **context)
@ac.route('/login',methods=['GET', 'POST']) def login(): if request.method == 'GET': return render_template('login.html') username = request.form.get('username') password = request.form.get('pwd') """数据库验证""" password = get_md5(password) # conn = Connect(host='localhost', user='root', password='0000', database='flask_code', charset='utf8') # cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 每一行是字典 # cursor.execute("select id,username,nickname from userinfo where username=%(us)s and password=%(pw)s",{'us':username,'pw':password}) # data = cursor.fetchone() data = fetchone("select id,username,nickname from userinfo where username=%(us)s and password=%(pw)s",{'us':username,'pw':password}) if not data: return render_template('login.html',error='用户名或密码错误') session['user_info'] = {'id':data['id'],'username':data['username'],'nickname':data['nickname']} if request.form.get('remember'): session.permanent = True ac.permanent_session_lifetime = timedelta(days=31) return redirect('/home')
五、响应相关
技术点:面向对象的封装
响应体:4种
return '欢迎使用' return jsonify({'k1':'v1'}) return render_template('xxx.html') return redirect('/index')
定制响应头
obj = make_response(render_template('index.html')) obj.headers['xxxxx'] = '123' obj.set_cookie('key','value') return obj
示例程序:用户访问限制
@app.route('/detail/<int:nid>') def detail(nid): if not session.get('user'): return redirect(url_for('login')) info = STUDENT_DICT[nid] return render_template('detail.html', info=info)
def auth(func): @wraps(func) def inner(*args,**kwargs): if not session.get('user'): return redirect(url_for('login')) ret = func(*args,**kwargs) return ret return inner @app.route('/index') @auth def index(): return render_template('index.html',stu_dict=STUDENT_DICT) 应用场景:比较少的函数中需要添加额外的功能
@app.before_request def xxxx(): if request.path == '/login': return None if not session.get('user'): return None return redirect(url_for('login')) 应用场景:比较多的函数中需要添加额外的功能
六、模板渲染
基本数据类型:可以执行python语法,如:dict.get() list['xx']
控制代码块:条件语句和循环语句
- if/else if /else / endif - for / endfor
<ul class="pagination"> {% if page_obj.num_pages > 1 %} {{ page_str }} {% else %} {% endif %} </ul>
<table class="table table-bordered"> <thead> <tr style="background-color: pink"> <th>序号</th> <th>文件名</th> <th>样本数</th> <th>特征数</th> <th>操作</th> </tr> </thead> <tbody> {% for k,v in data_list.items() %} <tr> <td>{{ loop.index }}</td> <td><a href="{{ url_for('main.data_list',table_name=k)}}">{{ k }}</a></td> <td>{{ v['data'].shape }}</td> <td>{{ v['data'].shape }}</td> <td><a href="{{ url_for('main.data_list',table_name=k)}}">查看</a></td> </tr> {% endfor %} </tbody> </table>
除此之外,还有一个特殊的循环函数loop
-
在循环内部,你可以使用一个叫做loop的特殊变量来获得关于for循环的一些信息
-
比如:要是我们想知道当前被迭代的元素序号,并模拟Python中的enumerate函数做的事情,则可以使用loop变量的index属性,例如:
-
{% for post in posts%} {{loop.index}}, {{post.title}} {% endfor %}
会输出这样的结果 1, Post title2, Second Post - cycle函数会在每次循环的时候,返回其参数中的下一个元素,可以拿上面的例子来说明:
-
{% for post in posts%} {{loop.cycle('odd','even')}} {{post.title}} {% endfor %} 会输出这样的结果: odd Post Title even Second Post
传入函数
- -django,自动执行
- -flask,不自动执行
自定义函数
@app.template_global() def sb(a,b): return a+b @app.template_filter() # 适合if 判断 def db(a,b,c): return a+b+c
html {{ 1|db(2,3) }} {% if 1|db(2,3) %} <div>666</div> {% else %} <div>999</div> {% endif %} @app.template_filter() # 适合if 判断 def db(a,b,c): return a+b+c
用装饰器来实现自定义过滤器。装饰器传入的参数是自定义的过滤器名称。 @app.template_filter('lireverse') def do_listreverse(li): # 通过原列表创建一个新列表 temp_li = list(li) # 将新列表进行返转 temp_li.reverse() return temp_li 在 html 中使用该自定义过滤器 <br/> my_array 原内容:{{ my_array }}<br/> my_array 反转:{{ my_array | lireverse }} 运行结果 my_array 原内容:[3, 4, 2, 1, 7, 9] my_array 反转:[9, 7, 1, 2, 4, 3]
模板继承
layout.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% block content %}{% endblock %} </body> </html> tpl.html {% extends 'layout.html' %} {% block content %} {% include 'form.html' %} {% macro ccccc(name,type='text', value='') %} <h1>宏</h1> <input type="{{ type }}" name="{{ name }}" value="{{ value }}"> <input type="submit" name="提交"> {% endmacro %} {{ ccccc('n1') }} {{ ccccc('n2') }} {% endblock %}
<include 'from.html'> form.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div>sasasasa sasasasasasa</div> </body> </html>
宏
{% macro ccccc(name,type='text', value='') %} <h1>宏</h1> <input type="{{ type }}" name="{{ name }}" value="{{ value }}"> <input type="submit" name="提交"> {% endmacro %} {{ ccccc('n1') }} {{ ccccc('n2') }}
安全
前端:{{ txt|safe }}
后端:Markup(txt)
七、session
原理:加密后放置在用户浏览器的cookie中
流程:
请求到来:flask读取cookie中的session对应的值:eyJrMiI6NDU2LCJ1c2VyIjoiZmVpIn0,将该值解密并反序列化成字典,放入内存以便视图函数使用。 视图函数: @app.route('/sess') def sess(): print(session.__class__) session['k1'] = 123 session['k2'] = 456 del session['k1'] return 'Session' 请求结束,flask会读取内存中字典的值,进行序列化+加密,写入用户cookie中。
实现原理(源码)
def wsgi_app(self, environ, start_response): """ 1.获取environ并对其进行再次封装 2.从environ中获取名称为session的cookie,解密,反序列化 3.两个东西放到‘某个神奇'的地方 """ # ctx = RequestContext(self, environ) #self是app对象,evviron是原始数据对象 # ctx.request = Request(environ) # ctx.session = None ctx = self.request_context(environ) error = None try: try: ctx.push() # 4. 执行视图函数 # 5.'某个神奇’获取session,加密,序列化,写入cookie response = self.full_dispatch_request() except Exception as e: error = e response = self.handle_exception(e) except: error = sys.exc_info()[1] raise return response(environ, start_response) finally: if self.should_ignore_error(error): error = None """ 6.'某个神奇'位置清空 """ ctx.auto_pop(error)
闪现(flash): 在session存储一个数据,读取时通过pop将数据删除,形成一种数据只能取一次的效果
from flask import Flask,flash,get_flashed_messages @app.route('/page1') def page1(): flash('临时数据存储','error') flash('sasasasa','error') flash('sasasas','info') return 'Session' @app.route('/page2') def page2(): print(get_flashed_messages(category_filter=['error'])) return 'Session'
八、中间件
一般不常用,因为它的执行顺序很靠前,无序获取request对象,与请求相关信息难以获得
- call方法什么时候触发?
-用户发起请求时,才执行
- -任务:在执行call方法之前,做一个操作,call方法执行之后,做一个操作
class Middleware(object): def __init__(self,old): self.old = old def __call__(self, *args, **kwargs): print('前') ret = self.old(*args,**kwargs) print('后') return ret if __name__ == '__main__': app.wsgi_app = Middleware(app.wsgi_app) app.run()
九、特殊的装饰器
技术点:before_request和after_request的实现原理
六大装饰器
before_first_request before_request 视图函数之前,原理是将视图函数放入到一个列表中,循环,如果有返回值停止循环,后面的函数也将不会执行 after_request 视图函数之后,原理是将视图函数放入到一个列表中reverse,循环执行 template_global template_filter errorhandler @app.errorhandler(404) def not_found(arg): print(arg) return '404 没找到'
# -*- coding: utf-8 -*- """ @Datetime: 2018/12/21 @Author: Zhang Yafei """ from flask import Flask app = Flask(__name__) @app.before_first_request def x(): print('before_first') @app.before_request def x1(): print('before:x1') @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' @app.errorhandler(404) def not_found(arg): print(arg) return '404 没找到' if __name__ == '__main__': app.run()
十、蓝图
(1) 目标:目录结构的划分
(2) 自定义模板、静态文件
admin = Blueprint( 'admin', __name__, template_folder='templates', static_folder='static' )
(3) 给某一类url添加前缀变量 app.register_blueprint(admin, url_prefix='/admin')
(4) 给一类url添加before_request
@app.before_request def x1(): print('app.before_request')
十一、多app
from flask import Flask from werkzeug.wsgi import DispatcherMiddleware from werkzeug.serving import run_simple app1 = Flask("app1") app1.config['DB'] = 123 app2 = Flask("app2") app1.config['DB'] = 456 @app1.route('/web') def web(): print('web') return '12213' @app1.route('/news') def news(): print('news') return '12213' @app2.route('/admin') def admin(): print('admin') return '12213' @app2.route('/article') def article(): print('article') return '12213' """ /web /new /app2/admin /app2/article """ app = DispatcherMiddleware(app1, { '/app2': app2, }) if __name__ == '__main__': run_simple(hostname='127.0.0.1', port=5000, application=app)
from multi_app import app1 from multi_app import app2 with app1.app_context(): pass # 为app1创建数据库 with app2.app_context(): pass # 为app2创建数据库
什么是响应式布局?
技术点:@media
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" /> </head> <body> <div class="row" style="background-color: #28a4c9"> <div class="col-lg-6">.col-lg-6</div> <div class="col-lg-6">.col-lg-6</div> </div> <div class="row" style="background-color: #67b168"> <div class="col-md-6">.col-md-6</div> <div class="col-md-6">.col-md-6</div> </div> <div class="row" style="background-color: red"> <div class="col-sm-6">.col-sm-6</div> <div class="col-sm-6">.col-sm-6</div> </div> <div class="row" style="background-color: gold"> <div class="col-xs-6">.col-xs-6</div> <div class="col-xs-6">.col-xs-6</div> </div> </body> </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"> <style> body{ margin: 0; } .pg{ width: 100%; background-color: rebeccapurple; } @media (min-width: 666px) { .pg{ background-color: green; } } @media (min-width: 888px) { .pg{ background-color: red; } } </style> </head> <body> <div> <div class="pg">asdfasdf</div> </div> </body> </html>
注:
1. 上下文管理的实现? 当请求到来的时候, flask会把请求相关和session相关信息封装到一个ctx = RequestContext对象中, 会把app和g封装到app_ctx = APPContext对象中去, 通过localstack对象将ctx、app_ctx对象封装到local对象中 获取数据(执行视图函数的时候) 通过导入一个模块,用模块.的方式获取我们想要的数据 实现细节: 通过localproxy对象+偏函数,调用localstack去local中获取对应的响应ctx、app_ctx中封装值 问题:为什么要把ctx = request/session app_ctx = app/g 因为离线脚本需要使用app_ctx 请求结束 调用localstk的pop方法,将ctx和app_ctx移除 2. 为什么把上下文管理分成: - 应用上下文:request/session - 请求上下文: app/g 离线脚本应用 3. Local的作用? 类似于threading.local的作用,但是是他的升级版(greentlet.get_current()) __storage__ = { 1231: {}, 1231: {} } 4. LocalStack作用? 将Local中__storage__的值维护成一下结构: __storage__ = { 1231: {stack:[],}, 1231: {stack:[],} } 5. 为什么要维护成一个栈? 6. 为什么导入request,就可以使用? 每次执行request.xx方法时,会触发LocalProxy对象的__getattr__等方法,由方法每次动态的使用 LocalStack去Local中获取数据