Распространенные проблемы/нюансы работы с функциями#

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

Очень часто в решении заданий встречается такой нюанс: функция должна собрать какие-то данные в список/словарь и список создан вне функции. Тогда вроде как функция работает правильно, но при этом тест не проходит. Это происходит потому что в таком варианте функция работает неправильно и каждый вызов добавляет элементы в тот же список:

In [1]: result = []

In [2]: 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]

Исправить это можно переносом строки создания списка в функцию:

In [20]: 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]

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

Значения по умолчанию в параметрах создаются во время создания функции#

Пример функции, которая должна выводить текущую дату и время при каждом вызове:

In [1]: from datetime import datetime

In [2]: import time

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

In [4]: 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() в теле функции:

In [5]: def print_current_datetime():
   ...:     print(f">>> {datetime.now()}")
   ...:

Второй пример где этот нюанс может привести к неожиданным результатам, если о нем не знать - изменяемые типы данных в значении по умолчанию.

Например, использование списка в значении по умолчанию:

In [15]: 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 был необязательным и по умолчанию создавался пустой список, надо сделать так:

In [22]: 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) дает ошибку.