flask基础
def index(): # 请求对象,是全局的,需要导入,这个全局的request,在哪个视图函数中,就是当次的request对象,不会乱 # request.method 提交的方法 print(request.method) # request.args get请求提及的数据 print(request.args) print(request.args.get('name')) # request.form post请求提交的数据 print(request.form) # request.values post和get提交的数据总和 print(request.values) # request.cookies 客户端所带的cookie print(request.cookies) # request.headers 请求头 print(request.headers) print('------') # request.path 不带域名,请求路径 print(request.path) # request.full_path 不带域名,带参数的请求路径 print(request.full_path) # request.script_root print('服务端:', request.script_root) # request.url 带域名带参数的请求路径 print(request.url) # request.base_url 带域名请求路径 print(request.base_url) # request.url_root 域名 print(request.url_root) # request.host_url 域名 print(request.host_url) # request.host 127.0.0.1:500 print(request.host) # request.files print(request.files) # obj = request.files['files'] # obj.save('./xx.jpg') print(request.data) # django的body return 'hellod'
@app.route('/',methods=['GET','POST']) def index(): # 1四件套 # -render_template # -redirect # -jsonify # -'' # 2写入响应头-->没有响应对象,先做出一个响应对象 # from .wrappers import Response res='helloe' res=make_response(res) # 往Response的对象中,放入响应头 res.headers['name']='lqz' # 3 写入cookie # res.set_cookie('xx','xx') res.delete_cookie('xx') ''' key, 键 value=’’, 值 max_age=None, 超时时间 cookie需要延续的时间(以秒为单位)如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止 expires=None, 超时时间(IE requires expires, so set it if hasn’t been already.) path=’/‘, Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问,浏览器只会把cookie回传给带有该路径的页面,这样可以避免将cookie传给站点中的其他的应用。 domain=None, Cookie生效的域名 你可用这个参数来构造一个跨站cookie。如, domain=”.example.com”所构造的cookie对下面这些站点都是可读的:www.example.com 、 www2.example.com 和an.other.sub.domain.example.com 。如果该参数设置为 None ,cookie只能由设置它的站点读取 secure=False, 浏览器将通过HTTPS来回传cookie httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖) ''' return res
# 前后端混合,cookie 是后端写入的 - res.set_cookie('xx','xx') 混合都是这么写的,这样写了,浏览就会把cookie保存到cookie中 -本质是后端把cookie放到响应头中,浏览器读到响应头中有cookie,把cookie写入到浏览器中 # 前后端分离后 -直接把客户端要存到cookie中的数据,放到响应体中 -前端(浏览器,app,小程序),自己取出来,放到相应的位置 浏览器使用js自己写入到cookie app 自己使用代码写入到某个位置
2.1 session的使用
# 放值 视图函数中 导入全局的session session['name']='bxf' # 取值 视图函数中 导入全局的session print(session['name'])
# django 的这一套,都在 from django.contrib.sessions.middleware import SessionMiddleware # flask 在flask源码中 -请求来了,会执行 app() # 整个flask,从请求进来,到请求走的整个流程 def wsgi_app(self, environ, start_response): ctx = self.request_context(environ) 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: ctx.pop(error) # ctx.push 的 373行左右 if self.session is None: session_interface = self.app.session_interface self.session = session_interface.open_session(self.app, self.request) if self.session is None: self.session = session_interface.make_null_session(self.app) # app.session_interface 就是Flask对象中有个session_interface对象 SecureCookieSessionInterface() -open_session:请求来了,从cookie中取出三段串,反序列化解密放到session中 -save_session:请求走了,把session字典中的值,序列化加密,放到cookie中 # open_session:请求来了执行 def open_session(self, app, request) : s = self.get_signing_serializer(app) if s is None: return None # val 就是取出的三段:eyJhZ2UiOiIxOSIsIm5hbWUiOiJscXoifQ.Y5ac9g.vOomQFqFuaqXWqRQhvSNyc61UIk val = request.cookies.get('session') if not val: return self.session_class() max_age = int(app.permanent_session_lifetime.total_seconds()) try: data = s.loads(val, max_age=max_age) return self.session_class(data) except BadSignature: return self.session_class() # 请求走了,执行save_session def save_session(self, app, session, response): name = self.get_cookie_name(app) domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) secure = self.get_cookie_secure(app) samesite = self.get_cookie_samesite(app) httponly = self.get_cookie_httponly(app) if not session: # 如果视图函数放了,不为空 session['name']='lqz' if session.modified: # response.delete_cookie( name, domain=domain, path=path, secure=secure, samesite=samesite, httponly=httponly, ) return if session.accessed: response.vary.add("Cookie") if not self.should_set_cookie(app, session): return expires = self.get_expiration_time(app, session) # 序列化---》加密了 val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore # 三段: response.set_cookie( name, # session val, # 三段: expires=expires, httponly=httponly, domain=domain, path=path, secure=secure, samesite=samesite, ) # 总结:session的执行流程 1 请求来的时候,会执行open_session--->取出cookie,判断是否为空,如果不为空,把它反序列化,解密---》字典---》转到session对象中----》视图函数 2 请求走的时候,会执行save_session---->把session转成字典----》序列化加密--》三段---》放到cookie中
# flash 翻译过来叫闪现 # 作用: 访问a页面,出了错,重定向到了b页面,要在b页面线上a页面的错误信息 在某个请求中放入值,另一个请求中取出,取出来后就没了 # 使用 设置值: flash('不好意思,没有权限看') 可以用多次 取值:取出列表 get_flashed_messages() # 使用方式二:分类设置和获取 设置值: flash('钱钱钱',category='lqz') flash('666',category='c1')') 可以用多次 取值:取出列表 errors = get_flashed_messages(category_filter=['lqz'])
# 异步框架 FastAPi async def index(): print('sdfasd') a++ await xxx # io操作 async def goods(): pass # 框架之前的web框架,开启进程,线程---》一条线程会运行多个协程函数----》协程函数中遇到io,读到await关键字,就会切换到别的协程函数 # 一旦使用了异步,以后所有的模块,都要是异步 -pymysql :同步的 -redis :同步 -aiomysql:异步 -aioredis:异步 -在fastapi或sanic中,要操作mysql,redis要使用异步的框架,否则效率更低 -django 3.x 以后页支持async 关键字 -没有一个特别好异步的orm框架 -sqlalchemy在做 -tortoise-orm https://tortoise-orm.readthedocs.io/en/latest/index.html # aiomysql import asyncio import aiomysql loop = asyncio.get_event_loop() async def test_example(): conn = await aiomysql.connect(host='127.0.0.1', port=3306, user='root', password='', db='mysql', loop=loop) cur = await conn.cursor() await cur.execute("SELECT Host,User FROM user") print(cur.description) r = await cur.fetchall() print(r) await cur.close() conn.close() loop.run_until_complete(test_example()) # aioredis import aioredis import asyncio class Redis: _redis = None async def get_redis_pool(self, *args, **kwargs): if not self._redis: self._redis = await aioredis.create_redis_pool(*args, **kwargs) return self._redis async def close(self): if self._redis: self._redis.close() await self._redis.wait_closed() async def get_value(key): redis = Redis() r = await redis.get_redis_pool(('127.0.0.1', 6379), db=7, encoding='utf-8') value = await r.get(key) print(f'{key!r}: {value!r}') await redis.close() if __name__ == '__main__': asyncio.run(get_value('key')) # need python3.7
# 在请求进入视图函数之前,执行一些代码 # 请求出了视图函数以后,执行一些代码 # 类似于django的中间件完成的功能 # 7个装饰器 # 1 before_request:在请求进视图函数之前执行 多个的话,会从上往下,依次执行, django:process_request 如果返回四件套之一,就直接返回了 在这里面,正常使用request对象 #2 after_request:在请求从视图函数走之后执行 多个的话,会从下往上,依次执行, django:process_response一样 要有参数,和返回值,参数就是response对象,返回值也必须是resposne对象 session,request 照常使用 向响应头写东西?向cookie中写东西 # 3 before_first_request:项目启动后,第一次访问会执行,以后再也不执行了 可以做一些初始化的操作 # 4 teardown_request:每一个请求之后绑定一个函数,即使遇到了异常,每个请求走,都会执行,记录错误日志 @app.teardown_request def tear_down(e): print(e) # 如果有异常,这是异常对象 print('我执行了') #5 errorhandler路径不存在时404,服务器内部错误500 # @app.errorhandler(404) # def error_404(arg): # print('404会执行我') # # return "404错误了" # return render_template('404.html') @app.errorhandler(500) # debug为False请情况下才能看到 def error_500(arg): print('500会执行我') return "服务器内部错误" # 6 template_global 标签 ,在模板中用 {{sb(1,2)}} @app.template_global() def sb(a1, a2): return a1 + a2 # 7 template_filter过滤器 在模板中用 {{10|db(1,2)}} @app.template_filter() def db(a1, a2, a3): return a1 + a2 + a3
# blueprint:对目录进行划分,因为之前所有代码都写在一个py文件中,后期肯定要分到多个文件中 # 蓝图就是为了划分目录的 # 使用步骤: -1 在不同的view的py文件中,定义蓝图 -2 使用app对象,注册蓝图 -3 使用蓝图,注册路由,注册请求扩展 # 不用蓝图划分目录 # 目录结构 flask_blueprint -static # 静态文件存放位置 -templates # 模板存放位置 -user.html # 用户html页面 -views # 视图函数的py文件 -__init__.py # 里面定义了Flask的app对象 goods.py # 商品相关视图 user.py # 用户相关视图 app.py #启动文件 # 蓝图小型项目 flask_blueprint_little # 项目名 -src # 项目代码所在路径 -__init__.py # app对象创建的地方 -templates # 模板 -user.html -static # 静态文件 -views # 视图函数存放位置 -user.py # 用户相关视图 -order.py # 订单相关视图 -manage.py # 启动文件 # 大型项目 flask_blurprint_big # 项目名字 -src # 项目代码所在位置 -__init__.py # src的init,falsk,app实例化 -settings.py # 配置文件 -admin # 类似于django的admin app -__init__.py # 蓝图初始化 -template # 模板 -backend.html -static # 静态文件 -xx.jpg -views.py # 视图层 -models.py # models层,后期咱们表模型 -api -__init__.py -template -static -models.py -views.py -manage.py # 启动文件
7 g对象
# g :global缩写,是关键字,不能用,就写成了g,对象,是一个全局对象,当此请求过程中,一直有效 # 作用:上下文 -其实是请求的上下文,从请求进来,就有,到请求走了,一直存在,所以在当次请求过程中,如果调用别的函数,不需要把参数传入,只需要放到g对象中,在别的函数中直接使用g获取即可 # djagno中有没有这个东西? request.context # 为什么不把变量放到request对象中? 担心把requets原来的属性替换掉 # g对象和session有什么区别 -g只针对于当次请求 -session针对于所有请求
8 flask-session使用
# flask内置的session,把数据加密后保存到浏览器了 # 能不能自己写个session的类,重写open_session和save_session,把数据保存到服务端的redis中 # 第三方:flask-session已经把这事干了,可以放到文件,redis,mongodb,关系型数据库 # 下载: pip3 install -U flask-session # 升级安装 # 使用: #方式一: 以后会保存到redis中 from flask_session.sessions import RedisSessionInterface from redis import Redis conn=Redis(host='127.0.0.0',port=6379) # app.session_interface = RedisSessionInterface(redis=None, key_prefix='lqz') app.session_interface = RedisSessionInterface(redis=conn, key_prefix='lqz') # 在视图函数中使用即可 @app.route('/') def index(): session['name'] = 'pyy' return 'hello' # 方式二: 通用方案,以后集成第三方插件,大致都按这个流程 from flask_session import Session #配置文件 app.config.from_pyfile('settings.py') # from redis import Redis # app.config['SESSION_TYPE'] = 'redis' # app.config['SESSION_REDIS'] = Redis(host='127.0.0.1',port='6379') Session(app) # 导入一个类,把app传入 # 方案二,源码分析 本质在 Session(app)--->就是根据配置文件,生成 RedisSessionInterface 对象,赋值给app.session_interface # 1 如何配置session的过期时间 配置文件:PERMANENT_SESSION_LIFETIME = timedelta(seconds=10) # 2 如何让cookie,关闭浏览器就失效 # expires 设置为None,就是浏览器关闭cookie就失效了 res = make_response('hello') res.set_cookie('name', 'lqz', expires=None) #session设置的cookie,关闭浏览器失效 -使用方式一:app.session_interface = RedisSessionInterface(redis=conn, key_prefix='lqz',permanent=False) -使用方式二:配置文件加入 SESSION_PERMANENT=False
import pymysql app = Flask(__name__) conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123', database='rbac') cursor = conn.cursor() cursor.execute('select * from rbac_prat') res = cursor.fetchall() print(res) cursor.close() conn.close()
# 每个请求过来,都打开mysql链接,操作,操作完了,关闭链接 # 如果并发量很高,如果有1w个并发,要开1w mysql的链接,mysql顶不住 -django就是这么做的 # 解决方案:使用,把连接对象和cursor定义成全局的 -每个视图函数使用同一个cursor,这样会错乱
# 使用第三方的:DBUtils ,创建数据库连接池 # pip3 install -U DBUtils # 使用步骤 #第一步:在py中实例化一个池 from dbutils.pooled_db import PooledDB import pymysql # 1 实例化得到对象 POOL = PooledDB( creator=pymysql, # 使用链接数据库的模块 maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数 mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建 maxcached=5, # 链接池中最多闲置的链接,0和None不限制 maxshared=0, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。 blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错 maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制 setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='123', database='cnblogs', charset='utf8' ) # 第二步:在视图函数中使用池 from db import POOL @app.route('/boys') def boys(): # 第一步,从连接池中取一个链接 conn = POOL.connection() cursor = conn.cursor() time.sleep(0.01) cursor.execute('select * from article') res = cursor.fetchall() print(res) return jsonify(res) # 总结, 如果使用池:无论客户端连接数有多大,mysql的连接数,最多就是6个 如果不使用池:mysql的连接数会过大,把mysql崩掉 # 压力测试:客户端代码 import requests from threading import Thread, get_ident def task(): res = requests.get('http://127.0.0.1:8080/boys') print('线程id号为:%s,获取的数据为:' % str(get_ident()), res.json()) if __name__ == '__main__': for i in range(5000): t = Thread(target=task) t.start() #链接mysql 查看连接数 show status like 'Threads%'
10 wtfroms(了解)
# 跟 django中学过的forms组件是一个东西 #作用: 1 校验数据 2 渲染错误信息 3 渲染页面 #第三方:下载 pip3 install wtfroms
from flask import Flask, render_template, request, redirect from wtforms import Form from wtforms.fields import simple from wtforms import validators from wtforms import widgets from wtforms.fields import choices app = Flask(__name__, template_folder='templates') app.debug = True class LoginForm(Form): # 字段(内部包含正则表达式) name = simple.StringField( label='用户名', validators=[ validators.DataRequired(message='用户名不能为空.'), validators.Length(min=6, max=18, message='用户名长度必须大于%(min)d且小于%(max)d') ], widget=widgets.TextInput(), # 页面上显示的插件 render_kw={'class': 'form-control'} ) # 字段(内部包含正则表达式) 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-z\d$@$!%*?&]{8,}", message='密码至少8个字符,至少1个大写字母,1个小写字母,1个数字和1个特殊字符') ], widget=widgets.PasswordInput(), render_kw={'class': 'form-control'} ) class RegisterForm(Form): name = simple.StringField( label='用户名', validators=[ validators.DataRequired() ], widget=widgets.TextInput(), render_kw={'class': 'form-control'}, default='pyy' ) 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 = simple.EmailField( label='邮箱', validators=[ validators.DataRequired(message='邮箱不能为空.'), validators.Email(message='邮箱格式错误') ], widget=widgets.TextInput(input_type='email'), render_kw={'class': 'form-control'} ) gender = choices.RadioField( label='性别', choices=( (1, '男'), (2, '女'), ), coerce=int # “1” “2” ) city = choices.SelectField( label='城市', choices=( ('bj', '北京'), ('sh', '上海'), ) ) hobby = choices.SelectMultipleField( label='爱好', choices=( (1, '篮球'), (2, '足球'), ), coerce=int ) favor = choices.SelectMultipleField( label='喜好', choices=( (1, '篮球'), (2, '足球'), ), widget=widgets.ListWidget(prefix_label=False), option_widget=widgets.CheckboxInput(), coerce=int, default=[1, 2] ) def __init__(self, *args, **kwargs): super(RegisterForm, self).__init__(*args, **kwargs) self.favor.choices = ((1, '篮球'), (2, '足球'), (3, '羽毛球')) def validate_pwd_confirm(self, field): """ 自定义pwd_confirm字段规则,例:与pwd字段是否一致 :param field: :return: """ # 最开始初始化时,self.data中已经有所有的值 if field.data != self.data['pwd']: # raise validators.ValidationError("密码不一致") # 继续后续验证 raise validators.StopValidation("密码不一致") # 不再继续后续验证 @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'GET': form = LoginForm() return render_template('login.html', form=form) else: form = LoginForm(formdata=request.form) if form.validate(): print('用户提交数据通过格式验证,提交的值为:', form.data) else: print(form.errors) return render_template('login.html', form=form) @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'GET': form = RegisterForm(data={'gender': 2, 'hobby': [1, ]}) # initial return render_template('register.html', form=form) else: form = RegisterForm(formdata=request.form) if form.validate(): print('用户提交数据通过格式验证,提交的值为:', form.data) else: print(form.errors) return render_template('register.html', form=form) if __name__ == '__main__': app.run()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>用户注册</h1> <form method="post" novalidate style="padding:0 50px"> {% for field in form %} <p>{{field.label}}: {{field}} {{field.errors[0] }}</p> {% endfor %} <input type="submit" value="提交"> </form> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录</h1> <form method="post" novalidate> <p>{{form.name.label}} {{form.name}} {{form.name.errors[0] }}</p> <p>{{form.pwd.label}} {{form.pwd}} {{form.pwd.errors[0] }}</p> <input type="submit" value="提交"> </form> </body> </html>
# 信号和信号量 # 信号量:Semaphore:信号量可以理解为多把锁,同时允许多个线程来更改数据 # 信号:signal:Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为 # 有什么用? -代码耦合性低 -before_render_template :只要模板渲染就会执行,记录日志,login页面被渲染了多少次 -统计今天用户访问量 -User表只要删除记录,就干个什么事 -向banner表存数据,双写一致性问题:定时更新 # django得信号:https://www.cnblogs.com/liuqingzheng/articles/9803403.html # 内置信号 request_started = _signals.signal('request-started') # 请求到来前执行 request_finished = _signals.signal('request-finished') # 请求结束后执行 before_render_template = _signals.signal('before-render-template') # 模板渲染前执行 template_rendered = _signals.signal('template-rendered') # 模板渲染后执行 got_request_exception = _signals.signal('got-request-exception') # 请求执行出现异常时执行 request_tearing_down = _signals.signal('request-tearing-down') # 请求执行完毕后自动执行(无论成功与否) message_flashed = _signals.signal('message-flashed') # 调用flashed在其中添加数据时,自动触发 appcontext_tearing_down = _signals.signal("appcontext-tearing-down") appcontext_pushed = _signals.signal("appcontext-pushed") appcontext_popped = _signals.signal("appcontext-popped") # 使用步骤: 1 定义函数 2 跟内置信号绑定:signals.before_render_template.connect(before_render) 3 等待信号被触发 # 自定义信号 使用步骤: 1 定义一个自定义信号 2 定义一个函数 3 函数跟自定义信号绑定 4 某种情况下触发信号的执行 # 自定义信号使用: ### 自定义信号 # 第一步:定义要给信号 lqz = _signals.signal('lqz') # 第二步:定义函数 def test(*args, **kwargs): print(args) print(kwargs) print('我执行了') # 第三步:绑定自定义的信号 lqz.connect(test) # 第四步:触发自定义的信号
from flask import Flask from werkzeug.serving import run_simple from werkzeug.middleware.dispatcher import DispatcherMiddleware app01 = Flask('app01') app02 = Flask('app02') @app01.route('/index') def index(): return "app01" @app02.route('/index2') def index2(): return "app2" dm = DispatcherMiddleware(app01, {'/sec': app02, }) if __name__ == '__main__': run_simple('localhost', 5000, dm) # 内部如何执行: ''' 1 请求来了,会执行dm(environ,start_response) 2 dm的__call__ 根据请求的地址,拿到不同的app,执行app(environ,start_response)--->Flask的__call__ '''
# flask 的一个第三方插件,完成像django 的 python manage.py runserver 命令操作 # 下载: pip3 install flask-script Flask==2.2.2 Flask_Script==2.0.3 # django 中自定义命令 -第一步:在app中新建包:management -第二步:在management下新建包:commands -第三步:commands新建py文件,py文件名就是命令名 init.py -第四步:init.py写入 from django.core.management.base import BaseCommand, CommandError class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument('--name', type=str) # 指令接受的参数列表,参数名左边有两个减号,可以添加类型限制 def handle(self, *args, **options): name = options['name'] # 执行这个命令的逻辑是什么 # python manage.py init --name=lqz
from flask import Flask from flask_script import Manager app = Flask('app01') # 使用第三方模块 manager = Manager(app) # 自定制命令 @manager.command def custom(arg): """ 自定义命令 python manage.py custom 123 """ print(arg) @manager.option('-n', '--name', dest='name') @manager.option('-u', '--url', dest='url') def cmd(name, url): """ 自定义命令(-n也可以写成--name) 执行: python manage.py cmd -n lqz -u http://www.oldboyedu.com 执行: python manage.py cmd --name lqz --url http://www.oldboyedu.com """ print(name, url) # 后期可以自己定制一些命令 @app.route('/index') def index(): return "app01" if __name__ == '__main__': manager.run()
# threading.local 对象 # 多个线程操作同一个变量,如果不加锁,会出现数据错乱问题 # 作用: 线程变量,意思是threading.local中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。threading.local为变量在每个线程中都创建了一个副本,
那么每个线程可以访问自己内部的副本变量 # 但是 多个线程同时操作 threading.local 对象 就不会出现数据错乱 -java:ThreadLocal -python:threading.local
###不使用local对象 # 不用local,会出现数据错乱问题,除非加锁解决 # from threading import Thread # import time # from threading import Lock # # lqz = -1 # lock = Lock() # # def task(): # lock.acquire() # global lqz # tem = lqz # time.sleep(0.0001) # lqz = tem + 1 # print('---', lqz) # # lock.release() # # # for i in range(10): # t = Thread(target=task) # t.start() # # print(lqz) ####演示2 # from threading import Thread # import time # from threading import Lock # lock = Lock() # lqz = -1 # def task(arg): # # lock.acquire() # global lqz # lqz = arg # time.sleep(0.01) # print(lqz) # # lock.release() # # for i in range(10): # t = Thread(target=task,args=(i,)) # t.start() # 使用local from threading import Thread,get_ident from threading import local import time # 特殊的对象 lqz = local() def task(arg): # 对象.val = 1/2/3/4/5 lqz.value = arg time.sleep(0.1) print('第:%s条线程的值为:%s'%(get_ident(),lqz.value)) for i in range(10): t = Thread(target=task, args=(i,)) t.start()
# flask 的request,和session 都是全局的,但是我们在不同的视图函数中使用的 是正对于当前这次请求的对象,它的底层就是基于local写的 # flask部署支持多进程线程架构,也支持协程架构,flask内部重写了local,让它支持线程和协程 # local的本质是如何实现的 -变量对其他线程而言是隔离的 -local: {'线程id号':{}} -设置值: -线程1:local.val='lqz' ---> {'线程1id号':{val:lqz},} -线程2:local.val='pyy' ---> {'线程1id号':{val:lqz},'线程2id号':{val:pyy},} -取值: -线程1:print(local.val) ---->l={'线程1id号':{val:lqz},'线程2id号':{val:pyy},}--》先当前线程的id号:get_ident() l[get_ident(线程1)]['val'] -线程2:print(local.val) ---->l={'线程1id号':{val:lqz},'线程2id号':{val:pyy},}--》先当前线程的id号:get_ident() l[get_ident(线程2)]['val']
# 1 通过字典自定义threading.local # from threading import get_ident, Thread # import time # # storage = {} # # # def set(k, v): # ident = get_ident() # 当前线程id号 # if ident in storage: #如果当前线程id号在字典中,表示修改值,直接改即可 # storage[ident][k] = v # # else: #新增 # storage[ident] = {k: v} # # # def get(k): # ident = get_ident() # return storage[ident][k] # # # def task(arg): # set('val', arg) # v = get('val') # time.sleep(0.01) # print(v) # # # for i in range(10): # t = Thread(target=task, args=(i,)) # t.start() # # 2 面向对象版 # from threading import get_ident, Thread # import time # # # class Local(object): # storage = {} # # def set(self, k, v): # ident = get_ident() # if ident in Local.storage: # Local.storage[ident][k] = v # else: # Local.storage[ident] = {k: v} # # def get(self, k): # ident = get_ident() # return Local.storage[ident][k] # # # obj = Local() # # # def task(arg): # obj.set('val', arg) # v = obj.get('val') # time.sleep(0.01) # print(v) # # # for i in range(10): # t = Thread(target=task, args=(i,)) # t.start() # 3 重写类的 __setattr__ __getattr # from threading import get_ident, Thread # import time # # # class Local(object): # storage = {} # # def __setattr__(self, k, v): # ident = get_ident() # if ident in Local.storage: # Local.storage[ident][k] = v # else: # Local.storage[ident] = {k: v} # # def __getattr__(self, k): # ident = get_ident() # return Local.storage[ident][k] # # # obj = Local() # 多个local对象公用一个storage # # def task(arg): # obj.val = arg # v = obj.val # time.sleep(0.01) # print(v) # # # for i in range(10): # t = Thread(target=task, args=(i,)) # t.start() # 4 每个对象有自己的存储空间(字典) # 取值:对象.属性,如果没有属性会触发 __getattr__ # 设置值:对象.属性='值',如果属性不存在,会触发 __setattr__ # 如果属性有,直接就拿回来了 # from threading import get_ident, Thread # import time # # # class Local(object): # def __init__(self): # # self.storage = {} # 只要self.属性,就会调用 __setattr__,内部又掉了self.storage--->递归了 # #类来调用对象的绑定方法__setattr__,这个方法就会变成函数,有几个值就要传几个值 # # 本质就是完成 self.storage = {} 要完成的事,但是不会触发递归调用 # object.__setattr__(self, 'storage', {}) # # setattr(self,'storage', {}) # 反射的方式设置值,也会触发递归 # # def __setattr__(self, k, v): # ident = get_ident() # if ident in self.storage: # self.storage[ident][k] = v # else: # self.storage[ident] = {k: v} # # def __getattr__(self, k): # ident = get_ident() # return self.storage[ident][k] # # # obj = Local() # 每个local对象,用自己的字典 # # # def task(arg): # obj.val = arg # v = obj.val # time.sleep(0.01) # print(v) # # # for i in range(10): # t = Thread(target=task, args=(i,)) # t.start() # 6 兼容线程和协程 try: from greenlet import getcurrent as get_ident except Exception as e: from threading import get_ident from threading import Thread import time class Local(object): def __init__(self): # self.storage = {} # 只要self.属性,就会调用 __setattr__,内部又掉了self.storage--->递归了 # 类来调用对象的绑定方法__setattr__,这个方法就会变成函数,有几个值就要传几个值 # 本质就是完成 self.storage = {} 要完成的事,但是不会触发递归调用 object.__setattr__(self, 'storage', {}) # setattr(self,'storage', {}) # 反射的方式设置值,也会触发递归 def __setattr__(self, k, v): ident = get_ident() if ident in self.storage: self.storage[ident][k] = v else: self.storage[ident] = {k: v} def __getattr__(self, k): ident = get_ident() # 在协程中,gevent中是获取协程id号,如果在线程中,获取的是线程id号 return self.storage[ident][k] obj = Local() # 每个local对象,用自己的字典 def task(arg): obj.val = arg v = obj.val time.sleep(0.01) print(v) for i in range(10): t = Thread(target=task, args=(i,)) t.start()
class Local(object): 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}
# 请求来了---》app()----->Flask.__call__--->self.wsgi_app(environ, start_response) def wsgi_app(self, environ, start_response): # environ:http请求拆成了字典 # ctx对象:RequestContext类的对象,对象里有:当次的requets对象,app对象,session对象 ctx = self.request_context(environ) error = None try: try: #ctx RequestContext类 push方法 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) # RequestContext :ctx.push def push(self): # _request_ctx_stack = LocalStack() ---》push(ctx对象)--》ctx:request,session,app _request_ctx_stack.push(self) #session相关的 if self.session is None: session_interface = self.app.session_interface self.session = session_interface.open_session(self.app, self.request) if self.session is None: self.session = session_interface.make_null_session(self.app) # 路由匹配相关的 if self.url_adapter is not None: self.match_request() # LocalStack() push --->obj 是ctx对象 def push(self, obj): #self._local _local 就是咱们刚刚自己写的Local的对象---》LocalStack的init初始化的_local---》self._local = Local()---》Local对象可以根据线程区分数据 rv = getattr(self._local, "stack", None) if rv is None: rv = [] self._local.stack = rv # self._local.stack 根据不同线程用的是自己的数据 rv.append(obj) # self._local.stack.append(obj) # {'线程id号':{stack:[ctx]},'线程id号2':{stack:[ctx]}} return rv # 再往后执行,就会进入到路由匹配,执行视图函数 # request = LocalProxy(partial(_lookup_req_object, "request")) # LocalProxy 代理类---》method---》代理类去当前线程的stack取出ctx,取出当时放进去的request 视图函数中:print(request.method) # print(request) 执行LocalProxy类的__str__方法 # request.method 执行LocalProxy类的__getattr__ def __getattr__(self, name): #name 是method # self._get_current_object() 就是当次请求的request return getattr(self._get_current_object(), name) # LocalProxy类的方法_get_current_object def _get_current_object(self): if not hasattr(self.__local, "__release_local__"): return self.__local() try: return getattr(self.__local, self.__name__) except AttributeError: raise RuntimeError("no object bound to %s" % self.__name__) # self.__local 是在 LocalProxy 类实例化的时候传入的local # 在这里实例化的:request = LocalProxy(partial(_lookup_req_object, "request")) # local 是 partial(_lookup_req_object, "request") #_lookup_req_object ,name=request def _lookup_req_object(name): top = _request_ctx_stack.top # 取出了ctx,是当前线程的ctx if top is None: raise RuntimeError(_request_ctx_err_msg) return getattr(top, name) #从ctx中反射出request,当次请求的request
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现