Python Context Managers (with statement) - සම්පත් නිදහස් කිරීමේ රහස | SC Guide

Python Context Managers (with statement) - සම්පත් නිදහස් කිරීමේ රහස | SC Guide

Python Context Managers: සම්පත් නිවැරදිව කළමනාකරණය කරන හැටි

ආයුබෝවන් කට්ටියටම! කොහොමද ඉතින්, වැඩේ ගොඩද? අද අපි කතා කරන්න යන්නේ Python වල තියෙන නියම tool එකක් ගැන. විශේෂයෙන්ම, ඔයාලා programs ලියද්දි resources, ඒ කියන්නේ files, network connections, database connections වගේ දේවල් එක්ක වැඩ කරනවා නම්, මේක නැතුවම බෑ. අද අපි කතා කරන්නේ Context Managers ගැන, ඒ කියන්නේ Python වල තියෙන with statement එක පාවිච්චි කරලා, ඔය resources හරියට handle කරන විදිහ ගැන.

ගොඩක් වෙලාවට අපිට resources open කරාට පස්සේ ඒවා close කරන්න අමතක වෙනවා. එහෙම වුණාම මොකද වෙන්නේ? Resource leaks ඇතිවෙනවා, program එක slow වෙනවා, සමහරවිට crash වෙන්නත් පුළුවන්. මේ වගේ කරදර නැතුව, පහසුවෙන්, ආරක්ෂාකාරීව resources manage කරගන්න තමයි Context Managers අපිට උදව් කරන්නේ. වැඩ කරන programmers ලාට මේක නියම tool එකක්, මොකද code එක clean වෙනවා, අඩු bugs ප්‍රමාණයක් එනවා, maintain කරන්නත් ලේසියි.

මොකක්ද මේ Context Manager එකක් කියන්නේ?

සරලවම කිව්වොත්, Context Manager එකක් කියන්නේ Python object එකක්. මේකෙන් අපිට පුළුවන් code block එකක් execute වෙන්න කලින් යම් ක්‍රියාවක් (resource එකක් open කිරීම වගේ) කරන්න, block එක execute වෙලා ඉවර වුණාට පස්සේ (හරි error එකක් ආවත්) තව ක්‍රියාවක් (resource එකක් close කිරීම වගේ) කරන්න. මේක හරියට පොතක් කියවන්න පුස්තකාලෙන් අරගෙන, කියවලා ඉවර වුණාම ආයෙත් භාර දෙනවා වගේ වැඩක්. පොත අරගන්න එක තමයි context එකට enter වෙන එක, පොත භාර දෙන එක තමයි context එකෙන් exit වෙන එක. මේ වැඩේ කරන්නේ with statement එක හරහා.

සාමාන්‍යයෙන් අපි file එකක් කියවනකොට මෙහෙම නේද ලියන්නේ?

file = open('my_data.txt', 'r')
try:
    content = file.read()
    print(content)
finally:
    file.close() # අනිවාර්යයෙන්ම close කරන්න ඕනේ

මෙහිදී try...finally block එකක් පාවිච්චි කරලා අපි file.close() කියන එක අනිවාර්යයෙන්ම call වෙන බවට සහතික කරනවා. එහෙත් මේක ටිකක් දිගයි, වරදින්නත් පුළුවන්. with statement එක පාවිච්චි කරනකොට මේ වැඩේම මෙහෙම කරන්න පුළුවන්:

with open('my_data.txt', 'r') as file:
    content = file.read()
    print(content)
# මෙතනට එනකොට file එක automatically close වෙලා ඉවරයි.

මොනවා හරි error එකක් ආවත්, with block එක ඉවර වුණ ගමන්ම file එක automatically close වෙනවා. මේක තමයි Context Manager එකක ප්‍රධානම වාසිය! open() function එක මෙතනදී Context Manager එකක් විදිහට ක්‍රියා කරනවා. ඒකට හේතුව තමයි ඒකේ __enter__ සහ __exit__ කියන special methods දෙක define කරලා තියෙන නිසා.

  • __enter__(self): with block එකට ඇතුල් වෙනකොට මේ method එක call වෙනවා. මේකෙන් තමයි resource එක initialize කරන්නේ. as keyword එකෙන් variable එකකට assign වෙන්නේ මේ method එක return කරන value එකයි.
  • __exit__(self, exc_type, exc_val, exc_tb): with block එකෙන් එලියට එනකොට මේ method එක call වෙනවා. Block එක සාර්ථකව execute වුණත්, error එකක් ආවත් මේක call වෙනවා. Resource එක clean up කරන්න (close කරන්න, release කරන්න) මේ method එක පාවිච්චි කරනවා. exc_type, exc_val, exc_tb කියන්නේ exception එකක් ආවොත් ඒකේ විස්තරයි. Exception එකක් handle කරලා, True return කරොත් exception එක suppress වෙනවා. නැත්නම් ඒක continue වෙනවා.

තමන්ගේම Context Manager එකක් හදමු

දැන් අපි බලමු අපිටම Context Manager එකක් හදාගන්නේ කොහොමද කියලා. අපි simple timer එකක් හදමු. මේකෙන් අපිට පුළුවන් code block එකක් run වෙන්න කොච්චර වෙලාවක් යනවාද කියලා බලන්න.

import time

class MyTimer:
    def __enter__(self):
        self.start_time = time.time()
        print("Timer started...")
        return self  # 'as' variable එකට යවන්න පුළුවන්

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.end_time = time.time()
        duration = self.end_time - self.start_time
        print(f"Timer stopped. Elapsed time: {duration:.4f} seconds")

        if exc_type:
            print(f"An error occurred during execution: {exc_val}")
            # Return False to propagate the exception
            return False
        # Return True to suppress the exception (if you want to handle it inside __exit__)
        # For this timer, we usually don't suppress, so no explicit return True needed.

# Timer එක පාවිච්චි කරන හැටි
print("\n--- Example 1: No error ---")
with MyTimer() as timer:
    print("Doing some heavy computation...")
    time.sleep(2.5) # තත්පර 2.5ක් නිදි කරවනවා
    print("Computation finished.")

print("\n--- Example 2: With an error ---")
try:
    with MyTimer() as timer:
        print("Doing some risky operation...")
        time.sleep(1)
        raise ValueError("Something went wrong inside the block!") # error එකක් ඇති කරනවා
        print("This line will not be executed.")
except ValueError as e:
    print(f"Caught the error outside: {e}")

print("\n--- Example 3: Multiple operations with one timer ---")
with MyTimer() as t:
    for i in range(3):
        print(f"Operation {i+1} starting...")
        time.sleep(0.5)
        print(f"Operation {i+1} finished.")

print("All operations done.")

මේ code එකෙන් පැහැදිලි වෙනවා MyTimer class එක කොහොමද Context Manager එකක් විදිහට ක්‍රියා කරන්නේ කියලා. __enter__ method එකෙන් timer එක පටන් ගන්නවා, __exit__ method එකෙන් timer එක නවත්වලා ගත වූ කාලය print කරනවා. Error එකක් ආවත්, __exit__ method එක අනිවාර්යයෙන්ම run වෙනවා, ඒ නිසා cleanup එක හරියට වෙනවා. අපේ timer එකේදී අපි exception එක handle නොකර propagate කරනවා (return False implicit).

contextlib module එකේ Magic එක

අපි දැක්කා class එකක් පාවිච්චි කරලා Context Manager එකක් හදන හැටි. ඒත් Python වල contextlib කියන module එකේ තියෙනවා @contextmanager decorator එක. මේක පාවිච්චි කරලා අපිට function එකකින්ම Context Manager එකක් හදන්න පුළුවන්. මේක ගොඩක් ලේසියි, code එකත් ලස්සනයි.

මේකට yield keyword එක පාවිච්චි කරනවා. yield keyword එකට කලින් තියෙන code එක __enter__ method එකේ වගේ run වෙනවා. yield කරන value එක තමයි as variable එකට assign වෙන්නේ. yield එකෙන් පස්සේ තියෙන code එක __exit__ method එකේ වගේ run වෙනවා.

අපි අර කලින් හදපු Timer එකම @contextmanager පාවිච්චි කරලා හදමු.

import time
from contextlib import contextmanager

@contextmanager
def my_function_timer():
    start_time = time.time()
    print("Timer (function-based) started...")
    try:
        yield # මෙතනින් තමයි control එක 'with' block එකට යන්නේ
    except Exception as e:
        print(f"An error occurred in the block: {e}")
        # You can choose to re-raise or suppress the exception here
        # raise # To re-raise the exception
    finally:
        end_time = time.time()
        duration = end_time - start_time
        print(f"Timer (function-based) stopped. Elapsed time: {duration:.4f} seconds")

# Timer එක පාවිච්චි කරන හැටි
print("\n--- Example 1 (function-based): No error ---")
with my_function_timer():
    print("Doing some light work...")
    time.sleep(1.2)
    print("Light work finished.")

print("\n--- Example 2 (function-based): With an error ---")
try:
    with my_function_timer():
        print("Doing another risky operation...")
        time.sleep(0.5)
        raise RuntimeError("Oops! Another problem!")
except RuntimeError as e:
    print(f"Caught the runtime error outside: {e}")

print("\n--- Example 3 (function-based): Using the yielded value ---")
@contextmanager
def open_my_file(filename, mode):
    print(f"Opening file: {filename} in mode {mode}")
    file = None
    try:
        file = open(filename, mode)
        yield file # file object එක 'as' variable එකට යවනවා
    except FileNotFoundError:
        print(f"Error: File '{filename}' not found!")
    finally:
        if file:
            print(f"Closing file: {filename}")
            file.close()

# Usage:
print("\n--- Example 4 (function-based): File handling ---")
with open_my_file('example.txt', 'w') as f:
    if f:
        f.write("Hello, Context Manager!\n")
        f.write("This is a test.\n")

with open_my_file('example.txt', 'r') as f:
    if f:
        content = f.read()
        print(f"File content:\n{content}")

with open_my_file('non_existent.txt', 'r') as f:
    if not f:
        print("File object was not returned due to an error.")

දැන් බලන්න, @contextmanager decorator එක කොච්චර ලේසිද කියලා. try...finally block එකක් පාවිච්චි කරලා, yield අවට cleanup code එක ලියන්න පුළුවන්. yield කරන value එක තමයි with statement එකේ as variable එකට ලැබෙන්නේ. File example එකෙන් ඒක පැහැදිලියි.

හොඳම Practices සහ බග් අවම කරගන්න

Context Managers පාවිච්චි කිරීමෙන් අපිට ගොඩක් වාසි ගන්න පුළුවන්. හැබැයි මේවා පාවිච්චි කරද්දි මතක තියාගන්න ඕන දේවල් ටිකක් තියෙනවා:

  • සම්පත් කළමනාකරණය (Resource Management):
    • Files: File open කරලා read/write කරද්දි අනිවාර්යයෙන්ම with open(...) පාවිච්චි කරන්න. මේක තමයි standard practice එක.
    • Locks: Multi-threading programs වලදී race conditions වළක්වාගන්න locks (e.g., threading.Lock) පාවිච්චි කරනවා. with lock: කියන එකෙන් lock එක acquire කරලා, block එකෙන් එලියට එනකොට release කරනවා.
    • Database Connections: Database connections manage කරන්නත් Context Managers පාවිච්චි කරන්න පුළුවන්. Connection එක open කරලා, queries run කරලා, commit/rollback කරලා, connection එක close කරන්න මේක හොඳයි.
    • Network Connections: Sockets වගේ network connections handle කරන්නත් use කරන්න පුළුවන්.
    • Temporary Resources: තාවකාලිකව directory එකක් හදලා, ඒක ඇතුලේ වැඩ කරලා, වැඩේ ඉවර වුණාම ඒක delete කරන්න වගේ දේවල් වලටත් contextlib.TemporaryDirectory වගේ දේවල් පාවිච්චි කරන්න පුළුවන්.
  • Error Handling:
    • __exit__ method එකට exception details එන නිසා, block එක ඇතුලේ error එකක් ආවත් cleanup code එක run වෙනවා. අවශ්‍ය නම්, __exit__ method එකෙන් True return කරලා exception එක suppress කරන්න පුළුවන්. (හැබැයි මේක හැමවිටම කරන්න හොඳ නෑ, exception එක handle කරන්න බැරි නම් propagate කරන්න දෙන්න ඕනේ.)
  • Nested with Statements:
    • ඔයාලට එකට files කිහිපයක් open කරන්න ඕන නම්, with statements nested කරන්න පුළුවන්. Python 3.1 වල ඉඳන් එක line එකක ලියන්නත් පුළුවන්.
  • Code Readability:
    • Context Managers පාවිච්චි කරනකොට code එක කියවන්න ලේසියි. Resource එකක් acquire කරලා, block එක ඇතුලේ පාවිච්චි කරලා, block එකෙන් එලියට එනකොට release වෙන බව පැහැදිලියි.
# Nested
with open('file1.txt', 'r') as f1:
    with open('file2.txt', 'w') as f2:
        content = f1.read()
        f2.write(content)

# Python 3.1+
with open('file1.txt', 'r') as f1, \
     open('file2.txt', 'w') as f2:
    content = f1.read()
    f2.write(content)

එහෙනම් ඉතින්

හරි, අද අපි Python Context Managers ගැන සෑහෙන්න දේවල් කතා කළා. with statement එක, __enter__ සහ __exit__ methods, contextlib module එකේ @contextmanager decorator එක වගේ ගොඩක් වැදගත් concepts අපි cover කළා. ඔයාලට දැන් තේරෙනවා ඇති resources manage කරන්න මේක කොච්චර වැදගත්ද කියලා.

මේ concepts හරියට තේරුම් අරගෙන practical projects වල apply කරන එක තමයි වැදගත්ම දේ. එතකොට ඔයාලගේ code එක වඩාත් robust වෙනවා, maintain කරන්න ලේසි වෙනවා, resource leaks වගේ කරදරත් නැති වෙනවා.

අද කතා කරපු දේවල් ගැන ඔයාලට මොනවද හිතෙන්නේ? ඔයාලා Context Managers පාවිච්චි කරන්නේ මොන වගේ අවස්ථාවලටද? කමෙන්ට් සෙක්ෂන් එකේ අපිට කියන්න. අලුත් අදහසක්, ප්‍රශ්නයක් තියෙනවා නම් ඒකත් mention කරන්න. මේ වගේ තවත් useful Python tips ගැන දැනගන්න අපිත් එක්ක එකතු වෙලා ඉන්න!

ඊළඟ ලිපියකින් හමුවෙමු! වැඩේ ගොඩ!