contextlib --- ابزارهایی برای زمینههای دستور with¶
کد منبع: Lib/contextlib.py
این ماژول ابزارهایی برای انجام کارهای رایج مربوط به دستور with ارائه میدهد. برای اطلاعات بیشتر، Context Manager Types و مدیران زمینهی دستور with را نیز ببینید.
ابزارهای کاربردی¶
توابع و کلاسهای ارائهشده:
- class contextlib.AbstractContextManager¶
یک کلاس پایه انتزاعی برای کلاسهایی که
__enter__()و__exit__()را پیادهسازی میکنند. یک پیادهسازی پیشفرض برای__enter__()ارائه شده است کهselfرا برمیگرداند، در حالی که__exit__()یک متد انتزاعی است که بهطور پیشفرضNoneرا برمیگرداند. همچنین تعریف Context Manager Types را نیز ببینید.اضافه شده در نسخهی 3.6.
- class contextlib.AbstractAsyncContextManager¶
یک کلاس پایه انتزاعی برای کلاسهایی که
__aenter__()و__aexit__()را پیادهسازی میکنند. پیادهسازی پیشفرضی برای__aenter__()فراهم شده است کهselfرا برمیگرداند، در حالی که__aexit__()یک متد انتزاعی است که بهطور پیشفرضNoneرا برمیگرداند. همچنین تعریف مدیرهای زمینه ناهمگام را ببینید.اضافه شده در نسخهی 3.7.
- @contextlib.contextmanager¶
این تابع یک دکوراتور است که میتوان از آن برای تعریف یک تابع کارخانهای برای مدیران زمینهی دستور
withاستفاده کرد، بدون نیاز به ایجاد یک کلاس یا متدهای جداگانهی__enter__()و__exit__().در حالی که بسیاری از شیءها بهصورت ذاتی از استفاده در دستورهای with پشتیبانی میکنند، گاهی نیاز است منبعی مدیریت شود که بهخودیخود مدیر زمینه نیست و متد
close()را برای استفاده باcontextlib.closingپیادهسازی نمیکند.برای اطمینان از مدیریت صحیح منابع، یک مثال انتزاعی بهصورت زیر خواهد بود:
from contextlib import contextmanager @contextmanager def managed_resource(*args, **kwds): # Code to acquire resource, e.g.: resource = acquire_resource(*args, **kwds) try: yield resource finally: # Code to release resource, e.g.: release_resource(resource)
سپس میتوان از تابع به این صورت استفاده کرد:
>>> with managed_resource(timeout=3600) as resource: ... # Resource is released at the end of this block, ... # even if code in the block raises an exception
تابع آرایهشده باید هنگام فراخوانی، یک پیمایهی تولیدگر را برگرداند. این پیمایشگر باید دقیقاً یک مقدار تولید کند که در صورت وجود بند
asدر دستورwith، به اهداف موجود در آن بند متصل خواهد شد.در نقطهای که تولیدگر yield میکند، بلوک نهفته در دستور
withاجرا میشود. سپس پس از خروج از بلوک، تولیدگر از سر گرفته میشود. اگر یک استثنای مدیریتنشده در بلوک رخ دهد، در نقطهای که yield رخ داده است، درون تولیدگر دوباره پرتاب میشود. بنابراین، میتوانید از یک دستورtry...except...finallyبرای رهگیری خطا (در صورت وجود) یا اطمینان از انجام برخی پاکسازیها استفاده کنید. اگر یک استثنا صرفاً برای ثبت آن یا انجام عملی (نه برای سرکوب کامل آن) گرفته شود، تولیدگر باید آن استثنا را دوباره پرتاب کند. در غیر این صورت، مدیر زمینهی تولیدگر به دستورwithاطلاع میدهد که استثنا مدیریت شده است، و اجرا با دستور بلافاصله پس از دستورwithاز سر گرفته میشود.@contextmanagerازContextDecoratorاستفاده میکند، بنابراین مدیرهای زمینهای که ایجاد میکند میتوانند هم بهعنوان دکوراتور و هم در دستورهایwithاستفاده شوند. هنگام استفاده بهعنوان دکوراتور، در هر فراخوانی تابع، یک نمونهی جدید از تولیدگر بهطور ضمنی ایجاد میشود (این امر به مدیرهای زمینهای که توسط@contextmanagerایجاد میشوند و در غیر این صورت «یکبارمصرف» هستند، اجازه میدهد الزام پشتیبانی از چندین فراخوانی برای استفاده بهعنوان دکوراتور را برآورده کنند).تغییر یافته در نسخهی 3.2: استفاده از
ContextDecorator.
- @contextlib.asynccontextmanager¶
مشابه
@~contextlib.contextmanager، اما یک مدیر زمینه ناهمگام ایجاد میکند.این تابع یک دکوراتور است که میتوان از آن برای تعریف یک تابع کارخانهای برای مدیران زمینه ناهمگام دستور
async withاستفاده کرد، بدون نیاز به ایجاد یک کلاس یا متدهای جداگانهی__aenter__()و__aexit__(). این دکوراتور باید به یک تابع تولیدگر ناهمگام اعمال شود.یک مثال ساده:
from contextlib import asynccontextmanager @asynccontextmanager async def get_connection(): conn = await acquire_db_connection() try: yield conn finally: await release_db_connection(conn) async def get_all_users(): async with get_connection() as conn: return conn.query('SELECT ...')
اضافه شده در نسخهی 3.7.
مدیران زمینه تعریفشده با
@asynccontextmanagerرا میتوان هم بهعنوان دکوراتور و هم با دستورهایasync withاستفاده کرد:import time from contextlib import asynccontextmanager @asynccontextmanager async def timeit(): now = time.monotonic() try: yield finally: print(f'it took {time.monotonic() - now}s to run') @timeit() async def main(): # ... async code ...
هنگام استفاده بهعنوان دکوراتور، در هر فراخوانی تابع، یک نمونه تولیدگر جدید بهطور ضمنی ایجاد میشود. این امر به مدیرهای زمینه ایجادشده توسط
@asynccontextmanager، که در غیر این صورت «یکبارمصرف» هستند، اجازه میدهد الزامی را برآورده کنند که طبق آن مدیرهای زمینه باید برای استفاده بهعنوان دکوراتور از فراخوانیهای متعدد پشتیبانی کنند.تغییر یافته در نسخهی 3.10: مدیرهای زمینه ناهمگام ایجادشده با
@asynccontextmanagerرا میتوان بهعنوان دکوراتور استفاده کرد.
- contextlib.closing(thing)¶
یک مدیر زمینه برمیگرداند که thing را پس از تکمیل بلوک میبندد. این اساساً معادل است با:
from contextlib import contextmanager @contextmanager def closing(thing): try: yield thing finally: thing.close()
و به شما امکان میدهد کدی مانند این بنویسید:
from contextlib import closing from urllib.request import urlopen with closing(urlopen('https://www.python.org')) as page: for line in page: print(line)
بدون نیاز به بستن
pageبهصراحت. حتی اگر خطایی رخ دهد، هنگام خروج از بلوکwith،page.close()فراخوانی خواهد شد.توجه
بیشتر انواعی که منابع را مدیریت میکنند، از پروتکل مدیر زمینه پشتیبانی میکنند، که شیء را در هنگام خروج از دستور
withمیبندد. از این رو،closing()برای انواع شخص ثالثی که از مدیران زمینه پشتیبانی نمیکنند، بیشترین کاربرد را دارد. این مثال صرفاً برای اهداف نمایشی است، زیراurlopen()بهطور معمول در یک مدیر زمینه استفاده میشود.
- contextlib.aclosing(thing)¶
یک مدیر زمینه ناهمگام برمیگرداند که پس از تکمیل بلوک، متد
aclose()متعلق به thing را فراخوانی میکند. این اساساً معادل است با:from contextlib import asynccontextmanager @asynccontextmanager async def aclosing(thing): try: yield thing finally: await thing.aclose()
بهطور قابلتوجهی،
aclosing()از پاکسازی قطعی تولیدگرهای ناهمگام در صورتی که بهواسطهیbreakیا یک استثنا زودتر خارج شوند، پشتیبانی میکند. برای مثال:from contextlib import aclosing async with aclosing(my_generator()) as values: async for value in values: if value == 42: break
این الگو تضمین میکند که کد خروج ناهمگام تولیدگر در همان زمینهای اجرا شود که تکرارهای آن اجرا میشوند (تا استثناها و متغیرهای زمینه همانطور که انتظار میرود کار کنند و کد خروج پس از طول عمر وظیفهی که به آن وابسته است اجرا نشود).
اضافه شده در نسخهی 3.10.
- contextlib.nullcontext(enter_result=None)¶
یک مدیر زمینه برمیگرداند که enter_result را از
__enter__()برمیگرداند، اما در غیر این صورت هیچ کاری انجام نمیدهد. این برای استفاده بهعنوان جایگزینی برای یک مدیر زمینه اختیاری در نظر گرفته شده است، برای مثال:def myfunction(arg, ignore_exceptions=False): if ignore_exceptions: # Use suppress to ignore all exceptions. cm = contextlib.suppress(Exception) else: # Do not ignore any exceptions, cm has no effect. cm = contextlib.nullcontext() with cm: # Do something
مثالی با استفاده از enter_result:
def process_file(file_or_path): if isinstance(file_or_path, str): # If string, open file cm = open(file_or_path) else: # Caller is responsible for closing file cm = nullcontext(file_or_path) with cm as file: # Perform processing on the file
همچنین میتواند بهعنوان جایگزینی برای مدیران زمینه ناهمگام استفاده شود:
async def send_http(session=None): if not session: # If no http session, create it with aiohttp cm = aiohttp.ClientSession() else: # Caller is responsible for closing the session cm = nullcontext(session) async with cm as session: # Send http requests with session
اضافه شده در نسخهی 3.7.
تغییر یافته در نسخهی 3.10: پشتیبانی از asynchronous context manager افزوده شد.
- contextlib.suppress(*exceptions)¶
یک مدیر زمینه برمیگرداند که در صورت وقوع هر یک از استثناهای مشخصشده در بدنهی دستور
with، آنها را سرکوب میکند و سپس اجرا را با نخستین دستور پس از پایان دستورwithاز سر میگیرد.همانطور که در مورد هر سازوکار دیگری که استثناها را بهطور کامل سرکوب میکند، این مدیر زمینه باید فقط برای پوشش خطاهای بسیار خاصی استفاده شود که در آنها ادامهی بیصدای اجرای برنامه کار درست شناخته میشود.
برای مثال:
from contextlib import suppress with suppress(FileNotFoundError): os.remove('somefile.tmp') with suppress(FileNotFoundError): os.remove('someotherfile.tmp')
این کد معادل است با:
try: os.remove('somefile.tmp') except FileNotFoundError: pass try: os.remove('someotherfile.tmp') except FileNotFoundError: pass
این مدیر زمینه بازورودپذیر است.
اگر کد درون بلوک
withیکBaseExceptionGroupرا پرتاب کند، استثناهای مهارشده از گروه حذف میشوند. هر یک از استثناهای گروه که مهارنشده باشند، در گروه جدیدی که با استفاده از متدderive()گروه اصلی ایجاد شده است، دوباره پرتاب میشوند.اضافه شده در نسخهی 3.4.
تغییر یافته در نسخهی 3.12:
suppressاکنون از مهار استثناهایی که بهعنوان بخشی از یکBaseExceptionGroupپرتاب میشوند، پشتیبانی میکند.
- contextlib.redirect_stdout(new_target)¶
مدیر زمینه برای تغییر مسیر موقت
sys.stdoutبه یک پرونده یا شیء شبهپرونده دیگر.این ابزار، انعطافپذیری را به توابع یا کلاسهای موجودی میافزاید که خروجی آنها به stdout متصل است.
برای مثال، خروجی
help()بهطور معمول به sys.stdout فرستاده میشود. شما میتوانید با هدایت خروجی به یک شیءio.StringIO، آن خروجی را در یک رشته ذخیره کنید. جریان جایگزین از متد__enter__()بازگردانده میشود و بنابراین بهعنوان هدف دستورwithدر دسترس است:with redirect_stdout(io.StringIO()) as f: help(pow) s = f.getvalue()
برای ارسال خروجی
help()به یک پرونده روی دیسک، خروجی را به یک پرونده معمولی تغییر مسیر دهید:with open('help.txt', 'w') as f: with redirect_stdout(f): help(pow)
برای ارسال خروجی
help()به sys.stderr:with redirect_stdout(sys.stderr): help(pow)
توجه داشته باشید که اثر جانبی سراسری بر
sys.stdoutبه این معناست که این مدیر زمینه برای استفاده در کد کتابخانهای و بیشتر برنامههای نخی مناسب نیست. همچنین بر خروجی زیرفرایندها اثری ندارد. با این حال، هنوز هم رویکردی مفید برای بسیاری از اسکریپتهای کاربردی است.این مدیر زمینه بازورودپذیر است.
اضافه شده در نسخهی 3.4.
- contextlib.redirect_stderr(new_target)¶
مشابه
redirect_stdout()است، اماsys.stderrرا به پرونده یا شیء شبهپرونده دیگری تغییر مسیر میدهد.این مدیر زمینه بازورودپذیر است.
اضافه شده در نسخهی 3.5.
- contextlib.chdir(path)¶
مدیر زمینهای که از نظر موازیسازی ایمن نیست، برای تغییر پوشه کاری جاری است. از آنجا که این کار یک وضعیت سراسری، یعنی پوشه کاری، را تغییر میدهد، برای استفاده در بیشتر زمینههای نخی یا ناهمگام مناسب نیست. همچنین برای بیشتر اجراهای غیرخطی کد، مانند تولیدگرها، مناسب نیست؛ جاهایی که اجرای برنامه بهطور موقت واگذار میشود -- مگر اینکه صریحاً مورد نظر باشد، نباید هنگامی که این مدیر زمینه فعال است، yield کنید.
این پوششی ساده بر روی
chdir()است که هنگام ورود، پوشه کاری جاری را تغییر میدهد و هنگام خروج، پوشه پیشین را بازمیگرداند.این مدیر زمینه بازورودپذیر است.
اضافه شده در نسخهی 3.11.
- class contextlib.ContextDecorator¶
کلاس پایهای که به یک مدیر زمینه امکان میدهد تا بهعنوان یک دکوراتور نیز استفاده شود.
مدیران زمینهای که از
ContextDecoratorارثبری میکنند باید__enter__()و__exit__()را بهصورت عادی پیادهسازی کنند.__exit__مدیریت اختیاری استثنا را حتی هنگام استفاده بهعنوان دکوراتور حفظ میکند.@contextmanagerازContextDecoratorاستفاده میکند، بنابراین این قابلیت بهصورت خودکار در دسترس شما قرار میگیرد.مثالی از
ContextDecorator:from contextlib import ContextDecorator class mycontext(ContextDecorator): def __enter__(self): print('Starting') return self def __exit__(self, *exc): print('Finishing') return False
سپس میتوان از کلاس به این صورت استفاده کرد:
>>> @mycontext() ... def function(): ... print('The bit in the middle') ... >>> function() Starting The bit in the middle Finishing >>> with mycontext(): ... print('The bit in the middle') ... Starting The bit in the middle Finishing
این تغییر صرفاً قند نحوی برای هر ساختاری با شکل زیر است:
def f(): with cm(): # Do stuff
ContextDecoratorبه شما اجازه میدهد در عوض بنویسید:@cm() def f(): # Do stuff
این امر روشن میکند که
cmبه کل تابع اعمال میشود، نه فقط به بخشی از آن (و یک سطح تورفتگی کمتر نیز خوب است).مدیران زمینه موجودی که از قبل یک کلاس پایه دارند، میتوانند با استفاده از
ContextDecoratorبهعنوان کلاس میکساین گسترش یابند:from contextlib import ContextDecorator class mycontext(ContextBaseClass, ContextDecorator): def __enter__(self): return self def __exit__(self, *exc): return False
توجه
از آنجا که تابع دکوراتشده باید بتواند چندین بار فراخوانی شود، مدیر زمینه زیربنایی باید از استفاده در چندین دستور
withپشتیبانی کند. در غیر این صورت، باید از ساختار اصلی با دستور صریحwithدرون تابع استفاده شود.اضافه شده در نسخهی 3.2.
- class contextlib.AsyncContextDecorator¶
مشابه
ContextDecoratorاما فقط برای توابع ناهمگام.مثالی از
AsyncContextDecorator:from asyncio import run from contextlib import AsyncContextDecorator class mycontext(AsyncContextDecorator): async def __aenter__(self): print('Starting') return self async def __aexit__(self, *exc): print('Finishing') return False
سپس میتوان از کلاس به این صورت استفاده کرد:
>>> @mycontext() ... async def function(): ... print('The bit in the middle') ... >>> run(function()) Starting The bit in the middle Finishing >>> async def function(): ... async with mycontext(): ... print('The bit in the middle') ... >>> run(function()) Starting The bit in the middle Finishing
اضافه شده در نسخهی 3.10.
- class contextlib.ExitStack¶
یک مدیر زمینه که طراحی شده است تا ترکیب سایر مدیرهای زمینه و توابع پاکسازی را بهصورت برنامهای آسان کند، بهویژه آنهایی که اختیاری هستند یا بهنحوی دیگر توسط دادههای ورودی هدایت میشوند.
برای مثال، مجموعهای از پروندهها را میتوان بهراحتی در یک دستور with واحد بهصورت زیر مدیریت کرد:
with ExitStack() as stack: files = [stack.enter_context(open(fname)) for fname in filenames] # All opened files will automatically be closed at the end of # the with statement, even if attempts to open files later # in the list raise an exception
متد
__enter__()نمونهExitStackرا برمیگرداند و هیچ عملیات اضافی انجام نمیدهد.هر نمونه پشتهای از کالبکهای ثبتشده را نگهداری میکند که هنگام بسته شدن نمونه، با ترتیب معکوس فراخوانی میشوند (چه بهصورت صریح و چه بهصورت ضمنی در پایان یک دستور
with). توجه داشته باشید که هنگام زبالهروبی نمونهی پشتهی زمینه، کالبکها بهصورت ضمنی فراخوانی نمیشوند.از این مدل پشته استفاده میشود تا مدیران زمینهای که منابع خود را در متد
__init__خود کسب میکنند (مانند اشیای پرونده) بتوانند بهدرستی مدیریت شوند.از آنجا که کالبکهای ثبتشده به ترتیب معکوس ثبت فراخوانی میشوند، این موضوع در نهایت رفتاری مانند آن دارد که گویی چندین دستور تودرتوی
withبا مجموعهی کالبکهای ثبتشده استفاده شده باشد. این موضوع حتی به مدیریت استثنا نیز گسترش مییابد؛ اگر یک کالبک داخلی استثنایی را سرکوب یا جایگزین کند، کالبکهای بیرونی آرگومانهایی بر اساس آن وضعیت بهروزشده دریافت خواهند کرد.این یک API نسبتاً سطح پایین است که جزئیات باز کردن صحیح پشتهی کالبکهای خروج را مدیریت میکند. این API پایهای مناسب برای مدیرهای زمینه سطح بالاتر فراهم میکند که پشتهی خروج را به روشهای مختص برنامه دستکاری میکنند.
اضافه شده در نسخهی 3.3.
- enter_context(cm)¶
وارد یک مدیر زمینه جدید میشود و متد
__exit__()آن را به پشتهی کالبک اضافه میکند. مقدار بازگشتی، نتیجهی متد__enter__()خود مدیر زمینه است.این مدیران زمینه ممکن است استثناها را مهار کنند، دقیقاً همانطور که معمولاً در صورت استفادهی مستقیم بهعنوان بخشی از دستور
withاین کار را میکنند.تغییر یافته در نسخهی 3.11: اگر cm یک مدیر زمینه نباشد، بهجای
AttributeError،TypeErrorپرتاب میشود.
- push(exit)¶
متد
__exit__()مدیر زمینه را به پشتهی کالبک اضافه میکند.از آنجا که
__enter__فراخوانی نمیشود، میتوان از این متد برای پوشش دادن بخشی از پیادهسازی__enter__()با متد__exit__()خودِ مدیر زمینه استفاده کرد.اگر شیءای که مدیریتگر زمینه نیست به این متد داده شود، این متد فرض میکند که آن شیء یک کالبک با همان امضای متد
__exit__()مدیریتگر زمینه است و آن را مستقیماً به پشته کالبک اضافه میکند.این کالبکها میتوانند با برگرداندن مقادیر درست، استثناها را دقیقاً مانند متدهای
__exit__()مدیر زمینه مهار کنند.شیء دادهشده از تابع بازگردانده میشود و امکان استفاده از این متد بهعنوان دکوراتور تابع را فراهم میکند.
- callback(callback, /, *args, **kwds)¶
یک تابع کالبک دلخواه و آرگومانها را میپذیرد و آن را به پشته کالبک اضافه میکند.
برخلاف متدهای دیگر، کالبکهایی که به این روش افزوده میشوند نمیتوانند استثناها را مهار کنند (زیرا جزئیات استثنا هرگز به آنها ارسال نمیشود).
کالبک ورودی از تابع بازگردانده میشود و به این متد اجازه میدهد بهعنوان دکوراتور تابع استفاده شود.
- pop_all()¶
پشتهی کالبکها را به یک نمونهی تازهی
ExitStackمنتقل میکند و آن را برمیگرداند. هیچ کالبکی با این عملیات فراخوانی نمیشود — در عوض، از این پس آنها زمانی فراخوانی خواهند شد که پشتهی جدید بسته شود (چه بهصورت صریح و چه بهصورت ضمنی در پایان یک دستورwith).برای مثال، میتوان گروهی از پروندهها را بهعنوان یک عملیات «همه یا هیچ» بهصورت زیر باز کرد:
with ExitStack() as stack: files = [stack.enter_context(open(fname)) for fname in filenames] # Hold onto the close method, but don't call it yet. close_files = stack.pop_all().close # If opening any file fails, all previously opened files will be # closed automatically. If all files are opened successfully, # they will remain open even after the with statement ends. # close_files() can then be invoked explicitly to close them all.
- close()¶
بلافاصله پشتهی کالبک را باز میکند و کالبکها را به ترتیب معکوس ثبت فراخوانی میکند. به ازای هر مدیر زمینه و کالبک خروج ثبتشده، آرگومانهای ارسالشده نشان میدهند که هیچ استثنایی رخ نداده است.
- class contextlib.AsyncExitStack¶
یک مدیر زمینه ناهمگام، مشابه
ExitStack، که از ترکیب مدیرهای زمینه همگام و ناهمگام، و همچنین از داشتن همروالهایی برای منطق پاکسازی پشتیبانی میکند.متد
close()پیادهسازی نشده است؛ باید بهجای آن ازaclose()استفاده شود.- async enter_async_context(cm)¶
مشابه
ExitStack.enter_context()است، اما انتظار یک مدیر زمینه ناهمگام را دارد.تغییر یافته در نسخهی 3.11: اگر cm یک مدیر زمینهی ناهمگام نباشد، بهجای
AttributeError،TypeErrorرا پرتاب میکند.
- push_async_exit(exit)¶
مشابه
ExitStack.push()است، اما انتظار دارد که یا یک مدیر زمینه ناهمگام باشد یا یک تابع همروال.
- push_async_callback(callback, /, *args, **kwds)¶
مشابه
ExitStack.callback()است، اما یک تابع همروال انتظار میرود.
- async aclose()¶
مشابه
ExitStack.close()است، اما awaitableها را بهدرستی مدیریت میکند.
در ادامهی مثال برای
@asynccontextmanager:async with AsyncExitStack() as stack: connections = [await stack.enter_async_context(get_connection()) for i in range(5)] # All opened connections will automatically be released at the end of # the async with statement, even if attempts to open a connection # later in the list raise an exception.
اضافه شده در نسخهی 3.7.
مثالها و دستور پختها¶
این بخش چند مثال و راهکار را برای استفادهی مؤثر از ابزارهای فراهمشده توسط contextlib شرح میدهد.
پشتیبانی از تعداد متغیری از مدیران زمینه¶
کاربرد اصلی ExitStack همان موردی است که در مستندات کلاس آمده است: پشتیبانی از تعداد متغیری از مدیرهای زمینه و سایر عملیات پاکسازی در یک دستور with. این تغییرپذیری ممکن است ناشی از تعداد مدیرهای زمینه مورد نیاز باشد که توسط ورودی کاربر تعیین میشود (مانند باز کردن مجموعهای از پروندههای مشخصشده توسط کاربر)، یا ناشی از اختیاری بودن برخی از مدیرهای زمینه باشد:
with ExitStack() as stack:
for resource in resources:
stack.enter_context(resource)
if need_special_resource():
special = acquire_special_resource()
stack.callback(release_special_resource, special)
# Perform operations that use the acquired resources
همانطور که نشان داده شد، ExitStack همچنین استفاده از دستورهای with را برای مدیریت منابع دلخواهی که بهصورت ذاتی از پروتکل مدیریت زمینه پشتیبانی نمیکنند، بسیار آسان میسازد.
گرفتن استثناها از متدهای __enter__¶
گاهی اوقات مطلوب است استثناهای پیادهسازی متد __enter__() گرفته شوند، بدون آنکه بهطور ناخواسته استثناهای بدنهی دستور with یا متد __exit__() مدیر زمینه گرفته شوند. با استفاده از ExitStack میتوان مراحل پروتکل مدیریت زمینه را کمی از هم جدا کرد تا این امکان فراهم شود:
stack = ExitStack()
try:
x = stack.enter_context(cm)
except Exception:
# handle __enter__ exception
else:
with stack:
# Handle normal case
نیاز واقعی به انجام این کار احتمالاً نشان میدهد که API زیربنایی باید یک رابط مستقیم مدیریت منابع برای استفاده در دستورهای try/except/finally فراهم کند، اما همهی APIها از این نظر بهخوبی طراحی نشدهاند. هنگامی که یک مدیر زمینه تنها API ارائهشده برای مدیریت منابع باشد، ExitStack میتواند مدیریت موقعیتهای مختلفی را که نمیتوان آنها را مستقیماً در یک دستور with مدیریت کرد، آسانتر کند.
پاکسازی در پیادهسازی __enter__¶
همانطور که در مستندات ExitStack.push() ذکر شده است، این متد میتواند در پاکسازی منبعی که از پیش تخصیص دادهشده است مفید باشد، اگر مراحل بعدی در پیادهسازی __enter__() با شکست مواجه شوند.
در اینجا مثالی از انجام این کار برای یک مدیر زمینه آورده شده است که توابع تهیه و آزادسازی منبع را بههمراه یک تابع اعتبارسنجی اختیاری میپذیرد و آنها را به پروتکل مدیریت زمینه نگاشت میکند:
from contextlib import contextmanager, AbstractContextManager, ExitStack
class ResourceManager(AbstractContextManager):
def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
self.acquire_resource = acquire_resource
self.release_resource = release_resource
if check_resource_ok is None:
def check_resource_ok(resource):
return True
self.check_resource_ok = check_resource_ok
@contextmanager
def _cleanup_on_error(self):
with ExitStack() as stack:
stack.push(self)
yield
# The validation check passed and didn't raise an exception
# Accordingly, we want to keep the resource, and pass it
# back to our caller
stack.pop_all()
def __enter__(self):
resource = self.acquire_resource()
with self._cleanup_on_error():
if not self.check_resource_ok(resource):
msg = "Failed validation for {!r}"
raise RuntimeError(msg.format(resource))
return resource
def __exit__(self, *exc_details):
# We don't need to duplicate any of our resource release logic
self.release_resource()
جایگزینی هرگونه استفاده از try-finally و متغیرهای پرچمی¶
الگویی که گاهی خواهید دید، یک دستور try-finally همراه با یک متغیر پرچم است که نشان میدهد آیا بدنهی بند finally باید اجرا شود یا خیر. در سادهترین شکل خود (که از قبل نتوان آن را صرفاً با استفاده از یک بند except مدیریت کرد)، چیزی شبیه به این است:
cleanup_needed = True
try:
result = perform_operation()
if result:
cleanup_needed = False
finally:
if cleanup_needed:
cleanup_resources()
مانند هر کد مبتنی بر دستور try، این موضوع میتواند برای توسعه و بازبینی مشکلساز شود، زیرا ممکن است در نهایت کد راهاندازی و کد پاکسازی توسط بخشهایی از کد با طول دلخواه از هم جدا شوند.
ExitStack این امکان را فراهم میکند که در عوض، یک کالبک را برای اجرا در پایان یک دستور with ثبت کنید و سپس بعداً تصمیم بگیرید که از اجرای آن کالبک صرفنظر کنید:
from contextlib import ExitStack
with ExitStack() as stack:
stack.callback(cleanup_resources)
result = perform_operation()
if result:
stack.pop_all()
این امکان را فراهم میکند که رفتار پاکسازی موردنظر از همان ابتدا بهصورت صریح مشخص شود، به جای اینکه به یک متغیر پرچم جداگانه نیاز باشد.
اگر یک برنامه خاص زیاد از این الگو استفاده کند، میتوان آن را با استفاده از یک کلاس کمکی کوچک حتی بیشتر سادهتر کرد:
from contextlib import ExitStack
class Callback(ExitStack):
def __init__(self, callback, /, *args, **kwds):
super().__init__()
self.callback(callback, *args, **kwds)
def cancel(self):
self.pop_all()
with Callback(cleanup_resources) as cb:
result = perform_operation()
if result:
cb.cancel()
اگر پاکسازی منابع از قبل بهخوبی در یک تابع مستقل گنجانده نشده باشد، همچنان میتوان از قالب دکوراتوری ExitStack.callback() برای اعلام پاکسازی منابع از پیش استفاده کرد:
from contextlib import ExitStack
with ExitStack() as stack:
@stack.callback
def cleanup_resources():
...
result = perform_operation()
if result:
stack.pop_all()
به دلیل نحوهی کار پروتکل دکوراتور، یک تابع کالبک که به این روش تعریف شود، نمیتواند هیچ پارامتری بگیرد. در عوض، هر منبعی که باید آزاد شود، باید بهصورت متغیرهای بستار (closure variables) در دسترس باشد.
استفاده از مدیر زمینه بهعنوان دکوراتور تابع¶
ContextDecorator امکان استفاده از یک مدیر زمینه را هم در یک دستور with معمولی و هم بهعنوان دکوراتور تابع فراهم میکند.
برای مثال، گاهی اوقات مفید است که توابع یا گروههایی از دستورها را با یک گزارشگیر پوشش دهید تا بتواند زمان ورود و زمان خروج را پیگیری کند. به جای نوشتن هر دو دکوراتور تابع و مدیر زمینه برای این کار، ارثبری از ContextDecorator هر دو قابلیت را در یک تعریف واحد فراهم میکند:
from contextlib import ContextDecorator
import logging
logging.basicConfig(level=logging.INFO)
class track_entry_and_exit(ContextDecorator):
def __init__(self, name):
self.name = name
def __enter__(self):
logging.info('Entering: %s', self.name)
def __exit__(self, exc_type, exc, exc_tb):
logging.info('Exiting: %s', self.name)
نمونههای این کلاس را میتوان هم بهعنوان یک مدیر زمینه استفاده کرد:
with track_entry_and_exit('widget loader'):
print('Some time consuming activity goes here')
load_widget()
و همچنین بهعنوان دکوراتور تابع:
@track_entry_and_exit('widget loader')
def activity():
print('Some time consuming activity goes here')
load_widget()
توجه داشته باشید که هنگام استفاده از مدیرهای زمینه بهعنوان دکوراتورهای تابع، یک محدودیت اضافی وجود دارد: هیچ راهی برای دسترسی به مقدار بازگشتی __enter__() وجود ندارد. اگر به آن مقدار نیاز دارید، همچنان لازم است از یک دستور with صریح استفاده کنید.
مدیران زمینهی یکبارمصرف، بازاستفادهپذیر و بازورودپذیر¶
بیشتر مدیرهای زمینه بهگونهای نوشته شدهاند که فقط یک بار میتوان بهطور مؤثر در یک دستور with از آنها استفاده کرد. این مدیرهای زمینهی یکبارمصرف باید هر بار که استفاده میشوند، از نو ایجاد شوند؛ تلاش برای استفاده از آنها برای بار دوم باعث پرتاب یک استثنا میشود یا در غیر این صورت بهدرستی کار نخواهد کرد.
این محدودیت رایج به این معناست که معمولاً توصیه میشود مدیرهای زمینه را مستقیماً در سرآیند دستور with در محل استفاده ایجاد کنید (همانطور که در تمام مثالهای استفاده بالا نشان داده شده است).
پروندهها نمونهای از مدیران زمینهای هستند که عملاً یکبارمصرفاند، زیرا اولین دستور with پرونده را خواهد بست و از هرگونه عملیات ورودی/خروجی بیشتر با استفاده از آن شیء پرونده جلوگیری میکند.
مدیرهای زمینهای که با استفاده از @contextmanager ایجاد میشوند نیز مدیرهای زمینه یکبارمصرف هستند و اگر تلاش شود برای بار دوم از آنها استفاده شود، از yield نکردن تولیدگر زیربنایی شکایت خواهند کرد:
>>> from contextlib import contextmanager
>>> @contextmanager
... def singleuse():
... print("Before")
... yield
... print("After")
...
>>> cm = singleuse()
>>> with cm:
... pass
...
Before
After
>>> with cm:
... pass
...
Traceback (most recent call last):
...
RuntimeError: generator didn't yield
مدیرهای زمینهی بازورودپذیر¶
مدیرهای زمینه پیشرفتهتر ممکن است «بازورودپذیر» (reentrant) باشند. این مدیرهای زمینه نهتنها میتوانند در چندین دستور with استفاده شوند، بلکه ممکن است داخل یک دستور with که از قبل از همان مدیر زمینه استفاده میکند نیز استفاده شوند.
threading.RLock نمونهای از یک مدیر زمینهی بازورودپذیر است، همانطور که suppress()، redirect_stdout() و chdir() نیز چنین هستند. در اینجا یک نمونهی بسیار ساده از استفادهی بازورودپذیر آمده است:
>>> from contextlib import redirect_stdout
>>> from io import StringIO
>>> stream = StringIO()
>>> write_to_stream = redirect_stdout(stream)
>>> with write_to_stream:
... print("This is written to the stream rather than stdout")
... with write_to_stream:
... print("This is also written to the stream")
...
>>> print("This is written directly to stdout")
This is written directly to stdout
>>> print(stream.getvalue())
This is written to the stream rather than stdout
This is also written to the stream
نمونههای دنیای واقعی از بازورودپذیری (reentrancy) بیشتر احتمال دارد شامل چندین تابع باشند که یکدیگر را فراخوانی میکنند و بنابراین بسیار پیچیدهتر از این مثال باشند.
همچنین توجه داشته باشید که بازورودپذیر بودن، همان ایمنی در برابر نخها نیست. برای مثال، redirect_stdout() بهطور قطع نخایمن نیست، زیرا با اتصال sys.stdout به یک جریان دیگر، تغییری سراسری در وضعیت سیستم ایجاد میکند.
مدیران زمینه قابلاستفاده مجدد¶
مدیران زمینه «قابلاستفاده مجدد» از هر دو نوع مدیر زمینه یکبارمصرف و بازورودپذیر متمایز هستند (یا برای صراحت کامل، مدیران زمینه «قابلاستفاده مجدد، اما نه بازورودپذیر»، زیرا مدیران زمینه بازورودپذیر نیز قابلاستفاده مجدد هستند). این مدیران زمینه از استفاده شدن چندین بار پشتیبانی میکنند، اما اگر نمونه خاص مدیر زمینه از قبل در یک دستور with دربرگیرنده استفاده شده باشد، شکست میخورند (یا در غیر این صورت بهدرستی کار نمیکنند).
threading.Lock نمونهای از یک مدیر زمینه قابل استفاده مجدد، اما غیربازورود است (برای یک قفل بازورود، لازم است بهجای آن از threading.RLock استفاده شود).
مثال دیگری از یک مدیر زمینه قابلاستفاده مجدد، اما نه بازورودپذیر ، ExitStack است، زیرا هنگام خروج از هر دستور with، همهی کالبکهای ثبتشدهی کنونی را، صرفنظر از اینکه آن کالبکها کجا افزوده شده باشند، فراخوانی میکند:
>>> from contextlib import ExitStack
>>> stack = ExitStack()
>>> with stack:
... stack.callback(print, "Callback: from first context")
... print("Leaving first context")
...
Leaving first context
Callback: from first context
>>> with stack:
... stack.callback(print, "Callback: from second context")
... print("Leaving second context")
...
Leaving second context
Callback: from second context
>>> with stack:
... stack.callback(print, "Callback: from outer context")
... with stack:
... stack.callback(print, "Callback: from inner context")
... print("Leaving inner context")
... print("Leaving outer context")
...
Leaving inner context
Callback: from inner context
Callback: from outer context
Leaving outer context
همانطور که خروجی مثال نشان میدهد، استفاده مجدد از یک شیء پشته در چندین دستور with بهدرستی کار میکند، اما تلاش برای تودرتو کردن آنها باعث میشود پشته در پایان داخلیترین دستور with پاک شود، که بعید است رفتار مطلوبی باشد.
استفاده از نمونههای جداگانه ExitStack به جای استفاده مجدد از یک نمونه واحد، از آن مشکل اجتناب میکند:
>>> from contextlib import ExitStack
>>> with ExitStack() as outer_stack:
... outer_stack.callback(print, "Callback: from outer context")
... with ExitStack() as inner_stack:
... inner_stack.callback(print, "Callback: from inner context")
... print("Leaving inner context")
... print("Leaving outer context")
...
Leaving inner context
Callback: from inner context
Leaving outer context
Callback: from outer context