Python: Почему по-разному обрабатываются параметры функций int и list?

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

def f(i):
    print("Calling f(i)...")
    print("id(i): {}\n".format(id(i)))
    print("Inside f(): i += 1")
    i += 1
    print("id(i): {}".format(id(i)))
    return

i = 1
print("\nBefore function call...")
print("id(i): {}\n".format(id(i)))
f(i)

Это оценивается как:

Before function call...
id(i): 507107200

Calling f(i)...
id(i): 507107200

Inside f(): i += 1
id(i): 507107232

Насколько я сейчас читаю, механизм вызова функций в Python - это «Вызов по ссылке на объект». Это означает, что аргумент изначально передается посредством ссылки на объект, но если он изменяется внутри функции, создается новая объектная переменная. Мне кажется разумным избегать дизайна, в котором функции непреднамеренно изменяют глобальные переменные.

Но что произойдет, если мы передадим список в качестве аргумента?

def g(l):
    print("Calling f(l)...")
    print("id(l): {}\n".format(id(l)))
    print("Inside f(): l[0] += 1")
    l[0] += 1
    print("id(l): {}".format(id(l)))
    return

l = [1, 2, 3]
print("\nBefore function call...")
print("id(l): {}\n".format(id(l)))
g(l)

Это приводит к:

Before function call...
id(l): 120724616

Calling f(l)...
id(l): 120724616

Inside f(): l[0] += 1
id(l): 120724616

Как видим, ссылка на объект осталась прежней! Итак, мы работаем с глобальной переменной, не так ли?

Я знаю, что мы можем легко преодолеть это, передав копию списка в функцию с помощью:

g(l[:])

Но мой вопрос: в чем причина того, что в Python реализованы два разных поведения параметров функции? Если мы намереваемся манипулировать глобальной переменной, мы могли бы также использовать «глобальное» -ключевое слово для списка, как мы это сделали бы для целых чисел, не так ли? Как такое поведение согласуется с дзеном Python «явное лучше, чем неявное»?


person barrios    schedule 06.11.2014    source источник
comment
Спасибо за подсказку, даже при интенсивном поиске я не нашел этот пост. На мой вопрос дан ответ.   -  person barrios    schedule 06.11.2014
comment
TL; DR: они не обрабатываются по-разному, но поскольку объекты list изменяемые и int объекты неизменяемые, их поведение отображается отличаться.   -  person jonrsharpe    schedule 06.11.2014


Ответы (4)


В Python есть два типа объектов - изменяемые и неизменяемые. Большинство встроенных типов, таких как int, string или float, неизменяемы. Это означает, что они не могут измениться. Такие типы, как list, dict или array, являются изменяемыми, что означает, что их состояние может быть изменено. Почти все объекты, определенные пользователем, тоже изменяемы.

Когда вы выполняете i += 1, вы присваиваете новое значение i, равное i + 1. Это никоим образом не изменяет i, а просто говорит, что он должен забыть i и заменить его значением i + 1. Затем i заменяется совершенно новым объектом. Но когда вы делаете i[0] += 1 в списке, вы говорите списку, что он должен заменить элемент 0 на i[0] + 1. Это означает, что id(i[0]) будет изменен новым объектом, и состояние списка i изменится, но его идентичность останется прежней - это тот же объект, что и был, только измененный.

Обратите внимание, что в Python это неверно для строк, поскольку они неизменяемы, и изменение одного элемента скопирует строку с обновленными значениями и создаст новый объект.

person Wikiii122    schedule 06.11.2014
comment
x += y присваивается x, но он также может изменять объект x, ранее упомянутый на месте, если он определяет __iadd__ соответствующим образом. Например, списки делают это. - person ; 07.11.2014
comment
В Python есть два типа объектов - изменяемые и неизменяемые. В языке нет концепции изменчивого или неизменного. Mutable - это просто неформальное описание программистами того, когда у метода есть API, с помощью которого вы можете изменять его содержимое. Нет никакой разницы в языковой семантике между изменяемым и неизменяемым. - person newacct; 08.11.2014

Почему по-разному обрабатываются параметры функций int и list?

Они не. Все параметры обрабатываются одинаково, независимо от типа.

Вы видите разное поведение в двух случаях, потому что вы делаете разные вещи с l.

Во-первых, давайте упростим += на = и +: l = l + 1 в первом случае и l[0] = l[0] + 1 во втором. (+= не всегда равно присвоению и +; это зависит от класса среды выполнения объекта на левой стороне, который может его переопределить; но здесь для ints это эквивалентно назначению и +.) Кроме того, правая часть задания просто читает материал и не представляет интереса, поэтому давайте пока просто проигнорируем это; так что у тебя есть:

l = something (in the first case)
l[0] = something (in the second case)

Второй - «присвоение элементу», который на самом деле является синтаксическим сахаром для вызова метода . __setitem__():

l.__setitem__(0, something)

Итак, теперь вы можете увидеть разницу между ними -

  • В первом случае вы присваиваете переменную l. Python - это передача по значению, поэтому это не влияет на внешний код. Назначение переменной просто указывает на новый объект; он не влияет на объект, на который он указывал. Если бы вы присвоили что-то l во втором случае, это также не повлияло бы на исходный объект.
  • Во втором случае вы вызываете метод объекта, на который указывает l. Этот метод является методом изменения списков и, таким образом, изменяет содержимое объекта списка, исходного объекта списка, указатель на который был передан методу. Верно, что int (класс среды выполнения l в первом случае) не имеет методов, которые изменяются, но это не относится к делу.

Если вы проделали то же самое с l в обоих случаях (если это было возможно), то вы можете ожидать одинаковой семантики.

person newacct    schedule 08.11.2014

Это довольно часто встречается во многих языках (например, в Ruby).

Сама переменная привязана к функции. Но эта переменная - просто указатель на объект, плавающий где-то в памяти, и этот объект можно изменить.

person Andy Jones    schedule 06.11.2014

В Python все является объектом, и, следовательно, все представлено ссылкой. Самое примечательное в переменных в Python - это то, что они содержат ссылки на объекты, а не сами объекты. Теперь, когда аргументы передаются функциям, они передаются по ссылке. Следовательно, внутри области действия функции каждый параметр назначается ссылке на аргумент, а затем обрабатывается как локальная переменная внутри функции. Когда вы назначаете новое значение параметру, вы меняете объект, на который он ссылается, и поэтому у вас есть новый объект, и любые его изменения (даже если это изменяемый объект) не будут видны за пределами области действия функции в вопрос, и никак не связанный с переданным аргументом. Тем не менее, когда вы не назначаете новую ссылку на параметр, он остается удерживающим ссылку на аргумент, и любые его изменения (если и только если он изменяемый) будут видны вне области действия функции.

person ultrasmart    schedule 08.11.2014