Meneruskan integer dengan referensi dengan Python

97

Bagaimana saya bisa melewatkan integer dengan referensi dengan Python?

Saya ingin mengubah nilai variabel yang saya teruskan ke fungsi. Saya telah membaca bahwa segala sesuatu dalam Python adalah nilai lewat, tetapi harus ada trik yang mudah. Misalnya, di Jawa Anda bisa melewati jenis referensi Integer, Long, dll

  1. Bagaimana cara melewatkan integer ke fungsi dengan referensi?
  2. Apa praktik terbaiknya?
CodeKingPlusPlus
sumber
lihat ini untuk cara yang bagus jika sedikit berbelit-belit untuk membungkus int Anda dalam kelas anonim (yang bisa berubah) yang akan berperilaku seperti 'referensi': stackoverflow.com/a/1123054/409638 yaitu ref = type ('', () , {'n': 1})
robert

Jawaban:

108

Itu tidak cukup berhasil dengan Python. Python meneruskan referensi ke objek. Di dalam fungsi Anda, Anda memiliki objek - Anda bebas untuk mengubah objek itu (jika memungkinkan). Namun, bilangan bulat tidak dapat diubah . Salah satu solusinya adalah dengan meneruskan integer dalam wadah yang dapat dimutasi:

def change(x):
    x[0] = 3

x = [1]
change(x)
print x

Ini jelek / kikuk, tetapi Anda tidak akan melakukan yang lebih baik dengan Python. Alasannya adalah karena dalam Python, assignment ( =) mengambil objek apa pun yang merupakan hasil dari sisi kanan dan mengikatnya ke apa pun yang ada di sisi kiri * (atau meneruskannya ke fungsi yang sesuai).

Memahami hal ini, kita dapat melihat mengapa tidak ada cara untuk mengubah nilai objek yang tidak dapat diubah di dalam fungsi - Anda tidak dapat mengubah atributnya karena tidak dapat diubah, dan Anda tidak dapat menetapkan "variabel" sebagai yang baru. nilai karena kemudian Anda benar-benar membuat objek baru (yang berbeda dari yang lama) dan memberinya nama yang dimiliki objek lama di namespace lokal.

Biasanya solusinya adalah dengan mengembalikan objek yang Anda inginkan:

def multiply_by_2(x):
    return 2*x

x = 1
x = multiply_by_2(x)

* Dalam contoh kasus pertama di atas, 3sebenarnya diteruskan ke x.__setitem__.

mgilson.dll
sumber
11
"Python melewati objek." Tidak. Python meneruskan referensi (penunjuk ke objek).
pengguna102008
6
@ user102008 - Poin yang adil. Semantik pada saat ini saya merasa sangat berlumpur. Pada akhirnya, mereka juga bukan "penunjuk". Setidaknya, tentu tidak dalam arti yang sama seperti yang Anda alami C. (Mereka tidak harus didereferensi, misalnya). Dalam model mental saya, lebih mudah untuk memikirkan hal-hal sebagai "Nama" dan "Objek". Tugas mengikat "Nama" di kiri ke "Objek" di kanan. Saat Anda memanggil suatu fungsi, Anda meneruskan "Objek" (mengikatnya secara efektif ke nama lokal baru di dalam fungsi).
mgilson
Ini tentu saja merupakan deskripsi tentang apa itu referensi python , tetapi tidak mudah menemukan cara untuk mendeskripsikannya secara ringkas kepada mereka yang tidak terbiasa dengan terminologi tersebut.
mgilson
2
Tidak heran kita bingung dengan terminologinya. Di sini kita mendeskripsikannya sebagai call-by-object dan di sini dijelaskan sebagai pass-by-value . Di tempat lain ini disebut "lewat-referensi" dengan tanda bintang pada apa yang sebenarnya berarti ... Pada dasarnya, masalahnya adalah bahwa komunitas belum tahu harus menyebutnya apa
mgilson
1
Referensi Python sama persis dengan referensi Java. Dan referensi Java sesuai dengan petunjuk JLS ke objek. Dan pada dasarnya ada bijection lengkap antara referensi Java / Python dan petunjuk C ++ ke objek dalam semantik, dan tidak ada yang lain yang menjelaskan semantik ini juga. "Mereka tidak perlu dirujuk" Nah, .operator hanya membedakan sisi kiri. Operator bisa berbeda dalam bahasa yang berbeda.
pengguna102008
31

Sebagian besar kasus di mana Anda perlu meneruskan dengan referensi adalah ketika Anda perlu mengembalikan lebih dari satu nilai kembali ke pemanggil. "Praktik terbaik" adalah menggunakan beberapa nilai pengembalian, yang jauh lebih mudah dilakukan dengan Python daripada bahasa seperti Java.

Berikut contoh sederhananya:

def RectToPolar(x, y):
    r = (x ** 2 + y ** 2) ** 0.5
    theta = math.atan2(y, x)
    return r, theta # return 2 things at once

r, theta = RectToPolar(3, 4) # assign 2 things at once
Gabe
sumber
9

Tidak persis meneruskan nilai secara langsung, tetapi menggunakannya seolah-olah itu diteruskan.

x = 7
def my_method():
    nonlocal x
    x += 1
my_method()
print(x) # 8

Peringatan:

  • nonlocal diperkenalkan di python 3
  • Jika cakupan yang melingkupi adalah yang global, gunakan globalsebagai pengganti nonlocal.
Uri
sumber
7

Sungguh, praktik terbaik adalah mundur dan bertanya apakah Anda benar-benar perlu melakukan ini. Mengapa Anda ingin mengubah nilai variabel yang Anda berikan ke fungsi?

Jika Anda perlu melakukannya untuk peretasan cepat, cara tercepat adalah melewatkan listmemegang bilangan bulat, dan tetap [0]menggunakannya setiap kali menggunakannya, seperti yang ditunjukkan oleh jawaban mgilson.

Jika Anda perlu melakukannya untuk sesuatu yang lebih penting, tulis a classyang memiliki intatribut, jadi Anda bisa menyetelnya. Tentu saja ini memaksa Anda untuk menemukan nama yang bagus untuk kelas tersebut, dan untuk atributnya — jika Anda tidak dapat memikirkan apa pun, kembali dan baca kalimat itu beberapa kali, lalu gunakan list.

Secara lebih umum, jika Anda mencoba mem-port beberapa idiom Java langsung ke Python, Anda salah melakukannya. Bahkan ketika ada sesuatu yang berhubungan langsung (seperti dengan static/ @staticmethod), Anda tetap tidak ingin menggunakannya di sebagian besar program Python hanya karena Anda akan menggunakannya di Java.

abarnert
sumber
JAVA tidak melewatkan bilangan bulat dengan referensi (bilangan bulat atau objek apa pun. Objek kotak diganti juga saat penugasan).
Luis Masuelli
@LuisMasuelli Nah, kotak primitif diperlakukan seperti objek, satu-satunya hal yang mencegah penggunaannya seperti yang diinginkan OP adalah kenyataan bahwa kotak tidak dapat diubah (dan karena primitif itu sendiri juga tidak dapat diubah, semuanya tidak dapat diubah dan hanya dapat diubah di tingkat variabel)
Kroltan
Contoh penggunaan yang baik untuk ini adalah menghitung jumlah panggilan (total, bukan kedalaman) di dalam fungsi rekursif. Anda memerlukan nomor yang dapat bertambah di semua panggilan bercabang. Sebuah int standar tidak cukup
z33k
4

Di Python, setiap nilai adalah referensi (penunjuk ke suatu objek), seperti non-primitif di Java. Juga, seperti Java, Python hanya memiliki nilai lewat. Jadi, secara semantik, keduanya hampir sama.

Karena Anda menyebutkan Java dalam pertanyaan Anda, saya ingin melihat bagaimana Anda mencapai apa yang Anda inginkan di Java. Jika Anda dapat menunjukkannya di Java, saya dapat menunjukkan kepada Anda bagaimana melakukannya persis sama dengan Python.

newacct
sumber
4

Sebuah numpy array tunggal-elemen bisa berubah dan belum untuk sebagian besar tujuan, dapat dievaluasi seolah-olah itu adalah variabel python numerik. Oleh karena itu, ini adalah penampung nomor referensi yang lebih nyaman daripada daftar elemen tunggal.

    import numpy as np
    def triple_var_by_ref(x):
        x[0]=x[0]*3
    a=np.array([2])
    triple_var_by_ref(a)
    print(a+1)

keluaran:

3
Trisoloriansunscreen
sumber
3
class PassByReference:
    def Change(self, var):
        self.a = var
        print(self.a)
s=PassByReference()
s.Change(5)     
shruti
sumber
3

Mungkin itu bukan cara pythonic, tapi Anda bisa melakukan ini

import ctypes

def incr(a):
    a += 1

x = ctypes.c_int(1) # create c-var
incr(ctypes.ctypes.byref(x)) # passing by ref
Sergey Kovalev
sumber
Man itu pintar.
xilpex
2

Mungkin sedikit lebih mendokumentasikan diri daripada trik daftar panjang 1 adalah trik tipe kosong yang lama:

def inc_i(v):
    v.i += 1

x = type('', (), {})()
x.i = 7
inc_i(x)
print(x.i)
mheyman
sumber
Atau bungkus nomor di kelas: github.com/markrages/python_mutable_number
markrages
0

Di Python, semuanya diteruskan oleh nilai, tetapi jika Anda ingin mengubah beberapa status, Anda dapat mengubah nilai integer di dalam daftar atau objek yang diteruskan ke metode.

rekursif
sumber
3
Saya pikir, karena everything is passed by valueitu tidak benar. Dokumen kutipan:arguments are passed using call by value (where the value is always an object reference, not the value of the object)
Astery
1
Daftar, objek, dan kamus diberikan sebagai referensi
AN88
2
Jika itu benar, penetapan ke parameter dalam suatu fungsi akan tercermin di situs panggilan. Karena Astery mengutip passing dengan nilai dan nilai-nilai itu adalah referensi objek.
rekursif
0

Jawaban yang benar, adalah dengan menggunakan kelas dan meletakkan nilai di dalam kelas, ini memungkinkan Anda meneruskan referensi persis seperti yang Anda inginkan.

class Thing:
  def __init__(self,a):
    self.a = a
def dosomething(ref)
  ref.a += 1

t = Thing(3)
dosomething(t)
print("T is now",t.a)

Chris Pugmire
sumber