ویدیو آموزش context manager در پایتون
بعد از این ویدیو ببینید: ویدیو آموزش ماژول contextlib در پایتون
دستور with در پایتون یک ابزار کاملا مفید برای مدیریت صحیح منابع خارجی در برنامه های شما است. with به شما این امکان را می دهد که از context manager برای مدیریت خودکار مراحل راه اندازی و حذف منابع خارجی، استفاده کنید.
علاوه بر این، پروتکل context manager به شما امکان می دهد context manager خود را ایجاد کنید تا بتوانید نحوه برخورد با منابع سیستم را سفارشی کنید.
با این دانش، کدهای گویاتری می نویسید و از نشت منابع در برنامه های خود جلوگیری می کنید. عبارت with به شما کمک می کند تا برخی از الگوهای رایج مدیریت منابع را با انتزاع کردن عملکرد آنها پیاده سازی کنید و اجازه دهید آنها را در نظر بگیرید و مجددا استفاده کنید.
# مدیریت منابع در پایتون
یکی از مشکلات رایجی که در برنامه نویسی با آن مواجه خواهید شد نحوه مدیریت صحیح منابع خارجی مانند فایل ها، lockها و اتصالات شبکه است. گاهی اوقات، یک برنامه آن منابع را برای همیشه حفظ می کند، حتی اگر دیگر به آنها نیاز نداشته باشید. به این نوع مشکل، نشت حافظه(memory leak) می گویند، زیرا با هر بار ایجاد و باز کردن نمونه جدیدی از یک منبع خاص بدون بستن منبع موجود، حافظه موجود کاهش می یابد.
مدیریت صحیح منابع اغلب یک مشکل پیچیده است. هم به مرحله راه اندازی(setup) و هم به مرحله تخریب(teardown) نیاز دارد. مرحله دوم شما را ملزم به انجام برخی اقدامات پاکسازی میکند، مانند بستن یک فایل، آزاد کردن lock یا بستن اتصال شبکه. اگر فراموش کردید که این اقدامات پاکسازی را انجام دهید، برنامه شما منبع را زنده نگه می دارد. این ممکن است منابع ارزشمند سیستم مانند حافظه و پهنای باند شبکه را به خطر بیندازد.
به عنوان مثال، یک مشکل رایج که می تواند هنگام کار توسعه دهندگان با پایگاه های داده ایجاد شود، زمانی است که یک برنامه به ایجاد اتصالات جدید بدون انتشار یا استفاده مجدد از آنها ادامه می دهد. در این صورت، پایگاه داده backend میتواند پذیرش اتصالات جدید را متوقف کند. این ممکن است نیاز به یک ادمین داشته باشد که وارد سیستم شود و به صورت دستی آن اتصالات قدیمی را از بین ببرد تا پایگاه داده دوباره قابل استفاده باشد.
یکی دیگر از مشکلات رایج زمانی که توسعه دهندگان با فایل ها کار می کنند ظاهر می شود. نوشتن متن روی فایل ها معمولاً یک عملیات بافر است. این بدان معنی است که فراخوانی .write() روی یک فایل بلافاصله منجر به نوشتن متن در فایل فیزیکی نمی شود، بلکه به یک بافر موقت منجر می شود. گاهی اوقات، زمانی که بافر پر نیست و توسعه دهندگان فراموش می کنند که .close() را فراخوانی کنند، بخشی از داده ها برای همیشه از بین می روند.
احتمال دیگر این است که برنامه شما با خطاها یا استثناهایی مواجه شود که باعث میشود جریان کنترل از کدی که مسئول انتشار منبع موجود است دور بزند. در اینجا مثالی وجود دارد که در آن از open() برای نوشتن متنی در یک فایل استفاده میکنید:
file = open("hello.txt", "w")
file.write("Hello, World!")
file.close()
این پیاده سازی تضمین نمی کند که اگر یک استثنا در طول فراخوانی .write رخ دهد بسته خواهد شد. در این مورد، کد هرگز .close() را فرا نخواهد خواند و بنابراین ممکن است برنامه شما یک توصیفگر فایل را لو دهد.
در پایتون می توانید از دو رویکرد کلی برای مدیریت منابع استفاده کنید. می توانید کد خود را در موارد زیر بپیچید:
- ساختار try...finally
- ساختار with
روش اول کاملاً کلی است و به شما امکان می دهد کد راه اندازی و حذف را برای مدیریت هر نوع منبعی ارائه دهید. با این حال، کمی پرمخاطب است. همچنین، اگر اقدامات پاکسازی را فراموش کردید، چه؟
رویکرد دوم راه ساده ای را برای ارائه و استفاده مجدد از کد راه اندازی و پاک کردن ارائه می دهد. در این مورد، شما این محدودیت را خواهید داشت که عبارت with فقط با context managerها کار می کند. در دو بخش بعدی، نحوه استفاده از هر دو روش را در کد خود خواهید آموخت.
+ روش try...finally
کار با فایل ها احتمالاً رایج ترین نمونه مدیریت منابع در برنامه نویسی است. در پایتون، میتوانید از دستور try … finally برای مدیریت صحیح باز و بسته کردن فایلها استفاده کنید:
# Safely open the file
file = open("hello.txt", "w")
try:
file.write("Hello, World!")
finally:
# Make sure to close the file after using it
file.close()
در این مثال، شما باید با خیال راحت فایل hello.txt را باز کنید، که می توانید با قرار دادن فراخوانی open() در یک عبارت try … except انجام دهید. بعداً، وقتی سعی میکنید در فایل بنویسید، بند finally تضمین میکند که فایل به درستی بسته شده است، حتی اگر یک استثنا در طول فراخوانی .write() در عبارت try رخ دهد. هنگامی که منابع خارجی را در پایتون مدیریت می کنید، می توانید از این الگو برای مدیریت منطق راه اندازی و حذف استفاده کنید.
بلوک try در مثال بالا به طور بالقوه می تواند استثناهایی مانند AttributeError یا NameError را ایجاد کند. شما می توانید آن استثناها را در یک عبارت استثنا مانند این مدیریت کنید:
# Safely open the file
file = open("hello.txt", "w")
try:
file.write("Hello, World!")
except Exception as e:
print(f"An error occurred while writing to the file: {e}")
finally:
# Make sure to close the file after using it
file.close()
در این مثال، هر استثنای احتمالی را که ممکن است هنگام نوشتن روی فایل رخ دهد، مییابید. در موقعیتهای واقعی، برای جلوگیری از عبور بیصدا خطاهای ناشناخته، باید به جای Exception کلی از یک نوع استثنای خاص استفاده کنید.
+ روش with
دستور with پایتون یک context زمان اجرا ایجاد می کند که به شما امکان می دهد گروهی از دستورات را تحت کنترل یک context manager اجرا کنید. PEP 343 عبارت with را اضافه کرد تا امکان فاکتور گرفتن موارد استفاده استاندارد از try…finally عبارت را فراهم کند.
در مقایسه با ساختارهای سنتی try … finally، دستور with می تواند کد شما را واضح تر، ایمن تر و قابل استفاده مجدد کند. بسیاری از کلاس ها در کتابخانه استاندارد از دستور with پشتیبانی می کنند. یک مثال کلاسیک از آن open() است که به شما امکان می دهد با استفاده از with با اشیاء فایل کار کنید.
برای نوشتن یک دستور with، باید از نحو کلی زیر استفاده کنید:
with expression as target_var:
do_something(target_var)
شیء context manager از ارزیابی expression بعد از with نتیجه می شود. به عبارت دیگر، expression باید شیئی را برگرداند که پروتکل context manager را پیاده سازی می کند. این پروتکل از دو روش ویژه تشکیل شده است:
- __enter__() توسط دستور with فراخوانی می شود تا وارد context manager شود.
- __exit__() زمانی فراخوانی می شود که اجرا از بلوک کد with خارج شود.
تعیین کننده as اختیاری است. اگر یک target_var را با as ارائه دهید، مقدار بازگشتی فراخوانی __enter__() در شیء context manager به آن متغیر محدود می شود.
برخی از context managerها مقدار None را از __enter__() برمی گردانند زیرا آنها هیچ شیء مفیدی برای پس دادن به تماس گیرنده ندارند. در این موارد، تعیین target_var معنی ندارد.
وقتی پایتون با آن برخورد میکند، دستور with چگونه ادامه مییابد:
- عبارت expression را فراخوانی میکند تا context manager را بدست آورد.
- متدهای __enter__() و __exit__() در context manager را برای استفاده بعدی ذخیره کنید.
- .__enter__() را در context manager فراخوانی کنید و در صورت ارائه مقدار بازگشتی آن را به target_var متصل کنید.
- بلوک کد with را اجرا کنید.
- وقتی بلوک کد with به پایان رسید، __exit__() را در context manager فراخوانی کنید.
در این مورد، .__enter__()، معمولا کد راه اندازی را ارائه می دهد. دستور with یک دستور ترکیبی است که یک بلوک کد را شروع می کند، مانند یک دستور شرطی یا یک حلقه for. در داخل این بلوک کد، می توانید چندین دستور را اجرا کنید. به طور معمول، شما از بلوک کد with برای دستکاری target_var در صورت وجود استفاده می کنید.
پس از اتمام بلوک کد with متد __exit__() فراخوانی می شود. این روش معمولاً منطق حذف یا کد پاکسازی را فراهم می کند، مانند فراخوانی ()close در یک شی فایل باز. به همین دلیل است که عبارت with بسیار مفید است. این امر به دست آوردن و آزادسازی صحیح منابع را آسان می کند.
در اینجا نحوه باز کردن فایل hello.txt برای نوشتن با استفاده از عبارت with آورده شده است:
with open("hello.txt", mode="w") as file:
file.write("Hello, World!")
وقتی این را با دستور with اجرا می کنید، open() یک شی io.TextIOBase را برمی گرداند. این شی همچنین یک context manager است، بنابراین عبارت with متد __enter__() را فراخوانی می کند و مقدار بازگشتی آن را به فایل اختصاص می دهد. سپس می توانید فایل را در داخل بلوک کد with دستکاری کنید. هنگامی که بلوک به پایان می رسد، __exit__() به طور خودکار فراخوانی می شود و فایل را برای شما می بندد، حتی اگر یک استثنا در داخل بلوک with مطرح شود.
این ساختار کوتاهتر از try...finally است، اما همان طور که قبلاً دیدید، کلیتر است. شما فقط می توانید از عبارت with با اشیایی استفاده کنید که از پروتکل context manager پشتیبانی می کنند، در حالی که try...finally به شما امکان می دهد تا اقدامات پاکسازی را برای اشیاء دلخواه بدون نیاز به پشتیبانی از پروتکل context manager انجام دهید.
در پایتون 3.1 و نسخه های بعدی، عبارت with از چندین context manager پشتیبانی می کند. شما می توانید هر تعداد context manager را که با کاما از هم جدا شده اند ارائه دهید:
with A() as a, B() as b:
pass
این مانند دستورات with تودرتو اما بدون تودرتو کار می کند. این ممکن است زمانی مفید باشد که بخواهید همزمان دو فایل را باز کنید، اولی برای خواندن و دومی برای نوشتن:
with open("input.txt") as in_file, open("output.txt", "w") as out_file:
# Read content from input.txt
# Transform the content
# Write the transformed content to output.txt
pass
در این مثال می توانید کدی برای خواندن و تبدیل محتوای input.txt اضافه کنید. سپس نتیجه نهایی را در همان بلوک کد در output.txt می نویسید.
اگرچه استفاده از چندین context manager در یک واحد دارای یک اشکال است. اگر از این ویژگی استفاده می کنید، احتمالاً محدودیت طول خط خود را شکسته اید. برای حل این مشکل، باید از بک اسلش (\) برای ادامه خط استفاده کنید، بنابراین ممکن است نتیجه نهایی زشتی داشته باشید.
استفاده از with به شما امکان می دهد تا بیشتر منطق مدیریت منابع را انتزاعی کنید. به جای اینکه مجبور باشید یک try...finally صریح برای اجرا و پاکسازی بنویسید، دستور with کد راه اندازی و پاکسازی را برای شما انجام میدهد.
# استفاده از دستور with پایتون
تا زمانی که توسعه دهندگان پایتون عبارت with را در برنامه نویسی خود گنجانده اند، نشان داده شده است که این ابزار چندین مورد استفاده ارزشمند دارد. اکنون اشیاء بیشتر و بیشتری در کتابخانه استاندارد پایتون از پروتکل context manager پشتیبانی می کنند تا بتوانید از آنها در یک دستور with استفاده کنید.
در این بخش، نمونه هایی را کدنویسی می کنید که نحوه استفاده از دستور with را با چندین کلاس هم در کتابخانه استاندارد و هم در کتابخانه های شخص ثالث نشان می دهد.
+ استفاده از with در کنار فایلهای پایتون
تا کنون، از open() برای ارائه یک context manager و دستکاری فایل ها در ساختار with استفاده کرده اید. باز کردن فایل ها با استفاده از دستور with به طور کلی توصیه می شود زیرا تضمین می کند که توصیف کننده های فایل باز به طور خودکار پس از خروج جریان اجرا از بلوک کد with بسته می شوند.
همانطور که قبلاً دیدید، رایج ترین راه برای باز کردن فایل با استفاده از open () است:
with open("hello.txt", mode="w") as file:
file.write("Hello, World!")
در این مورد، از آنجایی که context manager فایل را پس از خروج از بلوک کد with می بندد، یک اشتباه رایج ممکن است به شرح زیر باشد:
>>> file = open("hello.txt", mode="w")
>>> with file:
... file.write("Hello, World!")
...
13
>>> with file:
... file.write("Welcome to Real Python!")
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.
اولین with جمله "Hello world!" را در hello.txt با موفقیت می نویسد. توجه داشته باشید که .write() تعداد بایت های نوشته شده در فایل را برمی گرداند یعنی 13. با این حال، وقتی می خواهید with دوم را اجرا کنید، یک ValueError دریافت می کنید زیرا فایل شما قبلا بسته شده است.
راه دیگری برای استفاده از دستور with برای باز کردن و مدیریت فایل ها استفاده از pathlib.Path.open():
>>> import pathlib
>>> file_path = pathlib.Path("hello.txt")
>>> with file_path.open("w") as file:
... file.write("Hello, World!")
...
13
Path کلاسی است که نشان دهنده مسیرهای مشخص به فایل های فیزیکی در رایانه شما است. فراخوانی .open() روی یک شی Path که به یک فایل فیزیکی اشاره می کند، درست مانند open() آن را باز می کند. بنابراین، Path.open() مشابه open() کار می کند، اما مسیر فایل به طور خودکار توسط شی Path که متد را روی آن فراخوانی می کنید، ارائه می شود.
از آنجایی که pathlib یک روش زیبا، ساده و پایتونیک برای دستکاری مسیرهای سیستم فایل ارائه می دهد، باید استفاده از Path.open() را در دستورات خود به عنوان بهترین تمرین در پایتون در نظر بگیرید.
در نهایت، هر زمان که یک فایل خارجی را بارگذاری می کنید، برنامه شما باید مشکلات احتمالی مانند فایل از دست رفته، دسترسی به نوشتن و خواندن و غیره را بررسی کند. در اینجا یک الگوی کلی وجود دارد که باید هنگام کار با فایل ها از آن استفاده کنید:
import pathlib
import logging
file_path = pathlib.Path("hello.txt")
try:
with file_path.open(mode="w") as file:
file.write("Hello, World!")
except OSError as error:
logging.error("Writing to file %s failed due to: %s", file_path, error)
در این مثال، عبارت with را در یک عبارت try ... finally قرار می دهید. اگر در حین اجرای with یک خطای OSError رخ دهد، از ورود به سیستم برای ثبت خطا با یک پیام کاربرپسند و توصیفی استفاده میکنید.
+ استفاده از with برای پیمایش در دایرکتوریهای پایتون
ماژول os تابعی به نام scandir() ارائه می دهد که یک تکرار کننده را بر روی اشیاء os.DirEntry مربوط به ورودی های یک دایرکتوری معین برمی گرداند. این تابع به ویژه برای ارائه عملکرد بهینه در هنگام عبور از یک ساختار دایرکتوری طراحی شده است.
یک فراخوانی به scandir() با مسیر یک دایرکتوری معین به عنوان آرگومان، یک تکرار کننده را برمی گرداند که از پروتکل context manager پشتیبانی می کند:
>>> import os
>>> with os.scandir(".") as entries:
... for entry in entries:
... print(entry.name, "->", entry.stat().st_size, "bytes")
...
Documents -> 4096 bytes
Videos -> 12288 bytes
Desktop -> 4096 bytes
DevSpace -> 4096 bytes
.profile -> 807 bytes
Templates -> 4096 bytes
Pictures -> 12288 bytes
Public -> 4096 bytes
Downloads -> 4096 bytes
در این مثال، یک عبارت with با os.scandir() را به عنوان تامین کننده context manager می نویسید. سپس روی ورودی های دایرکتوری انتخاب شده (".") تکرار می کنید و نام و اندازه آنها را روی صفحه چاپ می کنید. در این حالت، __exit__() متد scandir.close() را فراخوانی می کند تا تکرار کننده را ببندد و منابع به دست آمده را آزاد کند. توجه داشته باشید که اگر این را بر روی دستگاه خود اجرا کنید، بسته به محتوای دایرکتوری فعلی خود، خروجی متفاوتی دریافت خواهید کرد.
+ استفاده از with برای انجام محاسبات با دقت بالا در پایتون
برخلاف اعداد float داخلی، ماژول decima راهی را برای تنظیم دقت برای استفاده در یک محاسبه معین که شامل اعداد اعشاری است، ارائه میکند. دقت پیشفرض روی 28 مکان است، اما میتوانید آن را تغییر دهید تا نیازهای مشکل خود را برآورده کند. یک راه سریع برای انجام محاسبات با دقت سفارشی استفاده از localcontext() از اعشار است:
>>> from decimal import Decimal, localcontext
>>> with localcontext() as ctx:
... ctx.prec = 42
... Decimal("1") / Decimal("42")
...
Decimal('0.0238095238095238095238095238095238095238095')
>>> Decimal("1") / Decimal("42")
Decimal('0.02380952380952380952380952381')
در اینجا، localcontext() یک context manager ارائه می دهد که یک زمینه اعشاری محلی ایجاد می کند و به شما امکان می دهد محاسبات را با استفاده از یک دقت سفارشی انجام دهید. در بلوک کد with، باید .prec را روی دقت جدیدی که میخواهید استفاده کنید، تنظیم کنید، که در مثال بالا 42 مکان است. وقتی بلوک کد with به پایان رسید، دقت به مقدار پیشفرض خود یعنی 28 مکان بازنشانی میشود.
+ استفاده از with برای مدیریت Lock در multithreading پایتون
مثال خوب دیگری برای استفاده موثر از دستور with در کتابخانه استاندارد پایتون، Threading.Lock است. این کلاس یک قفل اولیه را برای جلوگیری از تغییر همزمان چندین رشته یک منبع مشترک در یک برنامه چند رشته ای فراهم می کند.
می توانید از یک شی Lock به عنوان context manager در یک عبارت with استفاده کنید تا به طور خودکار یک Lock داده شده را بدست آورید و آزاد کنید. به عنوان مثال، بگویید که باید از موجودی حساب بانکی محافظت کنید:
import threading
balance_lock = threading.Lock()
# Use the try ... finally pattern
balance_lock.acquire()
try:
# Update the account balance here ...
finally:
balance_lock.release()
# Use the with pattern
with balance_lock:
# Update the account balance here ...
دستور with در مثال دوم به طور خودکار یک Lock را زمانی که جریان اجرا وارد دستور شده و از آن خارج می شود، می گیرد و آزاد می کند. به این ترتیب، میتوانید روی آنچه واقعاً در کدتان اهمیت دارد تمرکز کنید و آن عملیات تکراری را فراموش کنید.
در این مثال، Lock در عبارت with یک منطقه محافظت شده به نام بخش بحرانی ایجاد می کند که از دسترسی همزمان به موجودی حساب جلوگیری می کند.
+ استفاده از with برای آزمایش استثناها در pytest پایتون
تا کنون، چند مثال را با استفاده از context manager که در کتابخانه استاندارد پایتون موجود است، کدگذاری کردهاید. با این حال، چندین کتابخانه شخص ثالث شامل اشیایی هستند که از پروتکل context manager پشتیبانی می کنند.
فرض کنید که در حال آزمایش کد خود با pytest هستید. برخی از توابع و بلوک های کد شما در شرایط خاص استثناهایی را ایجاد می کنند و شما می خواهید آن موارد را آزمایش کنید. برای انجام این کار، می توانید از pytest.raises() استفاده کنید. این تابع به شما امکان می دهد ادعا کنید که یک بلوک کد یا یک فراخوانی تابع یک استثنا را ایجاد می کند.
از آنجایی که pytest.raises() یک context manager ارائه می دهد، می توانید از آن در یک عبارت with مانند این استفاده کنید:
>>> import pytest
>>> 1 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> with pytest.raises(ZeroDivisionError):
... 1 / 0
...
>>> favorites = {"fruit": "apple", "pet": "dog"}
>>> favorites["car"]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'car'
>>> with pytest.raises(KeyError):
... favorites["car"]
...
در مثال اول، از pytest.raises() برای گرفتن ZeroDivisionError استفاده می کنید که عبارت 1/0 افزایش می دهد. مثال دوم از تابع برای گرفتن KeyError استفاده می کند که هنگام دسترسی به کلیدی که در یک دیکشنری وجود ندارد، ایجاد می شود.
اگر تابع یا بلوک کد شما استثنای مورد انتظار را اجرا نمیکند، pytest.raises() یک استثنای fail را ایجاد می کند:
>>> import pytest
>>> with pytest.raises(ZeroDivisionError):
... 4 / 2
...
2.0
Traceback (most recent call last):
...
Failed: DID NOT RAISE <class 'ZeroDivisionError'>
یکی دیگر از ویژگیهای جالب pytest.raises() این است که میتوانید یک متغیر هدف را برای بررسی استثنای اجرا شده مشخص کنید. به عنوان مثال، اگر می خواهید پیام خطا را تأیید کنید، می توانید کاری شبیه به این انجام دهید:
>>> with pytest.raises(ZeroDivisionError) as exc:
... 1 / 0
...
>>> assert str(exc.value) == "division by zero"
شما می توانید از تمام این ویژگی های pytest.raises () برای گرفتن استثناهایی که از توابع و بلوک کد خود ایجاد می کنید استفاده کنید. این یک ابزار جالب و مفید است که می توانید آن را در استراتژی آزمایش فعلی خود بگنجانید.
# ساخت context manager سفارشی در پایتون
شما قبلاً با context manager از کتابخانه استاندارد و کتابخانه های شخص ثالث کار کرده اید. هیچ چیز خاص یا جادویی در مورد open()، threading.Lock، decimal.localcontext()، یا موارد دیگر وجود ندارد. آنها فقط اشیایی را برمی گردانند که پروتکل context manager را پیاده سازی می کنند.
میتوانید با پیادهسازی متدهای خاص __enter__() و __exit__() در context managerها مبتنی بر کلاس خود، عملکرد مشابهی را ارائه دهید. همچنین می توانید با استفاده از دکوراتور contextlib.contextmanager از کتابخانه استاندارد و یک تابع مولد کدگذاری شده مناسب، context manager مبتنی بر تابع سفارشی ایجاد کنید.
به طور کلی، context manager و دستور with محدود به مدیریت منابع نیستند. آنها به شما این امکان را می دهند که کدهای راه اندازی و حذف مشترک را ارائه و مجدداً استفاده کنید. به عبارت دیگر، با context manager، می توانید هر جفت عملیاتی را که باید قبل و بعد از عملیات یا رویه دیگری انجام شود، انجام دهید، مانند:
- باز و بسته کردن
- قفل و آزاد کردن
- تغییر و بازنشانی کردن
- ساخت و حذف کردن
- ورود و خروج
- شروع و توقف
- اجرا و پاکسازی
می توانید کدی را برای مدیریت ایمن هر یک از این جفت عملیات در یک context manager ارائه دهید. سپس می توانید از آن context manager با عبارات with در سراسر کد خود استفاده مجدد کنید. این از خطاها جلوگیری می کند و کدهای تکراری را کاهش می دهد. همچنین API های شما را ایمن تر، تمیزتر و کاربرپسندتر می کند.
در دو بخش بعدی، اصول اولیه ایجاد context managerهای مبتنی بر کلاس و مبتنی بر فانکشن را خواهید آموخت.
# ساخت context manager مبتنی بر کلاس در پایتون
برای پیاده سازی پروتکل context manager و ایجاد context manager مبتنی بر کلاس، باید هر دو متد .__enter__() و __exit__() را به کلاس های خود اضافه کنید. بخش زیر نحوه عملکرد این متدها، آرگومانهایی که میگیرند و منطقی که میتوانید در آنها قرار دهید، خلاصه میکند:
__enter__(self):
این متد منطق راه اندازی را کنترل می کند و هنگام وارد کردن یک context جدید با with فراخوانی می شود. مقدار بازگشتی آن به متغیر with target محدود می شود.
__exit__(self, exc_type, exc_value, exc_tb):
این روش منطق پاکسازی را کنترل می کند و زمانی فراخوانی می شود که جریان اجرا از context خارج شود. اگر یک استثنا رخ دهد، exc_type، exc_value و exc_tb به ترتیب اطلاعات نوع استثنا، مقدار و ردیابی را نگه می دارند.
هنگامی که دستور with اجرا میشود، .__enter__() را در شیء context manager فراخوانی میکند تا به شما سیگنال دهد که وارد یک context اجرا جدید میشوید. اگر یک متغیر هدف را با مشخص کننده as ارائه دهید، مقدار بازگشتی .__enter__() به آن متغیر اختصاص داده می شود.
هنگامی که جریان اجرا از context manager خارج می شود، .__exit__() فراخوانی می شود. اگر هیچ استثنایی در بلوک کد with رخ ندهد، سه آرگومان آخر به .__exit__() روی None تنظیم می شوند. در غیر این صورت، آنها نوع، مقدار، و traceback مربوط به استثنا را در دست نگه می دارند.
اگر متد .__exit__() مقدار True را برگرداند، آنگاه هر استثنایی که در بلوک with رخ می دهد، بلعیده می شود و اجرا در عبارت بعدی بعد از with ادامه می یابد. اگر .__exit__() مقدار False را برگرداند، استثناها خارج از context منتشر می شوند. این نیز رفتار پیشفرض زمانی است که متد چیزی را بهصراحت برمیگرداند. شما می توانید از این ویژگی برای کپسوله کردن مدیریت استثنا در context manager استفاده کنید.
+ ساخت یک context manager ساده مبتنی بر کلاس در پایتون
در اینجا یک نمونه context manager مبتنی بر کلاس وجود دارد که هر دو متد .__enter__() و .__exit__() را پیاده سازی می کند. همچنین نشان می دهد که چگونه پایتون آنها را در یک ساختار with فراخوانی می کند:
>>> class HelloContextManager:
... def __enter__(self):
... print("Entering the context...")
... return "Hello, World!"
... def __exit__(self, exc_type, exc_value, exc_tb):
... print("Leaving the context...")
... print(exc_type, exc_value, exc_tb, sep="\n")
...
>>> with HelloContextManager() as hello:
... print(hello)
...
Entering the context...
Hello, World!
Leaving the context...
None
None
None
HelloContextManager هر دو .__enter__() و .__exit__() را پیاده سازی می کند. در .__enter__()، ابتدا پیامی را چاپ می کنید تا سیگنالی را به شما نشان دهد که جریان اجرا در حال ورود به یک context manager جدید است. سپس شما "Hello world!" را برمیگردانید. در __exit__()، شما پیامی را چاپ می کنید تا سیگنالی را به شما نشان دهد که جریان اجرا از context manager خارج می شود. شما همچنین محتوای سه آرگومان آن را چاپ کنید.
هنگامی که دستور with اجرا می شود، پایتون یک نمونه جدید از HelloContextManager ایجاد می کند و متد .__enter__() آن را فراخوانی می کند. شما این را می دانید زیرا Entering the context... را روی صفحه چاپ می کنید.
سپس پایتون بلوک کد with را اجرا می کند که hello را روی صفحه نمایش می دهد. توجه داشته باشید که hello مقدار بازگشتی .__enter__ را دارد.
هنگامی که جریان اجرا از بلوک کد with خارج می شود، پایتون .__exit__() را فرا می خواند. شما می دانید که چون Leaving the context روی صفحه نمایش شما چاپ می شود. خط نهایی در خروجی تایید می کند که سه آرگومان .__exit__() روی None تنظیم شده اند.
حال اگر در حین اجرای بلوک with یک استثنا رخ دهد چه اتفاقی می افتد؟ ادامه دهید و عبارت زیر را با عبارت with بنویسید:
>>> with HelloContextManager() as hello:
... print(hello)
... hello[100]
...
Entering the context...
Hello, World!
Leaving the context...
<class 'IndexError'>
string index out of range
<traceback object at 0x7f0cebcdd080>
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
IndexError: string index out of range
در این مورد، شما سعی می کنید مقدار شاخص 100 را در رشته "Hello, World!" بازیابی کنید. این یک IndexError ایجاد می کند و آرگومان های .__exit__() به صورت زیر تنظیم می شوند:
- exc_type کلاس استثنا، IndexError است.
- exc_value نمونه استثنا است.
- exc_tb شی traceback است.
این رفتار زمانی بسیار مفید است که بخواهید مدیریت استثنا را در context manager خود کپسوله کنید.
+ مدیریت خطا در context manager پایتون
به عنوان نمونه ای از کپسوله کردن مدیریت استثنا در یک context manager، فرض کنید که انتظار دارید IndexError رایج ترین استثنا در هنگام کار با HelloContextManager باشد. ممکن است بخواهید آن استثنا را در context manager مدیریت کنید تا مجبور نباشید کد رسیدگی به استثنا را در هر بلوک کد تکرار کنید. در این صورت می توانید کاری شبیه به این انجام دهید:
# exc_handling.py
class HelloContextManager:
def __enter__(self):
print("Entering the context...")
return "Hello, World!"
def __exit__(self, exc_type, exc_value, exc_tb):
print("Leaving the context...")
if isinstance(exc_value, IndexError):
# Handle IndexError here...
print(f"An exception occurred in your with block: {exc_type}")
print(f"Exception message: {exc_value}")
return True
with HelloContextManager() as hello:
print(hello)
hello[100]
print("Continue normally from here...")
در .__exit__()، بررسی می کنید که exc_value یک نمونه از IndexError است یا خیر. اگر چنین است، چند پیام آموزنده را چاپ می کنید و در نهایت با True برمی گردید. برگرداندن یک مقدار true امکان پذیرفتن استثنا و ادامه اجرای عادی بعد از بلوک کد with را فراهم می کند.
در این مثال، اگر IndexError رخ ندهد، متد None را برمیگرداند و استثنا منتشر میشود. با این حال، اگر میخواهید واضحتر باشید، میتوانید False را از خارج از بلوک if برگردانید.
اگر exc_handling.py را از خط فرمان خود اجرا کنید، خروجی زیر را دریافت می کنید:
$ python exc_handling.py
Entering the context...
Hello, World!
Leaving the context...
An exception occurred in your with block: <class 'IndexError'>
Exception message: string index out of range
Continue normally from here...
HelloContextManager اکنون میتواند استثناهای IndexError را که در بلوک کد with رخ میدهند، مدیریت کند. از آنجایی که وقتی یک IndexError رخ می دهد True را برمی گردانید، جریان اجرا در خط بعدی، درست پس از خروج از بلوک کد with ادامه می یابد.
ارسال نظر