内容简介:在Python 3.7加入了一个新的模块contextvars,模块标题说的也非常直接:Context Variables,也就是「上下文变量」。Context是一个包含了相关环境内容的对象。这不是什么很高深的设计,其实和我们的日常生活也是息息相关的。举个比较实时的例子,权力的游戏第八季刚开播,如果你没看过前七季,不了解过去的剧情、人物关系、过去的种种主线副线发展,去看第八季第一集是完全看不懂的,因为你
在 Python 3.7加入了一个新的模块contextvars,模块标题说的也非常直接:Context Variables,也就是「上下文变量」。
什么是上下文(Context)?
Context是一个包含了相关环境内容的对象。这不是什么很高深的设计,其实和我们的日常生活也是息息相关的。
举个比较实时的例子,权力的游戏第八季刚开播,如果你没看过前七季,不了解过去的剧情、人物关系、过去的种种主线副线发展,去看第八季第一集是完全看不懂的,因为你 缺失了这个美剧的上下文 。
上下文就带着这些信息,如果有一人非常了解过去的那些剧情甚至看过原著,Ta可以把那些第八季能关联到的故事、剧情搞一个视频剪辑(上下文对象),那么你不需要把过去完整的七季完整看一遍,可能花一个小时看看这个视频(获得上下文对象),就能继续看第八季(完成之后的操作)。
Flask的设计中就包含了Context(下面不再说上下文,而统一用Context)。这个设计有什么用呢?简单地说: 可以在一些场景下隐式地传递变量
我们看一下Django和Sanic怎么传递请求对象Request:
# Django
from django.http import HttpResponse
def index(request):
text = request.GET.get('text')
return HttpResponse(f'Text is {text}')
# Sanic
from sanic import response
app = Sanic()
@app.route('/')
async def index(request):
text = request.args.get('text')
return response.text(f'Text is {text}')
这2个框架都有一个问题:视图函数上要显式的传递request(请求对象)。我们再看看Flask的效果:
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
text = request.args.get('text')
return f'Text is {text}'
在Flask中,request是import进来使用的(不需要就不用import),和视图解耦了。这种设计下,不需要像Django/Sanic那样把参数传来传去。
ThreadLocal
Flask怎么实现的呢?这就引出了ThreadLocal(本地线程)对象,看名字可以知道它是线程安全的,是单个线程自己的局部变量。Flask的实现中并没有直接用Python的ThreadLocal,而是自己实现了一个Local类,除了支持线程还支持了Greenlet的协程。
Q: 那为什么不用全局变量呢? A: 由于存在GIL,全局变量的修改必须加锁,会影响效率
先看一下线程库中ThreadLocal的例子:
❯ cat threadlocal_example.py
import random
import threading
local_data = threading.local()
def show():
name = threading.current_thread().getName()
try:
val = local_data.value
except AttributeError:
print(f'Thread {name}: No value yet')
else:
print(f'Thread {name}: {val}')
def worker():
show()
local_data.value = random.randint(1, 100)
show()
for i in range(2):
t = threading.Thread(target=worker)
t.start()
❯ python threadlocal_example.py
Thread Thread-1: No value yet
Thread Thread-1: 78
Thread Thread-2: No value yet
Thread Thread-2: 64
可以感受到2个线程的状态互不影响。回到Flask,请求Context在内部作为一个栈来维护(应用Context在另外一个栈)。每个访问Flask的请求,会绑定到当前的Context,等请求结束后再销毁。维护的过程由框架实现,开发者不需要关心,你只需要用flask.request就可以了,这样就提高了接口的可读性和扩展性。
contextvars例子
threading.local的隔离效果很好,但是他是针对线程的,隔离线程之间的数据状态。但是现在有了asyncio,怎么办?
biu~ 我们回到contextvars,这个模块提供了一组接口,可用于管理、储存、访问局部Context的状态。我们看个例子:
❯ cat contextvar_example.py
import asyncio
import contextvars
# 申明Context变量
request_id = contextvars.ContextVar('Id of request')
async def get():
# Get Value
print(f'Request ID (Inner): {request_id.get()}')
async def new_coro(req_id):
# Set Value
request_id.set(req_id)
await get()
print(f'Request ID (Outer): {request_id.get()}')
async def main():
tasks = []
for req_id in range(1, 5):
tasks.append(asyncio.create_task(new_coro(req_id)))
await asyncio.gather(*tasks)
asyncio.run(main())
❯ python contextvar_example.py
Request ID (Inner): 1
Request ID (Outer): 1
Request ID (Inner): 2
Request ID (Outer): 2
Request ID (Inner): 3
Request ID (Outer): 3
Request ID (Inner): 4
Request ID (Outer): 4
可以看到在数据状态协程之间互不影响。注意上面contextvars.ContextVar的传入的第一个参数(name)值是一个字符串,它主要是用来标识和调试的,并不一定要用一个单词或者用下划线连起来。
注意,这个模块不仅仅给aio加入Context的支持,也用来替代threading.local()。
在Python3.6使用contextvars
contextvars实现了PEP 567, 如果在Python3.6想使用可以用 MagicStack/contextvars 这个向后移植库,它和标准库都是同一个作者写的,可以放心使用。用之前你需要安装它:
pip install contextvars
aiotask_context
在Sanic里面request确实没有用Context,那在aio体系里面怎么用呢?原来我会使用一个独立的库 aiotask_context
,在我的技术博客项目中就有用到,我简化一下这部分的代码(延伸阅读3的commit):
# ext.py
import aiotask_context as context # noqa
# app.py
from ext import context
client = None
@app.listener('before_server_start')
async def setup_db(app, loop):
global client
client = aiomcache.Client(config.MEMCACHED_HOST, config.MEMCACHED_PORT, loop=loop)
loop.set_task_factory(context.task_factory)
@app.middleware('request')
async def setup_context(request):
context.set('memcache', client)
# models/mc.py
_memcache = None
async def get_memcache():
global _memcache
if _memcache is not None:
return _memcache
memcache = context.get('memcache')
_memcache = memcache
return memcache
按执行过程,我解释一下:
before_server_start
context.set('memcache', client)
context.get('memcache')
有一点要提,在Python 3.6, context接受的参数必须是ContextVar对象,要这么写:
if PY36:
import contextvars
memcache_var = contextvars.ContextVar('memcache')
else:
memcache_var = 'memcache'
try:
memcache = context.get(memcache_var)
except AttributeError:
# Hack for debug mode
memcache = None
这里捕获了AttributeError,主要是在ipython中调试,由于没有启动Sanic所以没有设置上下文,所以需要异常处理一下。
contextvars的真实例子
接着替换成contextvars(延伸阅读链接4的commit):
# models/var.py
import contextvars
memcache_var = contextvars.ContextVar('memcache')
# app.py
from models.var import memcache_var
client = None
@app.listener('before_server_start')
async def setup_db(app, loop):
global client
client = aiomcache.Client(config.MEMCACHED_HOST, config.MEMCACHED_PORT, loop=loop)
@app.middleware('request')
async def setup_context(request):
memcache_var.set(client)
# models/mc.py
from models.var import memcache_var
_memcache = None
async def get_memcache():
global _memcache
if _memcache is not None:
return _memcache
memcache = memcache_var.get()
_memcache = memcache
return memcache
在这种模式下,memcache(Redis)等实例对象不需要放在request对象里面,也不需要传来传去,而是放在一个上下文中,需要时直接通过 memcache_var.get()
就可以拿到,继而操作缓存了。
你学到了嘛?
延伸阅读
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Mastering Regular Expressions, Second Edition
Jeffrey E F Friedl / O'Reilly Media / 2002-07-15 / USD 39.95
Regular expressions are an extremely powerful tool for manipulating text and data. They have spread like wildfire in recent years, now offered as standard features in Perl, Java, VB.NET and C# (and an......一起来看看 《Mastering Regular Expressions, Second Edition》 这本书的介绍吧!