Почему функция может изменять некоторые аргументы так, как они воспринимаются вызывающей стороной, а не другие? - PullRequest
151 голосов
/ 22 февраля 2009

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

Ответы [ 10 ]

180 голосов
/ 22 февраля 2009

Некоторые ответы содержат слово «копировать» в контексте вызова функции. Я нахожу это запутанным.

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 .

14 голосов
/ 23 февраля 2009

У вас уже есть несколько ответов, и я полностью согласен с Дж. Ф. Себастьяном, но вы можете найти это полезным в качестве ярлыка:

Каждый раз, когда вы видите varname =, вы создаете новое связывание имени в области действия функции. Какое бы значение varname не было связано до того, как оно будет потеряно в этой области .

Каждый раз, когда вы видите varname.foo(), вы вызываете метод на varname. Метод может изменить varname (например, list.append). varname (или, скорее, объект с именами varname) может существовать в нескольких областях, и, поскольку это один и тот же объект, любые изменения будут видны во всех областях.

[обратите внимание, что ключевое слово global создает исключение для первого регистра]

12 голосов
/ 22 февраля 2009

f фактически не меняет значение x (которое всегда является одной и той же ссылкой на экземпляр списка). Скорее, он изменяет содержимое этого списка.

В обоих случаях копия ссылки передается в функцию. Внутри функции,

  • n получает новое значение. Изменяется только ссылка внутри функции, а не та, что за ее пределами.
  • x не присваивается новое значение: ни ссылка внутри, ни снаружи функции не изменяются. Вместо этого x ' значение изменяется.

Поскольку x внутри функции и за ее пределами ссылаются на одно и то же значение, оба видят модификацию. Напротив, n внутри функции и за ее пределами относятся к различным значениям после переназначения n внутри функции.

5 голосов
/ 22 февраля 2009

Я буду переименовывать переменные, чтобы уменьшить путаницу. 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 .

Надеюсь, это поможет. : -)

2 голосов
/ 22 февраля 2009

n - это int (неизменяемый), и копия передается функции, поэтому в функции вы изменяете копию.

X является списком (изменяемым), и копия указателя передается в функцию, поэтому x.append (4) изменяет содержимое списка. Однако, если вы сказали, что в своей функции x = [0,1,2,3,4], вы не измените содержимое x в main ().

2 голосов
/ 22 февраля 2009

Это потому, что список является изменяемым объектом. Вы не устанавливаете x в значение [0,1,2,3], вы определяете метку для объекта [0,1,2,3].

Вы должны объявить свою функцию f () следующим образом:

def f(n, x=None):
    if x is None:
        x = []
    ...
1 голос
/ 30 декабря 2017

Если функции переписаны с совершенно разными переменными, и мы вызываем 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 имеют одинаковый идентификатор. Просто разные теги для той же базовой структуры, что и в статье.

0 голосов
/ 21 октября 2018

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

Это согласуется со многими другими языками.

Запустите следующий короткий скрипт, чтобы увидеть, как он работает:

def func1(x, l1):
    x = 5
    l1.append("nonsense")

y = 10
list1 = ["meaning"]
func1(y, list1)
print(y)
print(list1)
0 голосов
/ 16 января 2016

Python копируется по значению ссылки. Объект занимает поле в памяти, и ссылка связана с этим объектом, но сама занимает поле в памяти. И имя / значение связано со ссылкой. В функции python всегда копируется значение ссылки, поэтому в вашем коде n копируется в новое имя, и когда вы его присваиваете, в стеке вызывающей стороны появляется новое пространство Но для списка также скопировано имя, но оно относится к той же памяти (поскольку вы никогда не назначаете списку новое значение). Это магия в питоне!

0 голосов
/ 04 декабря 2010

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

Сравните эти две функции

def foo(x):
    x[0] = 5

def goo(x):
    x = []

Теперь, когда вы печатаете в оболочке

>>> cow = [3,4,5]
>>> foo(cow)
>>> cow
[5,4,5]

Сравните это с липучкой.

>>> cow = [3,4,5]
>>> goo(cow)
>>> goo
[3,4,5]

В первом случае мы передаем копию адреса cow в foo, и foo изменяет состояние находящегося там объекта. Объект модифицируется.

Во втором случае вы передаете копию адреса коровы в Goo. Затем Goo приступает к изменению этой копии. Эффект: нет.

Я называю это принципом розового дома . Если вы сделаете копию своего адреса и сообщите маляр раскрась дом по этому адресу в розовый цвет, у тебя получится розовый дом. Если вы дадите художнику копию своего адреса и попросите его поменять его на новый, адрес вашего дома не меняется.

Объяснение устраняет большую путаницу. Python передает адреса переменных, хранящиеся по значению.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...