Pertanyaan Bagaimana cara saya mengirimkan variabel dengan referensi?


Dokumentasi Python tampaknya tidak jelas tentang apakah parameter dilewatkan oleh referensi atau nilai, dan kode berikut menghasilkan nilai tidak berubah 'Asli'

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

Apakah ada yang bisa saya lakukan untuk meneruskan variabel dengan referensi yang sebenarnya?


2053
2018-06-12 10:23


asal


Jawaban:


Argumennya dilewatkan oleh tugas. Alasan dibalik ini ada dua:

  1. parameter yang dilewatkan dalam sebenarnya adalah a referensi ke suatu objek (tetapi referensi dilewatkan oleh nilai)
  2. beberapa tipe data bisa berubah, tetapi yang lainnya tidak

Begitu:

  • Jika Anda lulus yg mungkin berubah objek ke dalam metode, metode mendapatkan referensi ke objek yang sama dan Anda dapat memecahnya untuk kesenangan hati Anda, tetapi jika Anda me-rebind referensi dalam metode, lingkup luar akan tahu apa-apa tentang hal itu, dan setelah Anda selesai, referensi luar akan tetap menunjuk pada objek aslinya.

  • Jika Anda lulus kekal keberatan dengan suatu metode, Anda masih tidak dapat me-rebind referensi luar, dan Anda bahkan tidak bisa bermutasi objek.

Untuk membuatnya lebih jelas, mari kita memiliki beberapa contoh.

Daftar - jenis yang bisa berubah

Mari kita coba memodifikasi daftar yang diteruskan ke suatu metode:

def try_to_change_list_contents(the_list):
    print('got', the_list)
    the_list.append('four')
    print('changed to', the_list)

outer_list = ['one', 'two', 'three']

print('before, outer_list =', outer_list)
try_to_change_list_contents(outer_list)
print('after, outer_list =', outer_list)

Keluaran:

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']

Karena parameter yang diteruskan adalah referensi untuk outer_list, bukan salinannya, kita dapat menggunakan metode daftar mutasi untuk mengubahnya dan memiliki perubahan yang tercermin dalam lingkup luar.

Sekarang mari kita lihat apa yang terjadi ketika kita mencoba mengubah referensi yang dilewatkan sebagai parameter:

def try_to_change_list_reference(the_list):
    print('got', the_list)
    the_list = ['and', 'we', 'can', 'not', 'lie']
    print('set to', the_list)

outer_list = ['we', 'like', 'proper', 'English']

print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)

Keluaran:

before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']

Sejak itu the_list parameter dilewatkan oleh nilai, menetapkan daftar baru untuk itu tidak berpengaruh bahwa kode di luar metode bisa melihat. Itu the_list adalah salinan dari outer_list referensi, dan kami punya the_list arahkan ke daftar baru, tetapi tidak ada cara untuk mengubah tempat outer_list runcing.

String - tipe yang tidak berubah

Tidak dapat diubah, jadi tidak ada yang bisa kita lakukan untuk mengubah isi string

Sekarang, mari kita coba mengubah referensi

def try_to_change_string_reference(the_string):
    print('got', the_string)
    the_string = 'In a kingdom by the sea'
    print('set to', the_string)

outer_string = 'It was many and many a year ago'

print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)

Keluaran:

before, outer_string = It was many and many a year ago
got It was many and many a year ago
set to In a kingdom by the sea
after, outer_string = It was many and many a year ago

Sekali lagi, sejak the_string parameter dilewatkan oleh nilai, menetapkan string baru untuk itu tidak berpengaruh bahwa kode di luar metode bisa melihat. Itu the_string adalah salinan dari outer_string referensi, dan kami punya the_string arahkan ke string baru, tetapi tidak ada cara untuk mengubah tempat outer_string runcing.

Saya harap ini sedikit lebih baik.

EDIT: Telah dicatat bahwa ini tidak menjawab pertanyaan bahwa @David awalnya bertanya, "Apakah ada yang bisa saya lakukan untuk meneruskan variabel dengan referensi yang sebenarnya?". Mari kita kerjakan itu.

Bagaimana cara kita mengatasi ini?

Seperti jawaban @ Andrea menunjukkan, Anda dapat mengembalikan nilai baru. Ini tidak mengubah cara hal-hal dilewatkan, tetapi membiarkan Anda mendapatkan informasi yang ingin Anda balas:

def return_a_whole_new_string(the_string):
    new_string = something_to_do_with_the_old_string(the_string)
    return new_string

# then you could call it like
my_string = return_a_whole_new_string(my_string)

Jika Anda benar-benar ingin menghindari menggunakan nilai kembalian, Anda dapat membuat kelas untuk menahan nilai Anda dan meneruskannya ke fungsi atau menggunakan kelas yang ada, seperti daftar:

def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
    new_string = something_to_do_with_the_old_string(stuff_to_change[0])
    stuff_to_change[0] = new_string

# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)

do_something_with(wrapper[0])

Meski ini sepertinya agak merepotkan.


2221
2018-06-12 11:18



Masalahnya berasal dari kesalahpahaman tentang apa variabel dalam Python. Jika Anda terbiasa dengan sebagian besar bahasa tradisional, Anda memiliki model mental tentang apa yang terjadi dalam urutan berikut:

a = 1
a = 2

Kamu percaya itu a adalah lokasi memori yang menyimpan nilai 1, kemudian diperbarui untuk menyimpan nilai 2. Itu bukan cara kerja Python. Agak, a dimulai sebagai referensi ke objek dengan nilai 1, kemudian ditetapkan ulang sebagai referensi ke objek dengan nilai 2. Kedua benda itu dapat terus hidup berdampingan meskipun a tidak mengacu pada yang pertama lagi; pada kenyataannya mereka dapat dibagi oleh sejumlah referensi lain dalam program ini.

Ketika Anda memanggil fungsi dengan parameter, referensi baru dibuat yang mengacu pada objek yang dilewatkan. Ini terpisah dari referensi yang digunakan dalam pemanggilan fungsi, jadi tidak ada cara untuk memperbarui referensi itu dan membuatnya mengacu pada objek baru. Dalam contoh Anda:

def __init__(self):
    self.variable = 'Original'
    self.Change(self.variable)

def Change(self, var):
    var = 'Changed'

self.variable adalah referensi ke objek string 'Original'. Ketika Anda menelepon Change Anda membuat referensi kedua var ke objek. Di dalam fungsi Anda menetapkan kembali referensi var ke objek string yang berbeda 'Changed', tetapi referensi self.variable terpisah dan tidak berubah.

Satu-satunya jalan di sekitar ini adalah untuk melewati objek yang bisa berubah. Karena kedua referensi mengacu pada objek yang sama, setiap perubahan pada objek tercermin di kedua tempat.

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

def Change(self, var):
    var[0] = 'Changed'

529
2017-11-15 17:45



Saya menemukan jawaban lain yang agak panjang dan rumit, jadi saya membuat diagram sederhana ini untuk menjelaskan cara Python memperlakukan variabel dan parameter. enter image description here


242
2017-09-04 16:05



Ini bukan pass-by-value atau pass-by-reference - itu adalah call-by-object. Lihat ini, oleh Fredrik Lundh:

http://effbot.org/zone/call-by-object.htm

Ini adalah kutipan yang signifikan:

"... variabel [nama] adalah tidak benda; mereka tidak dapat dilambangkan dengan variabel lain atau dirujuk oleh objek. "

Dalam contoh Anda, kapan Change metode disebut - a ruang nama diciptakan untuk itu; dan var menjadi nama, di dalam ruang nama itu, untuk objek string 'Original'. Objek itu kemudian memiliki nama dalam dua ruang nama. Berikutnya, var = 'Changed' mengikat var ke objek string baru, dan dengan demikian namespace metode lupa tentang 'Original'. Akhirnya, namespace itu dilupakan, dan string 'Changed' bersamaan dengan itu.


219
2018-06-12 12:55



Pikirkan hal-hal yang sedang berlalu dengan tugas bukannya dengan referensi / berdasarkan nilai. Dengan cara itu, semuanya jelas, apa yang terjadi selama Anda memahami apa yang terjadi selama penugasan normal.

Jadi, ketika melewati daftar ke fungsi / metode, daftar ditugaskan ke nama parameter. Menambahkan ke daftar akan menghasilkan daftar yang sedang diubah. Menugaskan kembali daftar dalam fungsi tidak akan mengubah daftar asli, karena:

a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b      # prints [1, 2, 3, 4] ['a', 'b']

Karena jenis yang tidak dapat diubah tidak dapat dimodifikasi, mereka terlihat seperti dilewatkan oleh nilai - melewatkan int ke fungsi berarti menugaskan int ke parameter fungsi. Anda hanya dapat menetapkan ulang itu, tetapi itu tidak akan mengubah nilai variabel asli.


142
2018-06-12 12:17



Effbot (alias Fredrik Lundh) telah menggambarkan gaya berpindah variabel Python sebagai panggilan-oleh-objek: http://effbot.org/zone/call-by-object.htm

Objek yang dialokasikan pada heap dan pointer ke mereka dapat diedarkan ke mana saja.

  • Saat Anda membuat tugas seperti x = 1000, Entri kamus dibuat yang memetakan string "x" dalam namespace saat ini ke pointer ke objek integer yang berisi seribu.

  • Saat Anda memperbarui "x" dengan x = 2000, objek integer baru dibuat dan kamus diperbarui untuk menunjuk objek baru. Yang lama seribu objek tidak berubah (dan mungkin atau mungkin tidak hidup tergantung pada apakah hal lain mengacu pada objek).

  • Ketika Anda melakukan tugas baru seperti y = x, entri kamus baru "y" dibuat yang menunjuk ke objek yang sama dengan entri untuk "x".

  • Objek seperti string dan bilangan bulat adalah kekal. Ini berarti tidak ada metode yang dapat mengubah objek setelah dibuat. Misalnya, setelah objek bilangan bulat seribu satu dibuat, itu tidak akan pernah berubah. Matematika dilakukan dengan membuat objek bilangan bulat baru.

  • Objek seperti daftar adalah yg mungkin berubah. Ini berarti isi objek dapat diubah oleh apa pun yang mengarah ke objek. Sebagai contoh, x = []; y = x; x.append(10); print y akan dicetak [10]. Daftar kosong telah dibuat. Baik "x" dan "y" menunjuk ke daftar yang sama. Itu menambahkan metode bermutasi (memperbarui) objek daftar (seperti menambahkan catatan ke database) dan hasilnya terlihat baik "x" dan "y" (hanya sebagai pembaruan basis data akan terlihat untuk setiap koneksi ke database itu).

Harapan yang menjelaskan masalah ini untuk Anda.


53
2018-03-29 04:41



Secara teknis, Python selalu menggunakan pass oleh nilai referensi. Saya akan mengulangi jawaban saya yang lain untuk mendukung pernyataan saya.

Python selalu menggunakan nilai-nilai pass-by-reference. Tidak ada pengecualian. Setiap tugas variabel berarti menyalin nilai referensi. Tanpa pengecualian. Setiap variabel adalah nama yang terikat ke nilai referensi. Selalu.

Anda dapat berpikir tentang nilai referensi sebagai alamat objek target. Alamat secara otomatis dereferenced ketika digunakan. Dengan cara ini, bekerja dengan nilai referensi, tampaknya Anda bekerja langsung dengan objek target. Namun selalu ada referensi di antaranya, satu langkah lebih untuk melompat ke target.

Berikut ini contoh yang membuktikan bahwa Python menggunakan passing dengan referensi:

Illustrated example of passing the argument

Jika argumen itu dilewatkan oleh nilai, yang terluar lst tidak dapat dimodifikasi. Hijau adalah objek target (hitam adalah nilai yang disimpan di dalam, merah adalah jenis objek), kuning adalah memori dengan nilai referensi di dalam - digambar sebagai panah. Panah padat berwarna biru adalah nilai referensi yang diteruskan ke fungsi (melalui jalur panah biru putus-putus). Warna kuning gelap jelek adalah kamus internal. (Ini sebenarnya bisa digambarkan juga sebagai elips hijau. Warna dan bentuknya hanya mengatakan itu adalah internal.)

Anda dapat menggunakan id() fungsi built-in untuk mempelajari apa nilai referensi (yaitu, alamat objek target).

Dalam bahasa yang dikompilasi, variabel adalah ruang memori yang mampu menangkap nilai dari tipe. Dalam Python, variabel adalah nama (ditangkap secara internal sebagai string) terikat ke variabel referensi yang memegang nilai referensi ke objek target. Nama variabel adalah kunci dalam kamus internal, bagian nilai dari item kamus menyimpan nilai referensi ke target.

Nilai referensi disembunyikan dengan Python. Tidak ada tipe pengguna eksplisit untuk menyimpan nilai referensi. Namun, Anda dapat menggunakan elemen daftar (atau elemen dalam jenis penampung lain yang sesuai) sebagai variabel referensi, karena semua penampung menyimpan elemen juga sebagai referensi ke objek target. Dengan kata lain, elemen sebenarnya tidak ada di dalam wadah - hanya referensi ke elemen.


53
2017-09-15 18:53