国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

怎么使用python contextvs實(shí)現(xiàn)管理上下文

89542767 / 430人閱讀


  python在比較新的版本,3.7這個(gè)版本中,引入了一個(gè)比較新的模塊contextvars,從名字上來看的話,它是形容為上下變量的,下文就給大家詳細(xì)的解答下,關(guān)于這方面的內(nèi)容。


  Python在3.7的時(shí)候引入了一個(gè)模塊:contextvars,從名字上很容易看出它指的是上下文變量(Context Variables),所以在介紹contextvars之前我們需要先了解一下什么是上下文(Context)。


  Context是一個(gè)包含了相關(guān)信息內(nèi)容的對(duì)象,舉個(gè)例子:"比如一部25集的電視劇,直接快進(jìn)到第24集,看到女主角在男主角面前流淚了"。相信此時(shí)你是不知道為什么女主角會(huì)流淚的,因?yàn)槟銢]有看前面幾集的內(nèi)容,缺失了相關(guān)的上下文信息。


  所以Context并不是什么神奇的東西,它的作用就是攜帶一些指定的信息。


  web框架中的request


  我們以fastapi和sanic為例,看看當(dāng)一個(gè)請(qǐng)求過來的時(shí)候,它們是如何解析的。


  #fastapi
  from fastapi import FastAPI,Request
  import uvicorn
  app=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)
  #-------------------------------------------------------
  #sanic
  from sanic import Sanic
  from sanic.request import Request
  from sanic import response
  app=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)


  發(fā)請(qǐng)求測(cè)試一下,看看結(jié)果是否正確。


  可以看到請(qǐng)求都是成功的,并且對(duì)于fastapi和sanic而言,其request和視圖函數(shù)是綁定在一起的。也就是在請(qǐng)求到來的時(shí)候,會(huì)被封裝成一個(gè)Request對(duì)象、然后傳遞到視圖函數(shù)中。


  但對(duì)于flask而言則不是這樣子的,我們看一下flask是如何接收請(qǐng)求參數(shù)的。


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


  我們看到對(duì)于flask而言則是通過import request的方式,如果不需要的話就不用import,當(dāng)然我這里并不是在比較哪種方式好,主要是為了引出我們今天的主題。首先對(duì)于flask而言,如果我再定義一個(gè)視圖函數(shù)的話,那么獲取請(qǐng)求參數(shù)依舊是相同的方式,但是這樣問題就來了,不同的視圖函數(shù)內(nèi)部使用同一個(gè)request,難道不會(huì)發(fā)生沖突嗎?


  顯然根據(jù)我們使用flask的經(jīng)驗(yàn)來說,答案是不會(huì)的,至于原因就是ThreadLocal。


  ThreadLocal


  ThreadLocal,從名字上看可以得出它肯定是和線程相關(guān)的。沒錯(cuò),它專門用來創(chuàng)建局部變量,并且創(chuàng)建的局部變量是和線程綁定的。


  import threading
  #創(chuàng)建一個(gè)local對(duì)象
  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
  #為不同的線程設(shè)置不同的值
  if name=="one":
  local.value="ONE"
  elif name=="two":
  local.value="TWO"
  #執(zhí)行g(shù)et函數(shù)
  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


  """


  可以看到兩個(gè)線程之間是互不影響的,因?yàn)槊總€(gè)線程都有自己唯一的id,在綁定值的時(shí)候會(huì)綁定在當(dāng)前的線程中,獲取也會(huì)從當(dāng)前的線程中獲取??梢园裈hreadLocal想象成一個(gè)字典:


 

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


  更準(zhǔn)確的說key應(yīng)該是線程的id,為了直觀我們就用線程的name代替了,但總之在獲取的時(shí)候只會(huì)獲取綁定在該線程上的變量的值。


  而flask內(nèi)部也是這么設(shè)計(jì)的,只不過它沒有直接用threading.local,而是自己實(shí)現(xiàn)了一個(gè)Local類,除了支持線程之外還支持greenlet的協(xié)程,那么它是怎么實(shí)現(xiàn)的呢?首先我們知道flask內(nèi)部存在"請(qǐng)求context"和"應(yīng)用context",它們都是通過棧來維護(hù)的(兩個(gè)不同的棧)。


 

 #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"))


  每個(gè)請(qǐng)求都會(huì)綁定在當(dāng)前的Context中,等到請(qǐng)求結(jié)束之后再銷毀,這個(gè)過程由框架完成,開發(fā)者只需要直接使用request即可。所以請(qǐng)求的具體細(xì)節(jié)流程可以點(diǎn)進(jìn)源碼中查看,這里我們重點(diǎn)關(guān)注一個(gè)對(duì)象:werkzeug.local.Local,也就是上面說的Local類,它是變量的設(shè)置和獲取的關(guān)鍵。直接看部分源碼:


  #werkzeug/local.py
  class Local(object):
  __slots__=("__storage__","__ident_func__")
  def __init__(self):
  #內(nèi)部有兩個(gè)成員:__storage__是一個(gè)字典,值就存在這里面
  #__ident_func__只需要知道它是用來獲取線程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):

 

  所以我們看到flask內(nèi)部的邏輯其實(shí)很簡(jiǎn)單,通過ThreadLocal實(shí)現(xiàn)了線程之間的隔離。每個(gè)請(qǐng)求都會(huì)綁定在各自的Context中,獲取值的時(shí)候也會(huì)從各自的Context中獲取,因?yàn)樗褪怯脕肀4嫦嚓P(guān)信息的(重要的是同時(shí)也實(shí)現(xiàn)了隔離)。


  相應(yīng)此刻你已經(jīng)理解了上下文,但是問題來了,不管是threading.local也好、還是類似于flask自己實(shí)現(xiàn)的Local也罷,它們都是針對(duì)線程的。如果是使用async def定義的協(xié)程該怎么辦呢?如何實(shí)現(xiàn)每個(gè)協(xié)程的上下文隔離呢?所以終于引出了我們的主角:contextvars。


  contextvars


  該模塊提供了一組接口,可用于在協(xié)程中管理、設(shè)置、訪問局部Context的狀態(tài)。


  import asyncio
  import contextvars
  c=contextvars.ContextVar("只是一個(gè)標(biāo)識(shí),用于調(diào)試")
  async def get():
  #獲取值
  return c.get()+"~~~"
  async def set_(val):
  #設(shè)置值
  c.set(val)
  print(await get())
  async def main():
  coro1=set_("協(xié)程1")
  coro2=set_("協(xié)程2")
  await asyncio.gather(coro1,coro2)
  asyncio.run(main())
  """


  協(xié)程1~~~


  協(xié)程2~~~


  """


  ContextVar提供了兩個(gè)方法,分別是get和set,用于獲取值和設(shè)置值。我們看到效果和ThreadingLocal類似,數(shù)據(jù)在協(xié)程之間是隔離的,不會(huì)受到彼此的影響。


  但我們?cè)僮屑?xì)觀察一下,我們是在set_函數(shù)中設(shè)置的值,然后在get函數(shù)中獲取值。可await get()相當(dāng)于是開啟了一個(gè)新的協(xié)程,那么意味著設(shè)置值和獲取值不是在同一個(gè)協(xié)程當(dāng)中。但即便如此,我們依舊可以獲取到希望的結(jié)果。因?yàn)镻ython的協(xié)程是無棧協(xié)程,通過await可以實(shí)現(xiàn)級(jí)聯(lián)調(diào)用。


  我們不妨再套一層:


  import asyncio
  import contextvars
  c=contextvars.ContextVar("只是一個(gè)標(biāo)識(shí),用于調(diào)試")
  async def get1():
  return await get2()
  async def get2():
  return c.get()+"~~~"
  async def set_(val):
  #設(shè)置值
  c.set(val)
  print(await get1())
  print(await get2())
  async def main():
  coro1=set_("協(xié)程1")
  coro2=set_("協(xié)程2")
  await asyncio.gather(coro1,coro2)
  asyncio.run(main())
  """
  協(xié)程1~~~
  協(xié)程1~~~
  協(xié)程2~~~
  協(xié)程2~~~
  """


  我們看到不管是await get1()還是await get2(),得到的都是set_中設(shè)置的結(jié)果,說明它是可以嵌套的。


  并且在這個(gè)過程當(dāng)中,可以重新設(shè)置值。


  import asyncio
  import contextvars
  c=contextvars.ContextVar("只是一個(gè)標(biāo)識(shí),用于調(diào)試")
  async def get1():
  c.set("重新設(shè)置")
  return await get2()
  async def get2():
  return c.get()+"~~~"
  async def set_(val):
  #設(shè)置值
  c.set(val)
  print("------------")
  print(await get2())
  print(await get1())
  print(await get2())
  print("------------")
  async def main():
  coro1=set_("協(xié)程1")
  coro2=set_("協(xié)程2")
  await asyncio.gather(coro1,coro2)
  asyncio.run(main())
  """


  ------------


  協(xié)程1~~~


  重新設(shè)置~~~


  重新設(shè)置~~~


  ------------


  ------------


  協(xié)程2~~~


  重新設(shè)置~~~


  重新設(shè)置~~~


  ------------


  """


  先await get2()得到的就是set_函數(shù)中設(shè)置的值,這是符合預(yù)期的。但是我們?cè)趃et1中將值重新設(shè)置了,那么之后不管是await get1()還是直接await get2(),得到的都是新設(shè)置的值。


  這也說明了,一個(gè)協(xié)程內(nèi)部await另一個(gè)協(xié)程,另一個(gè)協(xié)程內(nèi)部await另另一個(gè)協(xié)程,不管套娃(await)多少次,它們獲取的值都是一樣的。并且在任意一個(gè)協(xié)程內(nèi)部都可以重新設(shè)置值,然后獲取會(huì)得到最后一次設(shè)置的值。再舉個(gè)栗子:


  import asyncio
  import contextvars
  c=contextvars.ContextVar("只是一個(gè)標(biāo)識(shí),用于調(diào)試")
  async def get1():
  return await get2()
  async def get2():
  val=c.get()+"~~~"
  c.set("重新設(shè)置啦")
  return val
  async def set_(val):
  #設(shè)置值
  c.set(val)
  print(await get1())
  print(c.get())
  async def main():
  coro=set_("古明地覺")
  await coro
  asyncio.run(main())
  """
  古明地覺~~~
  重新設(shè)置啦
  """


  await get1()的時(shí)候會(huì)執(zhí)行await get2(),然后在里面拿到c.set設(shè)置的值,打印"古明地覺~~~"。但是在get2里面,又將值重新設(shè)置了,所以第二個(gè)print打印的就是新設(shè)置的值。


  如果在get之前沒有先set,那么會(huì)拋出一個(gè)LookupError,所以ContextVar支持默認(rèn)值:


 

 import asyncio
  import contextvars
  c=contextvars.ContextVar("只是一個(gè)標(biāo)識(shí),用于調(diào)試",
  default="哼哼")
  async def set_(val):
  print(c.get())
  c.set(val)
  print(c.get())
  async def main():
  coro=set_("古明地覺")
  await coro
  asyncio.run(main())
  """


  哼哼


  古明地覺


  """


  除了在ContextVar中指定默認(rèn)值之外,也可以在get中指定:


  import asyncio
  import contextvars
  c=contextvars.ContextVar("只是一個(gè)標(biāo)識(shí),用于調(diào)試",
  default="哼哼")
  async def set_(val):
  print(c.get("古明地戀"))
  c.set(val)
  print(c.get())
  async def main():
  coro=set_("古明地覺")
  await coro
  asyncio.run(main())
  """
  古明地戀
  古明地覺
  """

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


  當(dāng)ContextVar和get中都沒有指定默認(rèn)值,會(huì)拋出LookupError;


  只要有一方設(shè)置了,那么會(huì)得到默認(rèn)值;


  如果都設(shè)置了,那么以get為準(zhǔn);


  如果c.get之前執(zhí)行了c.set,那么無論ContextVar和get有沒有指定默認(rèn)值,獲取到的都是c.set設(shè)置的值。


  所以總的來說還是比較好理解的,并且ContextVar除了可以作用在協(xié)程上面,它也可以用在線程上面。沒錯(cuò),它可以替代threading.local,我們來試一下:


  import threading
  import contextvars
  c=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的表現(xiàn)是一樣的,但是更建議使用ContextVars。不過前者可以綁定任意多個(gè)值,而后者只能綁定一個(gè)值(可以通過傳遞字典的方式解決這一點(diǎn))。
  當(dāng)我們調(diào)用c.set的時(shí)候,其實(shí)會(huì)返回一個(gè)Token對(duì)象:
  import contextvars
  c=contextvars.ContextVar("context_var")
  token=c.set("val")
  print(token)
  """
  <Token var=<ContextVar name='context_var'at 0x00..>at 0x00...>
  """


  Token對(duì)象還有一個(gè)old_value屬性,它會(huì)返回上一次set設(shè)置的值,如果是第一次set,那么會(huì)返回一個(gè)<Token.MISSING>。


  import contextvars
  c=contextvars.ContextVar("context_var")
  token=c.set("val")
  #該token是第一次c.set所返回的
  #在此之前沒有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
  那么這個(gè)Token對(duì)象有什么作用呢?從目前來看貌似沒太大用處啊,其實(shí)它最大的用處就是和reset搭配使用,可以對(duì)狀態(tài)進(jìn)行重置。
  import contextvars
  ####
  c=contextvars.ContextVar("context_var")
  token=c.set("val")
  #顯然是可以獲取的
  print(c.get())#val
  #將其重置為token之前的狀態(tài)
  #但這個(gè)token是第一次set返回的
  #那么之前就相當(dāng)于沒有set了
  c.reset(token)
  try:
  c.get()#此時(shí)就會(huì)報(bào)錯(cuò)
  except LookupError:
  print("報(bào)錯(cuò)啦")#報(bào)錯(cuò)啦
  #但是我們可以指定默認(rèn)值
  print(c.get("默認(rèn)值"))#默認(rèn)值
  contextvars.Context


  它負(fù)責(zé)保存ContextVars對(duì)象和設(shè)置的值之間的映射,但是我們不會(huì)直接通過contextvars.Context來創(chuàng)建,而是通過contentvars.copy_context函數(shù)來創(chuàng)建。


  import contextvars
  c1=contextvars.ContextVar("context_var1")
  c1.set("val1")
  c2=contextvars.ContextVar("context_var2")
  c2.set("val2")
  #此時(shí)得到的是所有ContextVar對(duì)象和設(shè)置的值之間的映射
  #它實(shí)現(xiàn)了collections.abc.Mapping接口
  #因此我們可以像操作字典一樣操作它
  context=contextvars.copy_context()
  #key就是對(duì)應(yīng)的ContextVar對(duì)象,value就是設(shè)置的值
  print(context[c1])#val1
  print(context[c2])#val2
  for ctx,value in context.items():
  print(ctx.get(),ctx.name,value)
  """
  val1 context_var1 val1
  val2 context_var2 val2
  """
  print(len(context))#2
  除此之外,context還有一個(gè)run方法:
  import contextvars
  c1=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函數(shù)內(nèi)部,重新設(shè)置值
  #然后里面打印的也是新設(shè)置的值
  context.run(change,"VAL1","VAL2")
  """
  VAL1 VAL1
  VAL2 VAL2
  """
  print(c1.get(),context[c1])
  print(c2.get(),context[c2])
  """
  val1 VAL1
  val2 VAL2
  """


  我們看到run方法接收一個(gè)callable,如果在里面修改了ContextVar實(shí)例設(shè)置的值,那么對(duì)于ContextVar而言只會(huì)在函數(shù)內(nèi)部生效,一旦出了函數(shù),那么還是原來的值。但是對(duì)于Context而言,它是會(huì)受到影響的,即便出了函數(shù),也是新設(shè)置的值,因?yàn)樗苯影褍?nèi)部的字典給修改了。


  小結(jié)


  以上就是contextvars模塊的用法,在多個(gè)協(xié)程之間傳遞數(shù)據(jù)是非常方便的,并且也是并發(fā)安全的。如果你用過Go的話,你應(yīng)該會(huì)發(fā)現(xiàn)和Go在1.7版本引入的context模塊比較相似,當(dāng)然Go的context模塊功能要更強(qiáng)大一些,除了可以傳遞數(shù)據(jù)之外,對(duì)多個(gè)goroutine的級(jí)聯(lián)管理也提供了非常清蒸的解決方案。


  總之對(duì)于contextvars而言,它傳遞的數(shù)據(jù)應(yīng)該是多個(gè)協(xié)程之間需要共享的數(shù)據(jù),像cookie,session,token之類的,比如上游接收了一個(gè)token,然后不斷地向下透?jìng)?。但是不要把本?yīng)該作為函數(shù)參數(shù)的數(shù)據(jù),也通過contextvars來傳遞,這樣就有點(diǎn)本末倒置了。


  關(guān)于contextvars的內(nèi)容就為大家介紹到這里了,希望可以為各位讀者帶來幫助。


文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/127743.html

相關(guān)文章

  • 【FAILED】記一次Python后端開發(fā)面試的經(jīng)歷

    摘要:正確的思路是等概率隨機(jī)只取出共個(gè)數(shù),每個(gè)數(shù)出現(xiàn)的概率也是相等的隨機(jī)輸出把一段代碼改成,并增加單元測(cè)試。代碼本身很簡(jiǎn)單,即使沒學(xué)過也能看懂,改后的代碼如下但是對(duì)于單元測(cè)試則僅限于聽過的地步,需要用到,好像也有別的模塊。 在拉勾上投了十幾個(gè)公司,大部分都被標(biāo)記為不合適,有兩個(gè)給了面試機(jī)會(huì),其中一個(gè)自己覺得肯定不會(huì)去的,也就沒有去面試,另一個(gè)經(jīng)歷了一輪電話面加一輪現(xiàn)場(chǎng)筆試和面試,在此記錄一下...

    kohoh_ 評(píng)論0 收藏0
  • Python有什么好學(xué)的》之下文管理

    摘要:引上下文管理器太極生兩儀,兩儀為陰陽(yáng)。而最常用的則是,即上下文管理器使用上下文管理器用之后的文件讀寫會(huì)變成我們看到用了之后,代碼沒有了創(chuàng)建,也沒有了釋放。實(shí)現(xiàn)上下文管理器我們先感性地對(duì)進(jìn)行猜測(cè)。現(xiàn)實(shí)一個(gè)上下文管理器就是這么簡(jiǎn)單。 Python有什么好學(xué)的這句話可不是反問句,而是問句哦。 主要是煎魚覺得太多的人覺得Python的語法較為簡(jiǎn)單,寫出來的代碼只要符合邏輯,不需要太多的學(xué)習(xí)即可...

    qpwoeiru96 評(píng)論0 收藏0
  • 生成器進(jìn)化到協(xié)程 Part 2

    摘要:一個(gè)典型的上下文管理器類如下處理異常正如方法名明確告訴我們的,方法負(fù)責(zé)進(jìn)入上下的準(zhǔn)備工作,如果有需要可以返回一個(gè)值,這個(gè)值將會(huì)被賦值給中的??偨Y(jié)都是關(guān)于上下文管理器的內(nèi)容,與協(xié)程關(guān)系不大。 Part 1 傳送門 David Beazley 的博客 PPT 下載地址 在 Part 1 我們已經(jīng)介紹了生成器的定義和生成器的操作,現(xiàn)在讓我們開始使用生成器。Part 2 主要描述了如...

    fuyi501 評(píng)論0 收藏0
  • [譯] 從底層理解 Python 的執(zhí)行

    摘要:接下來,我們將注入到函數(shù)的字節(jié)碼中。首先我們來看一下幀的參數(shù)所能提供的信息,如下所示當(dāng)前幀將執(zhí)行的當(dāng)前的操作中的字節(jié)碼字符串的索引經(jīng)過我們的處理我們可以得知之后要被執(zhí)行的操作碼,這對(duì)我們聚合數(shù)據(jù)并展示是相當(dāng)有用的。 原文鏈接: Understanding Python execution from inside: A Python assembly tracer 以下為譯文 最近...

    wmui 評(píng)論0 收藏0
  • 如何成為一名優(yōu)秀的程序員

    摘要:前言羅子雄如何成為一名優(yōu)秀設(shè)計(jì)師董明偉工程師的入門和進(jìn)階董明偉基于自己實(shí)踐講的知乎為新人提供了很多實(shí)用建議,他推薦的羅子雄如何成為一名優(yōu)秀設(shè)計(jì)師的演講講的非常好,總結(jié)了設(shè)計(jì)師從入門到提高的優(yōu)秀實(shí)踐。 前言 羅子雄:如何成為一名優(yōu)秀設(shè)計(jì)師 董明偉:Python 工程師的入門和進(jìn)階 董明偉基于自己實(shí)踐講的知乎live為Python新人提供了很多實(shí)用建議,他推薦的羅子雄:如何成為一名優(yōu)秀...

    keelii 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<