Перейти до змісту

Поширені проблеми/нюанси роботи з функціями

Список/словник, у який збираються дані у функції, створений за межами функції

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

result = []

def func(items):
    for i in items:
        result.append(i*100)
    return result


In [3]: func([1, 2, 3])
Out[3]: [100, 200, 300]

In [4]: func([7, 8])
Out[4]: [100, 200, 300, 700, 800]

Виправити це можна перенесенням рядка створення списку в функцію:

def func(items):
    result = []
    for i in items:
        result.append(i*100)
    return result


In [21]: func([1, 2, 3])
Out[21]: [100, 200, 300]

In [22]: func([7, 8])
Out[22]: [700, 800]

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

Значення за замовчуванням у параметрах створюються під час створення функції

Приклад функції, яка повинна виводити поточну дату та час кожного виклику:

from datetime import datetime
import time

def print_current_datetime(ptime=datetime.now()):
    print(f">>> {ptime}")


for i in range(3):
    print("Імітуємо довге виконання...")
    time.sleep(1)
    print_current_datetime()

Імітуємо довге виконання...
>>> 2021-02-23 09:01:49.845425
Імітуємо довге виконання...
>>> 2021-02-23 09:01:49.845425
Імітуємо довге виконання...
>>> 2021-02-23 09:01:49.845425

Через те що datetime.now() вказано у значенні за замовчуванням, це значення створюється під час створення функції. І після, коли функція викликається, час один і той же. Для коректної роботи треба викликати datetime.now() в тілі функції:

def print_current_datetime():
    print(f">>> {datetime.now()}")

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

Наприклад, використання списку у значенні за замовчуванням:

def add_item(item, data=[]):
    data.append(item)
    return data

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

In [16]: add_item(1)
Out[16]: [1]

In [17]: add_item(2)
Out[17]: [1, 2]

In [18]: add_item(4)
Out[18]: [1, 2, 4]

Якщо потрібно зробити так, щоб параметр data був необов'язковим і за замовчуванням створювався порожній список, треба зробити так:

def add_item(item, data=None):
    if data is None:
        data = []
    data.append(item)
    return data


In [23]: add_item(1)
Out[23]: [1]

In [24]: add_item(2)
Out[24]: [2]

Виняток UnboundLocalError: local variable referenced before assignment

Помилка може виникнути у таких випадках:

  • звернення до змінної функції йде до її створення - це може бути випадковість (помилка) або наслідок того, що якась умова не виконалася
  • звернення всередині функції до глобальної змінної, але при цьому всередині функції створена така ж змінна пізніше

Перший випадок – звернулися до змінної до її створення:

def f():
    print(b)
    b = 55

In [6]: f()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Input In [6], in <cell line: 1>()
----> 1 f()

Input In [5], in f()
      1 def f():
----> 2     print(b)
      3     b = 55

UnboundLocalError: local variable 'b' referenced before assignment

Змінна створена в умові, а умова не виконана:

def f():
    if 5 > 8:
        b = 55
    print(b)

In [8]: f()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Input In [8], in <cell line: 1>()
----> 1 f()

Input In [7], in f()
      2 if 5 > 8:
      3     b = 55
----> 4 print(b)

UnboundLocalError: local variable 'b' referenced before assignment

Ім'я глобальної та локальної змінної однакове і всередині функції спочатку йде спроба звернення до глобальної, потім створення локальної:

a = 10

def f():
    print(a)
    a = 55
    print(a)


In [4]: f()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Input In [4], in <cell line: 1>()
----> 1 f()

Input In [3], in f()
      1 def f():
----> 2     print(a)
      3     a = 55
      4     print(a)

UnboundLocalError: local variable 'a' referenced before assignment

Python повинен визначити область видимості змінної. І у випадку функцій Python вважає, що змінна локальна, якщо вона створена всередині функції. На момент коли ми доходимо до виклику функції, Python вже вирішив, що a це локальна змінна і саме через це перший рядок print(a) дає помилку.