Python Decorators: මූලික සංකල්ප සහ භාවිතයන් | Python Decorator Basics SC Guide

Python Decorators: මූලික සංකල්ප සහ භාවිතයන් | Python Decorator Basics SC Guide

ආයුබෝවන් SC Guide එකත් එක්ක එකතු වෙලා ඉන්න ඔයාලා හැමෝටම!

අද අපි කතා කරන්න යන්නේ Python වල තියෙන හරිම වැදගත්, ඒ වගේම ටිකක් සංකීර්ණයි කියලා හිතෙන, ඒත් තේරුම් ගත්තොත් අපේ කෝඩ් ලිවීමේ හැකියාව තවත් ඉහළට ගෙනියන්න පුළුවන් "Decorators" ගැන. "Decorator" කියන වචනය ඇහෙනකොට සමහර විට ඔලුව අවුල් වෙන්න පුළුවන්. "මොකක්ද බන් මේක? අලංකරණයක්ද?" කියලා හිතෙන්නත් පුළුවන්. ඒත් මේක Python වල තියෙන නියම "magic" එකක් කිව්වොත් වැරැද්දක් නැහැ.

අපි හිතමුකෝ ඔයාලා ලොකු මෘදුකාංගයක් හදනවා කියලා. ඒකේ එක එක තැන්වල එකම විදියේ වැඩ කරන්න ඕනේ අවස්ථා එනවා. උදාහරණයක් විදියට, සියලුම Functions run වෙන්න කලින් යම් Check එකක් කරන්න ඕනේ, නැත්නම් ඒ Functions run වෙන්න ගත්ත වෙලාව මැනලා Log කරන්න ඕනේ. සාමාන්‍යයෙන් අපි මොකද කරන්නේ? හැම Function එක ඇතුලටම ඒ කෝඩ් එක දානවා, එහෙම නැත්නම් ඒ Function එකට කලින් වෙනම Function එකක් Call කරනවා. ඒත් එතකොට කෝඩ් එක අවුල් වෙනවා නේද? කියවන්න අමාරු වෙනවා, නඩත්තු කරන්නත් අමාරුයි.

අන්න ඒ වගේ වෙලාවට තමයි Decorators අපිට පිහිටට එන්නේ. Decorators කියන්නේ අපේ තියෙන functions වලට, ඒ functions වල මුල් කෝඩ් එක වෙනස් නොකරම, අමතර functionality එකක් එකතු කරන්න පුළුවන් සුපිරි ක්‍රමයක්. හරියට අපි Cake එකක් හදලා, ඒකේ රස වෙනස් නොකරම, ලස්සනට Decorate කරනවා වගේ වැඩක්.

හරි, එහෙනම් අපි බලමු මේ Decorators කොහොමද වැඩ කරන්නේ කියලා. මේක තේරුම් ගන්න කලින්, Python වල තියෙන මූලික සංකල්ප දෙකක් ගැන දැනගෙන ඉන්න එක වැදගත්. ඒ තමයි "Functions are First-Class Objects" සහ "Higher-Order Functions" කියන එක.

Functions are First-Class Objects (න්‍යායාත්මක පදනම)

ඔයාලා කවදා හරි හිතලා තියෙනවද Python වල Function එකක් කියන්නේ මොකක්ද කියලා? Python වල Functions කියන්නේ සරලවම "First-Class Objects". මෙයින් අදහස් වෙන්නේ, Functions වලට Python වල තියෙන අනිත් හැම Object එකකටම වගේ හැසිරෙන්න පුළුවන් කියන එක. හරියට int, str, list වගේම Functions වලටත් තමන්ගේම අනන්‍යතාවයක් තියෙනවා.

මේකෙන් මොකක්ද වෙන්නේ? මේකෙන් වෙන්නේ ඔයාලට Function එකක්:

  • Variable එකකට assign කරන්න පුළුවන්. (වෙනත් විචල්‍යයකට අගයක් වගේ පවරන්න පුළුවන්)
  • වෙනත් Function එකකට argument එකක් විදියට යවන්න පුළුවන්. (Parameter එකක් විදියට pass කරන්න පුළුවන්)
  • වෙනත් Function එකකින් return කරන්න පුළුවන්. (Function එකක ප්‍රතිඵලයක් විදියට යවන්න පුළුවන්)

මේක තේරුම් ගන්න අපි පොඩි උදාහරණයක් බලමු.


def wish_message(name):
    return f"ආයුබෝවන්, {name}!"

# 1. Function එකක් Variable එකකට assign කිරීම
greet = wish_message
print(greet("සඳුන්")) # Output: ආයුබෝවන්, සඳුන්!

# 2. Function එකක් argument එකක් විදියට යැවීම
def call_function_with_name(func, name):
    return func(name)

print(call_function_with_name(wish_message, "කසුන්")) # Output: ආයුබෝවන්, කසුන්!

# 3. Function එකක් return කිරීම
def create_greeter(greeting_type):
    if greeting_type == "formal":
        def formal_greet(name):
            return f"ගෞරවනීය {name} මහතාට/මහත්මියට සුභ පැතුම්!"
        return formal_greet
    else:
        def informal_greet(name):
            return f"හලෝ {name}!"
        return informal_greet

my_formal_greeter = create_greeter("formal")
print(my_formal_greeter("සමන්")) # Output: ගෞරවනීය සමන් මහතාට/මහත්මියට සුභ පැතුම්!

my_informal_greeter = create_greeter("informal")
print(my_informal_greeter("කමල්")) # Output: හලෝ කමල්!

දැන් ඔයාලට පැහැදිලියි නේද Python Functions වලට අනිත් Objects වගේම කොච්චර නිදහසක් තියෙනවද කියලා? මේක තමයි Decorators වලට පදනම.

Higher-Order Functions (ඉහළ පෙළේ ශ්‍රිතයන්ගේ බලය)

දැන් අපි කතා කරපු "First-Class Objects" සංකල්පය තේරුම් ගත්තා නම්, "Higher-Order Functions" (HOFs) තේරුම් ගන්න එක හරිම ලේසියි.

සරලවම කියනවා නම්, Higher-Order Function එකක් කියන්නේ,

  • වෙනත් Function එකක් Argument එකක් විදියට ගන්න Function එකක්, හෝ
  • වෙනත් Function එකක් Return කරන Function එකක්, හෝ
  • මේ දෙකම කරන Function එකක්.

පෙර උදාහරණයේ call_function_with_name සහ create_greeter කියන Functions දෙකම HOFs. මොකද call_function_with_name කියන්නේ තව Function එකක් argument එකක් විදියට ගන්න Function එකක්. ඒ වගේම create_greeter කියන්නේ තව Function එකක් return කරන Function එකක්.

Decorators කියන්නේ Higher-Order Functions වල විශේෂ ආකාරයක්. ඔවුන්ගේ ප්‍රධාන කාර්යය තමයි වෙනත් Function එකක් Argument එකක් විදියට අරගෙන, ඒකට අමතර functionality එකතු කරලා, අලුත් Function එකක් Return කරන එක. මේක හරියට "wrapper" එකක් වගේ වැඩක්.

Decorators as Syntactic Sugar (මොකක්ද මේ Decorator කියන්නේ?)

හරි, දැන් අපි මූලික අඩිතාලම දා ගත්තා. දැන් අපි කෙලින්ම Decorators වලට බහිමු.

Python වල Decorator එකක් කියන්නේ, වෙනත් Function එකක හැසිරීම (behavior) වෙනස් කිරීමට හෝ වැඩි දියුණු කිරීමට (enhance) භාවිතා කරන "Higher-Order Function" එකක්. මේක Function එකක් "wrap" කරනවා කියලා කියන්නත් පුළුවන්.

හැබැයි මේවා පාවිච්චි කරද්දී හරිම ලේසි Syntax එකක් තියෙනවා. ඒ තමයි @ සලකුණ. මේක නිසා තමයි Decorators වලට "Syntactic Sugar" කියලා කියන්නේ. මොකද මේ @ සලකුණ මගින් කෝඩ් එක වඩාත් කියවීමට පහසු (readable) සහ කෙටි (concise) කරනවා. මේක "අමුතු දෙයක්" වගේ පෙනුනත්, ඇත්තටම වෙන්නේ අපි වෙනම කරන්න ඕනේ Call එකක් Python වලින්ම Automatically කර දෙන එක.

අපි හිතමු අපිට my_function කියලා Function එකක් තියෙනවා කියලා. ඒ වගේම අපිට my_decorator කියලා Decorator Function එකක් තියෙනවා. සාමාන්‍යයෙන් Decorator එකක් නැතුව my_function එක Decorate කරනවා නම් මෙහෙම ලියන්න වෙනවා:


def my_decorator(func):
    def wrapper():
        print("Decorator එකට කලින්.")
        func()
        print("Decorator එකට පස්සේ.")
    return wrapper

def my_function():
    print("මම මගේ වැඩේ කරනවා.")

# සාමාන්‍ය ක්‍රමය (Decorator syntax නැතුව)
decorated_function = my_decorator(my_function)
decorated_function()

Output එක මේ වගේ වෙයි:


Decorator එකට කලින්.
මම මගේ වැඩේ කරනවා.
Decorator එකට පස්සේ.

මේකම අපි @ Syntactic Sugar එක පාවිච්චි කරලා ලියනවා නම්, කෝඩ් එක මේ වගේ හරිම ලස්සනට පේනවා:


def my_decorator(func):
    def wrapper():
        print("Decorator එකට කලින්.")
        func()
        print("Decorator එකට පස්සේ.")
    return wrapper

@my_decorator # මේක තමයි Syntactic Sugar එක
def my_function():
    print("මම මගේ වැඩේ කරනවා.")

my_function()

දැන් බලන්න, my_function එකට කලින් @my_decorator කියලා එකතු කරපු නිසා, අපි my_function() කියලා call කරපු ගමන්, ඇත්තටම call වෙන්නේ my_decorator(my_function) මගින් return කරන wrapper Function එක. මේක ඇතුලේ තමයි මුල් my_function එක call වෙන්නේ. නියමයි නේද? කෝඩ් එක කෙටි වුණා, කියවන්න ලේසි වුණා.

Practical Example: A Simple Timer Decorator (ප්‍රායෝගිකව කරලා බලමු: Timer Decorator එකක්!)

දැන් අපි ඉගෙන ගත්ත දේවල් වලින් ප්‍රයෝජන අරගෙන, අපි හදන Function එකක් run වෙන්න ගන්න වෙලාව මැනලා පෙන්නන "Timer Decorator" එකක් හදමු.


import time # වෙලාව මනින්න time module එක ඕනේ

def timer_decorator(func):
    """
    Function එකක් run වෙන්න ගන්න වෙලාව මනින Decorator එක.
    """
    def wrapper(*args, **kwargs): # ඕනෑම argument එකක් ගන්න පුළුවන් වෙන්න *args, **kwargs පාවිච්චි කරනවා
        start_time = time.time() # Function එක පටන් ගන්න වෙලාව
        result = func(*args, **kwargs) # මුල් Function එක run කරනවා
        end_time = time.time() # Function එක ඉවර වෙන වෙලාව
        execution_time = end_time - start_time
        print(f"'{func.__name__}' Function එක run වෙන්න ගත් කාලය: {execution_time:.4f} seconds.")
        return result # මුල් Function එකේ ප්‍රතිඵලය return කරනවා
    return wrapper

# දැන් අපි මේ Decorator එක අපේ Function වලට apply කරමු
@timer_decorator
def calculate_sum(n):
    """
    ලොකු සංඛ්‍යා ගණනක එකතුවක් ගණනය කරන Function එක.
    """
    total = 0
    for i in range(n):
        total += i
    return total

@timer_decorator
def greet_person(name, delay=1):
    """
    පුද්ගලයෙකුට සුභ පතන අතරතුර පොඩි delay එකක් තියෙන Function එක.
    """
    time.sleep(delay) # තත්පර කීපයක් නවත්වනවා
    return f"හලෝ, {name}! ඔයාට කොහොමද?"

# දැන් අපි මේ Functions call කරලා බලමු
print(f"\nSum: {calculate_sum(10000000)}")
print(f"Greeting: {greet_person('සඳුනි', 2)}")
print(f"Greeting (නෝමල්): {greet_person('කසුන්')}")

මේ කෝඩ් එක run කලාම ඔයාලට පෙනෙයි calculate_sum සහ greet_person කියන Functions දෙක run වෙන්න ගත්ත වෙලාව Log වෙනවා. මේකෙදි func(*args, **kwargs) කියන එකෙන් වෙන්නේ, අපේ calculate_sum(10000000) call එකේදී, 10000000 කියන argument එක func එකට pass කරන එක. මේ *args, **kwargs ගැන අපි තව දුරටත් පහලින් කතා කරමු.

Understanding the Wrapping Mechanism & Order of Decorators (වැදගත් දේවල් ටිකක්)

functools.wraps වල වැදගත්කම

අපි Decorator එකක් පාවිච්චි කරලා Function එකක් "wrap" කරනකොට පොඩි ප්‍රශ්නයක් මතුවෙන්න පුළුවන්. ඒ තමයි, Decorate කරපු Function එකේ නම (__name__), documentation string (__doc__), සහ වෙනත් metadata wrapper Function එකට copy වෙන එක. මේක නිසා Debug කරනකොට, නැත්නම් help() වගේ දේවල් පාවිච්චි කරනකොට අපිට මුල් Function එකේ විස්තර දකින්න ලැබෙන්නේ නැහැ. wrapper එකේ විස්තර තමයි පේන්නේ.

මේ ප්‍රශ්නය විසඳන්න Python වල functools කියන module එකේ wraps කියන decorator එකක් තියෙනවා. මේක අපේ wrapper Function එකට apply කලොත්, මුල් Function එකේ metadata අලුත් Function එකට copy වෙනවා.

අපේ Timer Decorator එක wraps එක්ක මේ වගේ වෙයි:


import time
from functools import wraps # මෙය import කර ගන්න

def timer_decorator(func):
    @wraps(func) # මෙන්න මෙතන wraps decorator එක apply කරනවා
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"'{func.__name__}' Function එක run වෙන්න ගත් කාලය: {execution_time:.4f} seconds.")
        return result
    return wrapper

@timer_decorator
def example_function(a, b):
    """
    උදාහරණයක් ලෙස සාදන ලද Function එක.
    """
    time.sleep(0.5)
    return a + b

print(example_function.__name__) # Output: example_function (wraps නැත්නම් output එක wrapper)
print(example_function.__doc__)  # Output: උදාහරණයක් ලෙස සාදන ලද Function එක. (wraps නැත්නම් output එක wrapper ගේ docstring)
print(example_function(5, 3))

දැන් බලන්න, example_function.__name__ සහ example_function.__doc__ වලට නිවැරදි අගයන් ලැබෙනවා. මේක හරිම වැදගත් දෙයක්, විශේෂයෙන්ම ලොකු project කරනකොට.

Order of Decorators (Decorators කිහිපයක් එකට පාවිච්චි කිරීම)

අපිට පුළුවන් එකම Function එකකට Decorators කිහිපයක් පාවිච්චි කරන්න. මේ වෙලාවේදී Decorators ක්‍රියාත්මක වෙන පිළිවෙල හරිම වැදගත්. Decorators ක්‍රියාත්මක වෙන්නේ පහළ ඉඳන් උඩට (bottom-up), ඒත් apply වෙන්නේ උඩ ඉඳන් පහළට (top-down).

අපි උදාහරණයක් බලමු:


def decorator_one(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Decorator One පටන් ගත්තා.")
        result = func(*args, **kwargs)
        print("Decorator One ඉවරයි.")
        return result
    return wrapper

def decorator_two(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Decorator Two පටන් ගත්තා.")
        result = func(*args, **kwargs)
        print("Decorator Two ඉවරයි.")
        return result
    return wrapper

@decorator_one
@decorator_two
def final_function(x):
    print(f"මම final_function, x = {x}")
    return x * 2

print(final_function(10))

මේකේ Output එක මේ වගේ වෙයි:


Decorator One පටන් ගත්තා.
Decorator Two පටන් ගත්තා.
මම final_function, x = 10
Decorator Two ඉවරයි.
Decorator One ඉවරයි.
20

ඔයාලට පේනවා නේද, @decorator_one මුලින්ම තියෙනවා, ඊට පස්සේ @decorator_two තියෙනවා. Python මේක interpret කරන්නේ මෙහෙමයි:


final_function = decorator_one(decorator_two(final_function))

ඒ කියන්නේ මුලින්ම decorator_two එක final_function එකට apply වෙනවා. ඒකෙන් return වෙන Function එකට decorator_one apply වෙනවා. Call කරනකොට, පිටතම Decorator එකේ සිට ඇතුලට ක්‍රියාත්මක වෙනවා. ඒ කියන්නේ decorator_one පටන් අරගෙන, ඊළඟට decorator_two පටන් අරගෙන, අන්තිමට final_function එක ක්‍රියාත්මක වෙනවා. ඊට පස්සේ ආපහු decorator_two ඉවර වෙලා decorator_one ඉවර වෙනවා.

*args සහ **kwargs වල වැදගත්කම

අපේ Decorator එකේ wrapper(*args, **kwargs) කියලා තිබුණා මතකද? මේ *args සහ **kwargs කියන්නේ Python වල Function එකකට විවිධ සංඛ්‍යාවකින් යුත් Positional Arguments (*args) සහ Keyword Arguments (**kwargs) යවන්න පුළුවන් කරන්න භාවිතා කරන ක්‍රමයක්.

  • *args: මේකෙන් වෙන්නේ Function එකකට යවන Positional Arguments ටික Tuple එකක් විදියට ගන්න එක.
  • **kwargs: මේකෙන් වෙන්නේ Function එකකට යවන Keyword Arguments ටික Dictionary එකක් විදියට ගන්න එක.

මේවා Decorators වලට අත්‍යවශ්‍යයි. මොකද Decorator එකක් හදනකොට අපිට කලින්ම දන්නේ නැහැ ඒක apply කරන Function එකට මොන වගේ arguments යයිද කියලා. ඉතින් *args සහ **kwargs පාවිච්චි කරලා, අපේ Decorator එක ඕනෑම argument set එකක් තියෙන Function එකකට වැඩ කරන්න පුළුවන් විදියට හදන්න පුළුවන්. මේකෙන් අපේ Decorator එක reusable වෙනවා.

Best Practices and Use Cases (කොහෙද මේවා පාවිච්චි කරන්නේ?)

Decorators කියන්නේ Python වල තියෙන හරිම බලවත් tool එකක්. මේවා ප්‍රධාන වශයෙන්ම භාවිතා වෙන්නේ "Cross-Cutting Concerns" කියලා හඳුන්වන දේවල් implement කරන්න. Cross-Cutting Concern එකක් කියන්නේ, එකම Functionality එකක් මෘදුකාංගයේ විවිධ කොටස් වලට අවශ්‍ය වෙන එක. මේවා Code එකේ Business Logic එකට කෙලින්ම සම්බන්ධ නැහැ, ඒත් මෘදුකාංගය නිවැරදිව වැඩ කරන්න ඒවා අත්‍යවශ්‍යයි.

Decorators පාවිච්චි කරන්න පුළුවන් පොදු අවස්ථා කිහිපයක් මෙන්න:

  1. Logging: Function එකක් Call කරද්දී හෝ ඉවර වෙද්දී ඒ ගැන විස්තර Log කරන්න. අපි හදපු Timer Decorator එකත් මේ ගණයට වැටෙනවා.
  2. Authentication/Authorization: යම් Function එකක් run කරන්න කලින් User කෙනෙක් Login වෙලාද, නැත්නම් ඒ Function එකට access තියෙනවද කියලා Check කරන්න. Flask, Django වගේ Web Frameworks වල මේවා බහුලව භාවිතා වෙනවා.
  3. Caching: Function එකක ප්‍රතිඵලය Cache කරන්න. ඒ කියන්නේ, එකම argument වලට Function එක Call කරොත්, ආයේ සැරයක් Function එක run කරන්නේ නැතුව, කලින් ගත්ත ප්‍රතිඵලයම දෙන්න. මේකෙන් Performance එක වැඩි වෙනවා.
  4. Validation: Function එකකට එන arguments වල දත්ත නිවැරදිද කියලා Check කරන්න.
  5. Rate Limiting: යම් Function එකක් යම් කාලයක් ඇතුලත Call කරන්න පුළුවන් වාර ගණන සීමා කරන්න. API Rate Limiting වලට මේවා හරිම ප්‍රයෝජනවත්.
  6. Measurement/Monitoring: අපි හදපු Timer Decorator වගේම Function එකක Performance metrics එකතු කරන්න.
  7. Retry Logic: යම් Function එකක් අසාර්ථක වුණොත්, ස්වයංක්‍රීයව නැවත Call කරන්න උත්සාහ කිරීම (Retrying).

ප්‍රධාන වාසි:

  • Code Reusability: එකම Decorator එකක් එක එක Functions වලට පාවිච්චි කරන්න පුළුවන්.
  • Cleaner Code: Business Logic එකෙන් "Cross-Cutting Concerns" වෙන් කරන නිසා කෝඩ් එක වඩාත් කියවීමට පහසු වෙනවා.
  • Easier Maintenance: යම් functionality එකක් වෙනස් කරන්න ඕනේ නම්, Decorator එක විතරක් වෙනස් කලොත් ඇති. හැම Function එකක්ම ගානේ ගිහින් වෙනස් කරන්න ඕනේ නැහැ.

Python වලට ආවේණික මේ Decorators කියන සංකල්පය, Function Programming වල තියෙන බලවත් බව හොඳටම පෙන්නුම් කරනවා. මේවා මුලින් තේරුම් ගන්න ටිකක් අමාරු වුණත්, ඒවා හරි විදියට පාවිච්චි කරන්න පටන් ගත්තොත්, ඔයාලගේ කෝඩ් එකේ ගුණාත්මකභාවය ලොකුවට වැඩි කරගන්න පුළුවන්.

අවසන් වශයෙන්...

හරි, අද අපි Python Decorators වල මූලික සංකල්ප සහ ඒවා කොහොමද ප්‍රායෝගිකව භාවිතා කරන්නේ කියලා කතා කලා. Functions are First-Class Objects කියන එකෙන් පටන් අරගෙන, Higher-Order Functions සහ Decorators කියන "Syntactic Sugar" එක දක්වා අපි ආවා. Timer Decorator එකක් හදලා, functools.wraps වල වැදගත්කම සහ Decorators වල ක්‍රියාත්මක වන පිළිවෙලත් අපි සාකච්ඡා කලා.

මේවා මුලින් අමාරු වුණත්, ටිකක් practice කරන්න. ඔයාලගේම පොඩි Decorators හදලා බලන්න. ඒක තමයි මේක තේරුම් ගන්න තියෙන හොඳම ක්‍රමය. මතක තියාගන්න, Python වල තියෙන මේ වගේ දේවල් හොඳට තේරුම් ගත්තොත්, ඔයාලට වඩාත් කාර්යක්ෂම, කියවීමට පහසු සහ නඩත්තු කිරීමට පහසු මෘදුකාංග හදන්න පුළුවන් වෙනවා.

මේ ලිපිය ගැන ඔයාලට මොනවා හරි ප්‍රශ්න තියෙනවා නම්, නැත්නම් මේ ගැන ඔයාලගේ අදහස් Comment Section එකේ දාලා යන්න. අපි උත්සාහ කරමු ඒවාට උත්තර දෙන්න. තවත් මේ වගේ වැදගත් දේවල් ගැන කතා කරන්න SC Guide එකත් එක්කම රැඳී සිටින්න! එහෙනම්, ආයෙත් ඉක්මනින්ම හම්බවෙමු! සුභ දවසක්!