Чому функція може змінювати деякі аргументи так, як сприймає абонент, а не інші?


182

Я намагаюся зрозуміти підхід 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]

7
добре пояснено тут nedbatchelder.com/text/names.html
Roushan

Відповіді:


212

Деякі відповіді містять слово "копіювати" в контексті виклику функції. Я вважаю це заплутаним.

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 .


3
Ця стаття допомогла мені краще зрозуміти проблему, і вона пропонує вирішення проблеми та деяких додаткових застосувань: Значення параметрів за замовчуванням у Python
Gfy

@Gfy, я бачив подібні приклади і раніше, але мені це не описує ситуацію в реальному світі. Якщо ви змінюєте щось, що передається в ньому, не має сенсу визначати його за замовчуванням.
Марк Викуп

@MarkRansom, я думаю , що це має сенс , якщо ви хочете , щоб забезпечити додатковий вихід призначення , як в: def foo(x, l=None): l=l or []; l.append(x**2); return l[-1].
Януш Ленар

Для останнього рядка коду Себастьяна написано, що "# вищевказане не впливає на початковий список". Але, на мою думку, це не впливає лише на "n", але змінило "x" у функції main (). Я прав?
користувач17670

1
@ user17670: x = []in f()не впливає на список xосновної функції. Я оновив коментар, щоб зробити його більш конкретним.
jfs

15

Ви вже отримали ряд відповідей, і я широко згоден з Дж. Ф. Себастьяном, але це може бути корисним як ярлик:

Кожен раз, коли ви бачите varname =, ви створюєте нову прив'язку імен у межах функції. Яке б значення не varnameбуло пов'язане раніше, втрачається в межах цієї межі .

Щоразу, коли ви бачите, varname.foo()що ви викликаєте метод varname. Метод може змінити varname (наприклад list.append). varname(а точніше, об'єкт, який varnameназиває) може існувати в більш ніж одній області, а оскільки це той самий об’єкт, будь-які зміни будуть помітні у всіх сферах.

[зауважте, що globalключове слово створює виняток із першого випадку]


13

fнасправді не змінює значення значення x(яке завжди однакове посилання на екземпляр списку). Це швидше змінює зміст цього списку.

В обох випадках копія посилання передається функції. Всередині функції

  • nотримує нове значення. Змінюється лише посилання всередині функції, а не посилання поза нею.
  • xне присвоюється нове значення: ні посилання всередині, ні зовні функції не змінюються. Замість цього, x«s значення змінюється.

Оскільки як xусередині функції, так і зовні вона посилається на одне і те ж значення, обидва бачать модифікацію. Навпаки, nвсередині функції та поза нею відносяться до різних значень після того, nяк всередині функції було перепризначено.


8
"копія" вводить в оману. У Python немає змінних типу C. Всі імена в Python є посиланнями. Ви не можете змінити ім'я, ви просто можете прив'язати його до іншого об'єкта, ось і все. Має сенс говорити про змінний і незмінний об'єкт в Python, але це не імена.
jfs

1
@JF Себастьян: Ваше твердження в кращому випадку вводить в оману. Недоцільно думати цифри як посилання.
Пітару

9
@dysfunctor: цифри - це посилання на незмінні об'єкти. Якщо ви вважаєте за краще подумати про них іншим способом, у вас є маса дивних випадків для пояснення. Якщо ви вважаєте їх непорушними, особливих випадків немає.
S.Lott

@ S.Lott: Незалежно від того, що відбувається під кришкою, Гвідо ван Россум доклав чимало зусиль, щоб розробити Python, щоб програміст міг зазначити числа як просто ... числа.
Пітару

1
@JF, посилання скопійовано.
ханабіт

7

Я перейменую змінні, щоб зменшити плутанину. 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
"У випадку n значення, яке скопіюється ..." - Це неправильно, тут не робиться копіювання (якщо ви не рахуєте посилання). Натомість python використовує "імена", які вказують на фактичні об'єкти. nf і xf вказують на nmain та xmain, до тих пір nf = 2, де ім'я не nfбуде змінено на вказівку 2. Числа незмінні, списки змінюються.
Кейсі Кубалл

2

Це тому, що список є об'єктом, що змінюється. Ви не встановлюєте значення x [0,1,2,3], ви визначаєте мітку для об'єкта [0,1,2,3].

Ви повинні оголосити свою функцію f () так:

def f(n, x=None):
    if x is None:
        x = []
    ...

3
Це не має нічого спільного з незмінністю. Якщо ви зробите це x = x + [4]замість цього x.append(4), ви також не побачите жодних змін у абонента, хоча список є змінним. Це стосується, якщо він дійсно мутований.
glglgl

1
OTOH, якщо у вас все- x += [4]таки xвідбувається вимкнення, як і те, що відбувається x.append(4), тому абонент побачить зміни.
14:00 17

2

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

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


3
Дивіться фразу "копія вказівника". Обидва місця отримують посилання на об’єкти. n - посилання на незмінний об'єкт; x - посилання на об'єкт, що змінюється.
S.Lott

2

Якщо функції перезаписані з абсолютно різними змінними і ми називаємо їх 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

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. Потім goo переходить до зміни цієї копії. Ефект: немає.

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

Пояснення усуває багато плутанини. Python передає значення змінних адрес, які зберігаються за значенням.


Чисте значення проходу вказівника не сильно відрізняється від пропуску за посиланням, якщо ви думаєте про це правильним способом ...
galinette

Подивіться на Гуо. Якби ти був чистим проходом посилань, це змінило б його аргумент. Ні, Python не є чистою мовою проходження посилань. Він передає посилання за значенням.
ncmathsadist

0

Python - це копія за значенням посилання. Об'єкт займає поле в пам'яті, і посилання пов'язане з цим об'єктом, але сам по собі займає поле в пам'яті. І ім’я / значення пов'язане з посиланням. У функції python вона завжди копіює значення посилання, тому у вашому коді n копіюється як нове ім'я, коли ви призначаєте це, у стеку виклику є новий простір. Але для списку ім’я також було скопійовано, але воно відноситься до тієї самої пам'яті (оскільки ви ніколи не присвоюєте цьому списку нове значення). Це магія в пітоні!


0

Моє загальне розуміння полягає в тому, що будь-яка змінна об'єкта (наприклад, список чи диктат, серед інших) може бути змінена за допомогою своїх функцій. Я вважаю, що ви не в змозі зробити це перепризначити параметр - тобто призначити його за посиланням у межах функції, що викликається.

Це відповідає багатьом іншим мовам.

Запустіть такий короткий сценарій, щоб побачити, як він працює:

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

y = 10
list1 = ["meaning"]
func1(y, list1)
print(y)
print(list1)

-3

Я багато разів модифікував свою відповідь і зрозумів, що не потрібно нічого говорити, пітон вже пояснив себе.

a = 'string'
a.replace('t', '_')
print(a)
>>> 'string'

a = a.replace('t', '_')
print(a)
>>> 's_ring'

b = 100
b + 1
print(b)
>>> 100

b = b + 1
print(b)
>>> 101

def test_id(arg):
    c = id(arg)
    arg = 123
    d = id(arg)
    return

a = 'test ids'
b = id(a)
test_id(a)
e = id(a)

# b = c  = e != d
# this function do change original value
del change_like_mutable(arg):
    arg.append(1)
    arg.insert(0, 9)
    arg.remove(2)
    return

test_1 = [1, 2, 3]
change_like_mutable(test_1)



# this function doesn't 
def wont_change_like_str(arg):
     arg = [1, 2, 3]
     return


test_2 = [1, 1, 1]
wont_change_like_str(test_2)
print("Doesn't change like a imutable", test_2)

Цей диявол не є посиланням / значенням / змінним або не / екземпляром, простором імен або змінною / списком або str, це СИНТАКС, РІВНІ ЗНАЧЕННЯ.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.