Mengapa suatu fungsi dapat memodifikasi beberapa argumen seperti yang dirasakan oleh pemanggil, tetapi tidak yang lain?

182

Saya mencoba memahami pendekatan Python untuk lingkup variabel. Dalam contoh ini, mengapa f()bisa mengubah nilai x, seperti yang dirasakan di dalam main(), tetapi bukan nilai 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()

Keluaran:

Before: 1 [0, 1, 2, 3]
In f(): 2 [0, 1, 2, 3, 4]
After:  1 [0, 1, 2, 3, 4]
FMc
sumber
7
dijelaskan dengan baik di sini nedbatchelder.com/text/names.html
Roushan

Jawaban:

212

Beberapa jawaban berisi kata "salin" dalam konteks panggilan fungsi. Saya merasa ini membingungkan.

Python tidak menyalin objek Anda lulus selama panggilan fungsi yang pernah .

Parameter fungsi adalah nama . Saat Anda memanggil fungsi, Python mengikat parameter-parameter ini ke objek apa pun yang Anda lewati (melalui nama dalam lingkup pemanggil).

Objek bisa berubah-ubah (seperti daftar) atau tidak berubah (seperti bilangan bulat, string dengan Python). Objek yang bisa berubah yang dapat Anda ubah. Anda tidak dapat mengubah nama, Anda hanya dapat mengikatnya ke objek lain.

Contoh Anda bukan tentang cakupan atau ruang nama , ini tentang penamaan dan pengikatan serta mutabilitas suatu objek dengan 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

Berikut adalah gambar-gambar bagus tentang perbedaan antara variabel dalam bahasa lain dan nama-nama dengan Python .

jfs
sumber
3
Artikel ini membantu saya untuk memahami masalah dengan lebih baik dan menyarankan solusi dan beberapa penggunaan tingkat lanjut: Nilai Parameter Default dalam Python
Gfy
@Gfy, saya pernah melihat contoh serupa sebelumnya tetapi bagi saya itu tidak menggambarkan situasi dunia nyata. Jika Anda memodifikasi sesuatu yang diteruskan di dalamnya, tidak masuk akal untuk memberikannya default.
Mark Ransom
@MarkRansom, saya pikir itu tidak masuk akal jika Anda ingin memberikan tujuan output opsional seperti di: def foo(x, l=None): l=l or []; l.append(x**2); return l[-1].
Janusz Lenar
Untuk baris terakhir kode Sebastian, kata "# di atas tidak berpengaruh pada daftar asli". Tetapi menurut saya, itu tidak hanya berpengaruh pada "n", tetapi mengubah "x" pada fungsi main (). Apakah saya benar?
user17670
1
@ user17670: x = []dalam f()tidak berpengaruh pada daftar xdi fungsi utama. Saya sudah memperbarui komentar, untuk membuatnya lebih spesifik.
jfs
15

Anda sudah mendapatkan sejumlah jawaban, dan saya secara luas setuju dengan JF Sebastian, tetapi Anda mungkin menemukan ini berguna sebagai jalan pintas:

Setiap kali Anda melihat varname =, Anda membuat nama baru yang mengikat dalam ruang lingkup fungsi. Nilai apa pun varnameyang terikat sebelumnya hilang dalam lingkup ini .

Setiap kali Anda melihat varname.foo()Anda memanggil metode varname. Metode ini dapat mengubah varname (misalnya list.append). varname(atau, lebih tepatnya, objek yang varnamenamanya) mungkin ada di lebih dari satu cakupan, dan karena itu adalah objek yang sama, setiap perubahan akan terlihat di semua lingkup.

[perhatikan bahwa globalkata kunci membuat pengecualian pada kasus pertama]

John Fouhy
sumber
13

fsebenarnya tidak mengubah nilai x(yang selalu merupakan referensi yang sama dengan turunan daftar). Sebaliknya, itu mengubah isi daftar ini.

Dalam kedua kasus, salinan referensi diteruskan ke fungsi. Di dalam fungsi,

  • nmendapat nilai baru. Hanya referensi di dalam fungsi yang diubah, bukan yang di luarnya.
  • xtidak mendapatkan nilai baru: referensi di dalam maupun di luar fungsi tidak dimodifikasi. Sebaliknya, x's nilai dimodifikasi.

Karena keduanya di xdalam fungsi dan di luarnya merujuk pada nilai yang sama, keduanya melihat modifikasi. Sebaliknya, di ndalam fungsi dan di luarnya merujuk ke nilai yang berbeda setelah ndipindahkan di dalam fungsi.

Konrad Rudolph
sumber
8
"copy" menyesatkan. Python tidak memiliki variabel seperti C. Semua nama dalam Python adalah referensi. Anda tidak dapat mengubah nama, Anda hanya dapat mengikatnya ke objek lain, itu saja. Ini hanya masuk akal untuk berbicara tentang bisa berubah dan abadi objek di Python tidak mereka adalah nama-nama.
jfs
1
@ JS Sebastian: Pernyataan Anda paling menyesatkan. Tidaklah berguna untuk menganggap angka sebagai referensi.
Pitarou
9
@dysfunctor: angka adalah referensi ke objek yang tidak dapat diubah. Jika Anda lebih suka memikirkannya dengan cara lain, Anda memiliki banyak kasus khusus yang aneh untuk dijelaskan. Jika Anda menganggap mereka tidak berubah, tidak ada kasus khusus.
S.Lott
@ S.Lott: Terlepas dari apa yang terjadi di bawah tenda, Guido van Rossum berusaha keras untuk mendesain Python sehingga pemrogram dapat menganggap angka sebagai hanya ... angka.
Pitarou
1
@ JF, referensi disalin.
habnabit
7

Saya akan mengganti nama variabel untuk mengurangi kebingungan. n -> nf atau nmain . x -> xf atau 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()

Ketika Anda memanggil fungsi f , runtime Python membuat salinan xmain dan menetapkannya ke xf , dan juga menetapkan salinan nmain ke nf .

Dalam hal n , nilai yang disalin adalah 1.

Dalam kasus x nilai yang disalin bukanlah daftar literal [0, 1, 2, 3] . Ini adalah referensi ke daftar itu. xf dan xmain menunjuk pada daftar yang sama, jadi ketika Anda memodifikasi xf, Anda juga memodifikasi xmain .

Namun, jika Anda menulis sesuatu seperti:

    xf = ["foo", "bar"]
    xf.append(4)

Anda akan menemukan bahwa xmain belum berubah. Ini karena, pada baris xf = ["foo", "bar"] Anda harus mengubah xf untuk menunjuk ke daftar baru . Setiap perubahan yang Anda lakukan pada daftar baru ini tidak akan berpengaruh pada daftar yang tetap ada masih poin ke.

Semoga itu bisa membantu. :-)

Pitarou
sumber
2
"Dalam kasus n, nilai yang disalin ..." - Ini salah, tidak ada penyalinan yang dilakukan di sini (kecuali jika Anda menghitung referensi). Alih-alih, python menggunakan 'nama' yang menunjuk ke objek yang sebenarnya. nf dan xf arahkan ke nmain dan xmain, sampai nf = 2, di mana namanya nfdiubah menjadi point to 2. Angka tidak berubah, daftar bisa berubah.
Casey Kuball
2

Itu karena daftar adalah objek yang bisa berubah. Anda tidak menyetel x ke nilai [0,1,2,3], Anda mendefinisikan label pada objek [0,1,2,3].

Anda harus mendeklarasikan fungsi Anda f () seperti ini:

def f(n, x=None):
    if x is None:
        x = []
    ...
Luiz Damim
sumber
3
Ini tidak ada hubungannya dengan sifat berubah-ubah. Jika Anda akan melakukan x = x + [4]bukan x.append(4), Anda akan melihat ada perubahan dalam penelepon juga meskipun daftar bisa berubah. Ini ada hubungannya dengan jika memang bermutasi.
glglgl
1
OTOH, jika Anda melakukannya x += [4]maka xdimutasi, seperti apa yang terjadi x.append(4), sehingga penelepon akan melihat perubahan.
PM 2Ring
2

n adalah int (tidak dapat diubah), dan salinannya diteruskan ke fungsi, jadi pada fungsi tersebut Anda mengubah salinannya.

X adalah daftar (dapat diubah), dan salinan dari pointer dilewatkan o fungsi sehingga x.append (4) mengubah isi daftar. Namun, Anda mengatakan x = [0,1,2,3,4] dalam fungsi Anda, Anda tidak akan mengubah konten x pada main ().

Jason Coon
sumber
3
Tonton ungkapan "copy of the pointer". Kedua tempat mendapatkan referensi ke objek. n adalah referensi ke objek abadi; x adalah referensi ke objek yang bisa berubah.
S.Lott
2

Jika fungsi yang ditulis ulang dengan variabel yang sama sekali berbeda dan kita sebut id pada mereka, kemudian menggambarkan titik dengan baik. Saya tidak mendapatkan ini pada awalnya dan membaca posting jfs dengan penjelasan yang bagus , jadi saya mencoba untuk memahami / meyakinkan diri saya sendiri:

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 dan x memiliki id yang sama. Hanya beda tag untuk struktur dasar yang sama dengan kata artikel.

jell
sumber
0

Python adalah bahasa murni nilai demi nilai jika Anda memikirkannya dengan benar. Variabel python menyimpan lokasi objek dalam memori. Variabel Python tidak menyimpan objek itu sendiri. Saat Anda meneruskan variabel ke suatu fungsi, Anda meneruskan salinan alamat objek yang ditunjuk oleh variabel.

Kontras dua fungsi ini

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

def goo(x):
    x = []

Sekarang, saat Anda mengetik ke dalam shell

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

Bandingkan ini dengan goo.

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

Dalam kasus pertama, kami mengirimkan salinan alamat sapi ke foo dan foo memodifikasi keadaan objek yang berada di sana. Objek akan dimodifikasi.

Dalam kasus kedua, Anda menyerahkan salinan alamat sapi ke goo. Kemudian goo mulai mengubah salinan itu. Efek: tidak ada.

Saya menyebutnya ini prinsip rumah merah muda . Jika Anda membuat salinan alamat Anda dan memberi tahu seorang pelukis untuk mengecat rumah itu dengan alamat itu, Anda akan berakhir dengan sebuah rumah merah muda. Jika Anda memberi pelukis salinan alamat Anda dan menyuruhnya untuk mengubahnya ke alamat baru, alamat rumah Anda tidak akan berubah.

Penjelasannya menghilangkan banyak kebingungan. Python melewati variabel alamat yang disimpan oleh nilai.

ncmathsadist
sumber
Nilai pass by pointer murni tidak jauh berbeda dengan pass by reference jika Anda memikirkannya dengan benar ...
galinette
Lihatlah goo. Jika Anda benar-benar lulus dengan referensi, itu akan mengubah argumennya. Tidak, Python bukan bahasa referensi murni. Itu melewati referensi dengan nilai.
ncmathsadist
0

Python disalin oleh nilai referensi. Objek menempati bidang dalam memori, dan referensi dikaitkan dengan objek itu, tetapi itu sendiri menempati bidang dalam memori. Dan nama / nilai dikaitkan dengan referensi. Dalam fungsi python, selalu menyalin nilai referensi, jadi dalam kode Anda, n disalin menjadi nama baru, ketika Anda menetapkan itu, ia memiliki ruang baru di tumpukan pemanggil. Tetapi untuk daftar, namanya juga disalin, tetapi merujuk pada memori yang sama (karena Anda tidak pernah menetapkan nilai baru pada daftar). Itu adalah keajaiban dalam python!

sunxd
sumber
0

Pemahaman umum saya adalah bahwa variabel objek apa saja (seperti daftar atau dict, antara lain) dapat dimodifikasi melalui fungsinya. Apa yang saya yakin tidak bisa Anda lakukan adalah menetapkan kembali parameter - yaitu, tetapkan dengan referensi dalam fungsi yang bisa dipanggil.

Itu konsisten dengan banyak bahasa lain.

Jalankan skrip pendek berikut untuk melihat cara kerjanya:

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

y = 10
list1 = ["meaning"]
func1(y, list1)
print(y)
print(list1)
Boris Epstein
sumber
-3

Saya telah memodifikasi jawaban saya beberapa kali dan menyadari bahwa saya tidak perlu mengatakan apa-apa, python sudah menjelaskan sendiri.

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)

Setan ini bukan referensi / nilai / bisa berubah atau tidak / contoh, ruang nama atau variabel / daftar atau str, IT IS THE SYNTAX, EQUAL SIGN.

HoD
sumber