# теория · шаг 4 из 7
Функции: аргументы, локальные имена и стек вызовов
Коротко
Функция задаётся def имя(параметры): и возвращает значение через return; без return она
возвращает None. При вызове аргумент не копируется: параметр становится вторым именем того же
значения. Поэтому spisok.append(x) внутри функции виден снаружи, а spisok = [0] - нет: это просто
переклеенная наклейка. Локальная переменная - имя, которому присвоили значение внутри функции; она
живёт до конца вызова. Изменяемое значение по умолчанию (def f(x, acc=[])) создаётся один раз на все
вызовы. Каждый вызов получает свой набор локальных имён на стеке вызовов, а глубина стека в Python по
умолчанию - около тысячи.
Если ты знаешь, почему a = a.sort() даёт None и чем опасен def f(x, acc=[]), листай до «Теперь сам» в конце.
Как думаешь, что напечатает программа
def dobavit(spisok, x):
spisok.append(x)
spisok = [0]
spisok.append(x)
a = [1]
dobavit(a, 2)
print(a)
Ответь себе до того, как читать дальше. Вариантов три: [1] - если функция получила копию, [0, 2] -
если функция может подменить список вызывающего, и [1, 2]. Языки отвечают на этот вопрос по-разному, а
Python - третьим способом.
На что это похоже
Ты даёшь коллеге второй ключ от своей квартиры. Если он переставит мебель, ты это увидишь: квартира одна. Если он выбросит твой ключ и получит ключ от другой квартиры, в твоей ничего не изменится - он просто теперь ходит в другую. Параметр функции - такой второй ключ. Изменения ВНУТРИ значения видны всем, у кого есть ключ; замена ключа видна только тому, кто его заменил.
Механизм: вызов, имена и стек
Вызов dobavit(a, 2) делает три вещи. Создаёт новый набор локальных имён. Привязывает параметр spisok
к тому же значению, на которое указывает a, а x - к числу 2. Выполняет тело функции, пока не
встретит return или конец, и после этого набор имён исчезает, а выполнение продолжается с места вызова.
Наборы имён складываются друг на друга. Если функция вызывает другую функцию, новый набор кладётся
сверху, а когда та закончит, снимается, и снова виден набор вызывающей. Такая конструкция - добавлять и
снимать только сверху - называется стеком, а этот конкретный стек - стеком вызовов. Его глубина
ограничена: Python по умолчанию разрешает около тысячи вложенных вызовов и потом падает с
RecursionError. Для функции, которая вызывает саму себя, это предел глубины.
Правило
Аргумент не копируется: параметр - второе имя того же значения. Изменение значения (append,
присваивание элементу) видно снаружи, переприсваивание параметра - нет. Имя, которому внутри функции
что-то присвоено, локально во всей функции. Без return функция возвращает None. Значение по
умолчанию вычисляется один раз, при создании функции.
Разбор: изменение против переприсваивания
def dobavit(spisok, x):
spisok.append(x)
spisok = [0]
spisok.append(x)
a = [1]
dobavit(a, 2)
print(a)
def uvelichit(n):
n = n + 1
k = 5
uvelichit(k)
print(k)
[1, 2]
5
Первый append менял тот самый список, который снаружи зовётся a. Строка spisok = [0] переклеила
локальное имя на новый список, и второй append ушёл в него - он исчез вместе с концом вызова. С числом
n = n + 1 происходит ровно то же: создаётся новое число, локальное имя переклеивается на него, а k
снаружи не тронут. Поэтому функция, которая должна вернуть результат, его возвращает, а не надеется
изменить аргумент.
Разбор: значение по умолчанию, которое помнит прошлое
def zapisat(x, zhurnal=[]):
zhurnal.append(x)
return zhurnal
print(zapisat(1))
print(zapisat(2))
def zapisat2(x, zhurnal=None):
if zhurnal is None:
zhurnal = []
zhurnal.append(x)
return zhurnal
print(zapisat2(1))
print(zapisat2(2))
[1]
[1, 2]
[1]
[2]
Пустой список [] в заголовке создаётся один раз, когда Python читает def, и дальше каждый вызов без
второго аргумента получает ТОТ ЖЕ список. Второй вызов видит запись первого. Лечится так, как во второй
функции: по умолчанию None, а новый список создаётся внутри тела, на каждом вызове свой. is None
спрашивает «это то самое значение None?», а не «равно ли»: так None и проверяют.
Что ломается без этого
def kvadrat(x):
x * x
print(kvadrat(3))
a = [3, 1, 2]
b = a.sort()
print(a, b)
print(sorted([3, 1, 2]))
schetchik = 0
def tik():
schetchik = schetchik + 1
try:
tik()
except UnboundLocalError as e:
print("ошибка:", type(e).__name__)
None
[1, 2, 3] None
[1, 2, 3]
ошибка: UnboundLocalError
Забытый return даёт None, и ошибка всплывает далеко от места, где её сделали. a.sort() сортирует
список на месте и возвращает None, поэтому a = a.sort() теряет список целиком; новый отсортированный
список возвращает sorted(a). Последняя функция падает, потому что присваивание schetchik = ... делает
имя локальным во ВСЕЙ функции, и справа читается локальная переменная, у которой ещё нет значения.
type(e).__name__ - имя типа ошибки, его и печатает последняя строка. Внешнее имя меняют, написав
первой строкой тела функции global schetchik, но обычно правильнее вернуть новое значение.
Зачем это в работе
Функция обработки заказа добавила служебное поле в словарь-аргумент, и через три модуля отчёт
показывает поле, которого никто не заказывал. Обработчик запросов с def handle(req, cache={}) годами
работает как кеш на весь процесс и однажды отдаёт одному пользователю данные другого. Рекурсивный обход
дерева каталогов падает на глубине в тысячу. Все три истории - это правила из этого урока, а не редкие
особенности языка.
Теперь сам
Что напечатает программа?
def obrabotat(a):
a[0] = 100
a = a + [5]
return len(a)
spisok = [1, 2]
n = obrabotat(spisok)
print(spisok, n)
Ответ. [100, 2] 3. a[0] = 100 меняет сам список, общий с spisok, - это видно снаружи. a = a + [5]
создаёт НОВЫЙ список из трёх элементов и переклеивает на него локальное имя; spisok остаётся из двух.
Длина нового списка, 3, возвращается через return.
Главное
Параметр функции - второе имя того же значения, а не копия: изменение значения видно вызывающему,
переприсваивание имени - нет. Всё, что функция должна отдать, она отдаёт через return, а каждый вызов
живёт в своём наборе локальных имён на стеке вызовов.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий