with در پایتون

نوشته از عرفان جهانشاهلو
1401/06/19
with در پایتون

کاربرد with در پایتون: در سال‌های اخیر پایتون به یکی از خفن‌ترین زبونای برنامه‌نویسی تبدیل شده . این زبون برنامه‌نویسی حاوی عبارات (Statements) مرموز و نکته داریه که کار برنامه‌نویسی رو راحت‌تر می‌کنن. یکی ازین عبارات، عبارت with هستش که خیلی در آموزش پایتون با ان برخورد میکنیم . ممکنه این سوال به وجود بیاد که with در پایتون اصلا چیه و چه وقتایی استفاده میشه؟ در پایتون with زمانی استفاده می‌شه که بخوایم با استفاده از الگوی استانداردی، به مدیریت منابع به صورت بهینه بپردازیم.

حالا این دقیقا یعنی چی؟ ادامه مقاله رو بخون تا بهتر متوجه بشی.

چرا از with استفاده کنیم؟

عبارت with در پایتون به برنامه نویسا کمک می‌کنه تا برخی از الگوهای رایج مدیریت منابع (Resource Management) رو به وسیله انتزاعی کردن کارکردشون و فراهم کردن امکان کنار گذاشتن و استفاده مجدد ازشون پیاده‌سازی کنن.
برای توضیح کارکرد with در پایتون بیاید بریم سراغ یکی از محبوب‌ترین کتابخونه‌های درونی پایتون به نام open. در کد زیر، فایل hello.txt توسط عبارت with در پایتون با حالت w که برای write هستش باز شده تا بتونیم مقادیری رو درون فایل بنویسیم.

with open (“hello.txt”, “w”) as file:

file.write(“Hello, world!”)

 

توصیه میشه فایل‌ها رو با استفاده از عبارت with در پایتون باز کنیم چرا که اینکار باعث میشه در انتهای برنامه ارتباط برنامه به طور خودکار با فایل قطع بشه و فایل بسته بشه. حالا اگه از with استفاده نکنیم چطوری باید کدشو بنویسیم؟

در واقع عبارت with باعث میشه کد ما در هسته پایتون به شکل زیر در بیاد و اجرا بشه:

file = open(“hello.txt”, “w”)
try:
file.write(“hello, world”)
finally:
file.close()

حالا تفاوت دو قطعه کدی که با with و بدون with نوشتیم چیه؟ در قطعه کدی که بدون with نوشتیم تضمین نمی‌شه که اگه هنگام اجراش خطایی بروز کنه ارتباط برنامه با فایل به طور خودکار قطع بشه، اما در قطعه کدی که با with نوشتیم تضمین میشه که ارتباط برنامه به طور خودکار با فایل چه در صورت اجرا چه در صورت بروز خطا قطع بشه. در واقع با استفاده از عبارت with در پایتون، پس از اتمام کار برنامه با منابع سیستم عامل، منابع به صورت خودکار آزاد میشن.

استفاده از with در پایتون

با استفاده از عبارت with در پایتون اعمالی مانند ارائه، راه‌اندازی مجدد و تخریب کدها تسهیل میشن. باید توجه کرد که با به کارگیری عبارت with در پایتون می‌توان تنها با Context Managerها کار کرد. به بیان ساده، عبارت with در پایتون یک Runtime 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 حاصل میشه. در حقیقت، این عبارت باید شی خاصی رو برگردونه که درش پروتکل مدیریت فضا پیاده‌سازی میشه. این پروتکل دارای دو متد مخصوص هستش:

with در پایتون

()__enter__: این متد برای ورود به فضای زمان اجرا در عبارت with فراخوانی میشه.
()__exit__: این متد زمانی فراخوانی میشه که اجرای بلوک کد with به اتمام می‌رسه.

لازم به ذکر است که Specifier (شناساگر) as انتخابی هستش. در صورتی که target_var همراه با as به کار بره، مقدار بازگشتی از فراخوانی __enter__() در شی Context Manager به آن متغیر محدود می‌شه.

عملکرد with در پایتون

زمانی که عبارت with در پایتون اجرا میشه، اعمال زیر رخ میدن:

1. فراخوانی Expression به منظور در اختیار گرفتن Context Manager
2. ذخیره‌سازی متدهای __enter__() و __exit__() مربوط به Context Manager برای استفاده در آینده
3. فراخوانی __enter__() در Context Manager و در صورت لزوم، نگاشت مقدار بازگشتی در target_var
4. اجرای بلوک کد مربوط به with
5. فراخوانی __exit__() در Context Manager پس از اتمام اجرای بلوک کد with

کاربردهای with در پایتون

امروزه توسعه دهندگان از عبارت with در پایتون به میزان زیادی استفاده می‌کنن. استفاده مداوم از این عبارت نشون میده که این ابزار چندین مورد استفاده ارزشمند داره. به عنوان مثال، عبارت with این اطمینان رو به وجود می‌آره که در صورت وقوع استثناها، فایل فرآیند جریان، فرآیندهای دیگر را بلوک نکنه و تنها خاتمه پیدا کنه. در حال حاضر اشیا متعددی در کتابخانه استاندارد پایتون از پروتکل مدیریت فضا پشتیبانی می‌کنن که می‌توان در یک دستور with این اشیا را به کار برد. در این بخش از آموزش with در پایتون، به نحوه استفاده از دستور with با چندین کلاس، هم در کتابخانه استاندارد و هم در کتابخانه‌های شخص ثالث پرداخته می‌شه. موارد استفاده و کاربردهای with در پایتون به شرح زیرن:

  •  کار با فایل‌ها
  •  پیمایش دایرکتوری‌ها (Traversing Directories)
  •  انجام محاسبات با دقت بالا
  •  مدیریت قفل‌ها در برنامه‌های چندنخی (Multithreaded Programs)
  •  آزمایش موارد استثنایی با Pytest

با توجه به اهمیت بالا و کاربردهای مختلف عبارت with در پایتون، در ادامه هر یک از این موارد استفاده شرح داده میشن:

کار با فایل‌ها

پیش از این از open() به منظور ارائه Context Manager و دستکاری فایل‌‌ها با عبارت with استفاده شد. به طور کلی، به کارگیری عبارت with در پایتون، یک روش پیشنهادی مرسوم برای باز کردن فایل به حساب می‌آد. چرا که در این رویکرد تضمین میشه که پس از خروج جریان اجرا از بلوک، توصیف‌کننده‌های فایل مجدداً به طور خودکار با کد بسته بشن. همون‌طور که پیش‌تر بهش اشاره شد، رایج‌ترین روش برای باز کردن فایل، استفاده از open()پیش‌ساخته (Built-in) هستش:

with open("hello.txt", mode="w") as file:
file.write("Hello, World!")

path:

یکی از راه‌های کاربردی دیگه برای باز کردن و مدیریت فایل‌ها، استفاده از pathlib.Path.open()‎ به حساب می‌آد که به صورت زیر هستش:

>>> import pathlib
>>> file_path = pathlib.Path("hello.txt")
>>> with file_path.open("w") as file:
... file.write("Hello, World!")
...

Path یه کلاسه که با کمک اون امکان نمایش مسیرهای مشخص به فایل‌های فیزیکی موجود در رایانه وجود داره. فراخوانی open()‎ روی یک شی Path که به یک فایل فیزیکی اشاره می‌کنه، دقیقاً مانند open()‎ فایل رو باز می‌کنه. در واقع، عملکرد Path.open()‎ مشابه ‎open()‎ هستش، اما مسیر فایل به طور خودکار توسط شی Path مخصوصی ارائه میشه که متد روی اون فراخوانی میشه.

با توجه به اینکه با کمک pathlib یک روش مطلوب و ساده برای دستکاری مسیرهای سیستم فایل ارائه می‌شه، بنابراین می‌توان استفاده از عبارت‌های with در پایتون رو به عنوان یه ابزار مناسب و کاربردی در برنامه نویسی در نظر گرفت. در نهایت، هر زمان که یه فایل خارجی بارگذاری بشه، لازمه مشکلات احتمالی گوناگونی مانند فایل از دست‌رفته، دسترسی به نوشتن و خواندن و سایر موارد به طور کامل مورد بررسی قرار بگیرن. در ادامه به الگوی کلی ضروری برای کار با فایل‌ها اشاره شده:

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)


پی

 

پیمایش دایرکتوری‌ها

 

در ماژول os تابعی به نام scandir()‎ ارائه میشه. این تابع یک تکرار کننده (Iterator) روی اشیا os.DirEntry مربوط به ورودی‌های ی دایرکتوری معین رو برمی‌گردونه. علاوه بر این، تابع scandir()‎ به طور ویژه برای ارائه عملکرد بهینه در هنگام عبور از یه ساختار دایرکتوری طراحی شده. با فراخوانی scandir()‎، همراه با مسیر، یه دایرکتوری معین به عنوان آرگومان و یک تکرار کننده نیز بازگردانده میشن که درش امکان پشتیبانی از پروتکل مدیریت فضا وجود دارد.

>>> 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

 

انجام محاسبات با دقت بالا

بر خلاف اعداد ممیز شناور پیش‌ساخته، ماژول اعشاری برای تنظیم دقت (Precision) در محاسبات معینی کاربرد داره که شامل اعداد اعشاری هستش. دقت به طور پیش‌فرض در مکان ۲۸ تنظیم شده. می‌توان بسته به نیازمندی‌ها، آن را برای اعمال مختلف تغییر داد. یک راه سریع برای انجام محاسبات با دقت سفارشی، استفاده از localcontext()‎ از Decimal هستش:

>>> 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')

 

برنامه‌های چند نخی

یکی دیگر از کاربردهای موثر عبارت with در پایتون Threading.Lock هستش. این کلاس یک قفل اولیه ایجاد می‌کنه. این قفل اولیه از تغییر همزمان یک منبع مشترک (توسط چندین رشته) در یک اپلیکیشن چندنخی جلوگیری می‌کنه. می‌توان یک شی Lock را به عنوان context Manager در یک عبارت With استفاده کرد تا بدین طریق، بدست آوردن و آزادسازی یک قفل خاص به طور خودکار انجام بشه. به عنوان مثال، کدهای زیر برای حفاظت از موجودی حساب بانکی کاربرد دارن:

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 ...

آزمایش موارد استثنایی با Pytest

تا این بخش از آموزش with در پایتون به برخی از کاربردهای مختلف 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"]
...

 

ایجاد Context Manager سفارشی

در بخش‌های پیشین از آموزش with در پایتون ، نحوه کار با Context Manager از کتابخانه استاندارد و کتابخانه‌های شخص ثالث بررسی شدن. همون‌طور که مشخصه هیچ ترفند جادویی یا ویژه‌ای در مورد کار با open()، decimal.localcontext()‎ ،threading.Lock و سایر موارد وجود نداره. در واقع، این توابع تنها اشیایی رو برمی‌گردونن که پروتکل مدیریت فضا در آن‌ها قابل پیاده‌سازی باشن. از سوی دیگر، با پیاده‌سازی متدهای خاص __enter__()‎. و __exit__()‎. در Context Managerهای مبتنی بر کلاس نیز می‌توان به عملکرد مشابهی دسترسی داشت.
علاوه بر این، با کمک دکوراتور contextlib.contextmanager از کتابخانه استاندارد و یک تابع مولد کدگذاری شده مناسب، امکان ایجاد Context Managerهای مبتنی بر تابع وجود داره. به طور کلی، کاربردهای عبارت with و Context Managerها تنها به مدیریت منابع محدود نمیشن، بلکه با به کارگیری اونا امکان ارائه و استفاده مجدد کدهای راه‌اندازی و تخریب رایج نیز وجود داره. به بیان ساده، با استفاده از Context Managerها، می‌توان جفت عملیاتی را انجام داد که باید قبل و بعد از عملیات یا رویه دیگری انجام بشن. در ادامه به برخی از این عملیات اشاره میشه:

  • باز کردن و بستن
  •  قفل و آزادسازی
  •  تغییر و بازنشانی
  •  ساخت و حذف
  •  ورود و خروج
  •  شروع و پایان
  •  راه‌اندازی و تخریب

برای اینکه جفت عملیات فوق به صورت ایمن مدیریت بشن، می‌توان کدهای خاصی را به کار برد. امکان استفاده مجدد آن در عبارت‌های With مختلف درون یک Context Manager وجود داره. علاوه بر اینکه معمولاً این ویژگی از بروز خطاهای مختلف و کدهای تکراری جلوگیری می‌کنه، باعث میشه APIها ایمن‌تر، خواناتر و کاربرپسندتر ارائه بشن.

سخن آخر

با توجه به اهمیت روزافزون برنامه نویسی به ویژه کدنویسی با پایتون در بازار کار کنونی، آموزش عبارت‌های خاص این زبان برنامه نویسی، از جمله یادگیری نحوه استفاده از عبارت with در پایتون می‌تونه به عنوان یک مهارت کلیدی و کاربردی برای برنامه نویسا بسیار کاربرد داشته باشه. بنابراین در این مقاله ابتدا به مفاهیم مربوط به عبارت with در پایتون پرداخته شد و در بخش‌های بعدی، کاربردهای مختلف این عبارت به طور اجمالی مورد بررسی قرار گرفت. برای آموزش حرفه‌ای عبارات و کلیدواژه‌های جادویی پایتون به آموزش پایتون ما مراجعه کنین و ازش استفاده کنین.

with در پایتون

نظرات

ثبت نظر جدید

این مطلب هنوز هیچ نظری نداره، تو اولیش رو بنویس :)