Функції

Можливо, ви знайомі з математичним поняттям функція. Це відношення між одним або декількома вхідними параметрами та набором результатів.

\[z = f(x, y)\]

Тут f - це функція, яка оперує параметрами x та y, а її результат прив’язаний до z.

У програмуванні функція - це автономний блок коду, який інкапсулює певну задачу або пов’язану групу задач.

Ви можете бути знайомі з деякими вбудованими функціями, такими як max, min, len тощо.

Звичайний синтаксис для визначення функції Python наступний:

def <function_name>([<parameters>]):
    <statement>

Компонент

Значення

def

Ключове слово, яке інформує Python про те, що визначається функція

<function_name>

Валідний ідентифікатор Python, який називає функцію

<parameters>

Необов’язковий список параметрів через кому, які можуть бути передані у функцію

<statement(s)>

Блок допустимих операторів Python; тіло функції

Синтаксис виклику функції Python наступний:

<function_name>([<arguments>])

Значення <arguments> - це значення, що передаються у функцію. Вони відповідають <parameters> у визначенні функції. Ви можете визначити функцію, яка не приймає жодних аргументів, але дужки все одно є обов’язковими. І визначення функції, і її виклик завжди повинні містити дужки, навіть якщо вони порожні.

Передача аргументів

Найчастіше вам потрібно передати дані у функцію.

Позиційні аргументи

Примітка

Через те, як вони визначаються і використовуються, позиційні аргументи також називають обов’язковими аргументами.

Найпростіший спосіб передачі аргументів у функцію - за допомогою позиційних аргументів. У визначенні функції ви вказуєте список параметрів, розділених комами, всередині круглих дужок. Коли функція викликається, ви вказуєте відповідний список аргументів.

Параметри поводяться як змінні, визначені локально у функції.

Хоча позиційні аргументи є найпростішим способом передачі даних у функцію, вони також забезпечують найменшу гнучкість. Для початку, порядок аргументів у виклику повинен відповідати порядку параметрів у визначенні.

Ніщо не заважає вам вказати позиційні аргументи в неправильному порядку. Функція навіть може запуститися, але навряд чи видасть правильні результати.

Примітка

Програміст, який визначає функцію, зобов’язаний задокументувати, якими мають бути відповідні аргументи, а користувач функції зобов’язаний знати цю інформацію і дотримуватися її.

З позиційними аргументами аргументи у виклику та параметри у визначенні повинні узгоджуватися не тільки за порядком, але й за кількістю.

Аргументи за ключовими словами

При виклику функції можна вказувати аргументи у вигляді <keyword>=<value>. У цьому випадку кожен <keyword> повинен відповідати параметру у визначенні функції. Звернення до ключового слова, яке не відповідає жодному з цих оголошених параметрів, генерує виключення.

Використання аргументів з ключовими словами знімає обмеження на порядок аргументів. Кожне ключове слово-аргумент явно вказує на конкретний параметр за назвою, тому ви можете вказати їх у довільному порядку, і Python все одно знатиме, який аргумент йде з яким параметром.

Як і у випадку з позиційними аргументами, кількість аргументів і параметрів має збігатися.

Ви можете викликати функцію, використовуючи як позиційні, так і ключові аргументи. Після того, як ви вказали ключове слово, праворуч від нього не може бути жодних позиційних аргументів.

Значення за замовчуванням

Якщо параметр, вказаний у визначенні функції, має вигляд <name>==<value>, то значенням за замовчуванням для цього параметра стає <value>. Параметри, визначені таким чином, називаються параметрами за замовчуванням або необов’язковими параметрами.

Змінювані значення параметрів за замовчуванням

Ситуація може ускладнитися, якщо ви вкажете значення параметра за замовчуванням, який є змінюваним об’єктом.

>>> def add_to_container(item, container = []):
...     container.append(item)
...
>>>
>>> add_to_container(42, [1, 2, 3])
[1, 2, 3, 42]
>>> add_to_container("foobar", ["foo", "bar"])
["foo", "bar", "foobar"]
>>> add_to_container(42)
[42]
>>> add_to_container("foobar")  # ["foobar"]
[42, "foobar"]

У Python значення параметрів за замовчуванням визначаються лише один раз під час визначення функції. Значення за замовчуванням не перевизначається при кожному виклику функції. У наведеному вище прикладі кожного разу, коли функція add_to_container викликається без передачі аргументу container, оператор .append виконується над тим самим списком.

Змінні та незмінні аргументи

Примітка

Грубо кажучи, ви можете вважати незмінний об’єкт переданим значенням, а змінювані об’єкти - переданим посиланням. Однак, це не зовсім так.

У мові програмування існує дві поширені парадигми передачі аргументу у функцію:

  • передати-за-значенням означає, що у функцію передається копія аргументу.

  • передати-за-посиланням означає, що у функцію передається посилання на аргумент.

Чи є параметри в Python переданими за значенням або за посиланням? Вони не є ні тими, ні іншими. Це тому, що посилання в Python означає не зовсім те саме, що в C-подібних мовах.

Нагадаємо, що у Python кожен фрагмент даних є об’єктом. Посилання вказує на об’єкт, а не на конкретну ділянку пам’яті.

Погляньте на код нижче:

1value = 24
2value = 42

Ці оператори присвоювання мають наступне значення:

  • Рядок 1 призводить до того, що value вказує на об’єкт, значенням якого є 24.

  • Рядок 2 перепризначає value як нове посилання на інший об’єкт, значенням якого є 42.

У Python, коли ви передаєте аргумент у функцію, відбувається аналогічне переприв’язування.

1def reassign(fx: int) -> None:
2    fx = 10
3
4
5x = 5
6reassign(x)

В основній програмі оператор x = 5 у рядку 5 створює посилання з іменем x, пов’язане з об’єктом, значенням якого є 5. потім у рядку 6 викликається функція reassign з x як аргументом. При першому запуску функції створюється нове посилання fx, яке спочатку вказує на той самий об’єкт 5. Однак, коли виконується оператор fx = 10 у рядку 2, reassign перепризначає fx на новий об’єкт, значенням якого є 10. Відтепер два посилання x і fx від’єднано одне від одного. Ніякі інші дії функції не вплинуть на x, і коли функція завершиться, x все ще вказуватиме на об’єкт 5, як це було до виклику функції.

Ви можете підтвердити все це за допомогою id(). Ось дещо доповнена версія коду вище:

 1def reassign(fx: int) -> None:
 2    print(f"{fx = }, {id(fx) = }")
 3    fx = 10
 4    print(f"{fx = }, {id(fx) = }")
 5
 6
 7x = 5
 8print(f"{x = }, {id(x) = }")
 9reassign(x)
10print(f"{x = }, {id(x) = }")

Результати будуть виглядати так:

x = 5, id(x) = 140706772804520
fx = 5, id(fx) = 140706772804520
fx = 42, id(fx) = 140706772805704
x = 5, id(x) = 140706772804520

Примітка

Механізм передачі аргументів у Python називається передача-за-присвоєнням (pass-by-assignment). Ви також можете зустріти терміни pass-by-object, pass-by-object-reference або pass-by-sharing. Це пов’язано з тим, що імена параметрів прив’язуються до об’єктів під час виклику функції у Python, а присвоювання - це також процес прив’язки імені до об’єкта.

Ключовим моментом тут є те, що функція Python не може змінити значення аргументу, перепризначивши відповідний параметр на щось інше.

Однак, функції можуть використовувати посилання для модифікацій всередині змінюваних об’єктів.

1>>> def insert_into_container(item, container, idx = 0):
2...    container.insert(idx, item)
3...
4>>>
5>>> numbers = [1, 2, 3]
6>>> insert_into_container(42, numbers)
7>>> numbers
8[42, 1, 2, 3]

Оператор повернення

Він служить двом цілям:

  • Він негайно завершує функцію і передає керування виконанням назад тому, хто її викликав.

  • Він надає механізм, за допомогою якого функція може передавати дані назад тому, хто її викликає.

Оператор return можна використовувати всередині функції або методу, щоб відправити результат назад тому, хто його викликає. Він складається з ключового слова return та необов’язкового значення, що повертається.

Значенням, що повертається функцією, може бути будь-який об’єкт Python (і ви повинні пам’ятати - все в Python є об’єктом).

Ви можете опустити значення, що повертається, і використовувати просто return без значення, що повертається. Ви також можете опустити всю інструкцію return. В обох випадках значенням, що повертається, буде None. Отже, функції Python завжди мають значення, що повертається, а якщо його не вказано - це None.

Важливо

return проти print

Якщо ви працюєте в інтерактивному сеансі, ви можете подумати, що виведення значення і повернення значення еквівалентні. Розглянемо дві наступні функції:

from typing import List


def print_evens(numbers: List[int]) -> None:
    print([number for number in numbers if not number % 2])


def return_evens(numbers: List[int]) -> List[int]:
    return [number for number in numbers if not number % 2]

Та результати їх викликів:

>>> print_evens([1, 2, 3, 4, 5, 6, 7, 8, 9])
[2, 4, 6, 8]
>>> return_evens([1, 2, 3, 4, 5, 6, 7, 8, 9])
[2, 4, 6, 8]

Здається, що обидві функції роблять одне й те саме. Але тільки друга функція насправді повертає значення, тоді як перша не повертає нічого (або NoneType об’єкт).

Повернення декількох значень

Ви можете використовувати інструкцію return для повернення декількох значень з функції. Для цього вам просто потрібно вказати декілька значень, що повертаються, розділених комами. Функція поверне їх як кортеж.

import statistics as st


def describe(data):
    return st.mean(data), st.median(data), st.mode(data)


sample = [8, 1, 9, 1, 4, 6, 1, 9, 8, 3]
mean, median, mode = describe(sample)

Вбудована функція divmod також є прикладом функції, яка повертає декілька значень. Функція отримує два (нескладних) числа як аргументи і повертає два числа - частку від ділення двох вхідних значень і залишок від ділення.

Список аргументів змінної довжини

У деяких випадках, коли ви визначаєте функцію, ви можете не знати заздалегідь, скільки аргументів ви хочете, щоб вона приймала.

Наприклад, функція, яка обчислює середнє арифметичне декількох значень, може виглядати приблизно так:

def avg(a, b, c):
    return (a + b + c) / 3

Однак, як ви вже бачили, коли використовуються позиційні аргументи, кількість переданих аргументів повинна відповідати кількості оголошених параметрів.

Пакування аргументів до кортежа

Коли імені параметра у визначенні функції передує зірочка (*), це означає упакування кортежу аргументів. Будь-які відповідні аргументи у виклику функції упаковуються у кортеж, на який функція може посилатися за вказаним іменем параметра.

def avg(*args):
    return sum(args) / len(args)

Можна використовувати будь-яке ім’я, але args є настільки поширеним, що стало практично стандартом.

Розпакування кортежу аргументів

Аналогічна операція доступна з іншого боку рівняння у виклику функції. Якщо перед аргументом у виклику функції стоїть зірочка, це означає, що аргумент є кортежем, який слід розпакувати і передати у функцію як окремі значення.

def demo_args_unpacking(a, b, c):
    print(f"{a = }, {b = }, {c = }")


args = 10, 20, 30
demo_args_unpacking(*args)

Примітка

Хоча цей тип розпакування називається розпакуванням кортежів, він працює не лише з кортежами. Зірочка може бути застосована до будь-якого ітеруємого об’єкта у виклику функції.

arguments_list = [10, 20, 30]
demo_args_unpacking(*arguments_list)
arguments_set = {10, 20, 30}
demo_args_unpacking(*arguments_set)
arguments_str = "ABC"
demo_args_unpacking(*arguments_str)

Примітка

Ви навіть можете одночасно пакувати та розпаковувати кортежі.

def avg(*args) -> float:
    return sum(args) / len(args)


numbers = 10, 20, 30
average = avg(*numbers)

Пакування аргументів до словника

У мові Python є подібний оператор, подвійна зірочка (**), який можна використовувати з параметрами функції для вказівки пакування словників. Передування параметру у визначенні функції подвійною зірочкою вказує на те, що відповідні аргументи, які, як очікується, є аргументами ключових слів (пари ключ=значення), мають бути упаковані у словник.

def display_person(first_name: str, last_name: str, **kwargs) -> None:
    print(f"{first_name.title()} {last_name.title()}")
    for key, value in kwargs.items():
        print(f"{key}:\t{value}")


display_person("serhii", "horodilov", school="A-Level", course="Python")

Знову ж таки, можна використовувати будь-яке ім’я, але особливе kwargs (що є скороченням від keyword args) є майже стандартним.

Розапакування аргументів зі словника

Це аналогічно розпакуванню кортежу аргументів. Коли подвійна зірочка стоїть перед аргументом у виклику функції, вона вказує на те, що аргумент є словником, який слід розпакувати, а отримані елементи передати у функцію як аргументи ключових слів.

person_data = {
    "first_name": "Serhii",
    "last_name": "Horodilov",
    "school": "A-Level",
    "course": "Python",
    "school_occupation": "teacher/mentor",
    "job_title": "Software Engineer",
}
display_person(**person_data)

Лямбда функції

Лямбда-вирази в Python та інших мовах програмування беруть свій початок у лямбда-обчисленні - моделі обчислень, винайденій Алонзо Черчем. За своєю суттю Python не є функціональною мовою, але вона рано перейняла деякі функціональні концепції. У січні 1994 року до мови було додано map(), filter(), reduce() та лямбда-оператор.

Загальний синтаксис лямбда-функції такий:

lambda [<parameters>]: <expression>

Компонент

Значення

lambda

Ключове слово, яке інформує Python про те, що визначається функція

<parameters>

Необов’язковий список параметрів через кому, які можуть бути передані у функцію

<expression>

Валідний вираз Python; оператор повернення

def get_fullname(first_name: str, last_name: str):
    return f"{first_name.title()} {last_name.title()}"


lambda first_name, last_name: f"{first_name.title()} {last_name.title()}"

Наведений вище приклад коду демонструє функцію Python get_fullname та її лямбда-версію. Лямбда-форма має синтаксичні відмінності від звичайної функції. Зокрема, лямбда-функція має наступні характеристики:

  • Він може містити лише вирази і не може включати в себе оператори.

  • Він написаний у вигляді одного рядка виконання.

  • Він не підтримує анотації типів.

  • Її можна негайно викликати (IIFE).

    (lambda number: "odd" if number % 2 else "even")(42)
    

Класичні функціональні конструкції

>>> # in-place data modifications
>>> list(map(lambda x: x.upper(), ["foo", "bar", "foobar"]))
["FOO", "BAR", "FOOBAR"]
>>> # filtering values
>>> list(filter(lambda x: not x % 2, [1, 2, 3, 4, 5, 6, 7]))
[2, 4, 6]
>>> # reduce values
>>> from functools import reduce
>>> reduce(lambda acc, x: acc ^ x, [1, 2, 3, 1234, 3, 2, 1])
1234