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

Я пытаюсь понять подход 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)