contextvars --- متغیرهای زمینه¶
این ماژول APIهایی را برای مدیریت، ذخیره و دسترسی به وضعیت محلی زمینه فراهم میکند. از کلاس ContextVar برای اعلام و کار با متغیرهای زمینه استفاده میشود. برای مدیریت زمینه جاری در چارچوبهای ناهمگام باید از تابع copy_context() و کلاس Context استفاده شود.
مدیران زمینهای که وضعیت دارند، باید بهجای threading.local() از متغیرهای زمینه استفاده کنند تا هنگام استفاده در کد همروند، از نشت وضعیتشان به سایر کدها بهطور غیرمنتظره جلوگیری شود.
همچنین برای جزئیات بیشتر، PEP 567 را ببینید.
اضافه شده در نسخهی 3.7.
متغیرهای زمینه¶
- class contextvars.ContextVar(name[, *, default])¶
این کلاس برای اعلام یک متغیر زمینه (Context Variable) جدید به کار میرود، برای مثال:
var: ContextVar[int] = ContextVar('var', default=42)
پارامتر الزامی name برای دروننگری و اهداف اشکالزدایی استفاده میشود.
پارامتر اختیاری فقط کلیدواژهای default توسط
ContextVar.get()برگردانده میشود، هنگامی که هیچ مقداری برای متغیر در زمینه جاری یافت نشود.مهم: متغیرهای زمینه باید در سطح بالای ماژول ایجاد شوند و هرگز در بستهها (closures) ایجاد نشوند. اشیای
Contextارجاعهای قوی به متغیرهای زمینه نگه میدارند که مانع از زبالهروبی صحیح متغیرهای زمینه میشود.ContextVarها نسبت به نوع مقدار درون خود عام هستند.- name¶
نام متغیر. این یک ویژگی فقطخواندنی است.
اضافه شده در نسخهی 3.7.1.
- get([default])¶
مقداری برای متغیر زمینه در زمینه جاری برمیگرداند.
اگر در زمینه فعلی مقداری برای متغیر وجود نداشته باشد، متد:
مقدار آرگومان default متد را، در صورتی که ارائه شده باشد، برمیگرداند؛ یا
مقدار پیشفرض متغیر زمینه را برمیگرداند، اگر با یک مقدار پیشفرض ایجاد شده باشد؛ یا
یک
LookupErrorپرتاب میکند.
- set(value)¶
فراخوانی برای تنظیم مقدار جدید برای متغیر زمینه در زمینه فعلی.
آرگومان value الزامی، مقدار جدید برای متغیر زمینه است.
یک شیء
Tokenبازمیگرداند که میتوان از آن برای بازگرداندن متغیر به مقدار پیشین آن از طریق متدContextVar.reset()استفاده کرد.برای سهولت، میتوان از شیء توکن بهعنوان مدیر زمینه استفاده کرد تا از فراخوانی دستی
ContextVar.reset()اجتناب شود:var = ContextVar('var', default='default value') with var.set('new value'): assert var.get() == 'new value' assert var.get() == 'default value'
این معادل کوتاهشدهی زیر است:
var = ContextVar('var', default='default value') token = var.set('new value') try: assert var.get() == 'new value' finally: var.reset(token) assert var.get() == 'default value'
اضافه شده در نسخهی 3.14: پشتیبانی از استفاده از توکنها بهعنوان مدیران زمینه افزوده شد.
- reset(token)¶
متغیر زمینه را به مقداری که پیش از استفاده از
ContextVar.set()برای ایجاد token داشت، بازنشانی میکند.برای مثال:
var = ContextVar('var') token = var.set('new value') # code that uses 'var'; var.get() returns 'new value'. var.reset(token) # After the reset call the var has no value again, so # var.get() would raise a LookupError.
نمیتوان از همان توکن دو بار استفاده کرد.
- class contextvars.Token¶
اشیای Token توسط متد
ContextVar.set()برگردانده میشوند. میتوان آنها را به متدContextVar.reset()ارسال کرد تا مقدار متغیر به مقداری که پیش از set مربوطه داشت برگردانده شود. یک توکن واحد نمیتواند یک متغیر زمینه را بیش از یک بار بازنشانی کند.توکنها از پروتکل مدیریت زمینه برای بازنشانی خودکار متغیرهای زمینه پشتیبانی میکنند.
ContextVar.set()را ببینید.توکنها نسبت به همان نوعی که
ContextVarآنها را ایجاد کرده است، عام هستند.اضافه شده در نسخهی 3.14: پشتیبانی از استفاده بهعنوان مدیر زمینه افزوده شد.
- var¶
یک ویژگی فقطخواندنی. به شیء
ContextVarکه توکن را ایجاد کرده است اشاره میکند.
- old_value¶
یک ویژگی فقطخواندنی. به مقداری تنظیم میشود که متغیر پیش از فراخوانی متد
ContextVar.set()که توکن را ایجاد کرد، داشت. اگر متغیر پیش از فراخوانی تنظیم نشده باشد، بهToken.MISSINGاشاره میکند.
- MISSING¶
یک شیء نشانگر که توسط
Token.old_valueاستفاده میشود.
مدیریت دستی زمینه¶
- contextvars.copy_context()¶
نسخهای از شیء
Contextفعلی را برمیگرداند.قطعهکد زیر یک کپی از زمینه فعلی را میگیرد و همه متغیرهایی را که در آن تنظیم شدهاند، به همراه مقادیرشان چاپ میکند:
ctx: Context = copy_context() print(list(ctx.items()))
این تابع دارای پیچیدگی O(1) است، یعنی هم برای زمینههایی با چند متغیر زمینه و هم برای زمینههایی که تعداد زیادی از آنها دارند، با سرعت یکسانی کار میکند.
- class contextvars.Context¶
یک نگاشت از
ContextVarsبه مقدارهای آنها.Context()یک زمینه خالی بدون هیچ مقداری در آن ایجاد میکند. برای گرفتن نسخهای از زمینه جاری، از تابعcopy_context()استفاده کنید.هر نخ، پشتهی مؤثر خود از اشیای
Contextرا دارد. زمینه جاری، شیءContextدر بالای پشتهی نخ جاری است. تمام اشیایContextدر پشتهها، واردشده در نظر گرفته میشوند.ورود به یک زمینه، که میتواند با فراخوانی متد
run()آن انجام شود، زمینه را با قرار دادن آن در بالای پشتهی زمینهی نخ جاری به زمینهی جاری تبدیل میکند.خروج از زمینه فعلی، که میتوان آن را با بازگشت از کالبک دادهشده به متد
run()انجام داد، با برداشتن زمینه از بالای پشتهی زمینه، زمینه فعلی را به وضعیتی که پیش از ورود به زمینه داشت بازمیگرداند.از آنجایی که هر نخ پشتهی زمینهی مختص به خود را دارد، اشیای
ContextVarهنگامی که مقدارها در نخهای مختلف اختصاص داده میشوند، رفتاری مشابهthreading.local()دارند.تلاش برای ورود به زمینهای که از قبل وارد شده است، از جمله زمینههایی که در نخهای دیگر وارد شدهاند، باعث پرتاب یک
RuntimeErrorمیشود.پس از خروج از یک زمینه، میتوان بعداً دوباره وارد آن شد (از هر نخی).
هرگونه تغییر در مقادیر
ContextVarاز طریق متدContextVar.set()در زمینه فعلی ثبت میشود. متدContextVar.get()مقدار مرتبط با زمینه فعلی را برمیگرداند. خروج از یک زمینه، عملاً هر تغییری را که در متغیرهای زمینه در مدتی که وارد زمینه شده بودید ایجاد شده باشد، برمیگرداند (در صورت نیاز، میتوان با ورود مجدد به زمینه، مقادیر را بازیابی کرد).Context رابط
collections.abc.Mappingرا پیادهسازی میکند.- run(callable, *args, **kwargs)¶
وارد Context میشود،
callable(*args, **kwargs)را اجرا میکند، سپس از Context خارج میشود. مقدار بازگشتی callable را بازمیگرداند، یا اگر استثنایی رخ داده باشد، آن را منتشر میکند.مثال:
import contextvars var = contextvars.ContextVar('var') var.set('spam') print(var.get()) # 'spam' ctx = contextvars.copy_context() def main(): # 'var' was set to 'spam' before # calling 'copy_context()' and 'ctx.run(main)', so: print(var.get()) # 'spam' print(ctx[var]) # 'spam' var.set('ham') # Now, after setting 'var' to 'ham': print(var.get()) # 'ham' print(ctx[var]) # 'ham' # Any changes that the 'main' function makes to 'var' # will be contained in 'ctx'. ctx.run(main) # The 'main()' function was run in the 'ctx' context, # so changes to 'var' are contained in it: print(ctx[var]) # 'ham' # However, outside of 'ctx', 'var' is still set to 'spam': print(var.get()) # 'spam'
- copy()¶
یک کپی سطحی از شیء زمینه برمیگرداند.
- var in context
اگر context دارای مقداری برای var تنظیمشده باشد،
Trueرا برمیگرداند؛ در غیر این صورتFalseرا برمیگرداند.
- context[var]
مقدار متغیر var از کلاس
ContextVarرا برمیگرداند. اگر متغیر در شیء زمینه تنظیمنشده باشد، استثنایKeyErrorپرتاب میشود.
- get(var[, default])¶
اگر var در شیء زمینه مقدار داشته باشد، مقدار var را برمیگرداند. در غیر این صورت، default را برمیگرداند. اگر default داده نشده باشد،
Noneرا برمیگرداند.
- iter(context)
یک پیمایشگر بر روی متغیرهای ذخیرهشده در شیء زمینه برمیگرداند.
- len(proxy)
تعداد متغیرهای تنظیمشده در شیء زمینه را برمیگرداند.
- keys()¶
فهرستی از همه متغیرهای شیء زمینه را برمیگرداند.
- values()¶
فهرستی از مقادیر تمام متغیرها در شیء زمینه برمیگرداند.
- items()¶
فهرستی از تاپلهای دوتایی را برمیگرداند که شامل تمام متغیرها و مقادیر آنها در شیء زمینه است.
پشتیبانی از asyncio¶
متغیرهای زمینه بهصورت بومی در asyncio پشتیبانی میشوند و بدون هیچگونه پیکربندی اضافی آماده استفاده هستند. برای مثال، در اینجا یک سرور اکو ساده آمده است که از یک متغیر زمینه استفاده میکند تا نشانی یک کلاینت دوردست را در Taskای که آن کلاینت را مدیریت میکند در دسترس قرار دهد:
import asyncio
import contextvars
client_addr_var = contextvars.ContextVar('client_addr')
def render_goodbye():
# The address of the currently handled client can be accessed
# without passing it explicitly to this function.
client_addr = client_addr_var.get()
return f'Good bye, client @ {client_addr}\r\n'.encode()
async def handle_request(reader, writer):
addr = writer.transport.get_extra_info('socket').getpeername()
client_addr_var.set(addr)
# In any code that we call is now possible to get
# client's address by calling 'client_addr_var.get()'.
while True:
line = await reader.readline()
print(line)
if not line.strip():
break
writer.write(b'HTTP/1.1 200 OK\r\n') # status line
writer.write(b'\r\n') # headers
writer.write(render_goodbye()) # body
writer.close()
async def main():
srv = await asyncio.start_server(
handle_request, '127.0.0.1', 8081)
async with srv:
await srv.serve_forever()
asyncio.run(main())
# To test it you can use telnet or curl:
# telnet 127.0.0.1 8081
# curl 127.0.0.1:8081