یکی از رایج ترین کارهایی که باید در برنامه های خود انجام دهید کار با منابع خارجی است. این منابع می توانند فایل های موجود در حافظه رایانه شما یا اتصال باز به سرویس شخص ثالث در اینترنت باشند.

برای سادگی، برنامه ای را تصور کنید که یک فایل را باز می کند، چیزی روی آن می نویسد و سپس فایل را می بندد.

یکی از راه های پیاده سازی این برنامه در پایتون به صورت زیر است:

def main():
    my_file = open('books.txt', 'w')
    my_file.write('If Tomorrow Comes by Sidney Sheldon')
    my_file.close()


if __name__ == '__main__':
    main()

با توجه به اینکه این برنامه را با مجوزهای مناسب روی رایانه خود اجرا می کنید، فایلی به نام ایجاد می کند books.txt و بنویس If Tomorrow Comes by Sidney Sheldon در آن

این open() function یکی از توابع داخلی در پایتون است. می تواند یک فایل را از یک مسیر مشخص باز کند و یک شی فایل مربوطه را برگرداند.

یک شی فایل یا یک شیء فایل مانند، همانطور که اغلب نامیده می شود، یک روش مفید برای کپسوله کردن روش هایی مانند read()، write()، یا close().

این write() می توان از روش نوشتن/ارسال شی بایت مانند به یک جریان باز مانند یک فایل استفاده کرد.

هر زمان که یک منبع خارجی را باز می کنید، باید آن را در زمانی که دیگر مورد نیاز نیست، ببندید close() روش فقط این کار را انجام می دهد.

این برنامه کاربردی است اما یک نقص بزرگ دارد. اگر برنامه نتواند فایل را ببندد، تا زمانی که خود برنامه بسته نشود، باز می ماند.

ببینید، هر برنامه ای که روی رایانه خود اجرا می کنید مقدار محدودی از حافظه به آن اختصاص داده می شود. همه متغیرهایی که ایجاد می‌کنید یا منابع خارجی که از یک برنامه باز می‌کنید در حافظه اختصاص داده شده به آن توسط رایانه شما باقی می‌مانند.

اگر برنامه ای مانند این به باز کردن فایل های جدید بدون بستن فایل های قبلی ادامه دهد، حافظه اختصاص داده شده همچنان کوچک می شود.

در یک نقطه برنامه به ناچار حافظه اش تمام می شود و به طور ناخوشایندی از کار می افتد. این مشکل به عنوان نشت حافظه نامیده می شود.

یک راه برای جلوگیری از این اتفاق در پایتون استفاده از a است try...except...finally بیانیه.

def main():
    my_file = open('books.txt', 'w')

    try:
        my_file.write('If Tomorrow Comes by Sidney Sheldon')
    except Exception as e:
        print(f'writing to file failed: {e}')
    finally:
        my_file.close()


if __name__ == '__main__':
    main()

کد داخل finally بلوک بدون توجه به هر چیزی اجرا خواهد شد. بنابراین حتی اگر برنامه در عمل درست شکست بخورد، باز هم اجرا خواهد شد.

بنابراین، این مشکل را حل می کند، اما تصور کنید هر بار که می خواهید چیزی در یک فایل بنویسید، این خطوط کد را بنویسید.

خیلی قابل استفاده مجدد نیست شما باید خودتان را زیاد تکرار کنید و شانس نادیده گرفتن بخشی از آن را دارید if...except...finally نردبان نیز یک امکان است.

اینجاست که مدیران زمینه وارد می شوند.

مدیر زمینه در پایتون چیست؟

با توجه به واژه نامه پایتون، مدیر زمینه:

جسمی که محیطی را که در الف مشاهده می شود کنترل می کند with بیانیه با تعریف __enter__() و __exit__() مواد و روش ها.

ممکن است برای شما واضح نباشد. اجازه دهید مفهوم را با یک مثال توضیح دهم.

این with دستور در پایتون به شما امکان می دهد بلوکی از کد را در یک زمینه زمان اجرا که توسط یک شی مدیر متن تعریف شده است اجرا کنید.

هنگامی که اجرای بلوک کد به پایان رسید، شیء مدیریت متن مراقب از بین بردن منابع خارجی است که دیگر مورد نیاز نیست.

می توانید با استفاده از این برنامه را بازنویسی کنید with بیانیه به شرح زیر

def main():
    with open('books.txt', 'w') as my_file:
        my_file.write('If Tomorrow Comes by Sidney Sheldon')


if __name__ == '__main__':
    main()

از آنجا که open() تابع با a جفت می شود with در این مثال، تابع یک مدیر زمینه ایجاد می کند.

شی فایل در بافت بلوک کد فرورفته قابل دسترسی خواهد بود، به این معنی که شی فایل خارج از آن محدوده وجود ندارد.

این as کلمه کلیدی زمانی مفید است که می خواهید یک متغیر هدف را به یک شی برگشتی اختصاص دهید. اینجا my_file متغیر هدف است و شی فایل را نگه می دارد.

شما می توانید هر کاری را که می خواهید در بلوک تورفتگی کد انجام دهید و نگران بستن فایل نباشید.

زیرا هنگامی که اجرای بلوک کد به پایان رسید، مدیر زمینه فایل را به طور خودکار می بندد.

بنابراین، شما کل را بازنویسی کرده اید try...except...finally نردبان در دو خط کد با استفاده از with بیانیه و مدیر زمینه

اما چگونه این اتفاق می افتد؟ چگونه یک شی مدیر زمینه وظیفه تنظیم و بستن منابع را انجام می دهد؟

و آن ها کجا هستند __enter__() و __exit__() روش هایی که در واژه نامه اسناد پایتون خوانده اید؟

خب خیلی خوشحالم که پرسیدی 🙂

نحوه ایجاد یک مدیر زمینه سفارشی در پایتون

فیزیکدان نظری آمریکایی، ریچارد فاینمن، به قول معروف:

آنچه را که نمی توانم خلق کنم، نمی فهمم.

بنابراین، برای درک عملکردهای یک مدیر زمینه، باید خودتان یکی را ایجاد کنید و دو روش مجزا برای انجام آن وجود دارد.

پیشنهاد می‌کنیم بخوانید:  پایتون: تاپل ها را به دیکشنری تبدیل کنید

اولی یک رویکرد مبتنی بر مولد و دومی یک رویکرد مبتنی بر کلاس است. در این بخش هر دو را به شما آموزش خواهم داد.

اما قبل از آن، اجازه دهید یک مثال پیچیده را برایتان بیاورم که بیش از باز کردن و بستن فایل‌ها در پایتون است.

برنامه پایتون دیگری را تصور کنید که باید با پایگاه داده SQLite برای خواندن و نوشتن داده ها ارتباط برقرار کند.

می توانید آن برنامه را به صورت زیر بنویسید:

import sqlite3

create_table_sql_statement="CREATE TABLE IF NOT EXISTS books(title TEXT, author TEXT)"
insert_into_table_sql_statement = "INSERT INTO books VALUES ('If Tomorrow Comes', 'Sidney Sheldon'), ('The Lincoln Lawyer', 'Michael Connelly')"
select_from_table_sql_statement="SELECT * FROM books"


def main():
    database_path=":memory:"

    connection = sqlite3.connect(database_path)
    cursor = connection.cursor()

    try:
        cursor.execute(create_table_sql_statement)
        connection.commit()

        cursor.execute(insert_into_table_sql_statement)
        connection.commit()

        cursor.execute(select_from_table_sql_statement)

        print(cursor.fetchall())
    except Exception as e:
        print(f'read or write action to the database failed: {e}')
    finally:
        connection.close()


if __name__ == '__main__':
    main()

# [('If Tomorrow Comes', 'Sidney Sheldon'), ('The Lincoln Lawyer', 'Michael Connelly')]

این برنامه پایتون با پایگاه داده SQLite ارتباط برقرار می کند. سپس یک جدول جدید به نام کتاب با دو ایجاد می کند TEXT ستون های نامگذاری شده title و author.

سپس این برنامه اطلاعات مربوط به سه کتاب را روی میز ذخیره می کند، آنها را از پایگاه داده بازیابی می کند و داده های بازیابی شده را روی کنسول چاپ می کند.

همانطور که از خروجی از print() بیانیه، برنامه با موفقیت داده های داده شده را از پایگاه داده ذخیره و بازیابی کرده است.

سه پرس و جوی SQL در این برنامه وجود دارد که مسئول اقدامات پایگاه داده ای است که من توضیح دادم.

create_table_sql_statement="CREATE TABLE IF NOT EXISTS books(title TEXT, author TEXT)"
insert_into_table_sql_statement = "INSERT INTO books VALUES ('If Tomorrow Comes', 'Sidney Sheldon'), ('The Lincoln Lawyer', 'Michael Connelly')"
select_from_table_sql_statement="SELECT * FROM books"

من این سه خط کد را در بالای فایل برای حفظ آن نگه داشته ام main() عملکرد به پاک کننده بقیه برنامه پایگاه داده را راه اندازی کرده و کوئری ها را اجرا می کند.

پایتون با پشتیبانی عالی از پایگاه‌های داده SQLite، به لطف این، ارائه می‌شود sqlite3 ماژول هایی که روش های مفیدی مانند sqlite3.connect() روش.

این متد مسیر یک پایگاه داده را به صورت رشته ای می گیرد، سعی می کند ارتباط برقرار کند و در صورت موفقیت، یک عدد را برمی گرداند. Connection هدف – شی.

اگر بگذرید :memory: به جای مسیر فایل، برنامه یک پایگاه داده موقت روی حافظه کامپیوتر شما ایجاد می کند.

هنگامی که اتصال برقرار کردید، به یک نیاز دارید Cursor هدف – شی. شی مکان نما لایه ای از انتزاع است که برای اجرای پرس و جوهای SQL لازم است.

این cursor() روش کپسوله شده در داخل Connection شی یک مکان نما جدید را به پایگاه داده متصل برمی گرداند.

داخل a try بلاک کنید، می توانید سعی کنید هر پرس و جوی را که می خواهید با استفاده از آن اجرا کنید execute() یا executemany() مواد و روش ها.

    try:
        cursor.execute(create_table_sql_statement)
        connection.commit()

        cursor.execute(insert_into_table_sql_statement)
        connection.commit()

        cursor.execute(select_from_table_sql_statement)

        print(cursor.fetchall())

باید با connection.commit() روش هر بار که چیزی در پایگاه داده می نویسید. در غیر این صورت تغییرات از بین خواهند رفت.

داده های بازگردانده شده از یک پایگاه داده در داخل باقی می ماند cursor شی و می توانید با استفاده از cursor.fetchone() یا cursor.fetchall() مواد و روش ها.

در صورت شکست، except بلوک فعال خواهد شد. این finally بلوک بدون قید و شرط اجرا می شود و در پایان اتصال پایگاه داده را می بندد.

این خوب و کاربردی است، اما همانطور که قبلاً گفتم، خیلی قابل استفاده مجدد نیست و مستعد خطا است.

متأسفانه، یا در مورد ما خوشبختانه پایتون با یک مدیر زمینه داخلی برای مدیریت ارتباطات با پایگاه‌های داده SQLite ارائه نمی‌شود.

بنابراین، بیایید تلاش کنیم و ببینیم که آیا می‌توانیم یکی از آنها را خودمان تولید کنیم.

نحوه ایجاد یک مدیر زمینه مبتنی بر کلاس در پایتون

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

class Database:
    def __init__(self):
        pass

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

واضح است که اولین مورد سازنده کلاس است که هنوز هیچ پارامتری را قبول نکرده است. مسئولیت پذیرش مسیر پایگاه داده را بر عهده خواهد داشت:

import sqlite3

class Database:
    def __init__(self, path: str):
        self.path = path

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

این __enter__() متد وظیفه تنظیم منبع را بر عهده دارد. این جایی است که اتصال را برقرار می کنید و مکان نما را نمونه برداری می کنید:

import sqlite3

class Database:
    def __init__(self, path: str):
        self.path = path

    def __enter__(self):
        self.connection = sqlite3.connect(self.path)
        self.cursor = self.connection.cursor()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

با این حال شما نمی توانید دو شی را همزمان برگردانید، بنابراین باید نمونه خود کلاس را برگردانید.

در نهایت، __exit__() متد وظیفه بستن منبع خارجی مورد نظر را بر عهده دارد.

import sqlite3

class Database:
    def __init__(self, path: str):
        self.path = path

    def __enter__(self):
        self.connection = sqlite3.connect(self.path)
        self.cursor = self.connection.cursor()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
    	if exc_type is not None:
            print(f'an error occurred: {exc_val}')

        self.connection.close()

می توانید از این مدیریت زمینه در ارتباط با with عبارت در کد شما به صورت زیر است:

import sqlite3

create_table_sql_statement="CREATE TABLE IF NOT EXISTS books(title TEXT, author TEXT)"
insert_into_table_sql_statement = "INSERT INTO books VALUES ('If Tomorrow Comes', 'Sidney Sheldon'), ('The Lincoln Lawyer', 'Michael Connelly')"
select_from_table_sql_statement="SELECT * FROM books"


class Database:
    def __init__(self, path: str):
        self.path = path

    def __enter__(self):
        self.connection = sqlite3.connect(self.path)
        self.cursor = self.connection.cursor()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
    	if exc_type is not None:
            print(f'an error occurred: {exc_val}')


def main():
    with Database(':memory:') as db:
        db.cursor.execute(create_table_sql_statement)
        db.connection.commit()

        db.cursor.execute(insert_into_table_sql_statement)
        db.connection.commit()
        
        db.cursor.execute(select_from_table_sql_statement)
        
        print(db.cursor.fetchall())


if __name__ == '__main__':
    main()

# [('If Tomorrow Comes', 'Sidney Sheldon'), ('The Lincoln Lawyer', 'Michael Connelly')]

مشهود از خروجی print() فراخوانی تابع، برنامه با موفقیت داده های داده شده را از پایگاه داده ذخیره و بازیابی کرده است.

پیشنهاد می‌کنیم بخوانید:  روش اضافه کردن شناور به لیست در پایتون

بدون with بیانیه، Database فقط یک کلاس قدیمی است با این حال، لحظه ای که شما قرار داده است with در مقابل آن، سه روش وارد عمل می شوند.

این __init__() متد اولیه ساز است و مانند سایر روش های اولیه کلاس پایتون ساده کار می کند. این مسیر را به پایگاه داده می گیرد.

این __enter__() متد اتصال به پایگاه داده را تنظیم می کند و نمونه کلاس مدیریت متن را به متغیر هدف برمی گرداند. db در این مورد.

این متغیر هدف اکنون هم اتصال و هم اشیاء مکان نما را محصور می کند. شما می توانید به آنها دسترسی داشته باشید db.connection و db.cursor به ترتیب.

هنگامی که کد داخل with بلوک در حال اجرا به پایان می رسد، __exit__() متد اتصال فعال به پایگاه داده را اجرا و بسته می کند.

شما می توانید هر استثنایی را که ممکن است در حین اجرا در داخل رخ دهد مدیریت کنید __exit__() روش. اگر استثناء وجود داشته باشد، exc_type نوع استثنا را دارد، exc_val ارزش استثنا را دارد، exc_tb ردیابی را نگه می دارد.

اگر هیچ استثنایی وجود نداشته باشد، سه متغیر دارای مقدار خواهند بود None. من در این مقاله وارد جزئیات رسیدگی به استثنا نمی شوم زیرا بسته به آنچه شما با آن سر و کار دارید، ممکن است اشکال مختلفی به خود بگیرد.

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

این راه حل به مراتب بهتر از try...except...finally نردبانی که قبلا دیدید شما مجبور نیستید خودتان را تکرار کنید و احتمال خطای انسانی کمتر است.

نحوه ایجاد یک مدیر زمینه مبتنی بر ژنراتور در پایتون

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

از نظر نحوی، ژنراتورها تقریباً مشابه توابع عادی هستند، با این تفاوت که باید از آن استفاده کنید yield بجای return در یک ژنراتور

نوشتن یک مدیر زمینه مبتنی بر مولد به کد کمتری نیاز دارد اما مقداری از خوانایی خود را نیز از دست می دهد.

می توانید معادل مبتنی بر مولد کلاس را بنویسید Database مدیر زمینه به شرح زیر است:

import sqlite3
from contextlib import contextmanager

@contextmanager
def database(path: str):
    connection = sqlite3.connect(path)
    try:
        cursor = connection.cursor()
        yield {'connection': connection, 'cursor': cursor}
    except Exception as e:
        print(f'an error occurred: {e}') 
    finally:
        connection.close()

به جای یک کلاس، یک تابع مولد در اینجا دارید، بنابراین هیچ مقدار اولیه وجود ندارد. در عوض، خود تابع می تواند مسیر پایگاه داده را به عنوان پارامتر بپذیرد.

در یک try مسدود کردن، می توانید یک اتصال به پایگاه داده برقرار کنید، مکان نما را نمونه برداری کنید و هر دو شی را به کاربر برگردانید.

می توانید بنویسید yield connection, cursor برای برگرداندن دو شیء، اما در این صورت ژنراتور آنها را به صورت یک تاپل برمی گرداند.

من ترجیح می دهم از رشته ها به جای اعداد به عنوان ابزار دسترسی استفاده کنم و به همین دلیل است که این دو شی را در یک دیکشنری با کلیدهای توصیفی قرار داده ام.

این except بلوک در صورت استثنا اجرا خواهد شد. با خیال راحت هر استراتژی رسیدگی به استثنائات را که مناسب می دانید اجرا کنید.

این finally بلوک بدون قید و شرط اجرا می شود و اتصال باز را در پایان می بندد with مسدود کردن.

از آنجایی که وجود ندارد __enter__() یا __exit__() در هر صورت، شما باید ژنراتور را با آن تزئین کنید @contextmanager دکوراتور

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

استفاده از این مدیر زمینه با همتای مبتنی بر کلاس آن به جز حروف بزرگ نام آن یکسان است.

import sqlite3
from contextlib import contextmanager

create_table_sql_statement="CREATE TABLE IF NOT EXISTS books(title TEXT, author TEXT)"
insert_into_table_sql_statement = "INSERT INTO books VALUES ('If Tomorrow Comes', 'Sidney Sheldon'), ('The Lincoln Lawyer', 'Michael Connelly')"
select_from_table_sql_statement="SELECT * FROM books"


@contextmanager
def database(path: str):
    connection = sqlite3.connect(path)
    try:
        cursor = connection.cursor()
        yield {'connection': connection, 'cursor': cursor}
    except Exception as e:
        print(f'an error occurred: {e}') 
    finally:
        connection.close()


def main():
    database_path=":memory:"

    with database(database_path) as db:
        db.get('cursor').execute(create_table_sql_statement)
        db.get('connection').commit()

        db.get('cursor').execute(insert_into_table_sql_statement)
        db.get('connection').commit()

        db.get('cursor').execute(select_from_table_sql_statement)

        print(db.get('cursor').fetchall())


if __name__ == '__main__':
    main()

# [('If Tomorrow Comes', 'Sidney Sheldon'), ('The Lincoln Lawyer', 'Michael Connelly')]

از آنجا که db یک دیکشنری به جای یک شی است، در این مورد باید از بریس های مربعی یا همان استفاده کنید get() روش دسترسی به اتصال یا شی مکان نما.

نتیجه

مدیریت زمینه در پایتون یکی از آن موضوعاتی است که بسیاری از برنامه نویسان از آن استفاده کرده اند اما به وضوح درک نمی کنند.

امیدوارم این مقاله برخی از ابهامات شما را برطرف کرده باشد.

اگر می خواهید به من وصل شوید، من همیشه در لینکدین در دسترس هستم. با خیال راحت پیامی بفرستید و من خوشحال می شوم پاسخ دهم. همچنین، اگر فکر می‌کنید این مفید بود، مهارت‌های مرتبط من را در پلتفرم تأیید کنید.

تا مورد بعدی، مراقب باشید و به کاوش ادامه دهید.