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 صریح استفاده کنید.

همچنین ملاحظه نمائید

PEP 343 - دستور «with»

مشخصات، پیشینه و مثال‌های دستور 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