django与 flask的对比
flask,是一个轻量级的框架,内置了:路由/视图/模板(jinja2)/cookie/session/中间件. 可扩展强,第三方组件非常多,例如:wtforms/flask-session/flask-sqlalchemy.
django,是一个重武器,django内置了很多功能方便我们使用,例如:admin/orm/模板/form/modelform/session/cookie/中间件/路由/缓存/信号/数据库的读写分离/分页...
flask,短小精悍可扩展强.
django,大而全重武器.
小一点的程序 使用flask比较好,
中大型, django 比较好
flask框架的安装
版本 1.1.1
依赖包:
jinjia2 是模板渲染用的
Markupsafe 返回安全标签 只要flask 返回模板或者标签的时候会用到
Werkzeug 德文“工具” == uWSGI 底层是 WSGI Flask项目启动都是基于Werkzeug
werkzurg
werkzurg是一个wsgi,本质上提供了socket服务端,用于接收用户请求.
django和flask一样,它们内部都没有实现socket服务端,需要依赖wsgi.
django, wsgiref
flask, werkzurg
原理是进来都会实例化对象 执行一个__call__方法
实现hello word
from flask import Flask 导入 21flask 类创建flask对象
app =Flask(__name__) app 是flask实例化出来的独享 __name__ 用来锁定文件的项目目录
@app.route('/index',method =["get","post"]) 为flask对象增加路由地址
def hello_world(): 与路由绑定的视图函数名 尽量保持唯一
return'Hello World!' 相当于 django中的httprespnse
if __name__ =='__main__': # 当前文件处于 开发阶段
app.debug = True
app.run("0.0.0.0",9527) 监听端口
flask 中的 Response
返回字符串 直接return django中 Httpresponse
返回页面 render_template 对比django中的 render
重定向 redirect 对比django redirect 相同的
原理: 在响应头中加了一个location : 地址
特殊返回值:
返回文件: send_file("文件路径")
原理 : flask打开文件识别内容,自动加入contype, 浏览器识别content-Type: 文件类型
如果不能识别,则是下载文件,如何识别呢? 每一个文件都会有一个文件头
jsonify("被转化的对象") 返回标准格式json字符串
也可以直接return 返回 但是不建议
直接返回字符串的时候会执行jsonify
快速导入模块的快捷键 :
alt + enter 选择要导入的包
flask 中的request
获取请求方法:
request.method
获取formdata 请求中的数据
request.form.get("username") 拿出对应的数值
request.form.to_dict() # 转换成字典
获取url中的数据:
request.args.get("id") 获取url中的数据
request.args.to_dict() # 将数据转化成字典
拿到get和post请求中所有的数据: 注意不要用,容易hash冲突
request.values()
获取并且保存一个文件
my_file = request.files.get("name属性")
os.path.join("image",myfile.filename) # 加入文件保存至指定路径
my_file.save(my_file.filename)
其他的请求数据:
request.heathers 获取请求信息
request.cookies 获取cookie信息
request.path == reques.url_rule 获取路由地址
request.host 获取服务器地址
requeat.host_url
特殊的数据获取方式:
Content-Type:application/json
request.json 获取Content-Type:application/json时提交的数据
Content-Type 无法被识别 或 不包含Form字眼
request.data 获取 原始请求体中的数据 b""
flask 中的session
flask中的session是存储在内存中的 django是默认存储在数据库中的
服务端必须拥有secret_key
客户端的交由机制:
加密过程:
首先创建session ---> json 序列化 --->用secret_key加密 ---> 返回给浏览器保存在浏览器
解密过程:
得到字符串---> secret_key解密 ---> 反序列化 得到字典
jinja2 和django 类似
{{}} 引用变量数据,执行函数
{%%} 写逻辑代码
利用装饰器,给每个视图函数实现登录功能?
装饰器的函数,会从当前位置向下找直到第一个函数出现,把该函数的内存地址作为参数传递上去,装饰器从下至上执行
结果
过程:
def func(f):
def inner(*args,**kwargs):
f()
return inner
@func
def func1():
pass
@func
def func2():
pass
print(func1.__name__) #inner
print(func2.__name__) #inner
增加了functools 之后
import functools
def func(f):
@functools.wraps(f)
def inner(*args,**kwargs):
f()
return inner
@func
def func1():
pass
@func
def func2():
pass
print(func1.__name__) # func1
print(func2.__name__) # func2
原因:
一个函数加上了装饰器之后,会把自己的属性赋值给装饰的函数
从装饰器的执行原理上看,一定会出现函数名称重复的问题
而在flask中 之所以要使用@functools.wraps(func) 装饰着
是因为
被同一个装饰器伪装之后的函数,会出现受伪装的函数名相同的情况
路由
@app.route("/index/<int:p>",endpoint="index_name",methods=["get","post"],redirect_to="/",strict_slashes=False,defaults={"id":1})
def func(id,p):
print(url_for("index_name")) # /index
return"".join(id)
路由参数:
endpoint 不能重复,它对应的视图函数与路由的对应关系
url_for 可以反向解析对应的关系
@app.route("/index",endpoint="index_name")
def func():
print(url_for("index_name")) # /index
return"hello"
method 允许的请求方式
在源码的内部含有set去重机制,如果没有指定method参数,默认是get方法
redirect_to 永久重定向,参数为"/"
strict_slashes 是否遵循路由严格模式
default : 视图函数默认的参数
<p> 动态路由参数, 利用send_file
flask 实例化参数
template_folder 网页模板存放路径
static_folder 静态文件存放路径 默认是static 假如修改成statics 对应的static_url_path也要修改成原来的 否则拿不到静态文件
static_url_path 静态文件访问路径 默认值为 "/{static_folder}"
static_host 静态文件访问服务HOST -> 指向到另外一台服务器
flask 的对象配置文件
app.config[] 可以获取所有flask内部内置的配置文件
{
'DEBUG': False, # 是否开启Debug模式,开启编辑时代码重启,LOG打印级别最低,错误信息透传
'TESTING': False, # 是否开启测试模式,无限接近生产环境,代码编辑时不会重启,LOG级别较高,错误信息不再透传
'PROPAGATE_EXCEPTIONS': None, # 异常传播(是否在控制台打印LOG) 当Debug或者testing开启后,自动为True
'PRESERVE_CONTEXT_ON_EXCEPTION': None, # 一两句话说不清楚,一般不用它
'SECRET_KEY': None, # 之前遇到过,在启用Session的时候,一定要有它
'PERMANENT_SESSION_LIFETIME':timedelta(days=31), # days ,Session的生命周期(秒)默认31天的秒数
'USE_X_SENDFILE': False, # 是否弃用 x_sendfile
'LOGGER_NAME': None, # 日志记录器的名称
'LOGGER_HANDLER_POLICY':'always',
'SERVER_NAME': None, # 服务访问域名
'APPLICATION_ROOT': None, # 项目的完整路径
'SESSION_COOKIE_NAME':'session', # 在cookies中存放session加密字符串的名字
'SESSION_COOKIE_DOMAIN': None, # 在哪个域名下会产生session记录在cookies中
'SESSION_COOKIE_PATH': None, # cookies的路径
'SESSION_COOKIE_HTTPONLY': True, # 控制 cookie 是否应被设置 httponly 的标志,
'SESSION_COOKIE_SECURE': False, # 控制 cookie 是否应被设置安全标志
'SESSION_REFRESH_EACH_REQUEST': True, # 这个标志控制永久会话如何刷新
'MAX_CONTENT_LENGTH': None, # 如果设置为字节数, Flask 会拒绝内容长度大于此值的请求进入,并返回一个 413 状态码
'SEND_FILE_MAX_AGE_DEFAULT':12, # hours 默认缓存控制的最大期限
'TRAP_BAD_REQUEST_ERRORS': False,
# 如果这个值被设置为 True ,Flask不会执行 HTTP 异常的错误处理,而是像对待其它异常一样,
# 通过异常栈让它冒泡地抛出。这对于需要找出 HTTP 异常源头的可怕调试情形是有用的。
'TRAP_HTTP_EXCEPTIONS': False,
# Werkzeug 处理请求中的特定数据的内部数据结构会抛出同样也是“错误的请求”异常的特殊的 key errors 。
# 同样地,为了保持一致,许多操作可以显式地抛出 BadRequest 异常。
# 因为在调试中,你希望准确地找出异常的原因,这个设置用于在这些情形下调试。
# 如果这个值被设置为 True ,你只会得到常规的回溯。
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME':'http', # 生成URL的时候如果没有可用的 URL 模式话将使用这个值
'JSON_AS_ASCII': True,
# 默认情况下 Flask 使用 ascii 编码来序列化对象。如果这个值被设置为 False ,
# Flask不会将其编码为 ASCII,并且按原样输出,返回它的 unicode 字符串。
# 比如 jsonfiy 会自动地采用 utf-8 来编码它然后才进行传输。
'JSON_SORT_KEYS': True,
#默认情况下 Flask 按照 JSON 对象的键的顺序来序来序列化它。
# 这样做是为了确保键的顺序不会受到字典的哈希种子的影响,从而返回的值每次都是一致的,不会造成无用的额外 HTTP 缓存。
# 你可以通过修改这个配置的值来覆盖默认的操作。但这是不被推荐的做法因为这个默认的行为可能会给你在性能的代价上带来改善。
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE':'application/json',
'TEMPLATES_AUTO_RELOAD': None,
}
flask 配置的快速配置
例如: 开发环境和测试环境配置是不一样的.如何租到快速切换?
# 导入两个debug配置类
from setting import DebugConfig, TestConfig
# 可以快速切换开发环境和测试环境
app.config.from_object(DebugConfig) # debug模式 开发环境
# app.config.from_object(TestConfig) # testing模式 测试环境
setings.py
classbase():
classDebugconfig(Base):
classDebugconfig(Base):
classDebugconfig(Base):
蓝图 Blueprint
蓝图的目录结构
作用:
功能隔离,路由隔离,类似于django中的应用
用法:
与django的文件目录相类似
app01 应用 views 里面写逻辑
主程序里面引入 然后注册
from flask import Blueprint
car =Blueprint("car",__name__,url_prefix="/car") # 访问前缀的意思 防止url冲突
# 蓝图名称且不能冲突
@car.route("/login")
def func():
return"car login "
'''''
from flask import Flask,url_for
app =Flask(__name__)
from app01.views import user
from app02.views import car
app.register_blueprint(user) # 注册蓝图
app.register_blueprint(car)
if __name__ =='__main__':
app.debug=True
app.run()
蓝图的注意事项
蓝图区分了目录结构,可以在__init__.py中定义全局的请求中间件,也可以在个别蓝图中定义个别请求中间件 根据应用场景不同进行划分
蓝图可以削减请求中间件
在蓝图的目录结构结构使用url_for做endpint反向解析的时候需要注意 url_for("包名.resgister") 才能解析到
from flask import Flask
app =Flask(__name__)
def f1():
print(f1)
def creare_app():
app.config["DEBUG"]=True
app.before_request(f1) # 两种写法相同
# @app.before_request
# def f1():
# print("f1")
app.register_blueprint()
return app
g的用法
g的作用就是在多个函数之间传递数值使用
g只在一次的请求中生效,再次请求上次设置的参数就会失效
from flask import Flask,current_app,globals,g
app = Flask(__name__)
@app.before_request
deffunc2():
g.aa=0
@app.route("/")
deffunc():
print(g.aa)
return"dd"
if __name__ =='__main__':
app.run()
g在使用中会出现数据混淆吗?
不会,因为localstack是根据线程id进行存储值的
g和session 有什么区别?
g和session都是存储在对象中的,
g存储在appcontext对象中
session存储在requestcontext对象中
请求来的时候都会创建,结束的时候都会被销毁,但是销毁之前session会被写到用户浏览器上
flask 中的中间件
@app.before_first_request 在请求第一次进入执行
@app.before_request 在请求进入视图函数之前
@app.after_request 请求结束,返回客户端之前
正常执行顺序: br1-br2-br3 views ar3-ar2-ar1
如果 before_request 执行中返回 则 after_request 顺序不会改变
@errorhandler 错误自定义
@app.errorhandler(404)
def error404(Erromessage):
returnrender_template("404.html")
flask请求的生命周期
wsgi, werkzeug模块
before request
视图(业务/模板处理)
after request
flask中的CBV
from flask import Flask
from flask.views import MethodView,View,MethodViewType
app =Flask(__name__)
classLogin(MethodView): #继承最高类
def get(self): # get方法走的逻辑
return"get 200 ok "
def post(self): # post方法走的逻辑
return"post 200 ok"
app.add_url_rule("/login",view_func=Login.as_view(name="Login"))
if __name__ =='__main__':
app.debug=True
app.run()
数据库连接池
pymysql 操作数据库的时候出现的问题
pymysql操作数据库的时候大致过程如下, 连接数据库 ---> 执行sql语句 --->关闭连接 其中频繁操作数据库就会出现一个问题
时间浪费在开启连接和关闭连接上,造成效率低下
数据库连接池
我们在使用flask框架的时候,使用了pymysql操作数据库,并且使用到了数据库连接池,自己封装了一个单例模式,
为什么要使用单例模式?
顾名思义,单利模式就是程序在运行的过程中只实例化一个对象,节省内存,提高程序的运行效率
数据库连接池是利用多线程的原理,每次一个线程进行操作
安装
pip install DBUTILS
在flask中的应用
import pymysql
from DBUtils.PooledDB import PooledDB
classSQLHelper(object):
def __init__(self):
# 创建数据库连接池
self.pool =PooledDB(
creator=pymysql,
maxconnections=5, # 最大同时处理数
mincached=2, # 最小连接数
blocking=True, # TRUE 如果池中没有可用连接就等待,FALSE 报错
host='127.0.0.1',
port=3306,
user='root',
password='123',
database='s23day02',
charset='utf8'
)
def connect(self):
'''开启连接'''
conn = self.pool.connection()
cursor = conn.cursor()
return conn,cursor
def disconnect(self,conn,cursor):
''' 关闭连接 '''
cursor.close()
conn.close()
def fetchone(self,sql,params=None):
"""
获取单条数据
:param sql:
:param params:
:return:
"""
if not params:
params =[]
conn,cursor = self.connect()
cursor.execute(sql, params)
result = cursor.fetchone()
self.disconnect(conn,cursor)
return result
def fetchall(self,sql,params=None):
"""
获取所有数据
:param sql:
:param params:
:return:
"""
import pymysql
if not params:
params =[]
conn, cursor = self.connect()
cursor.execute(sql,params)
result = cursor.fetchall()
self.disconnect(conn, cursor)
return result
def commit(self,sql,params):
"""
增删改数据操作
:param sql:
:param params:
:return:
"""
import pymysql
if not params:
params =[]
conn, cursor = self.connect()
cursor.execute(sql, params)
conn.commit()
self.disconnect(conn, cursor)
db =SQLHelper()
蓝图中的使用
from flask import Blueprint,url_for,request,render_template,session,redirect
from..utils.sqlhelper import db
# 创建了一个蓝图对象
account =Blueprint('account',__name__)
@account.route('/login',methods=['GET','POST'])
def login():
if request.method =='GET':
returnrender_template('login.html')
user = request.form.get('user')
pwd = request.form.get('pwd')
# 根据用户名和密码去数据库进行校验
# 连接/SQL语句/关闭
result = db.fetchone('select * from user where username=%s and password=%s',[user,pwd])
if result:
# 在session中存储一个值
session['user_info']= user
returnredirect(url_for('user.user_list'))
returnrender_template('login.html',error="用户名或密码错误")
```
如果需要配合锁的使用 直接sql语句加锁即可!
行锁 select *from user where id=1for update
表锁
pymysql加锁
cursor.execute("select * from user where id=1 for update")
Django加锁:
(1)装饰器:
@transaction.commit_on_success
def view():
a.save()
b.save()
return...
(2)with用法:
def func()
with transaction.commit_on_success():
a.save()
b.save()
return...
flask中的第三方组件
flask-sqlalchemy 关系型数据库映射到对象中
安装
pip install flask-sqlalchemy
使用方法:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datatime import datatime
app =Flask(__name__)
# 需要导入的配置信息 数据库名+驱动 账号 密码 端口 库名 编码
app.config["SQLALCHEMY_DATABASE_URI"]="mysql+pymysql://root:123@127.0.0.1:3306/test?charset=utf8"
app.config["SQLALCHEMY_POOL_SIZE"]=10 #数据库连接池
app.config["SQLALCHEMY_MAX_OVERFLOW"]=5 #溢出
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"]=True # 设置sqlalchemy自动更跟踪数据库
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']= False # 禁止自动提交数据处理
db =SQLAlchemy(app)
classStudent(db.Model):
__tablename__="student"
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
name = db.Column(db.String(16),nullable=False)
sex = db.Column(db.Integer,nullable=True)
#db.create_all() # 测试数据库是否连接正常
#db.drop_all() # 删除指定的表
@app.route("/",methods=["get"])
def func():
return"fjaodsjgf"
if __name__ =='__main__':
app.run(debug=True)
常用的数据类型
Integer:整形 # 年龄
Float:浮点类型 # 体重 价格
Boolean:布尔类型 # 删除项,展示项
DECIMAL:定点类型 #DECIMAL(4,2) 一共存储4位数字,小数点后最多有两位
enum:枚举类型 # Enum("男","女")
date:日期类型 # 出生日期 存储指定的年月日,不能存储时分秒 p =Person(create_time =datetime(2018,8,8))
DateTime:日期时间类型 #存储指定的年月日时分秒
Time:时间类型 # 只能存储时分秒,不能存储年月日
String:字符类型 # 姓名
text:文本类型 #比较长的时候可以用
longtext:长文本类型 # 非常非常长的时候可以用
column的常用参数
default:默认值
nullable:是否为空
primary_key:主键
index: 创建索引
unique:是否唯一
autoincrement:是否自增
onupdate:更新时执行的
name:数据库映射后的属性
增
student =Student(name="123",sex=1)
db.session.add(student)
db.session.commit() #提交事务
db.session.rollback() # 回滚事务
删
admin = User.query.filter_by(username='admin').first()
db.session.delete(admin)
db.session.commit()
改
stu_query=Student.query.filter_by(name="123").first()
stu_query.name ="donghaiqiao"
db.session.commit()
查
all() 查询所有
>>> users = User.query.all()
[<User u'ethan'>,<User u'admin'>,<User u'guest'>,<User u'joe'>,<User u'michael'>]
filter_by() 条件查询 参数指的是字典形式
>>> user = User.query.filter_by(username='joe').first()
<User u'joe'>
>>> user.email
u'joe@example.com'
filter() 里面指的是条件参数
>>> user = User.query.filter(User.username=='ethan').first()
<User u'ethan'>
user = User.query.filter(User.id>=5)
first() 查询到符合条件的第一条
一对多 的关系
# 数据库模型
关系使用 relationship() 函数表示。
然而外键必须用类 sqlalchemy.schema.ForeignKey 来单独声明:
classPerson(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
addresses = db.relationship('Address', backref='person',
lazy='dynamic')
classAddress(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50))
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
backref 在关系的另一模型中添加反向引用 primary join 明确指定两个模型之间使用的联结条件 uselist 如果为False,不使用列表,而使用标量值 order_by 指定关系中记录的排序方式 secondary 指定多对多中记录的排序方式 secondary join 在SQLAlchemy中无法自行决定时,指定多对多关系中的二级联结条件
多对多的关系
# 官方建议使用辅助表解决多对多关系
tags = db.Table('tags',
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
db.Column('page_id', db.Integer, db.ForeignKey('page.id'))
)
classPage(db.Model):
id = db.Column(db.Integer, primary_key=True)
tags = db.relationship('Tag', secondary=tags,
backref=db.backref('pages', lazy='dynamic'))
classTag(db.Model):
id = db.Column(db.Integer, primary_key=True)
Flask-Script通过命令行的形式来操作Flask.例如通过命令跑一个开发版本的服务器、设置数据库、定时任务等。
flask-login
flask-WTF 表单校验组件(集成wtforms)
增加csrf 跨域 验证码 等功能
wtforms (django中的form组件)
用途; 做登录注册表单等检验,相当于django中的form验证
用法:
pip install wtforms
简单的登录示例代码
from flask import Flask, render_template, request, redirect
from wtforms import Form
from wtforms.fields import core
from wtforms.fields import html5
from wtforms.fields import simple
from wtforms import validators
from wtforms import widgets
app =Flask(__name__, template_folder='templates')
app.debug = True
classLoginForm(Form): # 自定义一个类继承form 类
name = simple.StringField( #渲染变成的input框
label='用户名',
validators=[ # 检验的规则
validators.DataRequired(message='用户名不能为空.'),
validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d')
],
widget=widgets.TextInput(), # 自定义修改渲染的框 可以修改
render_kw={'class':'form-control'}, # 标签的属性
default="默认值" # 给标签指定默认值
)
pwd = simple.PasswordField(
label='密码',
validators=[
validators.DataRequired(message='密码不能为空.'),
validators.Length(min=8, message='用户名长度必须大于%(min)d'),
validators.Regexp(regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[$@$!%*?&])[A-Za-zd$@$!%*?&]{8,}",
message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符')
],
widget=widgets.PasswordInput(),
render_kw={'class':'form-control'}
)
def __init__(self,*args,**kwargs) # 会出现数据无法实时更新的问题
super().__init__(*args,**kwargs)
# self.hobby.choices = db.fetchall('select id,name from hobby',cursor=None)
self.hobby.choices =((1,'篮球'),(2,'足球'),(3,'羽毛球'))
def validate_name(self, field):
"""
自定义pwd_confirm字段规则,例:与pwd字段是否一致
:param field:
:return:
"""
# 最开始初始化时,self.data中已经有所有的值
"""
if field.data != self.data['pwd']:
# raise validators.ValidationError("密码不一致") # 继续后续验证
raise validators.StopValidation("密码不一致") # 不再继续后续验证
"""
iflen(field.data)<8:
# raise validators.ValidationError("用户名太短了")
raise validators.StopValidation("用户名太短了")
@app.route('/login', methods=['GET','POST'])
def login():
if request.method =='GET':
form =LoginForm()
returnrender_template('login.html', form=form)
else:
form =LoginForm(formdata=request.form) # 传入数据
if form.validate(): # 进行数据校验
print('用户提交数据通过格式验证,提交的值为:', form.data)
else:
print(form.errors)
returnrender_template('login.html', form=form)
if __name__ =='__main__':
app.run()
html
<h1>登录</h1>
<form method="post">
<!--<input type="text" name="name">-->
<p>{{form.name.label}}{{form.name}}{{form.name.errors[0]}}</p>
<!--<input type="password" name="pwd">-->
<p>{{form.pwd.label}}{{form.pwd}}{{form.pwd.errors[0]}}</p>
<input type="submit" value="提交">
</form>
flask-session 类django的session
优缺点对比:
原flask中的session机制特点:
交由客户端保管机制,基于浏览器cookie做的 安全性较差 ,
但是不占用服务器空间
flask-session 和 django中的session一样
将session存储到redis中
reids 可能会攻击服务器,从用户态到内核态,只能在内网使用
用法:
ef create_app():
app = Flask(__name__)
app.config.from_object('settings.ProConfig')
# Flask-Session: 第一步示例Session
Session(app)
return app
######
# Flask-Session: 第二步配置
# SESSION_TYPE='redis'
# SESSION_REDIS=Redis(host='192.168.0.94', port='6379')
flask配置redis缓存
源码解析部分
flask请求上下文管理相关(请求的周期)
调用__call__方法
classObh(object):
def __call__(self,*args,**kwargs):
pass
obj =Obj()
obj()# 此时会用调用__call__方法
面向对象相关 setattr 和 getattr 的应用
__getattr__:在访问对象访问类中不存在的属性时会自动调用
__setattr__:初始化对象成员的时候调用,即对属性赋值的时候将会被调用
setitem 和 getitem 的调用方法
classObj(object):
def __setitem__(self, key, value):
print(key,value)
def __getitem__(self, item):
print(item)
obj =Obj()
obj["name"]="dazhuang"
print(obj["name"]) #None
偏函数
给函数换个名,会将原函数和参数一并存放,当执行新函数时,会把值一并传入原函数返回结果给新函数
from functools import partial
def func(a,b):
'''
a 2
b 1
'''
print("a",a)
print("b",b)
return a+b
obj =partial(func,2)
print(obj(1))
示例代码一
classLocal():
def __init__(self):
# 相当于self.storage={} # __init__在内部维护的就是一个字典,数据存储的值全部都放在这里
object.__setattr__(self,"storage",{})
def __setattr__(self, key, value):
'''对象中没有时会执行'''
self.storage[key]=value
def __getattr__(self, item):
'''获取对象时候执行'''
return self.storage[item]
obj =Local()
obj.x =1
print(obj.x)
示例代码二 线性ID 的获取方法
import threading
def task():
print(threading.get_ident())
for i inrange(10):
t=threading.Thread(target=task)
t.start()
示例代码三 为每一个线程独立空间独立存取值
以下示例代码是线程,如果是协程对象 就可以改动代码为每一个协程存储不同的值
threadinglocal 的本质就是如下这个
import threading
classLocal(object):
"""
数据结构如下:
storage ={
1122:{key:value},
1123:{key:value}
}
"""
def __init__(self):
object.__setattr__(self,"storage",{})
def __setattr__(self, key, value):
ident = threading.get_ident()
if ident not in self.storage:
self.storage[ident]={key:value}
self.storage[ident][key]= value
def __getattr__(self, item):
ident = threading.get_ident()
return self.storage[ident][item]
obj =Local()
def task(args):
obj.x1=args
for i inrange(10):
t = threading.Thread(target=task,args=(i,))
t.start()
相关示例代码位置
from flask.globals import local
此代码的好处 :
在flask源码中 local这个类是帮助每个线程开辟独立空间的
若程序使用的线程,则用线程去调用 若程序使用的是协程,则用协程id 去存储
示例代码四 利用 local对象去维护一个栈
源码位置:from flask import globals
import threading
classLocal(object):
"""
数据结构如下:
storage ={1122:{key:value},
1123:{key:value}}
"""
def __init__(self):
object.__setattr__(self,"storage",{})
def __setattr__(self, key, value):
ident = threading.get_ident()
if ident not in self.storage:
self.storage[ident]={key:value}
self.storage[ident][key]= value
def __getattr__(self, item):
ident = threading.get_ident()
try:
return self.storage[ident][item]
except:
return
classLocalstack(object):
"""
storage:{
1122:{stack:[]}
}
{13576:{'stack':[123,456,456]}}
"""
def __init__(self):
self._local=Local()
def push(self,value):
if not self._local.stack: #判断值是否存在,不存在创建,存在添加
self._local.stack=[value,]
else:
self._local.stack.append(value)
# self._local.stack=[123] # 调用local对象内部的__setattr__ 方法创建
def pop(self):
"""
:return: 数据
"""
if self._local.stack:
return self._local.stack.pop()
return
def top(self):
"""
取栈顶的数据
:return:
"""
if self._local.stack:
return self._local.stack[-1]
return
obj =Localstack()
obj.push(123)
print(obj.pop())
print(obj.pop())
# obj.push(456)
# obj.push(456)
print(obj.top())
示例代码五 localstack 的应用示例
import threading
classLocal(object):
"""
数据结构如下:
storage ={1122:{key:value},
1123:{key:value}}
"""
def __init__(self):
object.__setattr__(self,"storage",{})
def __setattr__(self, key, value):
ident = threading.get_ident()
if ident not in self.storage:
self.storage[ident]={key:value}
self.storage[ident][key]= value
def __getattr__(self, item):
ident = threading.get_ident()
try:
return self.storage[ident][item]
except:
return
classLocalstack(object):
"""
storage:{
1122:{stack:[]}
}
{13576:{'stack':[123,456,456]}}
"""
def __init__(self):
self._local=Local()
def push(self,value):
if not self._local.stack: #判断值是否存在,不存在创建,存在添加
self._local.stack=[value,]
else:
self._local.stack.append(value)
# self._local.stack=[123] # 调用local对象内部的__setattr__ 方法创建
def pop(self):
"""
:return: 数据
"""
if self._local.stack:
return self._local.stack.pop()
return
def top(self):
"""
取栈顶的数据
:return:
"""
if self._local.stack:
return self._local.stack[-1]
return
ctx =Localstack() #各自实例化一个值用来存储各自的值
app_ctx =Localstack()
# 以下代码为了模拟多人同时操作类
import threading
def task():
print(threading.get_ident())
ctx.push(123)
app_ctx.push(456)
for i inrange(10):
t = threading.Thread(target=task)
t.start()
# 如果多线程去调用的时候,肯定可以把多线程存储的数据区分开
示例代码六 继上面localstack 维护的一个栈内部 存储着对象
classLocal(object):
"""
数据结构如下:
storage ={1122:{key:value},
1123:{key:value}}
"""
def __init__(self):
object.__setattr__(self,"storage",{})
def __setattr__(self, key, value):
ident = threading.get_ident()
if ident not in self.storage:
self.storage[ident]={key:value}
self.storage[ident][key]= value
def __getattr__(self, item):
ident = threading.get_ident()
try:
return self.storage[ident][item]
except:
return
classLocalstack(object):
"""
storage:{
1122:{stack:[]}
}
{13576:{'stack':[123,456,456]}}
"""
def __init__(self):
self._local=Local()
def push(self,value):
if not self._local.stack: #判断值是否存在,不存在创建,存在添加
self._local.stack=[value,]
else:
self._local.stack.append(value)
# self._local.stack=[123] # 调用local对象内部的__setattr__ 方法创建
def pop(self):
"""
:return: 数据
"""
if self._local.stack:
return self._local.stack.pop()
return
def top(self):
"""
取栈顶的数据
:return:
"""
if self._local.stack:
return self._local.stack[-1]
return
ctx =Localstack()
# 即将被存储的对象
classRequestContext(object):
def __init__(self):
self.request="request"
self.session="session"
classAppContext(object):
def __init__(self):
self.g="g"
self.app="app"
abc =RequestContext()
ctx.push(abc) # 存储对象进去
print(ctx.top().session) #取到对象对应的值
ctx.pop() # 删除存储的对象
flask上下文管理
请求上下文管理 和 应用上下文管理 本质一样,只不过封装的值不一样
请求上下文内部包含 request session
应用上下文内部包含 app 和 g
flask 请求上下文的管理机制
当用户的请求到来之后,flask 内部会创建两个对象
RequestContext() 内部封装了两个属性 request 和 cookie
Appcontext() 内部封装饿了两个属性 app / g
然后将此对象通过各自实例的localstack 对象 即 request_ctx_localstack 和 app_ctx_localstack
将各自的对象添加到local中,而local是一个特殊的结构,而它可以做到为每一个线程或者协程存储数据
localstack 的作用是将local 中维护成一个栈,内部更细节的东西我也研究过
storage ={1122:{stack:[ctx,]}}
storage ={1122:{stack:[app_ctx,]}}
视图函数如果想要获取: request /cookie , app/g, 可以直接导入即可,导入的本质是去各自的storage中获取对象,并且
调用封装其内部: request.session app g,获取栈顶的数据
如果请求处理完毕,将各自存储在staorage 中存储的数据进行销毁
flask 应用上下文管理
举一个简单flask程序源码:
from flask import Flask,globals
app =Flask(__name__) #去源码中执行 Flask 类中的 __call__方法
@app.route("/")
def func():
return"2020 你好"
if __name__ =='__main__':
app.run()
''' 源码中 '''
classFlask(_PackageBoundObject):
''''''
def __call__(self, environ, start_response):
'''
第二步 探索 wsgi__app 做了什么事情
'''
return self.wsgi_app(environ, start_response)
# 接下来到这里 还是Flask类汇总
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ) # ctx是RequestContext对象,RequestContext内部封装了 request和session
error = None
try:
try:
'''
执行RequestContext 内部push方法 ,最终将 request/session 的 ctx对象经过localstack 放到local的字典中
创建了app_ctx,内部封装了app 和g ,经过localstack 放在local中
最后执行完了再pop方法删除掉
'''
ctx.push()
response = self.full_dispatch_request() # 执行视图函数
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa:B001
error = sys.exc_info()[1]
raise
returnresponse(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
四篇文章
初识flask , 蓝图常见应用组件 ,threading.local 以及原理剖析
localstack对象维护栈 自定义 flask源码
flask源码入口 画一个详细的图 / Appcontext 和 requestcontext对象
flask源码的执行流程
项目启动阶段
先写一个简单的flask例子
from flask import Flask,current_app,globals
app =Flask(__name__) # 执行flask的__init__ 方法,通过装饰器,向init内部加内容
@app.before_request
def f1():
pass
@app.beforerequest
def f2():
pass
@app.route("/")
def func():
return" 加油"
if __name__ =='__main__':
app.run()
#内部调用flask的run()方法,
#执行werk_zuk的 run_simple(host, port, self,**options),# self=app
#紧接着执行了Flask 的__call__方法 将wsgi中的enviroon 交给flask
请求到来阶段
werkzurg是一个wsgi,本质上提供了socket服务端,wsgizurg接受到用户请求之后
写一个简单的werkzeug模拟flask运行,遵循wsgi标准
from werkzeug.wrappers import Response
from werkzeug.serving import run_simple
classFlask(object):
def__call__(self):
return....
执行Flask类的__call__方法,以下为源码部分
# flask/app.py
classFlask():
...
def__init__(self):
self.before_first_request_funcs=[]#存放所有before_first_request的函数
self.after_request_funcs={}# 调用的时候会反转
self.view_functions ={"login":login}#做路由匹配使用
...
...
def__call__(self, environ, start_response):
#environ请求相关最原始的数据
return self.wsgi_app(environ, start_response)
defwsgi_app(self, environ, start_response):
'''
ctx对象(RequestContext),内部封装了
request/session
'''
ctx = self.request_context(environ)# 相当于调用RequestContext(self, environ),会执行RequestContext的init方法
error =None
try:
try:
'''
将ctx通过 localstack 的push方法,把ctx加入到local对象中,
将app_ctx 通过localstack的push方法,放到自己的local中
路由匹配 , self.request.url_rule 含有匹配成功的endpoint 和 url
'''
ctx.push()
response = self.full_dispatch_request()#开始执行befor_request ,视图函数, after_request
except Exception as e:
error = e
response = self.handle_exception(e)
except:# noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error =None
ctx.auto_pop(error)
# 调用localstack 的pop方法,把ctx移除
deffull_dispatch_request(self):
self.try_trigger_before_first_request_functions()#执行before_first_request 列表内的函数,仅仅第一次访问执行,用了TRUE 状态控制执行
try:
request_started.send(self)
rv = self.preprocess_request()# '''执行所有的before_request方法'''
'''
before_request的执行规则是,如果发现其中有一个return了, 视图函数将不会继续在执行
'''
if rv isNone:# 继上,如果rv为空,继续执行视图函数
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)#执行after_request 并且返回给__call__方法,在返回给werkzeug
flask框架拓展部分
离线脚本
应用 利用一个脚本文件去定向完成一个功能,和web开发没有太大关系
文件夹常用命名 bin/script/
示例一 :
假如flask程序运行,获取配置信息的方法:
一,from flask import current_app ,可以从current_app 中获取
二, 可以直接从 settings配置文件中获取
三, 可以从自创建的文件目录中init文件,create_app中获取
四,with app.app_con_text():
pass
面向对象的上下文管理复习
classTest():
def __enter__(self):
print("with开始执行")
def __exit__(self, exc_type, exc_val,
exc_tb):
print("with关闭")
withTest():
pass
flask 项目部分