Python වල *args, **kwargs: ඕනෑම ගානකට Arguments දාමු! (SC Guide)

ආයුබෝවන් කට්ටියට! Python වල Functions වලට අලුත් බලයක්: *args සහ **kwargs

අද අපි කතා කරමු Python programming වලදී අපිට නැතුවම බැරි, ඒ වගේම අපේ code එකට නම්‍යශීලී බවක් (flexibility) ගේන සුපිරි concepts දෙකක් ගැන. ඒ තමයි Functions වලට අපිට ඕනෑම ගානක Arguments දෙන්න පුළුවන් කරන *args සහ **kwargs කියන magic කරන විදිය. මුලින්ම මේ නම් ටික අහලා චුට්ටක් අවුල් වුණාට, මේක ඉගෙන ගත්තම ඔයාලගේ code එක කොච්චර නම් නම්‍යශීලී වෙනවද කියලා හිතාගන්න බැරි වෙයි!

අපි හැමෝම දන්නවා functions කියන්නේ software development වල backbone එක කියලා. වැඩක් organize කරලා, නැවත නැවත පාවිච්චි කරන්න (reusability) පුළුවන් විදියට code ලියන්න functions අත්‍යාවශ්‍යයි. හැබැයි සාමාන්‍යයෙන් අපි functions හදනකොට, ඒකට බාරගන්න arguments ගාන කලින්ම define කරනවා නේද? උදාහරණයක් විදියට, අංක දෙකක් එකතු කරන function එකක් හදනකොට අපි ඒකට parameters දෙකක් දානවා.

def add_two_numbers(num1, num2):
    return num1 + num2

print(add_two_numbers(5, 10)) # Output: 15

ඒත් හිතන්න, දැන් ඔයාලට numbers තුනක්, හතරක්, නැත්නම් ඕනෑම ගානක් එකතු කරන්න වුණොත්? අලුත් function එකක් ලියන්න වෙනවා නේද? එහෙම නැත්නම් arguments ගාන කොච්චරද කියලා කලින්ම දන්නේ නැති scenario එකක් ආවොත්? ඊට පස්සේ මොකද කරන්නේ? ඔන්න ඔය වගේ වෙලාවලට තමයි *args සහ **kwargs වැඩේට එන්නේ. මේවාට කියනවා "Arbitrary Arguments" කියලා. ඒ කියන්නේ අපිට ඕන තරම් arguments ගානක් function එකකට දෙන්න පුළුවන්. හරි, අපි වැඩි කතා නැතුව බලමු මේවා වැඩ කරන්නේ කොහොමද කියලා.

මොකක්ද මේ Argument Packing & Unpacking?

*args සහ **kwargs තේරුම් ගන්න කලින්, අපි තේරුම් ගන්න ඕන concept එකක් තමයි "Argument Packing" සහ "Argument Unpacking" කියන එක. මේක ඉතාම සරලයි.

Packing: මේකේදී සිද්ධ වෙන්නේ, අපි function එකකට arguments ගණනක් දුන්නාම, ඒ හැම එකක්ම Python විසින් එකතු කරලා (pack කරලා) තනි data structure එකක් (tuple එකක් හෝ dictionary එකක්) විදියට function එක ඇතුළට දෙන එක. මේක හරියට බඩු ගොඩක් එකතු කරලා එක පෙට්ටියකට දානවා වගේ වැඩක්.

Unpacking: මේක තමයි packing එකේ අනිත් පැත්ත. අපි data structure එකක (list, tuple, dictionary වගේ) තියෙන values ටික function එකක arguments විදියට දෙන්න ඕන කරන වෙලාවට, ඒ data structure එකේ තියෙන values තනි තනිව වෙන් කරලා (unpack කරලා) arguments විදියට function එකට දෙන එක. මේක හරියට පෙට්ටියක තියෙන බඩු ටික තනි තනිව එළියට අරන් පාවිච්චි කරනවා වගේ වැඩක්.

මේ Packing සහ Unpacking තමයි *args සහ **kwargs වලට බලය දෙන්නේ. දැන් අපි බලමු ඒ දෙක වෙන වෙනම කොහොමද වැඩ කරන්නේ කියලා.

*args: ඕනෑම ගානකට Positional Arguments දාමු!

මුලින්ම අපි බලමු *args කියන්නේ මොකක්ද කියලා. *args කියන්නේ "arbitrary positional arguments" වලට. ඒ කියන්නේ, ඔයාලට function එකකට කොච්චර positional arguments දෙන්න ඕනද කියලා කලින්ම දන්නේ නැත්නම්, *args පාවිච්චි කරන්න පුළුවන්. මේ *args කරන්නේ, අපි දෙන arguments ටික එකතු කරලා tuple එකක් විදියට function එක ඇතුළට දෙන එක.

Syntax එක ඉතාම සරලයි. Parameter එකකට කලින් single asterisk (*) එකක් දැම්මම ඇති. සාමාන්‍යයෙන් අපි args කියලා නම දානවා, ඒත් ඕනෑම වලංගු නමක් පාවිච්චි කරන්න පුළුවන්. හැබැයි args කියන එක conventional නිසා code එක තේරුම් ගන්න පහසුයි.

def calculate_sum(*numbers):
    """ඕනෑම ගණනක අංක එකතු කරන function එකක්.
    *numbers මඟින් ලැබෙන arguments tuple එකක් ලෙස ලැබේ.
    """
    total = 0
    print(f"ලැබුනු arguments: {numbers} (මේක tuple එකක්!)")
    for num in numbers:
        total += num
    return total

# උදාහරණ 1: Positional arguments කීපයක් දීම
print(f"1, 2, 3 එකතුව: {calculate_sum(1, 2, 3)}") # Output: එකතුව: 6
print(f"10, 20, 30, 40, 50 එකතුව: {calculate_sum(10, 20, 30, 40, 50)}") # Output: එකතුව: 150
print(f"හිස් arguments: {calculate_sum()}") # Output: එකතුව: 0 (හිස් tuple එකක් ලැබෙනවා)

# උදාහරණ 2: List එකක් Unpack කර *args වලට දීම
my_numbers = [5, 10, 15, 20]
print(f"List එකේ එකතුව: {calculate_sum(*my_numbers)}") # List එකක් unpack කරන්නේ * එකක් දාලා

# උදාහරණ 3: Tuple එකක් Unpack කර *args වලට දීම
my_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
print(f"Tuple එකේ එකතුව: {calculate_sum(*my_tuple)}")

උඩ තියෙන code එක හොඳින් බලන්න. `calculate_sum` function එකේදී, අපි arguments ගණනක් දුන්නාම, ඒ හැම එකක්ම `numbers` කියන tuple එකට එකතු වෙනවා (packing). ඊට පස්සේ අපිට ඒ tuple එක ඇතුළේ loop කරන්න පුළුවන්, නැත්නම් වෙන ඕනම operation එකක් කරන්න පුළුවන්. `calculate_sum(*my_numbers)` කියන තැනදී සිද්ධ වෙන්නේ, `my_numbers` list එකේ තියෙන values ටික තනි තනිව arguments විදියට `calculate_sum` function එකට දෙන එක (unpacking). ඒක තමයි * එකේ බලය!

*args වල ප්‍රයෝජන:

  • Arguments ගණන කලින් නොදන්නා විට: ඔයාලා function එකක් ලියනවා නම්, ඒකට කොච්චර arguments එයිද කියලා කලින්ම දන්නේ නැත්නම් (උදා: logging functions, aggregation functions, print function එක වගේ), *args පාවිච්චි කරන්න පුළුවන්.
  • Iterables Unpack කිරීමට: List, tuple වගේ iterable එකක තියෙන values ටික function එකක positional arguments විදියට දෙන්න අවශ්‍ය නම්, * operator එක පාවිච්චි කරලා ඒ iterable එක unpack කරන්න පුළුවන්.
  • Function Overloading (අනුවර්තනය): Python වල function overloading කෙලින්ම නැතත්, *args පාවිච්චි කරලා එකම function එකෙන් arguments ගණන වෙනස් වුණත් වැඩ කරන්න පුළුවන් විදියට හදන්න පුළුවන්.

**kwargs: Keywords එක්ක අමතර බලයක්!

දැන් අපි බලමු **kwargs කියන්නේ මොකක්ද කියලා. *args වගේම, **kwargs කියන්නේ "arbitrary keyword arguments" වලට. ඒ කියන්නේ, ඔයාලට function එකකට කොච්චර keyword arguments දෙන්න ඕනද කියලා කලින්ම දන්නේ නැත්නම්, **kwargs පාවිච්චි කරන්න පුළුවන්. මේ **kwargs කරන්නේ, අපි දෙන keyword arguments ටික එකතු කරලා dictionary එකක් විදියට function එක ඇතුළට දෙන එක.

Syntax එකත් *args වගේමයි, හැබැයි මෙතන double asterisk (**) එකක් පාවිච්චි කරනවා. සාමාන්‍යයෙන් අපි kwargs කියලා නම දානවා, ඒක තමයි standard practice එක.

def create_user_profile(**details):
    """User profile එකක් හදන function එකක්. 
    **details මඟින් ලැබෙන keyword arguments dictionary එකක් ලෙස ලැබේ.
    """
    print(f"ලැබුනු user details: {details} (මේක dictionary එකක්!)")
    
    # dictionary එකේ key එකක් තියෙනවද කියලා බලලා access කරන්න පුළුවන්.
    if "name" in details:
        print(f"නම: {details['name']}")
    if "age" in details:
        print(f"වයස: {details['age']}")
    if "city" in details:
        print(f"නගරය: {details['city']}")
    
    # වෙනත් default values දීමටද පුළුවන්
    gender = details.get("gender", "Not Specified") # 'gender' නැත්නම් 'Not Specified' දෙනවා
    print(f"ස්ත්‍රී/පුරුෂ භාවය: {gender}")

    print("-" * 30)

# උදාහරණ 1: Keyword arguments කීපයක් දීම
create_user_profile(name="Kamal", age=30, city="Colombo", email="[email protected]")
create_user_profile(name="Nimal", email="[email protected]", occupation="Doctor")
create_user_profile(company="Tech Solutions", employees=50, country="Sri Lanka") # 'name' වැනි common keys නැතිව දීම

# උදාහරණ 2: Dictionary එකක් Unpack කර **kwargs වලට දීම
user_data = {"name": "Sarath", "occupation": "Engineer", "country": "Sri Lanka", "age": 45}
create_user_profile(**user_data) # Dictionary එකක් unpack කරන්නේ ** දෙකක් දාලා

# Unpacking වලදී, dictionary එකේ key, function parameter names වලට match වෙන්න ඕන නෑ. 
# ඒ හැම key-value pair එකක්ම kwargs dictionary එකට යනවා.
config_settings = {"theme": "dark", "notifications": True, "language": "Sinhala"}
create_user_profile(**config_settings)

මේ `create_user_profile` function එකෙන් අපිට පුළුවන්, User කෙනෙක්ගේ details කොච්චරක්ද කියලා කලින්ම නොදැන profile එකක් හදන්න. `details` කියන dictionary එක ඇතුළේ, අපි දුන්න keyword arguments key-value pairs විදියට save වෙනවා (packing). ඊට පස්සේ අපිට dictionary එකක් විදියට ඒ details access කරන්න පුළුවන්. `create_user_profile(**user_data)` කියන තැනදී සිද්ධ වෙන්නේ, `user_data` dictionary එකේ තියෙන key-value pairs ටික keyword arguments විදියට `create_user_profile` function එකට දෙන එක (unpacking).

**kwargs වල ප්‍රයෝජන:

  • විවිධ configuration settings හෝ optional parameters: ඔයාලගේ function එකකට විවිධ configuration settings හෝ optional parameters ගොඩක් දෙන්න ඕන වෙලාවට **kwargs ඉතාම ප්‍රයෝජනවත්. මේකෙන් code එක පිළිවෙලකට තියාගන්න පුළුවන්.
  • HTML attributes වගේ දේවල් dynamic විදියට generate කිරීමට: Web development frameworks වලදී (e.g., Flask, Django) HTML elements වල attributes dynamic විදියට generate කරන්න මේවා පාවිච්චි කරනවා.
  • Dictionaries Unpack කිරීමට: Dictionary එකක තියෙන key-value pairs ටික function එකක keyword arguments විදියට දෙන්න අවශ්‍ය නම්, ** operator එක පාවිච්චි කරලා ඒ dictionary එක unpack කරන්න පුළුවන්.

දෙකම එකට: (*args*, **kwargs*) Combo!

සමහර වෙලාවට අපිට *args සහ **kwargs දෙකම එකම function එකක් ඇතුළේ පාවිච්චි කරන්න ඕන වෙනවා. මේක ඉතාම ප්‍රයෝජනවත් වෙන්නේ flexible APIs හදනකොට, එහෙම නැත්නම් decorator functions හදනකොට. හැබැයි මතක තියාගන්න ඕන දෙයක් තමයි, මේවාට function signature එකක් ඇතුළේ තියෙන නිශ්චිත පිලිවෙල. ඒක මේ වගේ:

  1. සාමාන්‍ය Positional Arguments
  2. Default Arguments
  3. *args (Arbitrary Positional Arguments)
  4. Keyword-Only Arguments (Python 3 වල තියෙන feature එකක්, *args වලින් පස්සේ එන සාමාන්‍ය arguments keyword එකකින් විතරක් pass කරන්න පුළුවන්)
  5. **kwargs (Arbitrary Keyword Arguments)

සාමාන්‍යයෙන් අපි පාවිච්චි කරන්නේ 1, 3, 5 කියන පිලිවෙල. අපි උදාහරණයක් බලමු:

def process_order(order_id, *items, **options):
    """ඇණවුමක් process කරන function එකක්. 
    order_id (mandatory positional arg), items (*args), options (**kwargs) බාරගනී.
    """
    print(f"ඇණවුම් ID: {order_id}")
    print(f"ඇණවුම් කල භාණ්ඩ: {items} (මේක tuple එකක්)")
    print(f"අමතර විකල්ප: {options} (මේක dictionary එකක්)")

    if "discount" in options:
        print(f"වට්ටම්: {options['discount']}%")
    if "delivery_speed" in options:
        print(f"බෙදාහැරීමේ වේගය: {options['delivery_speed']}")
    if "payment_status" in options:
        print(f"ගෙවීම් තත්ත්වය: {options['payment_status']}")
    
    print("=" * 40)

# උදාහරණ: දෙකම පාවිච්චි කිරීම
process_order(1001, "Laptop", "Mouse", "Keyboard", discount=10, delivery_speed="Express", payment_status="Paid")
process_order(1002, "Phone", "Charger", tax_included=True, customer_note="Gift wrap needed")
process_order(1003, "Monitor", "Webcam", "Headphones", delivery_speed="Standard")
process_order(1004, "Pen Drive")

මේ `process_order` function එකෙන් අපිට පුළුවන්, order ID එකක් එක්ක (mandatory argument), item කීපයක් (variable positional arguments via *args) සහ අමතර විකල්ප (variable keyword arguments via **kwargs) දෙන්න. මේකෙන් අපිට ඉතාම නම්‍යශීලී විදියට orders process කරන්න පුළුවන්. Order එකට items කීයක් තියෙනවද කියලා කලින්ම දන්නේ නෑ, අමතර options මොනවද කියලා කලින්ම දන්නේ නෑ. ඔන්න ඔය වගේ වෙලාවලට මේ combo එක සුපිරි!

කවදාද පාවිච්චි කරන්නේ? (Best Practices)

*args සහ **kwargs කියන්නේ ඉතාම බලවත් features දෙකක්. ඒත් හැම වෙලාවෙම මේවා පාවිච්චි කරන එක එච්චර හොඳ නැහැ. අනවශ්‍ය විදියට පාවිච්චි කරන එකෙන් code එක තේරුම් ගන්න අමාරු වෙන්න පුළුවන් (readability අඩු වෙනවා). ඒ නිසා මේවා පාවිච්චි කරන්න ඕන විශේෂ අවස්ථාවලදී විතරයි. මෙන්න පොඩි guide එකක්:

  • Flexible APIs:ඔයාලා Library එකක් හදනවා නම්, නැත්නම් වෙන කෙනෙක්ට පාවිච්චි කරන්න පුළුවන් generic function එකක් හදනවා නම්, *args සහ **kwargs ප්‍රයෝජනවත් වෙන්න පුළුවන්. ඒකෙන් users ලාට ඕන විදියට arguments දෙන්න පුළුවන්. උදාහරණයක් විදියට Python වල print() function එක බලන්න. ඒකට ඕන තරම් arguments දෙන්න පුළුවන්. ඒක ඇතුළේ *args පාවිච්චි වෙනවා.
  • Function Overloading Simulation:වෙන සමහර languages වල තියෙන Function Overloading (එකම නම තියෙන functions වලට arguments ගණන වෙනස් වීම) Python වල කෙලින්ම නැතත්, *args සහ **kwargs පාවිච්චි කරලා ඒ වගේ behavior එකක් simulation කරන්න පුළුවන්. ඒත් මේක හැම විටම හොඳ විසඳුමක් නෙවෙයි, සමහර විට වෙන functions ලියන එක වඩා හොඳයි.
  • Avoid Overuse:ඔයාලා function එකකට arguments මොනවද කියලා කලින්ම දන්නවා නම්, *args සහ **kwargs පාවිච්චි නොකර ඉන්න. Explicit is better than implicit කියන Pythonic principle එක මතක තියාගන්න. ඒ කියන්නේ, code එක තේරුම් ගන්න පහසු වෙන්න, හැමදේම පැහැදිලිව define කරන එක හොඳයි. හැමදාම magic කරන්න යන්න එපා!

Logging / Debugging Functions:Messages ගොඩක් log කරන්න ඕන වෙලාවට, නැත්නම් debug info එකතු කරන්න ඕන වෙලාවට *args ප්‍රයෝජනවත්. ඔයාලට ඕන තරම් strings, numbers, objects combine කරලා log කරන්න පුළුවන්.

import datetime

def custom_logger(level, *messages, timestamp=True):
    """Custom logger function එකක්. *messages මඟින් ඕනෑම ගණනක messages බාරගනී.
    timestamp=True නම් log එකට timestamp එකක් එකතු වේ.
    """
    log_string = " ".join(map(str, messages)) # ඕනෑම data type එකක් string බවට පත් කරයි
    
    if timestamp:
        current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"[{current_time}] [{level.upper()}]: {log_string}")
    else:
        print(f"[{level.upper()}]: {log_string}")

custom_logger("INFO", "මෙම ක්‍රියාවලිය", "සාර්ථකව", "නිම", "විය.", 100, "%")
custom_logger("ERROR", "ගොනුව", "සොයා", "ගැනීමට", "නොහැකි", "විය:", "example.txt", timestamp=False)
custom_logger("DEBUG", "Variables loaded", count=5, status="OK") # **kwargs පාවිච්චි කරන්නේ නෑ, ඒත් illustrate කරන්න පුළුවන්

Decorators:Python වල decorators හදනකොට, ඔරිජිනල් function එකට යන arguments මොනවද කියලා කලින්ම දන්නේ නැති නිසා, *args සහ **kwargs අත්‍යවශ්‍ය වෙනවා. මේවා පාවිච්චි කරලා original function එකට යන arguments ඒ විදියටම pass කරන්න පුළුවන්. මේවා higher-order functions වලදී බහුලව පාවිච්චි වෙනවා.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("මම Decorator එක ඇතුළේ... function එක execute කරන්න කලින්!")
        result = func(*args, **kwargs) # Original function එකට arguments pass කරනවා
        print("function එක execute කරලා ඉවරයි. මම Decorator එක ඇතුළේ ආපහු!")
        return result
    return wrapper

@my_decorator
def greet(name, message):
    print(f"Hello {name}, {message}!")

greet("John", message="Have a great day")

අවසාන වදන්: ඔයත් දැන් Python Magic කරන කෙනෙක්!

ඉතින්, අද අපි කතා කළා Python Functions වලට ඕනෑම ගානකට arguments දෙන්න පුළුවන් *args සහ **kwargs කියන බලවත් Features දෙක ගැන. මේවා හරියටම තේරුම් ගත්තා නම්, ඔයාලට පුළුවන් ඉතාම නම්‍යශීලී, reusable code ලියන්න. මතක තියාගන්න, *args කියන්නේ tuple එකක්, **kwargs කියන්නේ dictionary එකක්. আর ඒව පාවිච්චි කරන පිළිවෙලත් (order of arguments).

දැන් ඔයාලා දන්නවා මේවා කොහොමද වැඩ කරන්නේ කියලා. ඉතින් දැන් ඔයාලට තියෙන්නේ මේවා ඔයාලගේ project වලට අරගෙන test කරලා බලන එකයි. මොකද, theory විතරක් මදි, practice එකෙන් තමයි හොඳටම ඉගෙන ගන්න පුළුවන්!

මේ ගැන ඔයාලට අදහස්, ප්‍රශ්න තියෙනවා නම්, නැත්නම් *args / **kwargs පාවිච්චි කරපු අලුත් විදියක් තියෙනවා නම්, පහළින් comment එකක් දාගෙන යන්න. අපි කතා කරමු! ඔයාලගේ අත්දැකීම් දැනගන්න අපි ආසයි.

නැවතත් මේ වගේ article එකකින් හමුවෙමු! Happy Coding!