Funktsioon, selle argumendid ning parameetrid

Iga mõistlik programm tegeleb kindla ülesande (probleemi) lahendamisega. Pisut suuremate programmide koostamisel (põhjuseid on teisigi, need on esitatud antud lehe lõpus) on arukas selle jaotamine alamülesanneteks. Pythonis saab alamülesandeid kirjeldada programmiüksustes, mida nimetatakse funktsioonideks. Lihtsustatult võib üht funktsiooni vaadelda kui koodi konteinerit, mis hoiab endas mingisugust koodi. Funktsiooni kirjeldamine algab võtmesõnaga def, millele järgneb funktsiooni nimi ning sulud. Sulud võivad olla tühjad või omada ühte või mitut parameetrit. Järgnevaid taandega esitatud ridu funktsiooni kirjelduses nimetatakse funktsiooni kehaks (function body).

Järgnev näide esitab parameetriteta funktsiooni “üldkuju”:

# Function without parameters
def func_without_params():
    # Some code (function body)

Parameetritega funktsioon:

# Function with two parameters – a and b
def func_with_two_parameters(a, b):
    # Some code (function body)

Argument ja parameeter

Funktsiooni kirjeldamise ja kasutamisega käivad (tihti) kaasas mõisted nagu argument ja parameeter. Parameetrid on eriliiki muutujad (nimelised kohad arvutimälus väärtuste salvestamiseks), mida kasutatakse andmete sissetoomiseks funktsiooni. (Üks põhjus, miks parameetrit nimetatakse eriliiki muutujaks, seisneb selles, et see muutuja ei saa oma väärtust omistuslauses, nagu tavalise muutuja puhul.) Parameetrid kirjeldatakse funktsiooni defineerimisel selle päisereas sulgude sees. Parameetrid saavad oma väärtused funktsiooni poole pöördumisel argumentidelt. Argumendiks võib olla konstant, muutuja, funktsiooniviide või avaldis. Võib öelda, et argumendid on funktsiooni sisendväärtused ehk väärtused, millega funktsiooni poole pöördutakse. Kui argumendiks on avaldis või funktsiooniviide, asutakse funktsiooni täitma alles siis, kui avaldise ja funktsiooniviite väärtused on leitud.

NB! Kuigi avaldis koosneb tüüpiliselt operandidest, operaatoritest (tehtemärkidest) ja sulgudest, loetakse avaldiseks (erijuhuna) ka vaid konstanti, muutujat, funktsiooniviidet.

Vahel kasutatakse kirjanduses mõistete argument ja parameeter asemel mõisteid tegelik parameeter (actual parameter) ja formaalne parameeter (formal parameter).

Järgnevas näites on kirjeldatud kahe parameetriga funktsioon ‘addition’, mis liidab parameeterite a ja b väärtused ja prindib need konsoolaknas. Funktsiooni kirjeldus koosneb siin kahest reast: päisereast, mis algab võtmesõnaga def ja funktsiooni kehast, mis algab taandega.

# Here, a and b are called "parameters" (or "formal parameters")
def addition(a, b):
    print(a + b)

# Here, 5 and 7 are called "arguments" (or "actual parameters")
addition(5, 7)

Selleks, et kirjeldatud funktsioonist ka mingit kasu oleks, tuleb see välja kutsuda (käivitada). Täpsemalt, funktsiooni käivitamiseks tuleb selle poole pöörduda selle kirjelduses antud nimega ning argumentidega (kui funktsiooni kirjelduses on parameetrid esitatud). Eelnevas koodis esitab funktsiooni poole pöördumist (ka funktsiooni väljakutset) addition(5, 7). Kui programm asub rida (pöördumislauset) addition(5, 7) täitma antakse esimese argumendi väärtus 5 parameetrile a ja teise argumendi väärtus 7 parameetrile b.

def greeting():
    print("Welcome to python, pal.")

# If we want to execute the defined print(), we should call the function
greeting() # --> Prints "Welcome to python, pal." to the console

Pärast funktsiooni sees oleva koodi käivitamist funktsioon võib (aga ei pea) tagastada väärtuse - see võib olla number, sõne, list vms.

# A function which returns something
def func(parameters):
    # Some code
    return [expression]

Ja siis üleval olev näide on ümberkirjutatav kujul

def func():
    return "Welcome to python, pal."

# The function returns the string "Welcome to python, pal.", and we print it to the console
print(func()) # --> Welcome to python, pal

Veel mõni näide. Olgu meil lihtne kalkulaator, mis liidab kokku kaks arvu:

# Variables
first_num = 5
second_num = 7

result = first_num + second_num

# Prints 12 to the console
print(result)

Ilma funktsioonita käivitub kood lihtsalt skripti käivitamisel. Kui aga paneme antud koodi funktsiooni calc() sisse, siis selleks, et funktsiooni sees olevat koodi käivitada, tuleb seda funktsiooni kutsuda:

def calc():
    """
    Calculate the sum of 5 and 7.

    :return: sum of two given numbers
    """
    # Variables
    first_num = 5
    second_num = 7

    result = first_num + second_num

    return result

# Calling a function
print(calc()) # --> 12

Interpretaator (interpreter) (programmikoodi tõlkiv ja täitev programm) vaatab algul pythoni (sise)funktsiooni print() sisse. Kuna print asub meie funktsiooni kirjeldusest väljaspool ning on ilma taaneteta, näeb interpretaator funktsiooni kutset ja pöördub selle poole. Pärast funktsiooni sees oleva koodi käivitamist, calc() tagastab kahe arvu summa, mis on antud juhul 12, ja meie prindime selle summa. Meie funktsiooni tagastatavaks väärtuseks on alati 12, kuna liidetavad arvud on määratud funktsiooni sees (5 ja 7) ja neid saab muuta ainult otseselt (panna näiteks first_num = 10, siis saame tulemuseks 10 + 7 = 17 vms). See ei ole eriti mugav. Õnneks funktsioon saab sisse võtta ka argumente:

def calc(first_num, second_num):
    """
    Calculate the sum of two numbers.

    :param first_num: first number
    :param second_num: second number
    :return: sum of two given numbers
    """
    result = first_num + second_num

    return result

# Calling the function with two arguments.
print(calc(5, 7))  # Prints 12
print(calc(25, 39))  # Prints 64

Ehk põhimõtteliselt funktsiooni parameetrid on samad muutujad, millele antakse väärtused funktsiooni väljakutsumisel. Pöörake tähelepanu, et viimase näite korral IDE võib kuvada teile hoiatuse - local variable result is redundant. Tal on õigus (IDE always knows it better, just deal with it). Viisakas oleks ümber kirjutada meie funktsiooni kujul

def calc(first_num, second_num):
    """
    Calculate the sum of two numbers.

    :param first_num: first number
    :param second_num: second number
    :return: sum of two given numbers
    """
    return first_num + second_num

Ehk result muutujat pole vaja, säästame veidi arvutimälu.

Üks funktsioon saab välja kutsuda (käivitada) ka teisi funktsioone. Näiteks, soovime, et meie kalkulaator liidaks kahe sisendarvu summale nende korrutise. Selleks loome eraldi funktsiooni:

# Creating a new function, which multiplies two numbers and returns the product
def multiply(first_num, second_num):
    """
    Multiply two numbers.

    :param first_num: first number
    :param second_num: second number
    :return: the result of multiplying two given numbers
    """
    return first_num * second_num


# Calling the multiply() function inside our main function calc():
def calc(first_num, second_num):
    """
    Calculate the sum of two numbers and their multiplication.

    :param first_num: first number
    :param second_num: second number
    :return: sum of two numbers + their multiplication result
    """
    return first_num + second_num + multiply(first_num, second_num)

# Calling the function with two arguments
print(calc(2, 3))  # --> 11
print(calc(5, 5))  # --> 35

Kui funktsioon tagastab mingi väärtuse, saab selle väärtuse salvestada muutujasse ja teha sellega kõike, mida on võimalik ühe muutujaga teha.

# Let's also use type hints (See: https://ained.ttu.ee/pydoc/func_overview.html#tuubi-vihjed-type-hints)
def repeat(word: str, times: int) -> str:
    """
    Repeat a word <times> amount.

    :param word: a word to repeat
    :param times: how many times parameter <word> is repeated
    :return: string, where word <word> is repeated <times> times
    """
    return word * times

# Saving the return value into variable <repeated_word>
# People do so to make their code easier to read
repeated_word = repeat("a", 3)
print(repeated_word)  # --> aaa

# From technical point of view it isn't necessary to save the return value into variable
print(repeat("abc", 4) == "abcabcabcabc")  # --> True
print(repeat("Python is easy. ", 1) + "Try Haskell")  # --> Python is easy. Try Haskell

Vaikeparameeter

Funktsioonil võib olla ka nn vaikeparameeter, mis on algväärtustatud. See annab meile võimaluse funktsiooni välja kutsudes argument välja jätta. Sel juhul kasutatakse vaikimisi ette antud väärtust:

def hello(name="World"):
    """Greet someone."""
    print(f"Hello, {name}!")


# tavaline väljakutse
hello("Bob") # -> Hello, Bob!

# argumenti ette andmata väljakutse
hello() # -> Hello, World!

Ettearvamatu hulk argumente (*args, **kwargs)

Tihti tuleb ette olukordi, kus soovime funktsioonile anda sisse erineva koguse argumente või nimelisi argumente (keyword arguments). Seega võib kohata funktsioonide definitsioonides järgnevat kirjapilti: def bacon(*args, **kwargs).

Vaatame kõigepealt *args (arguments) kasutust. Muutuja nimi ei pea tingimata olema args vaid võib vabalt olla ka midagi muud, args on lihtsalt kujunenud konventsiooniks. Tärni kasutus muutuja ees lubab saata funktsioonile määramata koguse (nimetuid) argumente.

Soovides luua funktsiooni, mis summeeriks sissetulevad arvud sõltumata kogusest, peaksime tegema funktsiooni kahe parameetriga, kolme parameetriga jne, aga * muutuja ees võimaldab seda mugavalt teha.

def sum_all(*numbers):
    total = 0
    # numbers is a tuple containing all the positional arguments
    for numbers in numbers:
        total += numbers
    return total


print(sum_all(45, 32, 89, 78))  # prints 244
print(sum_all(2))  # prints 2

Vaatame veel olukordi, kus muuseas kasutame lisaks positsioonilistele parameetritele on esimesel funktsioonil ka formaalne parameeter. Näidetest saab asi selgemaks.

def multiply(initial, *args):
    result = initial
    for arg in args:
        result *= arg
    return result

print(multiply(1, 2, 5))  # 10
print(multiply(0, 2))  # 0

# we can also use the same syntax to unpack
numbers = (1, 2, 3)
print(multiply(1, *numbers))


def print_all_args(*args):
    print(args)

print_all_args(1)  # (1,)
print_all_args(1, 2)  # (1, 2)
print_all_args(5, 'cookie', [13, 14])  # (5, 'cookie', [13, 14])


def join_numbers(*numbers, separator=','):
    """Join numbers into a string using given separator or default comma."""
    return separator.join(map(str, numbers))

print(join_numbers(1, 2, 3))
print(join_numbers(21, 10, 2018, separator='.'))
values = [3, 3]
print(join_numbers(*values, separator='|'))  # with a variable
print(join_numbers(*(0, 2, 3)))  # without a variable

**kwargs (keyword arguments) toimib analoogiliselt ja lubab määramata koguses nimelisi argumente. Samuti pole oluline muutuja nimi, aga muutuja ees peab olema **. Kui *args puhul oli parameetrina tegemist ennikuga (tuple), siis kwargs-i puhul luuakse sõnastik (dict). Vaatame näiteid.

def print_kwargs(**kwargs):
    print(kwargs)


# A dictionary will be made (not ordered before Python 3.6)
print_kwargs(name='Guido', age=62, is_cool=True)
# {'name': 'Guido', 'age': 62, 'is_cool': True}


def print_key_value_pairs(**kwargs):
    for key, value in kwargs.items():
        print(f'Key: {key}, Value: {value}')


print_key_value_pairs(attack=20, defense=12.5, hasEvolved=False)
# Key: attack, Value: 20
# Key: defense, Value: 12.5
# Key: hasEvolved, Value: False

# the same as above, but pass a dictionary
player_data = {'attack': 20, 'defense': 12.5, 'hasEvolved': False}
print_key_value_pairs(**player_data)


def foo(arg, *args, kwarg_1=True, **kwargs):
    print(arg, args, kwarg_1, kwargs)


# A lot of different ways to pass the same data
foo('a', *(1, 2), kwarg_1=True, other_kwarg=True, y=1)
foo('a', *(1, 2), other_kwarg=True, kwarg_1=True, y=1)
foo('a', *(1, 2), other_kwarg=True, y=1)
foo('a', *[1, 2], **dict(other_kwarg=True, y=1))
foo('a', *[1, 2], **dict([('other_kwarg', True), ('y', 1)]))
kwargs = {
    'other_kwarg': True,
    'y': 1,
}
args = (1, 2)
foo('a', *args, **kwargs)

# Some of these are for demonstration purposes, use the one that is most readable

Nüüd tasub vaadata Python3 sisseehitatud printimise funktsiooni deklaratsiooni ja aru saada kuidas see tegelikult toimib def print(self, *args, sep=' ', end='\n', file=None).

Põhjused, miks kasutada funktsioone

  1. Keeruka probleemi jagamine (tükeldamine) mitmeks väikeseks “tükiks”
  2. Vähendamaks sama koodiploki korduvat esitamist
  3. Peitmaks programmi realisatsiooni detaile kasutaja eest
  4. Parandamaks koodi loetavust/jälgitavust