11  Функції

Data Miorsh Ihor Miroshnychenko Youtube Monobank

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

11.1 Синтаксис

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

def add_numbers(a, b):
    sum = a + b
    return sum

Повернення значення: використовуйте ключове слово return для повернення значення з функції. Якщо у функції немає оператора return, вона все одно завершить своє виконання, але поверне значення None за замовчуванням.

Ось приклади використання функцій:

Приклад 11.1 (Проста функція, що обчислює суму двох чисел)  

def add_numbers(a, b):
    sum = a + b
    return sum
result = add_numbers(2, 3)
print(result)
5

Приклад 11.2 (Функція з вкладеними умовами для визначення часу доби)  

def get_time_of_day(hour):
    if hour < 12:
        return "Ранок"
    elif hour < 18:
        return "День"
    else:
        return "Вечір"
time = get_time_of_day(15)
print(time)
День

Приклад 11.3 (Функція з параметром за замовчуванням)  

def greet_person(name="гість"):
    print("Привіт,", name)

greet_person()
greet_person("Аліса")
Привіт, гість
Привіт, Аліса

11.2 Глобальні змінні

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

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

Приклад 11.4 (Використання глобальних змінних у Python)  

x = 10 # глобальна змінна
def some_function():
    global x
    x += 5 # зміна глобальної змінної
    print(x)
some_function()
print(x)
15
15

У цьому випадку змінна x оголошена в глобальній області видимості, за межами функції some_function(). За допомогою ключового слова global всередині функції ми можемо змінювати значення глобальної змінної x.

Ось приклад реальної задачі, де буде потрібно використовувати глобальну змінну.

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

total_orders = 0 # глобальна змінна для зберігання загальної кількості замовлень

def make_order(num_items):
    global total_orders
    total_orders += 1
    print(f"Замовлення зроблено! Усього замовлень: {total_orders}. Товарів у замовленні: {num_items}")

make_order(5) # Замовлення зроблено! Усього замовлень: 1. Товарів у замовленні: 5
make_order(3) # Замовлення зроблено! Усього замовлень: 2. Товарів у замовленні: 3
print(f"Усього замовлень: {total_orders}") # Висновок: Усього замовлень: 2
Замовлення зроблено! Усього замовлень: 1. Товарів у замовленні: 5
Замовлення зроблено! Усього замовлень: 2. Товарів у замовленні: 3
Усього замовлень: 2

У цьому прикладі у нас є глобальна змінна total_orders, яка зберігає загальну кількість замовлень. Функція make_order() використовує ключове слово global, щоб оголосити, що ми хочемо змінити значення глобальної змінної total_orders. При кожному виклику функції make_order() ми збільшуємо значення total_orders на 1 і виводимо повідомлення про замовлення, зокрема поточну кількість товарів і загальну кількість замовлень. Наприкінці програми ми виводимо загальну кількість замовлень.

Глобальна змінна total_orders допомагає нам відстежувати загальну кількість замовлень у нашому інтернет-магазині.

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

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

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

Приклад 11.5 (Найпростіший приклад лямбда-функції)  

add = lambda x, y: x + y
print(add(5, 3))
8

У цьому прикладі створюється лямбда-функція add, яка приймає два аргументи x і y і повертає їхню суму. Потім ми викликаємо функцію, передаючи їй аргументи 5 і 3.

Приклад 11.6 (Сортування списку)  

numbers = [2, 5, 1, 9, 3, 7]
sorted_numbers = sorted(numbers, key=lambda x: x % 3)
print(sorted_numbers)
[9, 3, 1, 7, 2, 5]

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

Приклад 11.7 (Фільтрація списку) Функція filter() - це вбудована функція в Python, яка використовується для фільтрації елементів ітерованого об’єкту на основі заданої умови.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers))
[2, 4, 6, 8, 10]

У цьому прикладі ми використовуємо лямбда-функцію разом із функцією filter() для фільтрації списку чисел. Лямбда-функція перевіряє, чи є число парним, і функція filter() залишає тільки числа, що задовольняють цій умові.

Лямбда-функції мають кілька переваг порівняно зі звичайними функціями. Ось деякі з них:

  1. Стислість і компактність: лямбда-функції дозволяють визначити функцію в одному рядку коду без використання оператора def. Вони корисні, коли вам потрібна проста функція, яка буде використовуватися тільки в одному місці програми. Використання лямбда-функцій робить код більш компактним і зосередженим на самій функції.

  2. Анонімність: лямбда-функції - це анонімні функції, тобто вони не мають імені. Це корисно, коли вам потрібно передати функцію як аргумент в іншу функцію або використовувати її разом з іншими конструкціями, такими як filter() або map(). Ви можете визначити функцію прямо всередині виклику іншої функції, без необхідності створювати окреме ім’я для функції.

  3. Зручність у використанні: лямбда-функції зручно використовувати для виконання простих операцій, таких як математичні обчислення, фільтрація або перетворення елементів у списку тощо. Вони надають спосіб визначення функцій більш лаконічним чином і дають змогу скоротити кількість коду.

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

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

11.4 Змінна кількість аргументів

Розглянемо ще одну більш просунуту тему - як створювати функції із заздалегідь невизначеним набором аргументів. Це може бути корисно, коли ви не знаєте заздалегідь, скільки аргументів буде передано у функцію, або коли вам необхідно обробляти різну кількість аргументів у різних ситуаціях. Для створення функції зі змінною кількістю аргументів використовується оператор * (зірочка) перед іменем аргументу. Давайте розглянемо приклади:

Приклад 11.8 (Функція з довільною кількістю позиційних аргументів) args (Positional Arguments) - це спеціальний параметр у функціях Python, який дозволяє передавати аргументи у вигляді кортежу.

def sum_numbers(*args):
    total = 0
    for num in args:
        total += num
    return total
print(sum_numbers(1, 2, 3))
print(sum_numbers(4, 5, 6, 7))
6
22

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

Приклад 11.9 (Функція з довільною кількістю іменованих аргументів) kwargs (Keyword Arguments)- це спеціальний параметр у функціях Python, який дозволяє передавати аргументи у вигляді словника.

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name='John', age=25)
print_info(country='USA', occupation='Engineer')
name: John
age: 25
country: USA
occupation: Engineer

Тут функція print_info приймає довільну кількість іменованих аргументів, які передаються у вигляді словника kwargs. Функція виводить на екран ключі та значення переданих аргументів.

Приклад 11.10 (Функція з комбінованим використанням позиційних та іменованих аргументів)  

def process_data(*args, **kwargs):
    for num in args:
        print(f"Number: {num}")
    for key, value in kwargs.items():
        print(f"{key}: {value}")
process_data(1, 2, 3, name='John', age=25)
Number: 1
Number: 2
Number: 3
name: John
age: 25

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

Давайте розглянемо приклад із реальним завданням:

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

def save_data(table_name, **columns):
    # Тут може бути ваш код для збереження даних у таблицю
    print(f"Data saved to table '{table_name}':")
    for column, value in columns.items():
        print(f"- {column}: {value}")
# Приклад використання функції save_data
save_data('students', name='John', age=25, grade='A')
save_data('products', name='Apple', price=1.99)
Data saved to table 'students':
- name: John
- age: 25
- grade: A
Data saved to table 'products':
- name: Apple
- price: 1.99

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

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

11.5 Завдання

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

У словнику products зберігаються назви та вартість товарів.

```{python}
products = {
    "ноутбук": 5000,
    "смартфон": 20000,
    "навушники": 1000,
    "монітор": 10000,
    "клавіатура": 500,
    "миша": 200,
    "роутер": 1500,
    "принтер": 5000,
    "флешка": 1000,
    "жорсткий диск": 3000
}
```

Напишіть функцію calculate_order_cost, яка отримує на вхід словник із товарами, а далі необмежену кількість аргументів: назва товарів, які вибрав покупець. Якщо товару немає в products, просто не враховуємо цей товар. Функція повертає два значення: сумарну вартість замовлення, словник куплених товарів, де ключ - назва товару, значення - число одиниць товару.

Приклад:

```{python}
total_cost, orders_info = calculate_order_cost(products, 'ноутбук', 'роутер') 
print(total_cost) # 6500
print(orders_info) # {'ноутбук': 1, 'роутер': 1}

total_cost, orders_info = calculate_order_cost(products , 'миша', 'флешка', 'монітор', 'кабель', 'миша') 
print(total_cost) # 11400
print(orders_info) # {'миша': 2, 'флешка': 1, 'монітор': 1}
```
Рішення
def calculate_order_cost(products, *args):
    total_cost = 0
    orders_info = {}
    for product in args:
        if product in products:
            total_cost += products[product]
            if product in orders_info:
                orders_info[product] += 1
            else:
                orders_info[product] = 1
    return total_cost, orders_info

Завдання 11.2
Напишіть функцію calculator, що приймає три аргументи: перший - операція, другі два - числа, над якими має бути здійснено операцію.

Наприклад: якщо перший аргумент +, то потрібно скласти числа a і b; якщо -, то відняти.

Функція повинна повернути одне число - результат виконання операції. Якщо операцію виконати не вдалося - повернути рядок "Error".

Приклад:

```{python}
calculator('+', 2, 4) # -> 6
calculator('^', 2, 4) # -> 16
calculator('', 2, 4) # -> 'Error'
```
Рішення
def calculator(op, a, b):
    if op in ['+', '-', '*', '/', '%', '//', '**'] and b != 0:
        return eval(f'{a}{op}{b}')
    elif op == '^':
        return a ** b
    elif op in ['/', '//', '%'] and b == 0:
        return 'Error'
    else:
        return 'Error'

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

  • Гриффіндор - хоробрість, благородство, честь;
  • Рейвенклов - розум, допитливість, творчість;
  • Слізерин - хитрість, амбітність, винахідливість;
  • Гафелпаф - працьовитість, вірність, чесність.

Напишіть функцію sorting_hat(), яка як аргумент прийматиме словник вигляду: new_students = {'Сіріус Блек': 'хоробрість'}, де ключ - ім’я учня, значення - якість, якою він володіє.

Функція має повернути 2 змінні:

  • sorted_students - словник виду: ключ - ім’я учня, значення - факультет, на який його було зараховано. Дані в словнику мають бути відсортовані за назвою факультету та за ім’ям учня.
  • department - словник виду: ключ - факультет, значення - кількість зарахованих на нього учнів. Відсортуйте словник за ключем.

Приклад:

```{python}
def sorting_hat(new_students):
    ...
    return sorted_students, departments

new_students = {
    'Сіріус Блек': 'хоробрість',
    'Аманда Коршун': 'допитливість',
    'Пенелопа Вулпінголд': 'винахідливість',
    'Артур Поттер': 'хоробрість',
    'Тесая Блек': 'розум'
}

sorting_hat(new_students) # -> ->
# sorted_students = {'Аманда Коршун': 'Рейвенклов', 'Артур Поттер': 'Гриффіндор', 'Пенелопа Вулпінголд': 'Слізерин', 'Сіріус Блек': 'Гриффіндор', 'Тесая Блек': 'Рейвенклов'}
# departments = {'Гафелпаф': 0, 'Гриффіндор': 2, 'Рейвенклов': 2, 'Слізерин': 1}
```
Рішення
def sorting_hat(new_students):
    departments = {'Гриффіндор': 0, 'Рейвенклов': 0, 'Гафелпаф': 0, 'Слізерин': 0}
    sorted_students = {}
    for student, quality in new_students.items():
        if quality in ['хоробрість', 'благородство', 'честь']:
            departments['Гриффіндор'] += 1
            sorted_students[student] = 'Гриффіндор'
        elif quality in ['розум', 'допитливість', 'творчість']:
            departments['Рейвенклов'] += 1
            sorted_students[student] = 'Рейвенклов'
        elif quality in ['хитрість', 'амбітність', 'винахідливість']:
            departments['Слізерин'] += 1
            sorted_students[student] = 'Слізерин'
        elif quality in ['працьовитість', 'вірність', 'чесність']:
            departments['Гафелпаф'] += 1
            sorted_students[student] = 'Гафелпаф'
    sorted_students = dict(sorted(sorted_students.items(), key=lambda x: x[0]))
    departments = dict(sorted(departments.items(), key=lambda x: x[0]))
    return sorted_students, departments

Завдання 11.4
Дано список кортежів input_list. Відсортуйте input_list за 2 елементом кортежу в порядку убування. Відфільтруйте список так, щоб залишилися тільки ті елементи, друге значення в кортежі в яких кратне 5.

Результат збережіть у result.

Для розв’язання задачі використовуйте lambda-функцію.

Приклад:

```{python}
input_list = [('Anna', 13), ('Ivan', 20), ('Irina', 23), ('Olga', 25), 
              ('Ivan', 30), ('Oleg', 24), ('Olga', 26)]
result = [('Ivan', 30), ('Olga', 25), ('Ivan', 20)]
```
Рішення
input_list = [('Anna', 13), ('Ivan', 20), ('Irina', 23), ('Olga', 25), 
              ('Ivan', 30), ('Oleg', 24), ('Olga', 26)]

result = list(filter(lambda x: x[1] % 5 == 0, sorted(input_list, key=lambda x: x[1], reverse=True)))

Завдання 11.5
Гіпотеза Коллатца: яке б початкове натуральне число n ми не взяли, рано чи пізно ми отримаємо одиницю, якщо виконуємо такі перетворення:

  • якщо n парне, то ділимо його на 2;
  • якщо n непарне, то множимо на 3 і додаємо 1 (отримуємо 3n + 1).

Напишіть функцію collatz_conjecture(), яка прийматиме натуральне число n і повертатиме послідовність чисел, отриманих зазначеними вище перетвореннями.

Приклад:

```{python}
def collatz_conjecture(n):
    ...

collatz_conjecture(8) # -> [8, 4, 2, 1]
collatz_conjecture(3) # -> [3, 10, 5, 16, 8, 4, 2, 1]
```
Рішення
def collatz_conjecture(n):
    result = [n]
    while n != 1:
        if n % 2 == 0:
            n //= 2
        else:
            n = 3 * n + 1
        result.append(n)
    return result

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

Приклад:

```{python}
def reverse_tuple(lst):
    ...

reverse_tuple([1, 3, 4]) # -> (4, 3, 1)
reverse_tuple([1, 3, 4, 4, 5, 2]) # -> (2, 5, 4, 3, 1)
```
Рішення
def reverse_tuple(lst: list):
    lst = lst[::-1]
    return tuple(sorted(set(lst), key=lst.index))

Завдання 11.7
Напишіть функцію update_tuple(), яка прийматиме кортеж input_tuple, видалятиме першу появу певного елемента el_del із кортежу за значенням і повертатиме кортеж без нього.

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

Приклад:

```{python}
def update_tuple(input_tuple, el_del):
    ...

update_tuple((1, 2, 3), 1) #-> (2, 3)
update_tuple((1, 2, 3, 3, 9, 5), 3) #-> (1, 2, 3, 9, 5)
update_tuple((1, 2, 3), 9) #-> (1, 2, 3)
```
Рішення
def update_tuple(input_tuple, el_del):
    if el_del in input_tuple:
        input_tuple = list(input_tuple)
        input_tuple.remove(el_del)
        return tuple(input_tuple)
    return input_tuple

Data Miorsh Ihor Miroshnychenko Youtube Monobank