文章目录
- 1. flask第一个应用
- 2. 配置文件的使用
- 3. 路由系统
- 4. 请求与响应相关数据
- 5. 登录案例
- 6. 学生管理案例
- 7. 模板渲染
- 8. session
- 9. flash
- 10. 中间件
- 11. 特殊的装饰器(重要)
- 11.1 before_request 与 after_request
- 11.2 before_first_request
- 11.3 template_global 与 template_filter
- 11.4 errorhandler(应用比较广)
- 12. 路由和视图
- 13. session实现的原理
- 14. 蓝图
- 15. threading.local
- 15.1 threading.local的作用
- 15.2 如何获取一个线程的唯一标记
- 15.3 根据字典自定义一个类似于threading.local功能
- 15.4 根据字典为每个协程开辟空间进行存取数据
- 15.5 __ getattr__ 与__ setattr__
- 15.6 通过getattr与setattr构造出threading.local
- 16. 上下文管理
- 17. 偏函数与面向对象
- 18. 基于列表实现栈
- 19. Local对象与LocalStack
- 20. flask请求上下文管理原理剖析(一)
- 22. flask请求上下文管理原理剖析(二)
- 23. flask第三方组件:flask-session
- 23.1 安装flask-session
- 23.2 session保存在redis中
- 23.3 查看redis中存入的session
- 23.4 flask-session的原理
- 23.5 flask-session源码
- 24. 代码统计项目
- 24.1 用户登录
- 24.2 用户列表
- 24.3 查看代码统计量
- 24.4 上传zip文件与解压
- 24.5 统计代码行数
- 24.6 每日提交一次
- 24.7 数据库连接池DBUtils
- 24.9 代码统计应用DBUtils
- 24.10 数据可视化
- 25. 再谈上下文管理
- 26. wtforms示例
- 27. SQLAlchemy
1. flask第一个应用
1.1 flask概述
Flask框架是一个短小精悍、可扩展强的Web框架。同Django框架一样依赖于wsgi(Tornado不依赖于wsgi,框架中自己定义了socket)。
特有:上下文管理机制
1.2 什么是wsgi(面试)
wsgi是服务网关接口(Web Server Gateway Interface),wsgi是一个协议,实现该协议的模块有。
- wsgiref
- werkzeug
实现其模块本质上就是socket服务端用于接收客户端的请求并处理。
一般web框架基于wsgi实现,这样实现关注点分离。
1.3 安装flask
pip3 install flask
1.4 werkzurg模块
Flask中实现wsgi的模块是werkzurg,它是Flask第三方模块。
Django中实现wsgi的模块是wsgiref
示例1:
from werkzeug.serving import run_simple
def run(environ, start_response):
return 'Hello Flask!'
if __name__ == '__main__':
run_simple('localhost', 5000, run)
示例2:
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple
@Request.application
def hello_flask(request):
return Response('Hello Flask!')
if __name__ == '__main__':
run_simple('localhost', 5000, hello_flask)
1.5 第一个flask应用
from flask import Flask
app = Flask(__name__) # 实例化Flask类
@app.route('/index')
def index():
return 'Hello Flask!'
if __name__ == '__main__':
app.run() # run方法启动socket
2. 配置文件的使用
2.1 配置文件导入原理
给一个类的字符串路径,找到类并且获取类中大写的静态字段。
main.py:
class FlaskClass:
FLAG = True
test.py:
import importlib
path = 'setting.FlaskClass'
p, c = path.rsplit('.', maxsplit=1)
# print(p, c) # setting FlaskClass
m = importlib.import_module(p)
# print(m) # <module 'setting' from 'C:\\Users\\Thanlon\\PycharmProjects\\flask_pro\\setting.py'>
cls = getattr(m, c)
print(cls) # 找到类:<class 'setting.FlaskClass'>
# print(dir(cls)) # 类成员
for key in dir(cls):
if key.isupper():
print('key:', key)
print('value:', getattr(cls, key))
<class ‘setting.FlaskClass’>
key: FLAG
value: True
2. 配置文件的使用
- Flask默认配置文件
Flask配置文件是一个flask.config.Config对象,它继承字典,默认配置文件如下:
print(type(app.config), app.config)
<class ‘flask.config.Config’>
{
'ENV': 'production',
'DEBUG': False,
'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'PERMANENT_SESSION_LIFETIME': datetime.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': datetime.timedelta(seconds=43200), '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}
- 修改默认配置文件
创建配置文件 settings.py,用于存放修改的配置文件:
settings.py:
# 存放开发环境、线上环境、测试环境共有的配置文件
class Config(object):
pass
# 线上环境配置文件
class ProductionConfig(Config):
DEBUG = False
# 开发环境配置文件
class DevelopmentConfig(Config):
DEBUG = True
# 测试环境配置文件
class TestingConfig(Config):
DEBUG = True
app.py:
from flask import Flask
app = Flask(__name__)
# print(type(app.config), app.config)
app.config.from_object('settings.DevelopmentConfig') # 引入配置文件
if __name__ == '__main__':
app.run()
3. 路由系统
3.1 反向生成URL
使用url_for方法生成url:
@app.route('/index', methods=['GET', 'POST'], endpoint='name')
def index():
print(url_for('name'))
return 'Flask!'
使用url_for方法生成url时设置参数:
@app.route('/index/<int:id>')
def index(id):
print(url_for('index', id=1))
return 'Flask!'
控制台:/index/1
如果不指定endpoint的值,默认为函数名:
def _endpoint_from_view_func(view_func):
assert view_func is not None, 'expected view func if endpoint ' \
'is not provided.'
return view_func.__name__ # 返回函数名
def add_url_rule(self,rule,endpoint=None,view_func=None,provide_automatic_options=None, **options):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options['endpoint'] = endpoint
methods = options.pop('methods', None)
@app.route('/index', methods=['GET', 'POST'])
def index():
print(url_for('index'))
return 'Flask!'
如果endpoint非要重名,必须保证函数相同。
3.2 动态路由的构造
@app.route('/index/<username>') # 没有指定类型是字符串类型
@app.route('/index/<int:id>')
@app.route('/index/<float:id>')
@app.route('/index/<path:path>')
示例:
@app.route('/index/<float:id>')
def index(id):
print(type(id),id)
return 'Flask!'
浏览器输入:http://127.0.0.1:5000/index/1.0
控制台输出:<class ‘float’> 1.0
4. 请求与响应相关数据
4.1 请求相关数据
常用请求的相关信息:
request.method
request.args
request.form
request.cookies
request.headers
其它请求的相关信息:
request.values
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('/uploads/'+secure_filename(f.filename))
4.2 响应响应体
响应字符串:
@app.route('/index')
def index():
return 'Flask' # 字符串
响应模板:
@app.route('/index')
def index():
return render_template() # 模板
响应重定向:
@app.route('/index')
def index():
return redirect('') # 重定向
响应json格式数据(from flask import json):
@app.route('/index')
def index():
return json.dumps({'name': 'thanlon'}) # {'name': 'thanlon'}
另一种响应json格式数据的方式(from flask import jsonify):
@app.route('/index')
def index():
return jsonify({'name': 'thanlon'}) # {'name': 'thanlon'}
注意:不能响应字典,即
return dic
是错误的
4.3 响应响应头
以上是响应响应体,还可以响应响应头和cookie,只需要从Flask模块中导入make_response进行封装即可。(from flask import make_response)
示例:将字符串类型的响应体与响应头和cookie一同封装响应:
@app.route('/index')
def index():
obj = make_response('Flask')
obj.headers['response_flag'] = 'response_info'
obj.set_cookie('key','value')
return obj
响应模板、响应重定向、响应json格式数据和示例就一样的。
5. 登录案例
5.1 案例目录结构
5.2 案例主要代码
app.py文件:
from flask import Flask, render_template, request, redirect, session
# app = Flask(__name__, static_folder='static', static_url_path='/thanlon') # 可指定前缀
app = Flask(__name__)
app.secret_key = 'thanlon'
@app.route('/login', methods=['GET', 'POST']) # 默认允许GET请求
def login():
if request.method == 'GET':
return render_template('login.html')
username = request.form.get('username')
pwd = request.form.get('pwd')
if username == 'Thanlon' and pwd == '123':
session['username'] = username # 默认session保存在签名或加密的cookie中
return redirect('/index')
# return render_template('login.html', error='用户名或者密码错误!')
return render_template('login.html', **{'error': '用户名或者密码错误!'})
@app.route('/index') # 默认允许GET请求
def index():
username = session.get('username')
if not username:
return redirect('/login')
return render_template('index.html')
if __name__ == '__main__':
app.run()
login页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row" style="margin-top:30px">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-primary">
<div class="panel-heading">登录</div>
<div class="panel-body">
<form method="POST">
<div class="form-group">
<label>用户名</label>
<input type="text" name="username" class="form-control" id="" placeholder="Username"
required="required">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="pwd" class="form-control" placeholder="Password"
required="required">
<div style="color:red">{{error}}</div>
</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
</html>
index页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>首页</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row" style="margin-top:30px">
<div class="panel panel-default">
<div class="panel-body">
欢迎{{session['username']}}进入首页!
</div>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
</html>
5.3 案例页面效果
登录失败:
登录成功:
6. 学生管理案例
6.1 第一种用户认证
settings.py:
# 存放开发环境、线上环境、测试环境共有的配置文件
class Config(object):
DEBUG = False
SECRET_KEY = 'thanlon'
……
app.py:
from flask import Flask, render_template, redirect, url_for, request, session
app = Flask(__name__)
app.config.from_object('settings.DevelopmentConfig') # 引入配置文件
USER_DICT = {
1: {'username': 'thanlon', 'age': '23', 'gender': '男'},
2: {'username': 'kiku', 'age': '25', 'gender': '女'}
}
@app.route('/login', methods=['GET', 'POST']) # 默认允许GET请求
def login():
if request.method == 'GET':
return render_template('login.html')
username = request.form.get('username')
pwd = request.form.get('pwd')
if username == 'Thanlon' and pwd == '123':
session['username'] = username # 默认session保存在签名或加密的cookie中
return redirect('/index')
# return render_template('login.html', error='用户名或者密码错误!')
return render_template('login.html', **{'error': '用户名或者密码错误!'})
@app.route('/index')
def hello_world():
if not session.get('username'):
return redirect(url_for('login'))
return render_template('index.html', user_dict=USER_DICT)
@app.route('/delete/<int:nid>')
def delete(nid):
if not session.get('username'):
return redirect(url_for('login'))
del USER_DICT[nid]
return redirect(url_for('hello_world')) # url_for('')写函数名,和路由无关
@app.route('/detail/<int:nid>')
def detail(nid):
if not session.get('username'):
return redirect(url_for('login'))
info = USER_DICT[nid]
return render_template('detail.html', info=info)
if __name__ == '__main__':
app.run()
index.html:
……
<div class="container">
<div class="row">
<div class="col-md-4">
<table class="table table-hover">
<tr>
<th style="text-align: center">ID</th>
<th style="text-align: center">用户名</th>
<th style="text-align: center">年龄</th>
<th style="text-align: center">性别</th>
<th style="text-align: center">操作</th>
</tr>
{% for k,v in user_dict.items() %}
<tr style="text-align: center">
<td>{{ k }}</td>
<td>{{ v.username }}</td>
<td>{{ v.age }}</td>
<td>{{ v.gender }}</td>
<td><a href="/detail/{{ k }}">详情</a> | <a href="/delete/{{ k }}">删除</a></td>
</tr>
{# <tr>#}
{# <td>{{ v['username'] }}</td>#}
{# <td>{{ v['age'] }}</td>#}
{# <td>{{ v['gender'] }}</td>#}
{# </tr> #}
{# <tr>#}
{# <td>{{ v.get('usename','默认') }}</td>#}
{# <td>{{ v.get('age') }}</td>#}
{# <td>{{ v.get('gender') }}</td>#}
{# </tr>#}
{% endfor %}
</table>
</div>
</div>
</div>
……
detail.html:
……
<body>
{#{'username': 'thanlon', 'age': '23', 'gender': '男'}#}
<br>
{% for item in info.values() %}{#% for item in info.values() %#}
{{ item }}
{% endfor %}
</body>
……
6.2 装饰器知识点补充
函数没有加装饰器:
# coding:utf-8
def auth(func):
def inner(*args, **kwargs):
ret = func(*args, **kwargs)
return ret
return inner
def index():
print('index')
print(index.__name__) # 打印函数名
'''index'''
函数加装饰器,让inner函数伪装成index函数:
# coding:utf-8
def auth(func):
def inner(*args, **kwargs):
ret = func(*args, **kwargs)
return ret
return inner
@auth
def index():
print('index')
@auth
def login():
print('login')
print(index.__name__) # 打印函数名
print(login.__name__) # 打印函数名
'''inner'''
'''inner'''
上面的例子中加装饰器虽然把名字伪装成功,但是内部没有伪装成功,可以使用functools.wraps(func):把原来函数执行的信息放到inner函数中(把原来的函数名放进闭包):
# coding:utf-8
import functools
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
ret = func(*args, **kwargs)
return ret
return inner
@auth
def index():
print('index')
@auth
def login():
print('login')
print(index.__name__) # 打印index函数名
print(login.__name__) # 打印login函数名
'''
index
login
'''
6.3 endpoint参数
可以指定,默认是函数名。作用是:反向生成url,如果同名会出错。查看route函数源码:
def add_url_rule(self, rule, endpoint=None, view_func=None,
provide_automatic_options=None, **options):
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError('View function mapping is overwriting an ''existing endpoint function: %s' % endpoint)
self.view_functions[endpoint] = view_func
……
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
6.4 装饰器的先后顺序
装饰器和路由配合,把url和函数加入到路由关系中:
@app.route('/login', methods=['GET', 'POST']) # 默认允许GET请求
def hello_world():
if not session.get('username'):
return redirect(url_for('login'))
return render_template('index.html', user_dict=USER_DICT)
如果想要再加入一个登录验证的装饰器,如果把@auth加入到@app.route(’/login’, methods=[‘GET’, ‘POST’]) 上面,那就是url和函数是一个大整体,再用@auth装饰,这显然不是我们的目标。函数应该优先和@auth结合,再去做路由和函数的路由对应关系。装饰完成之后,多个路由都对应inner,endpoint默认是函数名,这样所有的函数都是inner,这也是有问题。有两种解决方式。
方式一:自己定义endpoint,
@app.route('/index',endpoint='1')
……
@app.route('/delete/<int:nid>',endpoint='2')
……
方式二:使用@functools.wraps(func)
import functools
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
return result
return inner
@app.route('/index', endpoint='1')
@auth
def hello_world():
if not session.get('username'):
return redirect(url_for('login'))
return render_template('index.html', user_dict=USER_DICT)
6.5 第二种用户认证方式(装饰器)
app.py:
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
return result
return inner
@app.route('/index', endpoint='1')
@auth
def hello_world():
if not session.get('username'):
return redirect(url_for('login'))
return render_template('index.html', user_dict=USER_DICT)
这有个不好的地方,就是每一个需要做认证的都需要一个一个加入装饰器@auth。
应用场景:比较少的函数中需要额外加入功能。
6.6 第三种用户认证方式(推荐)
可以使用before_request,返回None表示可以通过执行,如果返回其它,不能通过执行:
@app.before_request
def auth():
if request.path == '/login':
return None
if session.get('username'):
return None
# return redirect('/login')
return redirect(url_for('login'))
应用场景:批量给函数加额外的功能
7. 模板渲染
7.1 基本数据类型的渲染
显示列表:
app.py
@app.route('/tpl')
def tpl():
content = {
'user': ['thanlon', 'kiku']
}
return render_template('tpl.html', **content)
tpl.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ user.0 }}{#支持django的写法#}
{{ user[1] }}{#支持python的写法#}
</body>
</html>
显示元组:
app.py
@app.route('/tpl')
def tpl():
content = {
'user': ('thanlon', 'kiku')
}
return render_template('tpl.html', **content)
tpl.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ user.0 }}
{{ user[1] }}
</body>
</html>
显示字符串:
app.py:
@app.route('/tpl')
def tpl():
content = {
'txt': '<input type="text"/>'
}
return render_template('tpl.html', **content)
tpl.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ txt }}
{{ txt|safe }}
</body>
</html>
使用Markup函数:
app.py:
@app.route('/tpl')
def tpl():
content = {
'txt': Markup('<input type="text"/>')
}
return render_template('tpl.html', **content)
tpl.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ txt }}
</body>
</html>
7.2 传入函数
app.py:
def func(arg):
return arg + 1
@app.route('/tpl')
def tpl():
content = {
'func': func
}
return render_template('tpl.html', **content)
tpl.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ func(1) }}<!--2-->
</body>
</html>
7.3 全局定义函数
传参的时候自己可以传,全局也可以传参,template_global() 装饰器就是用来传参的。
app.py:
@app.template_global()
def test(a, b):
# {{ test(1,2) }}
return a + b
@app.route('/tpl')
def tpl():
content = {
}
return render_template('tpl.html', **content)
tpl.html:
……
<body>
{{ test(1,2) }}
</body>
……
template_filter() 装饰器也就是用来传参的,
app.py:
@app.template_filter()
def test(a, b, c):
# {{ 1|test(2,3) }}
return a + b + c
@app.route('/tpl')
def tpl():
content = {
}
return render_template('tpl.html', **content)
tpl.html:
<body>
{{ 1|test(2,3) }}
{% if 1|test(2,3) %}
</body>
template_filter与template_global区别是:template_filter可以放在 if 后面做条件。
7.4 模板的继承
tpl.html 继承 layout.html,
tpl.html:
{% extends 'layout.html' %}
{% block content %}
{% if 1|test(2,3) %}
<div>True</div>
{% else %}
<div>False</div>
{% endif %}
{% endblock %}
layout.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h4>继承</h4>
{% block content %}{% endblock %}
</body>
</html>
7.5 模板的包含
tpl.html:
{% include 'form.html' %}
form.html:
<form action="">
form表单
</form>
7.6 宏定义的使用
tpl.html:
{#默认是不显示的#}
{% macro input(name,type='text',value='') %}
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}
{#如果想显示#}
{{ input('username') }}
7.7 模板安全
- 前端:{{u|safe}}
- 后端:Markup("<input type=“text”/ >")
8. session
8.1 session原理
当请求刚进来时,flask 读取 cookie 中 session 对应的值,将该值解密并反序列化为字典,放入内存以便视图函数使用。
视图函数:
@app.route('/session')
def sess():
# from flask.sessions import SecureCookieSession
print(type(session)) # session继承了字典,字典有什么方法,它就有什么方法
session['k1'] = 'v1'
return 'session'
'''
<class 'werkzeug.local.LocalProxy'>
'''
当请求结束时,flask会读取内存中字典的值,进行序列化加密,写入到用户的cookie中。
8.2 修改session默认配置
settings.py:
class Config(object):
# PERMANENT_SESSION_LIFETIME = datetime.timedelta(days=31) # 生命周期,最多的保存时间
# PERMANENT_SESSION_LIFETIME = datetime.timedelta(hours=1)
PERMANENT_SESSION_LIFETIME = datetime.timedelta(minutes=30) # 30min后过期
SESSION_COOKIE_NAME = 'session', # cookie的名称
SESSION_COOKIE_DOMAIN = None, # 域名
SESSION_COOKIE_PATH = None # 路径
SESSION_COOKIE_HTTPONLY = True # http读取
SESSION_COOKIE_SECURE = False # 安全设置
SESSION_REFRESH_EACH_REQUEST = True # 在最后访问的基础上加上生命周期
9. flash
在 session 中存储一个数据,读取时通过 pop 将数据移除。取一次就没有了。通过 session 可以做取一次数据就没有。如下面的例子:
@app.route('/page1')
def page1():
session['xxx'] = 123456
return 'session'
@app.route('/page2')
def page2():
print(session.get('xxx')) # print(session['xxx'])
del session['xxx']
session.pop('xxx')
return 'session'
也可以通过 flash:
@app.route('/page1')
def page1():
flash('临时数据存储') # 临时存在内存中一个数据
return 'session'
@app.route('/page2')
def page2():
print(get_flashed_messages()) # 取一次就没有了,实际上用的时pop
return 'session'
'''
'''
['临时数据存储', '临时数据存储']
[]
'''
'''
对临时数据也可以做分类:
@app.route('/page1')
def page1():
flash('临时数据存储','info')
flash('error','error')
return 'session'
@app.route('/page2')
def page2():
print(get_flashed_messages(category_filter=['error']))
return 'session'
'''
['error', 'error']
[]
'''
10. 中间件
10.1 call方法的执行
call方法在用户发起请求时才执行。
flask 依赖 wsgi ,程序启动时执行的是run_simple(host, port, self, **options)。在app.py文件中,会把 app 对象传入 run_simple 方法中。请求进来,第三个参数加括号,对象加括号执行__call__方法。
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
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
ctx.auto_pop(error)
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
所有的请求入口就在 call 方法。
if __name__ == '__main__':
app.run()
app.run()方法执行后,运行run_simple(host, port, self, **options),在内部就是死循环,等待客户端发送请求(socket在监听连接)。此时__call__方法不会执行,有请求就会执行该方法。
修改源码:
def __call__(self, environ, start_response):
"""The WSGI server calls the Flask application object as the
WSGI application. This calls :meth:`wsgi_app` which can be
wrapped to applying middleware."""
print('请求之前做的操作')
data = self.wsgi_app(environ, start_response)
print('请求之后做的操作')
return data
if __name__ == '__main__':
app.run()
'''
请求之前做的操作
请求之后做的操作
'''
这样做是不合适的,修改源代码是不可选的。
10.2 自定义中间件
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()
通过Middleware中间件,在请求之前和请求之后做一些操作。
11. 特殊的装饰器(重要)
11.1 before_request 与 after_request
……
app = Flask(__name__)
app.config.from_object('settings.DevelopmentConfig') # 引入配置文件
@app.before_request
def test1():
print('before_request')
@app.after_request
def test2(response):
print('after_request')
return response
@app.route('/index')
def index():
print('index')
return 'index'
@app.route('/login')
def login():
print('login')
return 'login'
if __name__ == '__main__':
app.run()
'''
before_request
index
after_request
'''
@app.before_request
def test1():
print('before_request_01')
@app.before_request
def test2():
print('before_request_02')
@app.after_request
def test3(response):
print('after_request_01')
return response
@app.after_request
def test4(response):
print('after_request_02')
return response
@app.route('/index')
def index():
print('index')
return 'index'
@app.route('/login')
def login():
print('login')
return 'login'
if __name__ == '__main__':
app.run()
'''
before_request_01
before_request_02
index
after_request_02
after_request_01
'''
如果 test1 函数有返回值,不执行 test2。
……
@app.before_request
def test1():
print('before_request_01')
return ''
@app.before_request
def test2():
print('before_request_02')
@app.after_request
def test3(response):
print('after_request_01')
return response
@app.after_request
def test4(response):
print('after_request_02')
return response
@app.route('/index')
def index():
print('index')
return 'index'
@app.route('/login')
def login():
print('login')
return 'login'
if __name__ == '__main__':
app.run()
'''
before_request_01
after_request_02
after_request_01
'''
flask和<=django1.9会执行所有的response返回,但是django1.10及之后的版本会这样执行:
11.2 before_first_request
第一次请求的时候才执行,
@app.before_first_request
def test():
print('test')
@app.route('/index')
def index():
return 'index'
11.3 template_global 与 template_filter
模板中有介绍,在这里不赘述。
11.4 errorhandler(应用比较广)
……
@app.errorhandler(404)
def page_not_found(arg):
return 'This page does not exit'
@app.route('/index')
def index():
return 'index'
if __name__ == '__main__':
app.run()
……
12. 路由和视图
12.1 路由和视图函数的对应关系
# /index和/login是路由;index和login是视图函数
[
('/index', 'index'),
('/login', 'login')
]
请求来了,一个个筛选,并且前面的匹配成功,后面的都不再进行匹配了。
12.2 路由设置的两种方式
① @app.route(’/xxx’)
@app.route('/')
def index():
return 'index'
② app.add_url_rule(’/index’,None,index)
def index():
return 'index'
app.add_url_rule('/index', None, index)
注意:不要让endpoint重名,如果重名函数一定要相同。
@app.route('/index', endpoint=1)
def index():
return 'index'
@app.route('/index', endpoint=1)
def index():
return 'index'
上面两个index函数是不同的。
12.3 @app.route和app.add_url_url的参数
- rule:url规则
- view_func:视图函数名称
- defaults = None:默认值,当url中无参数数时,使用defaults = {‘k’:‘v’}为函数提供参数
@app.route('/index', defaults={'k': 'v'})
def index(k):
return 'index'
- endpoint = None:名称,用于反向生成 url,即 url_for(‘名称’)
- methods = None:允许请求的方式,如:[‘get’,‘post’]
- strict_slashes = None:对url最后/符号是严格要求的。对于@app.route(’/login’,strict_slashes = False),访问https://www.blueflags.cn/login或https://www.blueflags.cn/login/都可以。但对于@app.route(’/login’,strict_slashes = True),仅能访问https://www.blueflags.cn/login。建议使strict_slashes = True,只有写了什么,才能访问什么,默认也是True。
- redirect_to = None:重定向到指定的地址
① 一般的重定向:
@app.route('/index', redirect_to='/login')
def index():
return 'index'
@app.route('/login')
def login():
return 'login'
② 重定向的时候传参数:
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')
def index():
return 'index'
@app.route('/home/<int:nid>')
def home(nid):
return str(nid)
def func(adapter, nid):
return '/home/' + str(nid)
@app.route('/index/<int:nid>', redirect_to=func)
def index():
return 'index'
- subdomain = None:子域名访问
绑定域名:
hosts
# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost
127.0.0.1 thanlon.com
127.0.0.1 home.thanlon.com
127.0.0.1 admin.thanlon.com
app.py:
from flask import Flask
app = Flask(__name__)
app.config['SERVER_NAME'] = 'thanlon.com:5000'
# http://admin.thanlon.com:5000
@app.route('/index', subdomain='admin')
def admin():
return 'admin.thanlon.com'
# http://home.thanlon.com:5000
@app.route('/index', subdomain='home')
def home():
return 'home.thanlon.com'
if __name__ == '__main__':
app.run()
from flask import Flask
app = Flask(__name__)
app.config['SERVER_NAME'] = 'thanlon.com:5000'
# http://xxxxx.thanlon.com:5000
@app.route('/', subdomain='<username>')
def index(username):
return username + '.thanlon.com'
if __name__ == '__main__':
app.run()
12.4 CBV
from flask import Flask, views
import functools
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'] # 表示只支持get请求
decorators = [wrapper] # 在执行get和post时,先执行这个装饰器
def get(self, *args, **kwargs):
return 'GET'
def post(self, *args, **kwargs):
return 'POST'
app.add_url_rule('/index', None, UserView.as_view('uuu'))
12.5 自定义正则
- python默认支持的转换器
#: the default converter mapping for the map.
DEFAULT_CONVERTERS = {
"default": UnicodeConverter,
"string": UnicodeConverter,
"any": AnyConverter,
"path": PathConverter,
"int": IntegerConverter,
"float": FloatConverter,
"uuid": UUIDConverter,
}
- 自定义正则的三个步骤
① 定制类
class RegexConverter(BaseConveter):
def __init__(self,map,regex):
super(RegexConverter,self).__ini__(map)
self.regex = regex
def to_python(self,value):
# 路由匹配时,匹配成功后传递给视图函数中参数的值
return int(value) # 可以做一些自定义操作
def to_url(self,value):
# 反向生成url
var = super(RegexConverter, self).to_url(value)
return var
② 添加转换器
app.url_map.converters['reg'] = RegexConverter # reg是自己定义
③ 使用自定义正则(to_python方法是匹配的时候使用,to_url方法是反向生成url的时候使用)
from flask import Flask, url_for
app = Flask(__name__)
from werkzeug.routing import BaseConverter
# 第一步:定制类
class RegexConverter(BaseConverter):
def __init__(self, map, regex):
super(RegexConverter, self).__init__(map)
self.regex = regex
def to_python(self, value):
return str(value)
def to_url(self, value):
var = super(RegexConverter, self).to_url(value)
return var
# 第二步:添加转换器
app.url_map.converters['reg'] = RegexConverter # reg是自己定义
# 第三步:使用自定义正则(to_python方法是匹配的时候使用,to_url方法是反向生成url的时候使用)
# 如果匹配成功,执行to_python方法,该方法返回什么,nid就是什么
@app.route("/index/<reg('\d+'):nid>", methods=['GET'])
def index(nid):
'''
1. 用户发送请求
2. flask内部进行正则匹配
3. 调用to_python(正则匹配的结果)方法
4. to_python方法的返回值会交给视图函数的参数
:param nid:
:return:
'''
# print(nid, type(nid)) # 查看nid的类型
print(url_for('index', nid=123)) # 如果反向生成url,执行流程,先将参数nid=123传入to_url方法,让to_url方法帮助我们去反向生成。
'''
控制台:/index/123
'''
return 'index'
if __name__ == '__main__':
app.run()
13. session实现的原理
13.1 session执行流程源码
13.2 session
14. 蓝图
14.1 蓝图(一)
实际项目中,需要进行项目目录结构的划分,蓝图就是用来帮助开发者进行目录结构的划分,下面是目录结构划分的几个步骤。
① 新建项目,在项目下新建一个和项目名同名的目录
② 在与项目名同名的目录中新建__ init __.py,用来实例化Flask对象
from flask import Flask
def create_app():
app = Flask(__name__)
return app
③ 在项目目录下新建manage.py(也可以是其它的名字),
将create_app函数导入manage.py,在manage.py中:
from test_blueprint import create_app
app = create_app()
if __name__ == '__main__':
app.run()
④ 在test_blueprint(非项目目录)下新建views目录,用来放视图函数,在视图函数中可以根据视图的不同进行分类。
⑤ 创建视图函数与app对象的关系,首先在视图函数中,创建Blueprint对象。
admin.py
from flask import Blueprint
admin_bp = Blueprint('admin_bp', __name__) # 创建Blueprint(蓝图)对象,第一个参数name不能同名
@admin_bp.route('/login')
def login():
return 'admin.login'
@admin_bp.route('/logout')
def logout():
return 'logout'
home.py
from flask import Blueprint
home_bp = Blueprint('home_bp', __name__) # 第一个参数是name,第一个参数name不能同名
@home_bp.route('/login')
def login():
return 'home.login'
@home_bp.route('/list')
def list():
return 'list'
需要修改__ init __.py,注册蓝图,
from flask import Flask
from .views.admin import admin_bp
from .views.home import home_bp
def create_app():
app = Flask(__name__)
# 注册蓝图
app.register_blueprint(home_bp)
app.register_blueprint(admin_bp)
return app
注意:如果两个或多个视图函数中有同名的路由,如home.py和admin.py都有/login路由,谁先注册先执行谁的视图函数。
⑥ 创建模板。在__ init __.py所在的目录中新建templates目录(Flask实例化的文件所在的目录中),如果有静态文件,放静态文件的目录static与templates目录处于同一级。
在templates目录中建模板文件login.html,可以修改admin.py,
@admin_bp.route('/login')
def login():
return render_template('login.html')
这时候就可以访问模板了。
14.2 蓝图(二)
① 自定义静态文件和模板的路径
admin_bp = Blueprint('admin_bp', __name__,template_folder='xxx')
@admin_bp.route('/login')
def login():
return render_template('login.html')
注意:蓝图优先去templates去找loging.html,找不到去xxx中找。当然静态文件也是可以指定的:
admin_bp = Blueprint('admin_bp', __name__,template_folder='xxx',static_folder='xxxx')
@admin_bp.route('/login')
def login():
return render_template('login.html')
② 为某一个蓝图地址加上前缀
def create_app():
app = Flask(__name__)
# 注册蓝图
app.register_blueprint(home_bp, url_prefix='/home')
app.register_blueprint(admin_bp)
return app
按照原先访问方式:http://127.0.0.1:5000/login ,访问失败。
现在需要:http://127.0.0.1:5000/home/login
③ 为所有蓝图加上@before_request
__ init __.py:
from flask import Flask
from .views.admin import admin_bp
from .views.home import home_bp
def create_app():
app = Flask(__name__)
@app.before_request
def xxx():
return '123'
# 注册蓝图
app.register_blueprint(home_bp)
app.register_blueprint(admin_bp)
return app
④ 为指定的蓝图加上@before_request
admin.py:
from flask import Blueprint, render_template
admin_bp = Blueprint('admin_bp', __name__, template_folder='xxx', static_folder='xxxx')
@admin_bp.before_request
def xxx():
return '123'
@admin_bp.route('/login')
def login():
return render_template('login.html')
@admin_bp.route('/logout')
def logout():
return 'logout'
应用场景:可以在指定蓝图中加入登录验证。
15. threading.local
threading.local本身与Flask无关,只是说根据threading.local做的local对象与Flask是有关系的。
# coding:utf-8
import threading
import time
v = 0
def task(i):
global v
v = i
time.sleep(1)
print(v)
for i in range(10):
t = threading.Thread(target=task, args=(i,))
t.start()
15.1 threading.local的作用
为每个线程创建一个独立的空间,使得线程对自己空间中的数据进行操作(数据隔离)。
# coding:utf-8
import threading
import time
from threading import local
obj = local()
def task(i):
obj.xxx = i
time.sleep(2)
print(obj.xxx,i)
for i in range(10):
t = threading.Thread(target=task, args=(i,))
t.start()
15.2 如何获取一个线程的唯一标记
# coding:utf-8
import threading
def task(i):
'''
获取线程的唯一标识
:param i:
:return:
'''
print(threading.get_ident(), i)
for i in range(10):
t = threading.Thread(target=task, args=(i,))
t.start()
15.3 根据字典自定义一个类似于threading.local功能
# coding:utf-8
import threading
import time
DIC = {
}
def task(i):
ident = threading.get_ident()
if ident in DIC:
DIC[ident]['xxx'] = i
else:
DIC[ident] = {'xxx': i}
time.sleep(2)
print(DIC[ident]['xxx'], i)
for i in range(10):
t = threading.Thread(target=task, args=(i,))
t.start()
15.4 根据字典为每个协程开辟空间进行存取数据
# coding:utf-8
import threading
import time
import greenlet
DIC = {
}
def task(i):
# ident = threading.get_ident()
ident = greenlet.getcurrent()
if ident in DIC:
DIC[ident]['xxx'] = i
else:
DIC[ident] = {'xxx': i}
time.sleep(2)
print(DIC[ident]['xxx'], i)
for i in range(10):
t = threading.Thread(target=task, args=(i,))
t.start()
15.5 __ getattr__ 与__ setattr__
class Local(object):
def __getattr__(self, item):
print('__getattr__', item)
def __setattr__(self, key, value):
print('__setattr__', key, value)
obj = Local()
obj.xx
obj.xx = 123
'''
__getattr__ xx
__setattr__ xx 123
'''
15.6 通过getattr与setattr构造出threading.local
不仅可以给线程做数据隔离还可以和协程做数据隔离。
import threading
import time
# 有协程就使用协程,没有使用线程
try:
import greenlet
get_ident = greenlet.getcurrent
except Exception as e:
get_ident = threading.get_ident
class Local(object):
DIC = {}
def __getattr__(self, item):
# print('__getattr__', item)
ident = get_ident
if ident in self.DIC:
return self.DIC[ident].get(item)
else:
return None
def __setattr__(self, key, value):
# print('__setattr__', key, value)
ident = get_ident
if ident in self.DIC:
self.DIC[ident][key] = value
else:
self.DIC[ident] = {key: value}
obj = Local()
def task(i):
obj.xxx = i
time.sleep(2)
print(obj.xxx, i)
for i in range(10):
t = threading.Thread(target=task, args=(i,))
t.start()
重要总结:如果同时来了几个人请求,每个人请求的数据都是不一样的。每个人请求的数据放在不同的位置,进行数据隔离。取每个请求的数据时,可以根据线程的不同。真正请求来的时候,会通过local对象为请求创建一个空间,在它里面放值,每个请求来都会做数据隔离。视图函数取数据的时候就不会混乱了。如果不使用数据隔离,会导致第一个请求来了,正在处理时第二个请求又来了,把第一个覆盖了。django里面没有这种东西,django请求和session都是通过参数传递过来了,请求之间不会混乱。在django中,请求过来,拿着请求的数据处理请求就行了。
16. 上下文管理
请求到来后创建一个RequestContext对象,里面有request和session。把它放在某个特殊的地方,以后视图函数再去取,这就叫做上下文管理。
① 请求到来时,将session和request封装到ctx对象中,ctx中有request和session
ctx = self.request_context(environ) # self是app对象,environ是请求相关的原始数据
ctx.request = Request(environ)
ctx.session = None
② 对session作补充
③ 将包含了request和session的ctx对象放到一个容器中(可以把这个容器认为是一个字典)。每个请求都会根据线程/协程加一个唯一标识:(线程与线程之间的隔离)
{
1234:{ctx:ctx对象},# 1234是线程/协程号
1235:{ctx:ctx对象},
1236:{ctx:ctx对象},
……
}
④ 视图函数使用的时候,需要根据当前线程获取数据。隐含的操作是:根据当前线程或协程的唯一标识,获取ctx对象,再取ctx对象中取request和session。
from flask import request,session
# 如request.method,是从request中取得method
⑤ 请求结束时,根据当前线程/协程的唯一标记,将这个容器上的数据移除。
17. 偏函数与面向对象
17.1 偏函数
偏函数可以帮助我们自动传参。
import functools
def index(a, b):
return a + b
# 原来的调用方式
# ret = index(1, 2)
# print(ret)
#偏函数:自动传递参数
new_func = functools.partial(index, 1) # “1”会被当作第一个参数传递进来
ret = new_func(2) # “2”会被当作第二个参数传递进来
print(ret)
17.2 super和执行类的区别
实例化对象时会执行构造方法,如果没有写,会执行父类的构造方法:
# coding:utf-8
class Foo(object):
pass
obj = Foo() # 会执行构造方法,如果没有写,会执行父类的构造方法
① 根据mro的顺序执行方法:super(Foo, self).func()
# coding:utf-8
class Base(object):
def func(self):
print('Base.func')
class Foo(Base):
def func(self):
# 方式一:根据mro的顺序执行方法
super(Foo, self).func()
print('Foo.func')
obj = Foo()
obj.func()
'''
Base.func
Foo.func
'''
# coding:utf-8
class Base(object):
def func(self):
super(Base, self).func()
print('Base.func')
class Bar(object):
def func(self):
print('Bar.func')
class Foo(Base, Bar):
pass
obj = Foo()
obj.func()
'''
Base.func
Foo.func
'''
# coding:utf-8
class Base(object):
def func(self):
super(Base, self).func()
print('Base.func')
class Bar(object):
def func(self):
print('Bar.func')
class Foo(Base, Bar):
pass
# obj = Foo()
# obj.func()
print(Foo.__mro__)
'''
(<class '__main__.Foo'>, <class '__main__.Base'>, <class '__main__.Bar'>, <class 'object'>)
'''
② 主动执行Base类的方法:Base.func(self)
# coding:utf-8
class Base(object):
def func(self):
print('Base.func')
class Foo(Base):
def func(self):
# 方式一:根据mro的顺序执行方法
# super(Foo, self).func()
# 方式二:主动执行Base类的方法
Base.func(self) # 如果是对象.func(),self会自动传进来
print('Foo.func')
obj = Foo()
obj.func()
'''
Base.func
Foo.func
'''
# coding:utf-8
class Base(object):
def func(self):
super(Base, self).func()
print('Base.func')
class Bar(object):
def func(self):
print('Bar.func')
class Foo(Base, Bar):
pass
obj = Base()
obj.func()
'''
AttributeError: 'super' object has no attribute 'func'
'''
17.3 面向对象中特殊的方法
- __ setattr__与__getattr__
当前类中有__setattr__或__getattr__方法会执行当前类的:
class Foo(object):
def __getattr__(self, item):
print(item)
def __setattr__(self, key, value):
print(key, value)
obj = Foo()
obj.xxx = 'thanlon'
obj.xxx # 会执行父类的__getattr__方法
'''
xxx thanlon
xxx
None
'''
类中没有__setattr__或__getattr__方法,会调用父类的:
class Foo(object):
pass
obj = Foo()
obj.xxx = 'thanlon'#Foo类中没有__setattr__方法,会调用的是父类的
obj.xxx
- __ init__
# coding:utf-8
class Foo(object):
def __init__(self):
self.storage = {}
def __getattr__(self, item):
print(item)
def __setattr__(self, key, value):
print(key, value)
obj = Foo() # 实例化的时候执行__init__方法,对象.?执行__setattr__方法。
obj.xxx = 'thanlon'
'''
storage {}
xxx thanlon
'''
# coding:utf-8
class Foo(object):
def __init__(self):
self.storage = {}
def __getattr__(self, item):
print(item)
def __setattr__(self, key, value):
print(self.storage)
obj = Foo()
obj.xxx = 'thanlon'
'''
storage
None
storage
None
'''
在构造方法中使用self.storage = {},可以通过object类的__setattr__设置其值。(重点理解)
# coding:utf-8
class Foo(object):
def __init__(self):
# self.storage = {}
object.__setattr__(self, 'storage', {})
def __setattr__(self, key, value):
print(key, value, self.storage)
obj = Foo()
obj.xxx = 'thanlon'
'''
xxx thanlon {}
'''
18. 基于列表实现栈
'''
基于列表实现栈
'''
class Stack(object):
def __init__(self):
self.data = []
def push(self, val):
self.data.append(val)
def pop(self):
return self.data.pop()
def top(self):
return self.data[-1]
stack = Stack()
stack.push('Thanlon')
stack.push('Kiku')
print(stack.data)
print(stack.pop())
print(stack.pop())
'''
['Thanlon', 'Kiku']
Kiku
Thanlon
'''
19. Local对象与LocalStack
19.1 Local对象与LocalStack作用
Local对象:帮助我们为每个线程/协程开辟空间。
LocalStack对象:帮助我们在Local中维护一个列表,把它维护成一个栈。然后对列表中的数据进行添加和维护。
19.2 自定义一个简单的Local
try:
from greenlet import getcurrent as get_ident
except:
from threading import get_ident
class local(object):
def __init__(self):
'''
实例化local的时候,在self中存了一个空字典
'''
object.__setattr__(self, 'storage', {})
def __setattr__(self, key, value):
ident = get_ident() # 获取这个标记
if ident not in self.storage:
self.storage[ident] = {key: value}
else:
self.storage[ident][key] = value
def __getattr__(self, item):
ident = get_ident()
if ident in self.storage:
return self.storage[ident].get(item)
19.3. 对栈(列表)中的数据进行添加和维护
try:
from greenlet import getcurrent as get_ident
except:
from threading import get_ident
class Local(object):
__slots__ = ("__storage__", "__ident_func__") # 对象只能.__storage__和.__ident_func__,不能.其它
def __init__(self):
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
obj = Local()
obj.name = 'thanlon'
print(obj.name)
'''thanlon'''
try:
from greenlet import getcurrent as get_ident
except:
from threading import get_ident
class Local(object):
__slots__ = ("__storage__", "__ident_func__") # 对象只能.__storage__和.__ident_func__,不能.其它
def __init__(self):
# __storage__ = {1234:{'session':[]}}
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
obj = Local()
obj.stack = []
obj.stack.append('Thanlon')
obj.stack.append('Kiku')
print(obj.stack)
'''thanlon'''
print(obj.stack.pop())
print(obj.stack)
print(obj.stack.pop())
'''
['Thanlon', 'Kiku']
Kiku
['Thanlon']
Thanlon
'''
try:
from greenlet import getcurrent as get_ident
except:
from threading import get_ident
class Local(object):
'''
为每个线程开辟空间
'''
__slots__ = ("__storage__", "__ident_func__") # 对象只能.__storage__和.__ident_func__,不能.其它
def __init__(self):
# __storage__ = {1234:{'session':[]}}
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
# obj = Local()
# obj.stack = []
# obj.stack.append('Thanlon')
# obj.stack.append('Kiku')
# print(obj.stack)
# '''thanlon'''
# print(obj.stack.pop())
# print(obj.stack)
# print(obj.stack.pop())
'''
['Thanlon', 'Kiku']
Kiku
['Thanlon']
Thanlon
'''
class LocalStack(object):
'''
帮助我们方便对local中的一个列表维护成一个栈,没有它也可以在local中存取数据,有了LocalStack操作跟家便捷
'''
def __init__(self):
'''
__storage__ = {}
'''
self._local = Local()
def push(self, value):
rv = getattr(self._local, 'stack', None) # self._local.stack=>local.getattr
if rv is None:
self._local.stack = rv = [] # self._local.stack=>local.setattr
rv.append(value)
return rv
def pop(self):
stack = getattr(self._local, "stack", None)
if stack is None:
return None
elif len(stack) == 1:
return stack[-1]
else:
return stack.pop()
xxx = LocalStack()
xxx.push('thanlon')
xxx.push('Kiku')
xxx.pop()
try:
from greenlet import getcurrent as get_ident
except:
from threading import get_ident
class Local(object):
'''
为每个线程开辟空间
'''
__slots__ = ("__storage__", "__ident_func__") # 对象只能.__storage__和.__ident_func__,不能.其它
def __init__(self):
# __storage__ = {1234:{'session':[]}}
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
class LocalStack(object):
'''
帮助我们方便对local中的一个列表维护成一个栈,没有它也可以在local中存取数据,有了LocalStack操作跟家便捷
'''
def __init__(self):
'''
__storage__ = {}
'''
self._local = Local()
def push(self, value):
rv = getattr(self._local, 'stack', None) # self._local.stack=>local.getattr
if rv is None:
self._local.stack = rv = [] # self._local.stack=>local.setattr
rv.append(value)
return rv
def pop(self):
stack = getattr(self._local, "stack", None)
if stack is None:
return None
elif len(stack) == 1:
return stack[-1]
else:
return stack.pop()
def top(self):
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
ls_obj = LocalStack()
class RequestContent(object):
def __init__(self):
self.request = 'request'
self.session = 'session'
ls_obj.push(RequestContent())
obj = ls_obj.top()
print(obj)
print(obj.request)
print(obj.session)
'''
<__main__.RequestContent object at 0x0000021DC4A3CA90>
request
session
'''
19.4 使用LocalStack向Local添加对象
try:
from greenlet import getcurrent as get_ident
except:
from threading import get_ident
class Local(object):
'''
为每个线程开辟空间
'''
__slots__ = ("__storage__", "__ident_func__") # 对象只能.__storage__和.__ident_func__,不能.其它
def __init__(self):
# __storage__ = {1234:{'session':[]}}
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
class LocalStack(object):
'''
帮助我们方便对local中的一个列表维护成一个栈,没有它也可以在local中存取数据,有了LocalStack操作跟家便捷
'''
def __init__(self):
'''
__storage__ = {}
'''
self._local = Local()
def push(self, value):
rv = getattr(self._local, 'stack', None) # self._local.stack=>local.getattr
if rv is None:
self._local.stack = rv = [] # self._local.stack=>local.setattr
rv.append(value)
return rv
def pop(self):
stack = getattr(self._local, "stack", None)
if stack is None:
return None
elif len(stack) == 1:
return stack[-1]
else:
return stack.pop()
def top(self):
try:
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
class RequestContent(object):
def __init__(self):
self.request = 'request'
self.session = 'session'
_request_ctx_stack = LocalStack()
_request_ctx_stack.push(RequestContent())
def get_request():
ctx = _request_ctx_stack.top()
return ctx.request
def get_session():
ctx = _request_ctx_stack.top()
return ctx.session
def get_request_or_session(arg):
ctx = _request_ctx_stack.top()
return getattr(ctx, arg) # ctx.arg,如果是arg是request就是ctx.request,如果arg是session就是ctx.session
# print(get_request())
# print(get_session())
# print(get_request_or_session('request'))
# print(get_request_or_session('session'))
import functools
# 使用偏函数
request = functools.partial(get_request_or_session, 'request')
session = functools.partial(get_request_or_session, 'session')
print(request())
print(session())
'''
request
session
'''
20. flask请求上下文管理原理剖析(一)
20.1 request上下文管理
请求的流程:
① wsgi
② 创建ctx = RequestContext(session,request),ctx.push()
③ LocalStack,把ctx对象添加到Local中
④ Local中存取数据使用__storage__ = {唯一标识: {stack: [ctx, ]}}
获取请求相关的数据(视图函数取请求相关的数据):
# coding:utf-8
from flask import Flask,Request
from flask.globals import _request_ctx_stack
app = Flask(__name__)
@app.route('/')
def index():
ctx = _request_ctx_stack.top # 获取ctx对象
print(ctx) # <RequestContext 'http://127.0.0.1:5000/' [GET] of request上下文管理>
print(ctx.request) # <Request 'http://127.0.0.1:5000/' [GET]>
print(ctx.request.environ) #{'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': <_io.BufferedReader name=816>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'werkzeug.server.shutdown': <function WSGIRequestHandler.make_environ.<locals>.shutdown_server at 0x000001D68A80BBF8>, 'SERVER_SOFTWARE': 'Werkzeug/0.15.4', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'PATH_INFO': '/', 'QUERY_STRING': '', 'REQUEST_URI': '/', 'RAW_URI': '/', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': 55003, 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '5000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_HOST': '127.0.0.1:5000', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_CACHE_CONTROL': 'max-age=0', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36', 'HTTP_SEC_FETCH_MODE': 'navigate', 'HTTP_SEC_FETCH_USER': '?1', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'HTTP_SEC_FETCH_SITE': 'none', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9', 'HTTP_COOKIE': 'session=.eJwVzDEOgCAQRNG7bO0muyOoy2UMAoWxw2BjvLvQTF4x-S-lVuve7tKnnZkCZVEVD-GcLLETr2yIkRFNt2OFMxSa6NL-fXQIQ-gaGQqK2fnl-wG6SBh8.XVNcEA.0NiiN7ldvhokI3sKsstrb0GhOQs', 'werkzeug.request': <Request 'http://127.0.0.1:5000/' [GET]>}
print(ctx.request.method) # GET
return 'index'
if __name__ == '__main__':
app.run()
# app.__call__ # 请求进来执行__call__,把数据交给__call__方法
# app.wsgi_app # __call__方法会调用wsgi_app
22. flask请求上下文管理原理剖析(二)
22.1 请求相关数据流程图
如果是多线程(并发),如果同时来了多个请求,wsgi_app对请求数据进行分别封装(每个线程有自己的ctx),然后交给LocalStack,LocalStack负责把数据放在Local中。注意使用一个LocalStack,一个Local。为了标记不同的请求,使用线程的唯一标识。最后
__storage__{
123:{stack:[ctx,]},
345:{stack:[ctx,]}},
取数据时,根据线程的ID取各自的值。
22.2 session上下文管理
① wsgi
② 创建ctx
ctx = RequestContext(session=None,request)
ctx.push()
③ LocalStack,把ctx对象添加到Local中
④ Local中存取数据使用
__storage__ = {
唯一标识: {stack: [ctx, ]}
}
⑤ 通过LocalStack获取ctx中的session,给session赋值(从cookie中读取数据,进行解密反序列化)。调用的是open_session,处理完执行save_session。
获取session:
# coding:utf-8
from flask import Flask,Request
from flask.globals import _request_ctx_stack
app = Flask(__name__)
@app.route('/')
def index():
ctx = _request_ctx_stack.top # 获取ctx对象
print(ctx.session)
return 'index'
if __name__ == '__main__':
app.run()
# app.__call__ # 请求进来执行__call__,把数据交给__call__方法
# app.wsgi_app # __call__方法会调用wsgi_app
23. flask第三方组件:flask-session
flask-sesion组件可以帮助我们把默认的session放入到加密的cookie中。
23.1 安装flask-session
pip install flask-session
23.2 session保存在redis中
# coding:utf-8
from flask import Flask, request, session
from flask.sessions import SecureCookieSessionInterface
# from flask.ext.session import Session#以前导入session的方式,与下面是一样的
from flask_session import Session
import redis
app = Flask(__name__)
'''
session保存在redis中
'''
# app.session_interface = SecureCookieSessionInterface() # 默认
# app.session_interface 换成flaks-session中的内容
# app.session_interface =RedisSessionInterface()
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.Redis(host='主机IP', port=6739, password='密码!')
Session(app)
@app.route('/login')
def login():
session['user'] = 'thanlon'
return '……'
@app.route('/')
def index():
print(session.get('user')) # 没有不报错,session['user']没有会报错
return 'index'
if __name__ == '__main__':
app.run()
# app.__call__ # 请求进来执行__call__,把数据交给__call__方法
# app.wsgi_app # __call__方法会调用wsgi_app
23.3 查看redis中存入的session
look_cookie.py:
# coding:utf-8
import redis
conn = redis.Redis(host='106.12.115.136',port=6379)
print(conn.keys())
'''
[b'session:.eJwVzDEOgCAQRNG7bO0muyOoy2UMAoWxw2BjvLvQTF4x-S-lVuve7tKnnZkCZVEVD-GcLLETr2yIkRFNt2OFMxSa6NL-fXQIQ-gaGQqK2fnl-wG6SBh8.XVNcEA.0NiiN7ldvhokI3sKsstrb0GhOQs']
'''
23.4 flask-session的原理
① session数据保存在redis。redis的key是session:随机字符串,每一个随机字符串对应一个自己的值。每一个用户都会有一个随机字符串。
② 将随机字符串返回给用户。
23.5 flask-session源码
from flask_session import RedisSessionInterface
24. 代码统计项目
24.1 用户登录
① 数据相关
1、创建数据库
create database codecount;
2、选择这个数据库
use codecount;
3、创建数据表
create table userinfo(
id int primary key auto_increment,
user varchar(32) not null,
pwd varchar(64) not null,
nickname varchar(16)
);
4、插入数据
insert userinfo values(null,'thanlon','ea48576f30be1669971699c09ad05c94','thanlon');
insert userinfo values(null,'kiku','ea48576f30be1669971699c09ad05c94','kiku');
+----+---------+----------------------------------+----------+
| id | user | pwd | nickname |
+----+---------+----------------------------------+----------+
| 1 | thanlon | ea48576f30be1669971699c09ad05c94 | thanlon |
| 2 | kiku | ea48576f30be1669971699c09ad05c94 | kiku |
+----+---------+----------------------------------+----------+
② 主要代码
account.py:
# -*- coding:utf-8 -*-
from flask import Blueprint, render_template, request, session, redirect
from ..utils.md5 import md5
account = Blueprint('account', __name__)
@account.route('/login', methods=['get', 'post'])
def login():
if request.method == 'GET':
return render_template('login.html')
username = request.form.get('user')
password = request.form.get('password')
# print(username)
# print(password)
'''
对密码进行加密
'''
pwd_md5 = md5(password)
print(pwd_md5)
'''
数据库中进行校验
'''
import pymysql
conn = pymysql.Connect(host='localhost', user='root', password='wwwnxl',
database='codecount') # 如果中文不能显示,设置charset = 'utf8'
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 设置拿到的是字典,不设置是元组
# 不要自己去拼接
# sql = "select *from userinfo where user = '%s' and pwd = '%s'" % (
# "thanl' or 1=1-- ", '63d7d0d52449193ba1f1321cd8b89a62')
cursor.execute('select id,nickname from userinfo where user = %s and pwd = %s',
(username, pwd_md5))
# cursor.execute('select *from userinfo where user = %(us)s and pwd = %(pw)s',
# {'us': "thanlon", 'pw': '63d7d0d52449193ba1f1321cd8b89a62'})
# data = cursor.fetchall()
data = cursor.fetchone() # 匹配成功的第一条数据,如果失败,返回None
# print(data)
cursor.close()
conn.close()
if not data:
return render_template('login.html', error='用户名或密码错误')
# session['user_id'] = data['id']
# session['user_nickname'] = data['nickname']
# session['user_info'] = {'user_id': data['id'], 'user_nickname': data['nickname']}
session['user_info'] = data
return redirect('/index')
md5.py:
# -*- coding:utf-8 -*-
import hashlib
from settings import Config
def md5(password):
hash = hashlib.md5(Config.SALT)
# hash.update(b'thanlon')
hash.update(bytes(password, encoding='utf-8'))
return hash.hexdigest()
__ init __.py
# -*- coding:utf-8 -*-
from flask import Flask
from .views.account import account
from .views.index import bp_index
def create_app():
app = Flask(__name__)
app.config.from_object('settings.Config')
app.register_blueprint(account) # 注意:注册的是蓝图对象,不是py文件
app.register_blueprint(bp_index)
return app
settings.py:
# -*- coding:utf-8 -*-
class Config(object):
SALT = b'123456'
SECRET_KEY = 'THANLON'
manage.py:
# -*- coding:utf-8 -*-
from code_count import create_app
app = create_app()
if __name__ == '__main__':
app.run()
③ 代码下载
链接:用户登录.zip
24.2 用户列表
① 主要代码
index.py:
from flask import render_template, Blueprint, session, redirect
bp_index = Blueprint('index', __name__)
@bp_index.before_request
def process_request():
if not session.get('user_info'):
return redirect('/login')
@bp_index.route('/index')
def index():
title = {'title': '欢迎使用代码统计系统!'}
return render_template('index.html', title=title)
@bp_index.route('/user_list')
def user_list():
title = {'title': '用户列表'}
import pymysql
conn = pymysql.Connect(host='localhost', user='root', password='wwwnxl', database='![在这里插入图片描述](https://img-blog.csdnimg.cn/20190826092207321.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoYW5sb24=,size_16,color_FFFFFF,t_70)codecount')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute('select id,user,nickname from userinfo')
# data = cursor.fetchone() # 字典
data_list = cursor.fetchall() # 列表里面套字典
print(data_list)
cursor.close()
conn.close()
return render_template('user_list.html', title=title, data_list=data_list)
@bp_index.route('/detail/<int:nid>') # 默认nid是string类型
def detail(nid):
title = {'title': '提交代码详情页面'}
return render_template('detail.html', title=title)
user_list.html:
{%extends 'layout.html'%}
{%block content%}
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<table class="table table-hover ">
<thead>
<tr>
<th class="text-center">编号</th>
<th class="text-center">用户名</th>
<th class="text-center">昵称</th>
<th class="text-center">提交记录</th>
</tr>
</thead>
<tbody>
{%for row in data_list%}
<tr>
<td class="text-center">{{row['id']}}</td>
<td class="text-center">{{row['user']}}</td>
<td class="text-center">{{row['nickname']}}</td>
<td class="text-center"><a href="/detail/{{row['id']}}">查看</a></td>
</tr>
{%endfor%}
</tbody>
</table>
</div>
</div>
</div>
{%endblock%}
{%block js%}
<script>
$(document).ready(function () {
$('ul #user_list').addClass('active')
});
</script>
{%endblock%}
account.py:
……
@account.route('/logout')
def logout():
if 'user_info' in session:
del session['user_info']
return redirect('/index')
② 效果图
③ 代码下载
链接:https://pan.baidu.com/s/1LqEOghiAsDFbpciq0F2PPw
24.3 查看代码统计量
① 数据相关
mysql> use codecount;
create table record
(
id int primary key,
line int ,
ctime datetime,
user_id int
) ;
mysql> alter table record add foreign key(user_id) references userinfo(id);
mysql> alter table record modify id int auto_increment;
mysql> insert record values(null,1000,now(),1);
mysql> insert record values(null,2000,now(),2);
mysql> insert record values(null,3000,now(),1);
mysql> insert record values(null,4000,now(),2);
② 主要代码
index.py:
@bp_index.route('/detail/<int:nid>') # 默认nid是string类型
def detail(nid):
import pymysql
conn = pymysql.Connect(host='localhost', user='root', password='123456', database='codecount')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute('select id,line,ctime from record where user_id = %s', nid)
record_list = cursor.fetchall() # 列表里面套字典
print(record_list)
cursor.close()
conn.close()
title = {'title': '提交代码详情页面'}
return render_template('detail.html', title=title, record_list=record_list)
detail.html:
{%extends 'layout.html'%}
{%block content%}
<div class="container">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<table class="table table-hover ">
<thead>
<tr>
<th class="text-center">编号</th>
<th class="text-center">行数</th>
<th class="text-center">日期</th>
</tr>
</thead>
<tbody>
{%for row in record_list%}
<tr>
<td class="text-center">{{row['id']}}</td>
<td class="text-center">{{row['line']}}</td>
<td class="text-center">{{row['ctime']}}</td>
</tr>
{%endfor%}
</tbody>
</table>
</div>
</div>
</div>
{%endblock%}
③ 效果图
④ 代码下载
链接:https://pan.baidu.com/s/13U2I4POr2_tY2DFNbex1nQ【提取码: 5p1b】
24.4 上传zip文件与解压
① 主要代码
index.py:
@bp_index.route('/upload', methods=['get', 'post']) # 默认nid是string类型
def upload():
title = {'title': '代码上传'}
if request.method == 'GET':
return render_template('upload.html', title=title)
file_obj = request.files.get('code') # <FileStorage: '' ('application/octet-stream')>,把上传的东西方内存了
# print(file_obj.filename) # 上传的文件名(带扩展名)
# print(file_obj.stream) # 文件的内容被封装到对象中
# 1.检查上传文件的后缀名
filename_ext = file_obj.filename.rsplit('.', maxsplit=1) # 元组
if len(filename_ext) != 2:
return '请上传压缩文件'
if filename_ext[1] != 'zip':
return '请上传后缀名为zip的文件'
# 2.接受用户上传的文件并写入到服务器本地
file_path = os.path.join('files', file_obj.filename)
file_obj.save(file_path) # save的本质是:从file_obj.stream中读取内容写入到文件
# 3.解压zip文件
import shutil
target_path = os.path.join('files', str(uuid.uuid4())) # 允许上传的文件名相同
# 通过open打开i压缩文件,读取内容再进行解压
shutil._unpack_zipfile(file_path, target_path)
# 2和3步合并,接受用户上传的文件,并解压至指定目录
# import shutil
# shutil._unpack_zipfile(file_obj.stream, r'/home/thanlon/PycharmProjects/code_count/files')
return 'success'
upload.html:
<form action="" method="post" enctype='multipart/form-data'>
<input type="file" name="code">
<input type="submit" value="上传">
</form>
② 效果图
③ 代码下载
链接:https://pan.baidu.com/s/1RwKRPaagwDXLNxY3nbnZPQ【提取码: qsyj】
24.5 统计代码行数
index.py:
@bp_index.route('/upload', methods=['get', 'post']) # 默认nid是string类型
def upload():
title = {'title': '代码上传'}
if request.method == 'GET':
return render_template('upload.html', title=title)
file_obj = request.files.get('code') # <FileStorage: '' ('application/octet-stream')>,把上传的东西方内存了
# print(file_obj.filename) # 上传的文件名(带扩展名)
# print(file_obj.stream) # 文件的内容被封装到对象中
# 1.检查上传文件的后缀名
filename_ext = file_obj.filename.rsplit('.', maxsplit=1) # 元组
if len(filename_ext) != 2:
return '请上传压缩文件'
if filename_ext[1] != 'zip':
return '请上传后缀名为zip的文件'
# 2.接受用户上传的文件并写入到服务器本地
file_path = os.path.join('files', file_obj.filename)
file_obj.save(file_path) # save的本质是:从file_obj.stream中读取内容写入到文件
# 3.解压zip文件
import shutil
target_path = os.path.join('files', str(uuid.uuid4())) # 允许上传的文件名相同
# print(target_path)#files/549fb0e2-924c-460d-a993-935fcc8e3274
# 通过open打开i压缩文件,读取内容再进行解压
shutil._unpack_zipfile(file_path, target_path)
# 2和3步合并,接受用户上传的文件,并解压至指定目录
# import shutil
# shutil._unpack_zipfile(file_obj.stream, r'/home/thanlon/PycharmProjects/code_count/files')
# 4. 遍历目录下的所有文件
# for item in os.listdir(target_path):
# print(item)
# for item in os.walk(target_path):
# # 每一个item是一个元组,每一个元组有三个元素,分别是当前路径、当前路径下所有文件夹、当前路径下的所有文件
# print(item) # ('files/79d81a3d-455f-4124-ba24-7d7b8a990c4a/keymaps', [], ['Default Proper Redo.xml'])
sum_num = 0
for base_path, folder_list, file_list in os.walk(target_path):
for file_name in file_list:
file_path = os.path.join(base_path, file_name)
# print(file_path) # files/b39a2b87-69b3-4094-942a-58103e70b8a6/options/databaseDrivers.xml
file_ext = file_path.rsplit('.', 1)
if len(file_ext) != 2:
continue
if file_ext[1] != 'py':
continue
file_num = 0
with open(file_path, 'rb') as f:
for line in f:
line = line.strip()
if not line:
continue
if line.startswith(b'#'):
continue
file_num += 1
# print(file_num, file_path) # 每一个文件行数
sum_num += file_num
print(sum_num)
return '上传成功!'
24.6 每日提交一次
① 主要代码
index.py:
import datetime
# ctime = datetime.datetime.now()
ctime = datetime.date.today() # 当前日期
# print(sum_num, ctime, session['user_info']['id'])
'''
插入数据之前需要查询今天是否已经提交
'''
……
import pymysql
conn = pymysql.Connect(host='localhost', user='root', password='123456', database='codecount')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute('select id from record where ctime = %s and user_id = %s', (ctime, session['user_info']['id']))
data = cursor.fetchone()
cursor.close()
conn.close()
if data:
return '今天的代码已经上传了!'
import pymysql
conn = pymysql.Connect(host='localhost', user='root', password='123456', database='codecount')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute('insert record(line,ctime,user_id) values(%s,%s,%s)', (sum_num, ctime, session['user_info']['id']))
conn.commit()
cursor.close()
conn.close()
return '上传成功!'
② 代码下载
链接:https://pan.baidu.com/s/1FCuJV_zlZQ2_cEnUxRipNQ【提取码: stpy】
24.7 数据库连接池DBUtils
上面的例子是每次请求和每次操作都创建一次数据库连接,如果用一个线程处理请求,来一次请求处理一次,并且不能关闭链接,这是没问题的。如果是多线程,数据库用一个连接,第一线程想要插入一条数据,第二个线程想要查询一条数据。第一个还没执行完,第二个请求来了,数据就混乱了。很明显,使用一个数据库连接是不支持多线程的, 开多个数据库连接又太多了。所以,我们想着使用数据库连接池。
这里我们使用DBUtils模块,该模块是python的一个用于实现数据库连接池的模块。
DBUtils的安装:pip install DBUtils
DBUtils有两种模式,第一种模式:为每一个线程创建一个连接,线程即使调用close方法,也不会被关闭。只是把连接重新放到连接池,供自己线程再次使用,当线程终止,连接自动关闭。第二种模式:创建一批连接到连接池,供所有线程共享使用。对于模式一,数据库连接数是不可控的,它是由线程的多少决定的。一般我们使用模式二,创建一批连接到连接池,供所有线程共享使用。比如一开始就开启8个连接,当8个线程占用了这8个连接,又有一个线程需要连接,只需要等到这8个线程中的某一个线程用完连接,给该线程即可。
如果不用连接池,使用pymysql来连接数据库时,单线程应用完全没问题。但如果涉及到多线程应用,那么就需要加锁,一旦加锁那么连接就会出现等待现象,请求比较多时,性能就会降低。
连接池示例(单线程):
from DBUtils.PooledDB import PooledDB, SharedDBConnection
import pymysql
POOL = PooledDB(
creator=pymysql,
maxconnections=6,
mincached=2,
maxcached=5,
maxshared=3,
blocking=True,
maxusage=None,
setsession=[],
ping=0,
host='127.0.0.1',
port=3306,
user='root',
password='123456',
database='codecount',
charset='utf8'
)
conn = POOL.connection() # 去连接池中获取一个连接
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute('select *from record')
rst = cursor.fetchall()
print(rst)
conn.close() # 释放连接,并不是真正关闭连接
连接池示例(多线程):
from DBUtils.PooledDB import PooledDB, SharedDBConnection
import pymysql
POOL = PooledDB(
creator=pymysql, # 使用连接数据库的模块
maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数
mincached=2, # 初始化时,连接池中至少创建的控线连接,0表示不创建
maxcached=5, # 连接池中最多闲置的连接,0和None表示不限制,超过闲置数会被关闭
maxshared=3, # 连接池中最多共享的连接数量,0和None。pymysql和mysqldb等模块的threadsafety都是1,所以在这里是无效的
blocking=True, # 连接池中如果没有可用的连接,是否阻塞等待,True:等待,False:不等待报错`
maxusage=None, # 一个连接被重复使用的次数,None表示无限制
setsession=[], # 开始会话前执行的命令列表,执行sql之前先发一些命令,可以写一些sql命令
# ping mysql服务端,检查是否可用,如果0和None表示不检查,1是每次请求(连接数据库)的时候,默认是1;2表示创建cursor的时候检查;4:执行cursor的execute方法检查;7表示总是检查
ping=0,
host='127.0.0.1',
port=3306,
user='root',
password='123456',
database='codecount',
charset='utf8'
)
def task():
conn = POOL.connection() # 去连接池中获取一个连接
cursor = conn.cursor(pymysql.cursors.DictCursor)
cursor.execute('select sleep(2)') # 执行sleep函数2s后返回
rst = cursor.fetchall()
print(rst)
conn.close() # 释放连接,并不是真正关闭连接
import threading
for i in range(20):
t = threading.Thread(target=task)
t.start()
关于maxshared无效的原因:
if threadsafety > 1 and maxshared:
self._maxshared = maxshared
self._shared_cache = [] # the cache for shared connections
当threadsafety>1时才有效,但是pymysql模块中的threadsafety是等于1的,所以在这里设置这个是没有效果的。
连接池连接数据库本质上使用也是pymysql,只不过帮助我们控制连接池,连接池的作用是:项目运行起来的时候,连接池会向数据库创建几个连接,不断开。多线程操作数据库时,不需要再连接数据库了。可以省去每一次创建连接。
24.9 代码统计应用DBUtils
创建连接池需要以单例模式存在,可以放在settings中,也可以单独放在一个python文件中。
再utils目录下创建helper.py:
from settings import Config
import pymysql
def fetchone(sql, args):
conn = Config.POOL.connection()
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute(sql, args)
result = cursor.fetchone()
cursor.close()
conn.close()
return result
def fetch_all(sql, args):
conn = Config.POOL.connection()
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute(sql, args)
result = cursor.fetchall()
cursor.close()
conn.close()
return result
def insert(sql, args):
'''
向数据库中插入数据
:param sql: 执行插入的sql语句
:param args: 其它参数
:return: 受影响的行数
'''
conn = Config.POOL.connection()
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
row = cursor.execute(sql, args)
conn.commit()
cursor.close()
conn.close()
return row
在项目中使用:
@bp_index.route('/user_list')
def user_list():
title = {'title': '用户列表'}
sql = 'select id,user,nickname from userinfo'
args = []
data_list = helper.fetch_all(sql, args)
return render_template('user_list.html', title=title, data_list=data_list)
代码下载链接:https://pan.baidu.com/s/10gctEt0VlzRuzwgsDv15tA【提取码: thw9】
24.10 数据可视化
可视化工具:ECHARTS
detail.html:
<script type="text/javascript">
var dom = document.getElementById("container");
var myChart = echarts.init(dom);
var app = {};
option = null;
app.title = '坐标轴刻度与标签对齐';
option = {
color: ['#3398DB'],
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
data: {{time_list}},
axisTick:{
alignWithLabel: true}}
],
yAxis: [
{
type: 'value'
}
],
series:[{
name: '行数',
type: 'bar',
barWidth: '60%',
data: {{data_list}}
}]
}
if (option && typeof option === "object") {
myChart.setOption(option, true);
}
</script>
详细看detail.html
25. 再谈上下文管理
26. wtforms示例
26.1 用户登录
form.py:
# coding:utf-8
from wtforms import Form
from wtforms.fields import simple
from wtforms import widgets, validators
class LoginForm(Form):
# name = simple.StringField()
name = simple.StringField(
validators=[
validators.DataRequired(message='用户名不能为空'),
validators.Length(min=6,max=20,message='用户名长度必须大于%(min)d且小于%(max)d')
],
widget=widgets.TextInput(),
render_kw={'placeholder': '请输入用户名', 'class': 'form-control'}
)
# pwd = simple.PasswordField()
pwd = simple.PasswordField(
validators=[
validators.DataRequired(message='密码不能为空')
],
render_kw={'placeholder': '请输入密码','class': 'form-control'}
)
app.py:
from flask import Flask, render_template, request
from forms import LoginForm
app = Flask(__name__)
app.debug = True
@app.route('/')
def index():
return 'index'
@app.route('/login', methods=['get', 'post'])
def login():
if request.method == 'GET':
form = LoginForm()
# print(form.name, type(form.name)) # name是一个对象,print的时候执行这个对象的__str__方法,
# print(form.pwd, type(form.pwd)) # name是一个对象,print的时候执行这个对象的__str__方法
'''
<input id="name" name="name" type="text" value=""> <class 'wtforms.fields.core.StringField'>
<input id="pwd" name="pwd" type="password" value=""> <class 'wtforms.fields.simple.PasswordField'>
'''
return render_template('login.html', form=form)
form = LoginForm(formdata=request.form)
if form.validate():
print(form.data) # {'name': '123456', 'pwd': '123456'}
return '验证成功!'
else:
# print(form.errors) # {'name': ['用户名不能为空'], 'pwd': ['密码不能为空']}
return render_template('login.html', form=form)
if __name__ == '__main__':
app.run()
login.html:
<form method="POST" novalidate>
<div class="form-group">
<label>用户名</label>
{{form.name}}
<div style="color:red">{{form.name.errors[0]}}</div>
</div>
<div class="form-group">
<label>密 码</label>
{{form.pwd}}
<div style="color:red">{{form.pwd.errors[0]}}</div>
</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
26.2 用户注册
form.py:
class RegisterForm(Form):
name = simple.StringField(
label='用户名',
validators=[
validators.DataRequired(message='密码不能为空!')
],
widget=widgets.TextInput(),
render_kw={'class': 'form-control'},
default='thanlon'
)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空!')
],
widget=widgets.PasswordInput(),
render_kw={
'class': 'form-control'
}
)
pwd_confirm = simple.PasswordField(
label='重复密码',
validators=[
validators.DataRequired(message='重复密码不能为空!'),
validators.EqualTo('pwd', message='两次密码输入不一致!')
],
widget=widgets.PasswordInput(),
render_kw={
'class': 'form-control'
}
)
email = html5.EmailField(
label='邮箱',
validators=[
validators.DataRequired(message='邮箱不能为空!'),
validators.Email(message='邮箱格式错误!')
],
widget=widgets.TextInput(input_type='email'),
render_kw={
'class': 'form-control'
}
)
gender = core.RadioField(
label='性别',
choices=(
(1, '男'), (2, '女')
),
coerce=int, # int('1')
)
city = core.SelectField(
label='城市',
choices=(
('bj', '北京'),
('sh', '上海')
)
)
hobby = core.SelectMultipleField(
label='爱好',
choices=(
(1, '羽毛球'),
(2, '篮球')
),
coerce=int
)
favor = core.SelectMultipleField(
label='喜好',
choices=(
(1, '羽毛球'),
(2, '篮球')
),
widget=widgets.ListWidget(prefix_label=False),
option_widget=widgets.CheckboxInput(),
coerce=int,
default=[2]
)
register.html:
<form method="post" novalidate>
{% for field in form %}
{{field.label}} {{field}}{{field.errors[0]}}
{% endfor %}
<button type="submit" class="btn btn-primary">登录</button>
</form>
app.py:
@app.route('/register',methods=['get','post'])
def register():
if request.method == 'GET':
form = RegisterForm()
return render_template('register.html', form=form)
form = RegisterForm(formdata=request.form)
if form.validate():
print(form.data)
return redirect('https://www.blueflags.cn')
return render_template('register.html', form=form)
26.3 数据库实时更新(解决这个坑)
① 数据相关
mysql> create table city(id int primary key auto_increment,city_name varchar(20) not null );
mysql> insert city values(null,'beijin');
mysql> insert city values(null,'shanghai');
mysql> insert city values(null,'shenzhen');
② 效果图
③ 主要代码
class UserForm(Form):
city = core.SelectField(
label='城市',
choices=helper.fetch_all('select *from city', [], type=None), # 只是在第一次从数据库获取一次
coerce=int, # 自动把用户提交的字符串转换为数字
)
选项中的数据只是在第一次请求的时候从数据库获取一次,当数据更新时,用户页面中的数据并没有实时更新。所以,代码是存在问题的。
解决方案:对于UserForm类,每次实例化的时候都去从数据库中获取一次数据,可以这样写:
class UserForm(Form):
city = core.SelectField(
label='城市',
choices=(), # 只是在第一次从数据库获取一次
coerce=int, # 自动把用户提交的字符串转换为数字
)
# 如果没有写执行Form的构造方法
def __init__(self, *args, **kwargs):
super(UserForm, self).__init__(*args, **kwargs) # 在UserForm中执行什么在父类中还要执行什么
self.city.choices = helper.fetch_all('select *from city', [], type=None)
④ 代码下载
链接:https://pan.baidu.com/s/1DCTvJJz4rT1EQlmW_Eqh6A【提取码: nb6k】
补充: 实例化UserForm的时候传值
@app.route('/user', methods=['get', 'post'])
def user():
if request.method == 'GET':
form = UserForm(data = {'name':'thanlon','city':3})
return render_template('user.html',form=form)
在编辑页面的时候会用到。
26.4 wtforms的作用与django示例
wtforms有两个作用:1. 自动生成html标签;2.对用户请求数据进行校验
在django中想实现实时更新,也需要重写构造方法。
views.py:
from django.shortcuts import render
from django.forms import Form
from django.forms import fields
from app01 import models
class IndexForm(Form):
title = fields.CharField()
# group = fields.ChoiceField(
# choices=(
# (1, 'A'),
# (2, 'B')
# )
# )
group = fields.ChoiceField(
choices=()
)
def __init__(self, *args, **kwargs):
super(IndexForm, self).__init__(*args, **kwargs)
self.fields['group'].choices = models.UserGroup.objects.all().values_list('id', 'title')
# Create your views here.
def index(request):
if request.method == 'GET':
form = IndexForm()
return render(request, 'index.html', {'form': form})
27. SQLAlchemy
27.1 简介
SQLAlchemy是ORM(对象关系映射)框架,类名对应表名,类中的字段对应数据表的列,对象对应数据表的一行。SQLAlchemy可以帮助我们使用类和对象快速实现数据库操作。我们在公司里要么是用原生sql,要么是用ORM框架。原生sql中可以用MySQLdb和pymysql模块,MySQLdb的用法和pymysql基本上是完全一样的。两者在使用上是有区别的,MySQLdb不支持python3,pymysql支持python2和python3。对于python web框架,如果有自己的使用自己的,如果没有ORM一般都用SQLAlchemy。
27.2 增删查改
① 创建一个数据表
models.py:
# coding:utf-8
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, Date, DateTime
from sqlalchemy import create_engine
Base = declarative_base()
class Users(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(32), index=True, nullable=False)
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
# Base.metadata.drop_all(engine)#删除这个数据表
Base.metadata.create_all(engine) # 创建这个数据表
users表:
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(32) | NO | MUL | NULL | |
+-------+-------------+------+-----+---------+----------------+
注意:SQLAlchemy默认是不可以修改创建好的数据表的,需要用到第三方组件
② 封装操作:防止models.py被导入的时候执行,把创建表和删除表封装在函数中
# coding:utf-8
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, Date, DateTime
from sqlalchemy import create_engine
Base = declarative_base()
class Users(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(32), index=True, nullable=False)
def create_all():
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
Base.metadata.create_all(engine) # 创建这个数据表
def drop_all():
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
Base.metadata.drop_all(engine) # 删除这个数据表
if __name__ == '__main__':
pass
# create_all()
# drop_all()
③ 添加数据
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Users
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
SessionFactory = sessionmaker(bind=engine)
'''
根据Users类对users表进行增删改查
'''
session = SessionFactory() # 创建一个连接
'''
增加单条数据
'''
# obj = Users(name='thanlon')
# session.add(obj)
'''
添加多条数据
'''
# obj = Users(name='thanlon')
# obj2 = Users(name='kiku')
# session.add(obj)
# session.add(obj2)
'''
添加多条数据的时候也可以把这些对对象放在列表中
'''
session.add_all([
Users(name='thanlon'),
Users(name='kiku')
])
session.commit()
session.close()
④ 查询数据
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Users
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
SessionFactory = sessionmaker(bind=engine)
'''
根据Users类对users表进行增删改查
'''
session = SessionFactory() # 创建一个连接
'''
查询表中所有内容
'''
# ret = session.query(Users).all()
# # print(ret)#拿到的是对象
# # [<models.Users object at 0x7f9cc4ca1080>, <models.Users object at 0x7f9cc4ca10f0>, <models.Users object at 0x7f9cc4ca1160>, <models.Users object at 0x7f9cc4ceee80>, <models.Users object at 0x7f9cc4cf50f0>, <models.Users object at 0x7f9cc4cf5160>]
# for row in ret:
# print(row.id)
# print(row.name)
'''
根据条件查询
'''
# ret = session.query(Users).filter(Users.id > 3) # filter中直接加表达式
# for row in ret:
# print(row.id, row.name)
# '''
# 4 kiku
# 5 thanlon
# 6 kiku
# '''
'''
根据条件查询,拿到第一条数据
'''
ret = session.query(Users).filter(Users.id > 3).first() # filter中直接加表达式
print(ret.id, ret.name)
'''
4 kiku
'''
session.commit()
session.close()
⑤ 删除数据
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Users
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
SessionFactory = sessionmaker(bind=engine)
session = SessionFactory() # 创建一个连接
'''
删除数据
'''
# session.query(Users).delete()
session.query(Users).filter(Users.id > 3).delete()
session.commit()
⑥ 修改数据
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Users
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
SessionFactory = sessionmaker(bind=engine)
session = SessionFactory() # 创建一个连接
'''
修改数据
'''
# session.query(Users).filter(Users.id == 1).update({Users.name: 'liuyuqin'})
# session.query(Users).filter(Users.id == 1).update({'name': Users.name + 'hello'}, synchronize_session=False)#等效于下面的
session.query(Users).filter(Users.id == 1).update({Users.name: Users.name + 'hello'}, synchronize_session=False)
session.commit()
27.3 常用操作
① 指定列
ret = session.query(Users.id, Users.name.label('cname')).all()
for item in ret:
print(item, type(item), item[0], item.id, item.cname)
# item是<class 'sqlalchemy.util._collections.result'>,打印的是元组,实际不是元组类型,但可以通过元组去值。能够通过索引去取,说明该类中有__item__方法
query = session.query(Users.id, Users.name.label('cname')) # query的值就是sql语句
② 1. 默认条件是and
session.query(Users).filter(Users.id == 1, Users.name == 'thanlon').all()
③ between
session.query(Users).filter(Users.id.between(1, 2), Users.name == 'thanlon').all()
④ in
session.query(Users).filter(Users.id.in_([1, 2, 3])).all()
session.query(Users).filter(~Users.id.in_([1, 2, 3])).all()
⑤ 子查询
session.query(Users).filter(Users.id.in_(session.query(Users.id).filter(Users.name == 'thanlon'))).all()
⑥ and和or
from sqlalchemy import and_, or_
session.query(Users).filter(and_(Users.id > 1, Users.name == 'thanlon')).all() # 没有and_也是一样的表示and
session.query(Users).filter(or_(Users.id > 1, Users.name == 'thanlon')).all() # 或者
session.query(Users).filter(
or_(Users.id > 1, and_(Users.name == 'thanlon', Users.id > 3))
).all()
⑦ filter_by
session.query(Users).filter_by(name='thanlon').all() # 条件,内部也是使用filter
⑧ 通配符
session.query(Users).filter(Users.name.like('t_')).all() # t后面有一个字符
session.query(Users).filter(~Users.name.like('t%')).all() # 以t开头
⑨ 切片/分页
session.query(Users)[1, 3]
排序:
session.query(Users).order_by(Users.name.desc()).all() # 按照姓名从大到小
session.query(Users).order_by(Users.name.desc(), Users.id.asc()).all() # 先按照name从大到小排列,如果同名按照id升序排列
分组:
from sqlalchemy.sql import func
'''
第一种情况
'''
# ret = session.query(func.max(Users.id)).group_by(Users.depart_id).all()
# for item in ret:
# print(item)
'''
第二种情况
'''
# ret = session.query(func.min(Users.id)).group_by(Users.depart_id).all()
# for item in ret:
# print(item)
'''
第三种情况
'''
# ret = session.query(Users.depart_id,func.count(Users.id)).group_by(Users.depart_id).all()
# for item in ret:
# print(item)
# '''
# (1, 2)
# (2, 1)
# '''
'''
第四种情况:根据聚合条件二次筛选用having(部门人数大于等于2)
'''
ret = session.query(Users.depart_id, func.count(Users.id)).group_by(Users.depart_id).having(
func.count(Users.id) >= 2).all()
for item in ret:
print(item)
'''
(1, 2)
'''
# 注意:如果使用group,根据聚合的结果进行二次筛选时,只能用having
组合:将两个表上下拼接(union和union all),而join是左右拼接
'''
union:不会去重
'''
# q = session.query(Users.name).filter(Users.id > 0)
# q2 = session.query(Admin.admin_name).filter(Admin.id > 0)
# ret = q.union(q2).all()
# for item in ret:
# print(item.name)
'''
union:去重
'''
q = session.query(Users.name).filter(Users.id > 0)
q2 = session.query(Admin.admin_name).filter(Admin.id > 0)
ret = q.union_all(q2).all()
for item in ret:
print(item.name)
27.4 外键操作
① 创建两个表,注意Users表中是加了外键的
class Depart(Base):
__tablename__ = 'depart'
id = Column(Integer, primary_key=True)
title = Column(String(32), index=True, nullable=False)
class Users(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(32), index=True, nullable=False)
depart_id = Column(Integer, ForeignKey('depart.id')) # depart小写代表表名,不是类名
插入数据:
mysql> insert depart values(null,'technology');
mysql> insert depart values(null,'develop');
mysql> insert users values(null,'thanlon',1);
mysql> insert users values(null,'yuqin',2);
② 外键操作:
我们在这里可以查询所有用户的信息和所属部门名称,具体操作如下:
# ret = session.query(Users, Depart).join(Depart).all() #depart_id = Column(Integer, ForeignKey('depart.id')),默认根据depart.id等效于下面的
ret = session.query(Users, Depart).join(Depart, Users.depart_id == Depart.id).all() # on的条件是:depart_id == Depart.id
for row in ret:
print(row[0].id, row[0].name, row[1].title)
也可以指定要查询的具体内容,
# 也可以指定获取指定的内容
ret = session.query(Users.id, Users.name, Depart.title.label('as_title')).join(Depart,
Users.depart_id == Depart.id).all()
for row in ret:
print(row.id, row.name, row.as_title)
可以使用多个join,但只有左连接没有右连接的概念,右连接是没有意义的。此外,下面query变量打印出来是一个sql语句,query变量的类型是:<class ‘sqlalchemy.orm.query.Query’>。
query = session.query(Users.id, Users.name, Depart.title).join(Depart, Users.depart_id == Depart.id,
isouter=True) # 没有right join是没有意义的
我们把在对应users的Users类中添加一个dp字段,注意这个字段不会在表中存在,只有Column实例化的对象才会对应数据表中的列。加入relationship可以帮助我们(跨表)做关联查询和创建关联数据。
from sqlalchemy.orm import relationship
class Users(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(32), index=True, nullable=False)
depart_id = Column(Integer, ForeignKey('depart.id')) # depart小写代表表名,不是类名
dp = relationship('Depart',backref = 'pers')
比如上面的“查询所有用户的信息和所属部门名称”可以这样操作:
ret = session.query(Users).all()
print(ret) # [<models.Users object at 0x7f5b935bb7f0>, <models.Users object at 0x7f5b935bb860>……]
for row in ret:
print(row.id, row.name, row.depart_id, row.dp.title)
还可以反向跨表,比如查询销售部为“develop”的所有人员,我们可以这样操作:
obj = session.query(Depart).filter(Depart.title == 'develop').first()
print(obj.pers) # [<models.Users object at 0x7fef08122160>]
for row in obj.pers:
print(row.id, row.name, obj.title)
上面两个例子是使用表示relationship可以做关联查询,下面的例子则会介绍一次性创建关联数据。
比如有这样的需求“创建一个名称是‘finance’的部门并在该部门中添加员工:thanlon”,一般情况下我们是这样操作的:
dp = Depart(title='finance')
session.add(dp)
session.commit()
#再根据根据finance部门id创建员工
u = Users(name='thanlon', depart_id=dp.id)
session.add(u)
session.commit()
上面是一种方式,有了relationship,我们可以这样创建:
u = Users(name='thanlon', dp=Depart(title='finance'))
session.add(u)
session.commit()
可以看到简单多了吧!
问题来了,如果创建一个部门再在这个部门创建多个员工,如果按照上面的例子,会又创建若干个finance部门。当然,如果按照上上个例子一个个添加员工也是可以的。但是,我们还有简便的方式,你可以这样做:
# 创建一个部门,添加多个员工
dp = Depart(title='finance')
dp.pers = [
Users(name='a'),
Users(name='b'),
Users(name='c')
]
session.add(dp)
session.commit()
27.5 多对多操作
① 多对多表的创建
创建学生表、课程表和中间表(部分代码省略)
from sqlalchemy import Column, Integer, String, Text, Date, DateTime, ForeignKey, UniqueConstraint, Index
class Student(Base):
__tablename__ = 'student'
id = Column(Integer, primary_key=True)
name = Column(String(32), index=True, nullable=False)
class Course(Base):
__tablename__ = 'course'
id = Column(Integer, primary_key=True)
title = Column(String(32), index=True, nullable=False)
class Student2Course(Base):
__tablename__ = 'student2course'
id = Column(Integer, primary_key=True, autoincrement=True)
student_id = Column(Integer, ForeignKey('student.id'))
course_id = Column(Integer, ForeignKey('course.id'))
__table_args__ = (
UniqueConstraint('student_id', 'course_id', name='uc_student_course'), # 联合唯一索引
# Index('i_student_course', 'student_id', 'course_id')#联合索引
)
② 添加数据
添加Student表和Course表数据:
session.add_all([
Student(name='thanlon'),
Student(name='Aurora'),
Student(name='kiku'),
Course(title='English'),
Course(title='Computer'),
])
session.commit()
添加中间表数据:
session.add_all([
Student2Course(student_id=4,course_id=1),
Student2Course(student_id=4,course_id=2),
])
session.commit()
session.add_all([
Student2Course(student_id=5,course_id=1),
])
session.commit()
session.add_all([
Student2Course(student_id=6,course_id=2),
])
session.commit()
③ 多对多操作
操作1:查询学生姓名和对应的选课课程名(三张表关联查询)
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Users, Depart, Student, Course, Student2Course
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
SessionFactory = sessionmaker(bind=engine)
session = SessionFactory()
ret = session.query(Student2Course.id, Student.name, Course.title).join(Student,
Student2Course.student_id == Student.id,
isouter=True).join(
Course, Student2Course.course_id == Course.id, isouter=True).order_by(Student2Course.id.asc())
for row in ret:
print(row)
'''
(1, 'thanlon', 'English')
(2, 'thanlon', 'Computer')
(3, 'Aurora', 'English')
(4, 'kiku', 'Computer')
'''
session.close()
操作2:学生“thanlon”选的所有课
……
ret = session.query(Student2Course.id, Student.name, Course.title).join(Student,
Student2Course.student_id == Student.id,
isouter=True).join(
Course, Student2Course.course_id == Course.id, isouter=True).filter(Student.name == 'Thanlon').order_by(
Student2Course.id.asc()).all()
print(ret)
# [(1, 'thanlon', 'English'), (2, 'thanlon', 'Computer')]
……
上面两个例子是一般多对关联查询操作,下面使用relationship,首先需要修改建Student表语句:
model.py:
class Student(Base):
__tablename__ = 'student'
id = Column(Integer, primary_key=True)
name = Column(String(32), index=True, nullable=False)
course_list = relationship('Course', secondary='student2course', backref='student_list') # 根据与Course做关联
使用relationship来查询来执行操作2:学生“thanlon”选的所有课,可以这样:
obj = session.query(Student).filter(Student.name == 'thanlon').first()
for item in obj.course_list:
print(item.title)
'''
Computer
English
'''
除了使用relationship正向查找,还可以反向查找,比如:查找选择“Computer”的所有人,
obj = session.query(Course).filter(Course.title == 'computer').first()
# print(obj)#<models.Course object at 0x7fe264215668>
for item in obj.student_list:
print(item.name)
'''
thanlon
kiku
'''
操作3:插入数据示例,需求“创建一个课程,创建两个学生,两个学生选新创建的课程”
这里使用relationship,
obj = Course(title='math')
obj.student_list = [Student(name='Maria'), Student(name='Michael')]
session.add(obj)#在course表增加数据,在student表增加两条数据,在中间关系表中增加两条
session.commit()
27.6 两种连接数据库的方式
① 自己实例化session对象
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Users
from threading import Thread
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
SessionFactory = sessionmaker(bind=engine)
def task():
# 去连接池获取一个连接
session = SessionFactory()
ret = session.query(Users).all()
# 将连接交给连接池
session.close()
if __name__ == '__main__':
t = Thread(target=task)
t.start()
如果用同一个连接,多线程请求数据时,这个连接很可能忙不过来,数据很有可能出现混乱。所以,实例化session对象(创建一个连接)的代码是不可以放在task函数外面(全局)。此外,session.close()会把仅有的一个连接释放,其它线程是不可以用到的,因为这种情况下连接只创建了一次。
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Users
from threading import Thread
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
SessionFactory = sessionmaker(bind=engine)
# 去连接池获取一个连接
session = SessionFactory()
def task():
ret = session.query(Users).all()
# 将连接交给连接池
session.close()
if __name__ == '__main__':
t = Thread(target=task)
t.start()
② 通过Threading.Local实现的(推荐使用)
基于Threading.Local也是为每个线程获取一个连接。
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Users
from threading import Thread
from sqlalchemy.orm import scoped_session
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
SessionFactory = sessionmaker(bind=engine)
# 去连接池获取一个连接
session = scoped_session(SessionFactory)
def task():
ret = session.query(Users).all()
# 将连接交给连接池
session.remove()
if __name__ == '__main__':
t = Thread(target=task)
t.start()
27.5 原生sql
django里面可以执行原生sql,SQLAlchemy也可以执行原生sql,如果sql语句比较复杂,orm解决不了,那么可以使用原生sql。SQLAlchemy执行原生sql的方式有好多种,在这里介绍其中两种。
方式1:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Users
from threading import Thread
from sqlalchemy.orm import scoped_session
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
SessionFactory = sessionmaker(bind=engine)
# 去连接池获取一个连接
session = scoped_session(SessionFactory)
def task():
'''
方式1
'''
# 查询
# cursor = session.execute('select *from users')
# ret = cursor.fetchall()
# 插入
cursor = session.execute('insert users(name) values(:value)', params={'value': 'thanlon'})
session.commit()
print(cursor.lastrowid)
if __name__ == '__main__':
t = Thread(target=task)
t.start()
方式2:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Users
from threading import Thread
from sqlalchemy.orm import scoped_session
engine = create_engine(
'mysql+pymysql://root:[email protected]:3306/sqlalchemy_test?charset=utf8',
max_overflow=0, # 超过连接池大小外最多创建的连接
pool_size=5, # 连接池大小
pool_timeout=30, # 连接池中没有连接最多等待的时间,否则会报错,30s
pool_recycle=-1, # 多久之后对线程池中的线程中进行一次连接的回收(重置)-1表示不重置
)
SessionFactory = sessionmaker(bind=engine)
# 去连接池获取一个连接
session = scoped_session(SessionFactory)
def task():
'''
方式2
'''
conn = engine.raw_connection()
cursor = conn.cursor()
cursor.execute(
'select *from users'
)
ret = cursor.fetchall()
print(ret) # ((1, 'liuyuqinhellohello'), (2, 'thanlon'), (3, 'thanlon'), (7, 'thanlon'))
cursor.close()
conn.close()
if __name__ == '__main__':
t = Thread(target=task)
t.start()
PS:我的站点:https://www.blueflags.cn
技术交流加微信: