Почему функция может изменять некоторые аргументы, воспринимаемые вызывающим, но не другие?
Я пытаюсь понять подход Python к переменной области. В этом примере, почему f()
может изменять значение x
, как это воспринимается в main()
, но не значение n
?
def f(n, x):
n = 2
x.append(4)
print('In f():', n, x)
def main():
n = 1
x = [0,1,2,3]
print('Before:', n, x)
f(n, x)
print('After: ', n, x)
main()
Выход:
Before: 1 [0, 1, 2, 3]
In f(): 2 [0, 1, 2, 3, 4]
After: 1 [0, 1, 2, 3, 4]
Ответы
Ответ 1
Некоторые ответы содержат слово "копировать" в контексте вызова функции. Я нахожу это запутанным.
Python не копирует объекты, которые вы передаете во время вызова функции.
Параметры функции являются именами. Когда вы вызываете функцию, Python связывает эти параметры с любыми объектами, которые вы передаете (через имена в области вызова).
Объекты могут быть изменяемыми (например, списки) или неизменяемыми (например, целые числа, строки в Python). Изменчивый объект вы можете изменить. Вы не можете изменить имя, вы можете просто привязать его к другому объекту.
Ваш пример касается не областей или пространств имен, а именования, привязки и изменчивости объекта в Python.
def f(n, x): # these 'n', 'x' have nothing to do with 'n' and 'x' from main()
n = 2 # put 'n' label on '2' balloon
x.append(4) # call 'append' method of whatever object 'x' is referring to.
print('In f():', n, x)
x = [] # put 'x' label on '[]' ballon
# x = [] has no effect on the original list that is passed into the function
Вот хорошие картинки о разнице между переменными в других языках и именами в Python.
Ответ 2
У вас уже есть несколько ответов, и я в целом согласен с Я. Ф. Себастьяном, но вы можете найти это полезным в качестве ярлыка:
Когда вы видите varname =
, вы создаете новое связывание имен в пределах области функций. Независимо от того, какое значение varname
было связано до того, как оно потеряно в этой области действия.
Когда вы видите varname.foo()
, вы вызываете метод на varname
. Метод может изменить varname (например, list.append
). varname
(или, вернее, объект, который имеет имена varname
), может существовать в нескольких областях, и поскольку он является одним и тем же объектом, любые изменения будут видны во всех областях.
[обратите внимание, что ключевое слово global
создает исключение для первого случая]
Ответ 3
f
фактически не изменяет значение x
(которое всегда является одной и той же ссылкой на экземпляр списка). Скорее, это изменяет содержимое этого списка.
В обоих случаях копия ссылки передается функции. Внутри функции,
-
n
получает новое значение. Изменена только ссылка внутри функции, а не та, которая находится за ее пределами. -
x
не получает новое значение: ни ссылка внутри, ни вне функции не изменяется. Вместо этого значение x
изменяется.
Поскольку и внутри функции x
и снаружи она относится к одному и тому же значению, оба видят модификацию. Напротив, n
внутри функции и вне ее относится к разным значениям после того, как n
было переназначено внутри функции.
Ответ 4
Я буду переименовывать переменные, чтобы уменьшить путаницу. n → nf или nmain. x → xf или xmain:
def f(nf, xf):
nf = 2
xf.append(4)
print 'In f():', nf, xf
def main():
nmain = 1
xmain = [0,1,2,3]
print 'Before:', nmain, xmain
f(nmain, xmain)
print 'After: ', nmain, xmain
main()
Когда вы вызываете функцию f, среда выполнения Python создает копию xmain и присваивает ее xf и аналогично присваивает копию nmain nf.
В случае n значение, которое копируется, равно 1.
В случае x значение, которое копируется, не литеральный список [0, 1, 2, 3]. Это ссылка к этому списку. xf и xmain указывают на один и тот же список, поэтому при изменении xf вы также изменяете xmain.
Если, однако, вы должны написать что-то вроде:
xf = ["foo", "bar"]
xf.append(4)
вы обнаружите, что xmain не изменился. Это связано с тем, что в строке xf = [ "foo", "bar" ] у вас есть изменение xf, чтобы указать на новый список. Любые изменения, внесенные в этот новый список, не будут влиять на список, который xmain все еще указывает на.
Надеюсь, что это поможет.: -)
Ответ 5
Это потому, что список является изменяемым объектом. Вы не устанавливаете x в значение [0,1,2,3], вы определяете метку для объекта [0,1,2,3].
Вы должны объявить свою функцию f() следующим образом:
def f(n, x=None):
if x is None:
x = []
...
Ответ 6
n - это int (неизменяемый), и копия передается функции, поэтому в функции вы меняете копию.
X - это список (изменяемый), и копия указателя передается o функции, поэтому x.append(4) изменяет содержимое списка. Однако вы сказали x = [0,1,2,3,4] в своей функции, вы не измените содержимое x в main().
Ответ 7
Если функции переписываются с совершенно разными переменными, и мы называем их id, то он хорошо иллюстрирует точку. Сначала я не получил этого и прочитал пост jfs с большим объяснением, поэтому я попытался понять/убедить себя:
def f(y, z):
y = 2
z.append(4)
print ('In f(): ', id(y), id(z))
def main():
n = 1
x = [0,1,2,3]
print ('Before in main:', n, x,id(n),id(x))
f(n, x)
print ('After in main:', n, x,id(n),id(x))
main()
Before in main: 1 [0, 1, 2, 3] 94635800628352 139808499830024
In f(): 94635800628384 139808499830024
After in main: 1 [0, 1, 2, 3, 4] 94635800628352 139808499830024
z и x имеют одинаковый идентификатор. Просто разные теги для той же базовой структуры, что и в статье.
Ответ 8
Python - это чистый язык с перекрестным значением, если вы думаете об этом правильно. Переменная python сохраняет местоположение объекта в памяти. Переменная Python не сохраняет сам объект. Когда вы передаете переменную функции, вы передаете копию адреса объекта, на который указывает эта переменная.
Противопоставление этих двух функций
def foo(x):
x[0] = 5
def goo(x):
x = []
Теперь, когда вы вводите в оболочку
>>> cow = [3,4,5]
>>> foo(cow)
>>> cow
[5,4,5]
Сравните это с goo.
>>> cow = [3,4,5]
>>> goo(cow)
>>> goo
[3,4,5]
В первом случае мы передаем копию адреса коровы в foo и foo, изменив состояние объекта, находящегося там. Объект будет изменен.
Во втором случае вы передаете копию адреса коровы. Затем goo продолжает менять эту копию. Эффект: нет.
Я называю это принципом розового дома. Если вы сделаете копию своего адреса и
художник, чтобы покрасить дом по этому адресу розовым, вы окажетесь с розовым домом.
Если вы дадите художнику копию своего адреса и скажите ему, чтобы он изменил его на новый адрес,
адрес вашего дома не меняется.
Объяснение устраняет много путаницы. Python передает значения переменных адресов по значению.
Ответ 9
Python копирует значение ссылки. Объект занимает поле в памяти, и ссылка связана с этим объектом, но сама занимает поле в памяти. И имя/значение связано с ссылкой. В функции python она всегда копирует значение ссылки, поэтому в вашем коде n копируется как новое имя, когда вы назначаете это, оно имеет новое пространство в стеке вызывающего абонента. Но для списка имя также скопировано, но оно относится к той же памяти (поскольку вы никогда не назначаете списку новое значение). Это волшебство в python!
Ответ 10
Мое общее понимание состоит в том, что любая переменная объекта (например, список или dict) может быть изменена с помощью ее функций. Я полагаю, что вы не можете сделать это переназначить параметр - т.е. назначить его по ссылке в вызываемой функции.
Это согласуется со многими другими языками.
Запустите следующий короткий скрипт, чтобы увидеть, как он работает:
def func1(x, l1):
x = 5
l1.append("nonsense")
y = 10
list1 = ["meaning"]
func1(y, list1)
print(y)
print(list1)