枫枫知道个人博客
首页
新闻
心情
关于
文章搜索
网站导航
项目相关
官方文档
首页
新闻
心情
关于
文章搜索
网站导航
项目相关
官方文档
登录
注册
web框架之flask
这是我们学过的第二个python-web框架,第一个是Dja
搜索
[[ slide_text ]]
[[ item.content ]]
1
0
web框架之flask
发布时间:
2022-10-25
作者:
枫枫
来源:
枫枫知道个人博客
python
flask
这是我们学过的第二个python-web框架,第一个是Django框架 Django给我的感受就是: - 大而全,除了 web 框架,自带 ORM 和模板引擎,灵活和自由度不够高 - ORM及其方便,傻瓜式使用 - Django 自带的模板引擎简单好用,但其强大程度和综合评价略低于 Jinja2 - Django 非常适合企业级网站的开发:快速、靠谱、稳定 - Django 上手也比较容易,开发文档详细、完善,相关资料丰富 flask给我的感受就是: - Flask 确实很“轻”,不愧是 Micro Framework ,从 Django 转向 Flask 的开发者一定会如此感慨,除非二者均为深入使用过 - Flask 自由、灵活,可扩展性强,第三方库的选择面广,开发时可以结合自己最喜欢用的轮子,也能结合最流行最强大的 Python 库 - 入门简单,即便没有多少 web 开发经验,也能很快做出网站 - 非常适用于小型网站 - 非常适用于开发 web 服务的 API - 开发大型网站无压力,但代码架构需要自己设计,开发成本取决于开发者的能力和经验 - 各方面性能均等于或优于 Django - Django 自带的或第三方的好评如潮的功能, Flask 上总会找到与之类似第三方库 - Flask 灵活开发, Python 高手基本都会喜欢 Flask ,但对 Django 却可能褒贬不一 - Flask 与关系型数据库的配合使用不弱于 Django ,而其与 NoSQL 数据库的配合远远优于 Django # 为什么要学习flask 1. 微服务化 2. 由单体架构向微服务架构演进 # 学习路线? # Hello world ```Python from flask import Flask # 创建app app = Flask(__name__) # 创建路由,使用装饰器 @app.route('/') def index(): return "hello world" if __name__ == '__main__': app.run(debug=True) ``` # 路由 ## 路由的第二种写法 路由除了装饰器的写法,还有其他写法吗? ```Python from flask import Flask app = Flask(__name__) # 第一种路由使用方式 @app.route('/') def index(): return "Hello world" def home(): return "Home" # 路由的第二种方式 第一个参数是url路径 view_func参数对应的是视图函数 # app.add_url_rule('/home', view_func=home) # 完整写法 url路径 别名 视图函数 app.add_url_rule('/home', 'home', home) if __name__ == '__main__': app.run(debug=True) ``` ## 反向生成url ```Python from flask import Flask, url_for app = Flask(__name__) # 第一种路由使用方式 @app.route('/') def index(): return "Hello world" def home(): print(url_for('home')) # /home print(url_for('index')) # / return "Home" # 路由的第二种方式 第一个参数是url路径 view_func参数对应的是视图函数 # app.add_url_rule('/home', view_func=home) # 完整写法 url路径 别名 视图函数 app.add_url_rule('/home', 'home', home) if __name__ == '__main__': app.run(debug=True) ``` 如果是使用装饰器的情况下,路由的别名就是函数名!!! ## 路由别名的坑! ```Python from flask import Flask, request app = Flask(__name__) def decorator(fn): def inner(*args, **kwargs): print('来了') res = fn(*args, **kwargs) print('走了') return res return inner @app.route('/') @decorator def index(): print(request.endpoint) return "Hello world" @app.route('/home') @decorator def home(): return "Home" if __name__ == '__main__': app.run(debug=True) ``` 项目运行会报错 AssertionError: View function mapping is overwriting an existing endpoint function: index 原因: 被装饰器装饰的这个函数,它的endpoint就会变成实际执行的这个函数,最终就会变成inner这个函数名 如果两个路由的别名是一样的,就会报错 验证: ```Python app.add_url_rule('/', 'index', index) app.add_url_rule('/home', 'index', home) ``` ### 如何解决? 保留原函数信息 ```Python import functools def decorator(fn): @functools.wraps(fn) def inner(*args, **kwargs): print('来了') res = fn(*args, **kwargs) print('走了') return res return inner ``` ## 路由匹配 ### 内置转换器 首先,我们通过打印app中的converters可以看到,flask内置的转换器有如下: ```Python print('converters:', app.url_map.converters) converters:{ 'default': <class 'werkzeug.routing.UnicodeConverter'>, # 匹配除/外的所有字符 'string': <class 'werkzeug.routing.UnicodeConverter'>, 'any': <class 'werkzeug.routing.AnyConverter'>, # 枚举 'path': <class 'werkzeug.routing.PathConverter'>, # 匹配所有字符 'int': <class 'werkzeug.routing.IntegerConverter'>, 'float': <class 'werkzeug.routing.FloatConverter'>, 'uuid': <class 'werkzeug.routing.UUIDConverter'> } ``` any ```Python @app.route('/index/<any("2", "3", "xxx"):nid>') ``` ### 正则匹配 1. 编写一个类,继承BaseConverter 2. 重写`to_python`,`to_url`两个方法 3. regex就是匹配的关键 4. 注册到转换器中 ```Python from flask import Flask from werkzeug.routing import BaseConverter app = Flask(__name__) # 1.编写一个类,继承BaseConverter class RegexConverter(BaseConverter): def __init__(self, map, regex): super(RegexConverter, self).__init__(map) self.regex = regex def to_python(self, value): """路由匹配时,传递给视图函数参数的值""" return value def to_url(self, value): """用于反向生成url""" res = super(RegexConverter, self).to_url(value) return res # 2.注册到转换器中 app.url_map.converters['regex'] = RegexConverter ``` 使用 ```Python @app.route('/index/<regex("\d+"):r>') ``` # 视图 ## 请求对象request 1. 需要导入,局部对象,只能在视图函数中使用 当content_type为application/json 的时候,request.json就是传递来的json数据,并且会反序列化json为dict,如果不是,那么调用这个属性就会直接截断 request.data只有当content_type为application/json的时候才有值,其值为bytes ```Python from flask import Flask, request app = Flask(__name__) @app.route('/', methods=["GET", "POST"]) def index(): # request是请求对象 局部对象 只能在视图中使用 print(request.endpoint) # 别名 print(request.path) # 访问的路径 print(request.full_path) # 完整的路径 # print(request.headers) # 请求头 print(request.query_string) # 查询参数 b'name=%E5%BC%A0%E4%B8%89' # print(request.json) # 针对content_type为application/json的请求 {'user': 'xxx'} print(request.content_type) print(request.data) # 原始请求体 print(request.form) # 会解析x-www-form-urlencoded和form-data的请求体 print(request.method) # 请求方式 GET POST return "Hello world" if __name__ == '__main__': app.run(debug=True) ``` ## FBV和CBV ### 第一种类视图 直接继承自`views.View` 必须要重写`dispatch_request`这个方法 ```Python from flask import Flask, views, request app = Flask(__name__) # 第一种类视图 class IndexView(views.View): methods = ["GET", "POST"] def dispatch_request(self): if request.method == 'GET': return self.get() return self.post() def get(self): return "GET" def post(self): return "POST" app.add_url_rule('/', view_func=IndexView.as_view(name='index')) ``` ### 第二种类视图 继承自`views.MethodView` ```Python from flask import Flask, views, request app = Flask(__name__) # 第二种类视图 class HomeView(views.MethodView): methods = ["GET", "POST"] def get(self): return "GET" def post(self): return "POST" app.add_url_rule('/home/', view_func=HomeView.as_view(name='home')) ``` ## 响应 flask里面可以直接返回字符串 返回字典 → 会自动变为json 响应头:`application/json` ```Python from flask import Flask, jsonify, make_response app = Flask(__name__) # 第一种类视图 @app.route('/') def index(): # 直接返回字符串 # return "index" # 返回字典 # return {"msg": "你好"} res = make_response(jsonify({"msg": "你好"})) # 响应对象 res.headers['Xxx'] = 'xxx' # 设置响应头 res.set_cookie('xxx', 'yyy', 10) # 设置cookie return res ``` ## 文件上传 ```Python file_dict = request.files file_obj = file_dict.get('xxx') # 保存文件 file_obj.save(file_obj.filename) ``` 上传到指定目录 ```Python file_obj.save('media/upload/' + file_obj.filename) ``` ## 文件下载 实现下载文件的关键就是响应头 ```Python Content-Disposition: attachment; filename=xxx.jpg ``` ### 原始实现 ```Python from flask import Flask, make_response app = Flask(__name__) @app.route('/downloads/', ) def files(): # 获取文件字典 with open('media/upload/xxx.png', 'rb')as f: data = f.read() res = make_response(data) res.headers['Content-Disposition'] = "attachment; filename=xxx.png" return res ``` ### send_file ```Python from flask import Flask, send_file @app.route('/download/2', ) def download1(): # as_attachment 保存文件 还是显示文件内容 # download_name 下载的文件内容 return send_file('media/upload/xxx.png', as_attachment=True, download_name='xx34.png') ``` ### 文件乱码问题 有的情况下,文件名是中文可能会有乱码 如果是这样的情况,那么最简单的解决方式就是直接设置响应头即可 ```Python @app.route('/download') def download(): res = send_file('media/upload/xxx.png', as_attachment=True, download_name='你好.png') res.headers['Content-Disposition'] += "; filename*=utf-8''{}".format(filename) return res ``` # 模板(jinja2) 设置模板  和django的模板引擎 - 支持list[] - 函数默认不加括号 ```Python from flask import Flask, render_template, Markup app = Flask(__name__) @app.route('/') def index(): title = "枫枫知道" like_list = ['足球', '羽毛球', '乒乓球'] user_info = { "name": "张三", "age": 34 } def get_name(): return user_info['name'] not_safe_html = "<a href='http://www.fengfengzhidao.com'>枫枫知道</a>" safe_html = Markup(not_safe_html) def set_html(val): return val return render_template('index.html', **locals()) ``` ```Python <div> title: {{ title }} </div> <div> list: {{ like_list }} list[0]: {{ like_list.0 }} -- {{ like_list[0] }} </div> <div> dict: {{ user_info }} dict["name"]: {{ user_info.name }} -- {{ user_info['name'] }} </div> <div> fun: {{ get_name }} fun(): {{ get_name() }} </div> <div> no_safe: {{ not_safe_html }} safe: {{ not_safe_html|safe }} safe_2: {{ safe_html }} </div> <div> fun(222): {{ set_html("222") }} </div> <ul> {% for like in [] %} <li>{{ like }}</li> {% else %} <li>没有任何内容</li> {% endfor %} </ul> <div> {% if 1 == 1 %} <span>成立</span> {% else %} <span>不成立</span> {% endif %} </div> ``` ## 内置过滤器 字符串相关 ```Python {# 当变量未定义时,显示默认字符串,可以缩写为d #} <p>{{ name | default('No name', true) }}</p> {# 单词首字母大写 #} <p>{{ 'hello' | capitalize }}</p> {# 单词全小写 #} <p>{{ 'XML' | lower }}</p> {# 去除字符串前后的空白字符 #} <p>{{ ' hello ' | trim }}</p> {# 字符串反转,返回"olleh" #} <p>{{ 'hello' | reverse }}</p> {# 格式化输出,返回"Number is 2" #} <p>{{ '%s is %d' | format("Number", 2) }}</p> {# 关闭HTML自动转义 #} <p>{{ '<em>name</em>' | safe }}</p> {% autoescape false %} {# HTML转义,即使autoescape关了也转义,可以缩写为e #} <p>{{ '<em>name</em>' | escape }}</p> {% endautoescape %} ``` 数值操作 ```Python {# 四舍五入取整,返回13.0 #} <p>{{ 12.8888 | round }}</p> {# 向下截取到小数点后2位,返回12.88 #} <p>{{ 12.8888 | round(2, 'floor') }}</p> {# 绝对值,返回12 #} <p>{{ -12 | abs }}</p> ``` 列表操作 ```Python {# 取第一个元素 #} <p>{{ [1,2,3,4,5] | first }}</p> {# 取最后一个元素 #} <p>{{ [1,2,3,4,5] | last }}</p> {# 返回列表长度,可以写为count #} <p>{{ [1,2,3,4,5] | length }}</p> {# 列表求和 #} <p>{{ [1,2,3,4,5] | sum }}</p> {# 列表排序,默认为升序 #} <p>{{ [3,2,1,5,4] | sort }}</p> {# 合并为字符串,返回"1 | 2 | 3 | 4 | 5" #} <p>{{ [1,2,3,4,5] | join(' | ') }}</p> {# 列表中所有元素都全大写。这里可以用upper,lower,但capitalize无效 #} <p>{{ ['tom','bob','ada'] | upper }}</p> ``` 字典操作 ```Python {% set users=[{'name':'Tom','gender':'M','age':20}, {'name':'John','gender':'M','age':18}, {'name':'Mary','gender':'F','age':24}, {'name':'Bob','gender':'M','age':31}, {'name':'Lisa','gender':'F','age':19}] %} {# 按指定字段排序,这里设reverse为true使其按降序排 #} <ul> {% for user in users | sort(attribute='age', reverse=true) %} <li>{{ user.name }}, {{ user.age }}</li> {% endfor %} </ul> {# 列表分组,每组是一个子列表,组名就是分组项的值 #} <ul> {% for group in users|groupby('gender') %} <li>{{ group.grouper }}<ul> {% for user in group.list %} <li>{{ user.name }}</li> {% endfor %}</ul></li> {% endfor %} </ul> {# 取字典中的某一项组成列表,再将其连接起来 #} <p>{{ users | map(attribute='name') | join(', ') }}</p> ``` ## 自定义过滤器 和路由同样有两种写法 ```Python # @app.template_filter('one') def get_one(lis): return lis[0] app.add_template_filter(get_one, 'one') ``` 在flask里面自定义标签意义不大 ## 模板继承 和django的模板一致 include也是一致的 # 特殊的装饰器 ## before_request 请求之前执行的函数 ```Python # 请求之前执行的函数 @app.before_request def before(): """请求之前去判断,如果请求头里面没有xxx=xxx的请求,都返回特定的内容""" xxx = request.headers.get('xxx') if not xxx: return "you not xxx!" return None ``` 1. 在这个被装饰的函数中,可以使用request请求对象 2. `return None`代表放行,return其他值就代表拦截 3. 还可以拦截静态文件 4. 适合做全局验证。登录验证 ### 多个请求中间件 顺序就是从上往下 ```Python @app.before_request def before(): print('第一个请求中间件') @app.before_request def before1(): print('第二个请求中间件') ``` ## after_request ```Python @app.after_request def after(response): print('请求走了') return response # 这个函数先执行 @app.after_request def after1(response): response.headers['xxx'] = 'xxx' # 设置响应头 return make_response('xxx') # 不能直接返回字符串,字典 ``` 1. 有参数,参数就是响应对象 2. 设置响应头,设置cookie 3. 拦截之后会继续往下走 ## 只执行一次的函数 ```Python @app.before_first_request def before_first(): print('我只执行一次') ``` ## 自定义错误页面 ```Python # 自定义错误页面 @app.errorhandler(404) def _404(error): print(error) return '404' ``` ## 全局传值 在模板中进行使用 ```Python # 全局传值 @app.context_processor def global_value(): version = '5.3.21' return dict(version=version) ```
1
0
上一篇:Scrapy爬虫框架
下一篇:docker安装es数据库
你觉得文章怎么样
发布评论
319 人参与,0 条评论