שימוש ב ExitStack כדי לצמצם with-ים מקוננים

פקודת with והשימוש שלה בתוך with אחר.

בואו נכתוב פונקציה קצרה שמקבלת שמות של שני קבצים ומעתיקה את תוכן הקובץ הראשון לשני.

אני יודע יש פונקציות ספריה טובות לעשות את זה, אבל בשביל המשחק והטיפ בואו נהיה יצירתיים ונכתוב לבד.

 

אני חושב שניסיון ראשון עשוי להיראות כך:

 

def v1(fin_name, fout_name):

    with open(fin_name, encoding='utf8') as fin:

        with open(fout_name, 'w') as fout:

            for line in fin:

                fout.write(line)

 

 

כתיבת קוד הכתיבה והקריאה בתוך with היא רעיון טוב כיוון שאם תהיה שגיאה באחת הפעולות ופייתון יזרוק Exception באופן אוטומטי שני הקבצים ייסגרו לפני שעוזבים את הפונקציה.

הבעיה שהקוד נראה לא משהו בגלל ה with בתוך with, וככל שיהיו יותר קבצים שנצטרך לעבוד איתם סיכוי טוב שהמצב רק ילך ויתדרדר.

 

פיתרון קל למצב הזה אפשר למצוא במודול contextlib שמגיע כחלק מהספריה הסטנדרטית של פייתון.

הפונקציה ExitStack מתוך אותו מודול היא Context Manager שמחבר יחד Context Managers אחרים,

או בשפה פשוטה היא מאפשרת להשתמש בבלוק with אחד כדי לתפוס מספר דברים שיכולים להישבר.

 

הנה הקוד אחרי שהוספתי את ExitStack 

 

from contextlib import ExitStack

def v2(fin_name, fout_name):

    with ExitStack() as stack:

        fin  = stack.enter_context(open(fin_name, encoding='utf8'))

        fout = stack.enter_context(open(fout_name, 'w'))

        for line in fin:

            fout.write(line)

 

 

הפונקציה ExitStack מחזירה אוביקט שיש לו פונקציות __enter__ ו __exit__

וגם פונקציה בשם enter_context.

הפונקציה enter_context  מצפה בתורה לקבל משהו שיש לו __enter__ ו __exit__,

מפעילה מיד את ה __enter__ של מה שקיבלה,

וכשמגיע ה __exit__ של ה ExitStack הוא קורא באופן אוטומטי ל __exit__ של כל האוביקטים שעברו אליו דרך enter_context.

 

 

 

המאמר לקוח מהאתר https://www.tocode.co.il/