創(chuàng)新互聯(lián)www.cdcxhl.cn八線動態(tài)BGP香港云服務(wù)器提供商,新人活動買多久送多久,劃算不套路!
為大通等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務(wù),及大通網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為成都做網(wǎng)站、網(wǎng)站設(shè)計、大通網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達到每一位用戶的要求,就會得到認可,從而選擇與我們長期合作。這樣,我們也可以走得更遠!不懂Python3中contextvars模塊是什么?其實想解決這個問題也不難,下面讓小編帶著大家一起學(xué)習(xí)怎么去解決,希望大家閱讀完這篇文章后大所收獲。
什么是上下文(Context)?
Context Variables,也就是「上下文變量」。
Context是一個包含了相關(guān)環(huán)境內(nèi)容的對象。這不是什么很高深的設(shè)計,其實和我們的日常生活也是息息相關(guān)的。
舉個比較實時的例子,權(quán)力的游戲第八季剛開播,如果你沒看過前七季,不了解過去的劇情、人物關(guān)系、過去的種種主線副線發(fā)展,去看第八季第一集是完全看不懂的,因為你缺失了這個美劇的上下文。
上下文就帶著這些信息,如果有一人非常了解過去的那些劇情甚至看過原著,Ta可以把那些第八季能關(guān)聯(lián)到的故事、劇情搞一個視頻剪輯(上下文對象),那么你不需要把過去完整的七季完整看一遍,可能花一個小時看看這個視頻(獲得上下文對象),就能繼續(xù)看第八季(完成之后的操作)。
Flask的設(shè)計中就包含了Context(下面不再說上下文,而統(tǒng)一用Context)。這個設(shè)計有什么用呢?簡單地說:可以在一些場景下隱式地傳遞變量
我們看一下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個框架都有一個問題:視圖函數(shù)上要顯式的傳遞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),和視圖解耦了。這種設(shè)計下,不需要像Django/Sanic那樣把參數(shù)傳來傳去。
ThreadLocal
Flask怎么實現(xiàn)的呢?這就引出了ThreadLocal(本地線程)對象,看名字可以知道它是線程安全的,是單個線程自己的局部變量。Flask的實現(xiàn)中并沒有直接用Python的ThreadLocal,而是自己實現(xiàn)了一個Local類,除了支持線程還支持了Greenlet的協(xié)程。
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個線程的狀態(tài)互不影響?;氐紽lask,請求Context在內(nèi)部作為一個棧來維護(應(yīng)用Context在另外一個棧)。每個訪問Flask的請求,會綁定到當(dāng)前的Context,等請求結(jié)束后再銷毀。維護的過程由框架實現(xiàn),開發(fā)者不需要關(guān)心,你只需要用flask.request就可以了,這樣就提高了接口的可讀性和擴展性。
contextvars例子
threading.local的隔離效果很好,但是他是針對線程的,隔離線程之間的數(shù)據(jù)狀態(tài)。但是現(xiàn)在有了asyncio,怎么辦?
biu~ 我們回到contextvars,這個模塊提供了一組接口,可用于管理、儲存、訪問局部Context的狀態(tài)。我們看個例子:
? 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
可以看到在數(shù)據(jù)狀態(tài)協(xié)程之間互不影響。注意上面contextvars.ContextVar的傳入的第一個參數(shù)(name)值是一個字符串,它主要是用來標識和調(diào)試的,并不一定要用一個單詞或者用下劃線連起來。
注意,這個模塊不僅僅給aio加入Context的支持,也用來替代threading.local()。
在Python 3.6使用contextvars
contextvars實現(xiàn)了PEP 567, 如果在Python3.6想使用可以用MagicStack/contextvars這個向后移植庫,它和標準庫都是同一個作者寫的,可以放心使用。用之前你需要安裝它:
pip install contextvars aiotask_context
在Sanic里面request確實沒有用Context,那在aio體系里面怎么用呢?原來我會使用一個獨立的庫aiotask_context,在我的技術(shù)博客項目中就有用到,我簡化一下這部分的代碼(延伸閱讀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
按執(zhí)行過程,我解釋一下:
app.py默認client是None,在before_server_start中會設(shè)置初始化一個aiomcache.Client,用global設(shè)置給client
每次請求,通過context.set('memcache', client)把client設(shè)置到Context里面
在實際業(yè)務(wù)中,直接用context.get('memcache')獲取這個client。整個邏輯中見不到client傳來傳去,也不需要給request設(shè)置額外的屬性
有一點要提,在Python 3.6, context接受的參數(shù)必須是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中調(diào)試,由于沒有啟動Sanic所以沒有設(shè)置上下文,所以需要異常處理一下。
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()就可以拿到,繼而操作緩存了。
感謝你能夠認真閱讀完這篇文章,希望小編分享Python3中contextvars模塊是什么內(nèi)容對大家有幫助,同時也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司行業(yè)資訊頻道,遇到問題就找創(chuàng)新互聯(lián),詳細的解決方法等著你來學(xué)習(xí)!