程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

How Python uses contextvars to manage context variables

編輯:Python

Python如何利用contextvarsImplement management context variables

這篇文章主要講解了“Python如何利用contextvarsImplement management context variables”,文中的講解內容簡單清晰,易於學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Python如何利用contextvarsImplement management context variables”吧!

Python 在 3.7 Introduces a module when the:contextvars,It's easy to see from the name it refers to the context variable(Context Variables),所以在介紹 contextvars Before, we need to know what is the context(Context).

Context Is an object contains information content,舉個例子:"比如一部 13 Set the anime,You directly into the eighth set,See the leading lady in front of the hero tears".Believe that you don't know why the leading lady is tears,Because you didn't read the content of the previous set,Missing the context information.

所以 Context Is not what magical things,Its role is to carry some specified information.

web 框架中的 request

我們以 fastapi 和 sanic 為例,Take a look at when a request to come,How they are resolved.

# fastapifrom fastapi import FastAPI, Requestimport uvicornapp = FastAPI()@app.get("/index")async def index(request: Request):    name = request.query_params.get("name")    return {"name": name}uvicorn.run("__main__:app", host="127.0.0.1", port=5555)# -------------------------------------------------------# sanicfrom sanic import Sanicfrom sanic.request import Requestfrom sanic import responseapp = Sanic("sanic")@app.get("/index")async def index(request: Request):    name = request.args.get("name")    return response.json({"name": name})app.run(host="127.0.0.1", port=6666)

Send request test,看看結果是否正確.

Can see that the request was a success,並且對於 fastapi 和 sanic 而言,其 request 和 The view function are tied together.That is, when the arrival of the request,會被封裝成一個 Request 對象、And then passed to the view function in the.

但對於 flask It is not like this,我們看一下 flask How to receive request parameters.

from flask import Flask, requestapp = Flask("flask")@app.route("/index")def index():    name = request.args.get("name")    return {"name": name}app.run(host="127.0.0.1", port=7777)

我們看到對於 flask For by import request 的方式,If you don't need you don't have to import,Of course I here is not in what is good way,Mainly in order to lead to our theme today.首先對於 flask 而言,If I define a view function again,So get request parameter is still in the same way,但是這樣問題就來了,Different view functions used within the same request,Wouldn't conflict?

Apparently according to we use flask 的經驗來說,答案是不會的,至於原因就是 ThreadLocal.

ThreadLocal

ThreadLocal,Look from the name can be concluded that it must be associated with the thread.沒錯,It is dedicated to create local variables,And create a local variable is and thread binding.

import threading# 創建一個 local 對象local = threading.local()def get():    name = threading.current_thread().name    # 獲取綁定在 local 上的 value    value = local.value    print(f"線程: {name}, value: {value}")def set_():    name = threading.current_thread().name    # Set different values for different threads    if name == "one":        local.value = "ONE"    elif name == "two":        local.value = "TWO"    # 執行 get 函數    get()t1 = threading.Thread(target=set_, name="one")t2 = threading.Thread(target=set_, name="two")t1.start()t2.start()"""線程 one, value: ONE線程 two, value: TWO"""

Can be seen between the two threads is of no effect,Because each thread has its own unique id,At the time of binding values will be bound in the current thread,Access will be obtained from the current thread.可以把 ThreadLocal Imagine a dictionary:

{    "one": {"value": "ONE"},    "two": {"value": "TWO"}}

更准確的說 key Should be a thread id,In order to intuitive we use thread name 代替了,But anyhow when access will only get binding in this thread on the value of the variable.

而 flask Internal design so,But it is not directly with threading.local,而是自己實現了一個 Local 類,In addition to support thread also support greenlet 的協程,那麼它是怎麼實現的呢?首先我們知道 flask 內部存在 "請求 context" 和 "應用 context",They are all through the stack to maintain(Two different stack).

# flask/globals.py_request_ctx_stack = LocalStack()_app_ctx_stack = LocalStack()current_app = LocalProxy(_find_app)request = LocalProxy(partial(_lookup_req_object, "request"))session = LocalProxy(partial(_lookup_req_object, "session"))

Every request binding in the current Context 中,Wait until after the request to destroy again,This is accomplished by the framework,開發者只需要直接使用 request 即可.So request details of the process can point into the source code to see,Here we focus on an object:werkzeug.local.Local,也就是上面說的 Local 類,It is a variable set and get the key.Directly see part of the source code:

# werkzeug/local.pyclass Local(object):    __slots__ = ("__storage__", "__ident_func__")    def __init__(self):        # 內部有兩個成員:__storage__ 是一個字典,Value is there        # __ident_func__ Only need to know that it is used to retrieve the thread id 的即可        object.__setattr__(self, "__storage__", {})        object.__setattr__(self, "__ident_func__", get_ident)    def __call__(self, proxy):        """Create a proxy for a name."""        return LocalProxy(self, proxy)    def __release_local__(self):        self.__storage__.pop(self.__ident_func__(), None)    def __getattr__(self, name):        try:            # 根據線程 id 得到 value(一個字典)            # 然後再根據 name 獲取對應的值            # So will only get binding in the value of the current thread            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:            # 將線程 id 作為 key,Then set the value in the corresponding dictionary            # So will only set the value in the current thread            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)

所以我們看到 flask Internal logic is very simple,通過 ThreadLocal To realize the isolation between the threads.Each request will be binding on their respective Context 中,Get the value from their respective Context 中獲取,Because it is used to store information(It is important to also realize the isolation).

Accordingly this moment you already understand the context,但是問題來了,不管是 threading.local 也好、還是類似於 flask 自己實現的 Local 也罷,They are for the thread.如果是使用 async def Definition of coroutines what to do?The context of how to implement each collaborators routine isolation?So finally raises our leading role:contextvars.

contextvars

This module provides a set of interface,Can be used in coroutines management、設置、訪問局部 Context 的狀態.

import asyncioimport contextvarsc = contextvars.ContextVar("只是一個標識, 用於調試")async def get():    # 獲取值    return c.get() + "~~~"async def set_(val):    # 設置值    c.set(val)    print(await get())async def main():    coro1 = set_("協程1")    coro2 = set_("協程2")    await asyncio.gather(coro1, coro2)asyncio.run(main())"""協程1~~~協程2~~~"""

ContextVar 提供了兩個方法,分別是 get 和 set,Used to get the value and set value.We see the effect and ThreadingLocal 類似,Data is the isolation between coroutines,Not be affected by each other.

But we look again carefully,我們是在 set_ 函數中設置的值,然後在 get Function to derive value.可 await get() Rather then opened up a new coroutines,That means the setting values and get the value is not in the same collaborators cheng.但即便如此,We can still get to the hope that the result of.因為 Python 的協程是無棧協程,通過 await Level to achieve alignment with.

We might as well set a layer again:

import asyncioimport contextvarsc = contextvars.ContextVar("只是一個標識, 用於調試")async def get1():    return await get2()async def get2():    return c.get() + "~~~"async def set_(val):    # 設置值    c.set(val)    print(await get1())    print(await get2())async def main():    coro1 = set_("協程1")    coro2 = set_("協程2")    await asyncio.gather(coro1, coro2)asyncio.run(main())"""協程1~~~協程1~~~協程2~~~協程2~~~"""

我們看到不管是 await get1() 還是 await get2(),得到的都是 set_ 中設置的結果,It can be nested.

並且在這個過程當中,You can reset the value.

import asyncioimport contextvarsc = contextvars.ContextVar("只是一個標識, 用於調試")async def get1():    c.set("重新設置")    return await get2()async def get2():    return c.get() + "~~~"async def set_(val):    # 設置值    c.set(val)    print("------------")    print(await get2())    print(await get1())    print(await get2())    print("------------")async def main():    coro1 = set_("協程1")    coro2 = set_("協程2")    await asyncio.gather(coro1, coro2)asyncio.run(main())"""------------協程1~~~重新設置~~~重新設置~~~------------------------協程2~~~重新設置~~~重新設置~~~------------"""

先 await get2() 得到的就是 set_ 函數中設置的值,這是符合預期的.但是我們在 get1 Will value to set up,So after either await get1() 還是直接 await get2(),The set is made up of new value in the form of.

這也說明了,The collaborators process inside await 另一個協程,The other collaborators process inside await Another other collaborators process,Regardless of the dolls(await)多少次,They get values are the same.And in any collaborators process inside can be reset value,Then place the value of the gain will be the last time.再舉個栗子:

import asyncioimport contextvarsc = contextvars.ContextVar("只是一個標識, 用於調試")async def get1():    return await get2()async def get2():    val = c.get() + "~~~"    c.set("Reset")    return valasync def set_(val):    # 設置值    c.set(val)    print(await get1())    print(c.get())async def main():    coro = set_("古明地覺")    await coroasyncio.run(main())"""古明地覺~~~Reset"""

await get1() 的時候會執行 await get2(),Then in there c.set 設置的值,打印 "古明地覺~~~".但是在 get2 裡面,The value reset again,所以第二個 print Printing is a new set of values.\

如果在 get Not before set,那麼會拋出一個 LookupError,所以 ContextVar 支持默認值:

import asyncioimport contextvarsc = contextvars.ContextVar("只是一個標識, 用於調試",                           default="哼哼")async def set_(val):    print(c.get())    c.set(val)    print(c.get())async def main():    coro = set_("古明地覺")    await coroasyncio.run(main())"""Hem incorporated to sleep"""

除了在 ContextVar Specify a default value in outside,也可以在 get 中指定:

import asyncioimport contextvarsc = contextvars.ContextVar("只是一個標識, 用於調試",                           default="哼哼")async def set_(val):    print(c.get("古明地戀"))    c.set(val)    print(c.get())async def main():    coro = set_("古明地覺")    await coroasyncio.run(main())"""Incorporated ground incorporated to sleep"""

所以結論如下,如果在 c.set 之前使用 c.get:

  • 當 ContextVar 和 get Didn't specify a default value in,會拋出 LookupError;

  • As long as one set up,So will get default values;

  • 如果都設置了,那麼以 get 為准;

如果 c.get 之前執行了 c.set,那麼無論 ContextVar 和 get Do you have any specify a default value,獲取到的都是 c.set 設置的值.

So in general or better understand,並且 ContextVar In addition to effects on coroutines,It can also be used in the thread above.沒錯,它可以替代 threading.local,我們來試一下:

import threadingimport contextvarsc = contextvars.ContextVar("context_var")def get():    name = threading.current_thread().name    value = c.get()    print(f"線程 {name}, value: {value}")def set_():    name = threading.current_thread().name    if name == "one":        c.set("ONE")    elif name == "two":        c.set("TWO")    get()t1 = threading.Thread(target=set_, name="one")t2 = threading.Thread(target=set_, name="two")t1.start()t2.start()"""線程 one, value: ONE線程 two, value: TWO"""

和 threading.local 的表現是一樣的,但是更建議使用 ContextVars.But the former can bind multiple values,While the latter can only bind a value(Issues can be resolved by passing a dictionary of that).

c.Token

當我們調用 c.set 的時候,其實會返回一個 Token 對象:

import contextvarsc = contextvars.ContextVar("context_var")token = c.set("val")print(token)"""<Token var=<ContextVar name='context_var' at 0x00..> at 0x00...>"""

Token 對象有一個 var 屬性,它是只讀的,Will return to this token 的 ContextVar 對象.

import contextvarsc = contextvars.ContextVar("context_var")token = c.set("val")print(token.var is c)  # Trueprint(token.var.get())  # valprint(    token.var.set("val2").var.set("val3").var is c)  # Trueprint(c.get())  # val3

Token 對象還有一個 old_value 屬性,It returns the last time set 設置的值,如果是第一次 set,那麼會返回一個 <Token.MISSING>.

import contextvarsc = contextvars.ContextVar("context_var")token = c.set("val")# 該 token 是第一次 c.set 所返回的# Before this no set,所以 old_value 是 <Token.MISSING>print(token.old_value)  # <Token.MISSING>token = c.set("val2")print(c.get())  # val2# 返回上一次 set 的值print(token.old_value)  # val

那麼這個 Token What role does the object?For the moment seems not much use,Actually it is the largest use and reset 搭配使用,The state can be reset.

import contextvars#### c = contextvars.ContextVar("context_var")token = c.set("val")# Obviously can be obtainprint(c.get())  # val# 將其重置為 token 之前的狀態# 但這個 token 是第一次 set 返回的# So before is equivalent to no set 了c.reset(token)try:    c.get()  # 此時就會報錯except LookupError:    print("報錯啦")  # 報錯啦# But we can specify a default valueprint(c.get("默認值"))  # 默認值

contextvars.Context

它負責保存 ContextVars Object and set the value of the mapping between the,But we don't directly through contextvars.Context 來創建,而是通過 contentvars.copy_context 函數來創建.

import contextvarsc1 = contextvars.ContextVar("context_var1")c1.set("val1")c2 = contextvars.ContextVar("context_var2")c2.set("val2")# Get is all at this time ContextVar Object and set the value of the mapping between the# 它實現了 collections.abc.Mapping 接口# So we can operate it like a operating dictionarycontext = contextvars.copy_context()# key 就是對應的 ContextVar 對象,value Is set the value of theprint(context[c1])  # val1print(context[c2])  # val2for ctx, value in context.items():    print(ctx.get(), ctx.name, value)    """    val1 context_var1 val1    val2 context_var2 val2    """print(len(context))  # 2

除此之外,context 還有一個 run 方法:

import contextvarsc1 = contextvars.ContextVar("context_var1")c1.set("val1")c2 = contextvars.ContextVar("context_var2")c2.set("val2")context = contextvars.copy_context()def change(val1, val2):    c1.set(val1)    c2.set(val2)    print(c1.get(), context[c1])    print(c2.get(), context[c2])# 在 change 函數內部,重新設置值# Then print is also a new set of values insidecontext.run(change, "VAL1", "VAL2")"""VAL1 VAL1VAL2 VAL2"""print(c1.get(), context[c1])print(c2.get(), context[c2])"""val1 VAL1val2 VAL2"""

我們看到 run 方法接收一個 callable,If changed the inside ContextVar 實例設置的值,那麼對於 ContextVar In terms of effective only within the function,一旦出了函數,So is the value of the same.但是對於 Context 而言,It will be affected,Even if the function,Is also a new set of value,Because it is directly to modify the internal dictionary.

感謝各位的閱讀,以上就是“Python如何利用contextvarsImplement management context variables”的內容了,經過本文的學習後,相信大家對Python如何利用contextvarsImplement management context variable this problem more profound experience,具體使用情況還需要大家實踐驗證.這裡是億速雲,小編將為大家推送更多相關知識點的文章,歡迎關注!


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved