Bagaimana cara mengembalikan beberapa nilai dari suatu fungsi? [Tutup]

1067

Cara kanonik untuk mengembalikan beberapa nilai dalam bahasa yang mendukungnya seringkali tupling .

Opsi: Menggunakan tuple

Pertimbangkan contoh sepele ini:

def f(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return (y0, y1, y2)

Namun, ini dengan cepat menjadi bermasalah karena jumlah nilai yang dikembalikan meningkat. Bagaimana jika Anda ingin mengembalikan empat atau lima nilai? Tentu, Anda bisa terus menggelitiknya, tetapi mudah melupakan nilai mana. Ini juga agak jelek untuk membongkar mereka di mana pun Anda ingin menerimanya.

Opsi: Menggunakan kamus

Langkah logis berikutnya tampaknya adalah memperkenalkan semacam 'catatan notasi'. Dalam Python, cara yang jelas untuk melakukan ini adalah dengan cara a dict.

Pertimbangkan yang berikut ini:

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0': y0, 'y1': y1 ,'y2': y2}

(Untuk lebih jelasnya, y0, y1, dan y2 hanya dimaksudkan sebagai pengidentifikasi abstrak. Seperti yang ditunjukkan, dalam praktiknya Anda akan menggunakan pengidentifikasi yang bermakna.)

Sekarang, kami memiliki mekanisme di mana kami dapat memproyeksikan anggota tertentu dari objek yang dikembalikan. Sebagai contoh,

result['y0']

Opsi: Menggunakan kelas

Namun, ada opsi lain. Kami malah bisa mengembalikan struktur khusus. Saya telah membingkai ini dalam konteks Python, tapi saya yakin itu berlaku untuk bahasa lain juga. Memang, jika Anda bekerja di C ini mungkin satu-satunya pilihan Anda. Ini dia:

class ReturnValue:
  def __init__(self, y0, y1, y2):
     self.y0 = y0
     self.y1 = y1
     self.y2 = y2

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return ReturnValue(y0, y1, y2)

Dalam Python dua sebelumnya mungkin sangat mirip dalam hal pipa - setelah semua { y0, y1, y2 }hanya berakhir menjadi entri dalam internal __dict__dari ReturnValue.

Ada satu fitur tambahan yang disediakan oleh Python untuk objek kecil, yaitu __slots__atribut. Kelas dapat dinyatakan sebagai:

class ReturnValue(object):
  __slots__ = ["y0", "y1", "y2"]
  def __init__(self, y0, y1, y2):
     self.y0 = y0
     self.y1 = y1
     self.y2 = y2

Dari Manual Referensi Python :

The __slots__deklarasi mengambil urutan variabel instance dan cadangan cukup ruang di setiap contoh untuk mengadakan nilai untuk setiap variabel. Spasi disimpan karena __dict__tidak dibuat untuk setiap instance.

Opsi: Menggunakan dataclass (Python 3.7+)

Menggunakan kacamata baru Python 3.7, kembalikan kelas dengan metode khusus yang ditambahkan secara otomatis, pengetikan dan alat lain yang bermanfaat:

@dataclass
class Returnvalue:
    y0: int
    y1: float
    y3: int

def total_cost(x):
    y0 = x + 1
    y1 = x * 3
    y2 = y0 ** y3
    return ReturnValue(y0, y1, y2)

Opsi: Menggunakan daftar

Saran lain yang saya abaikan berasal dari Bill the Lizard:

def h(x):
  result = [x + 1]
  result.append(x * 3)
  result.append(y0 ** y3)
  return result

Ini adalah metode yang paling tidak kusukai. Saya kira saya dinodai oleh paparan Haskell, tetapi gagasan daftar tipe campuran selalu terasa tidak nyaman bagi saya. Dalam contoh khusus ini daftarnya adalah -tidak- jenis campuran, tetapi bisa dibayangkan bisa.

Daftar yang digunakan dengan cara ini benar-benar tidak mendapatkan apa pun sehubungan dengan tuple sejauh yang saya tahu. Satu-satunya perbedaan nyata antara daftar dan tupel dalam Python adalah bahwa daftar dapat diubah , sedangkan tupel tidak.

Saya pribadi cenderung membawa konvensi dari pemrograman fungsional: gunakan daftar untuk sejumlah elemen dari jenis yang sama, dan tuple untuk sejumlah elemen tetap dari tipe yang telah ditentukan.

Pertanyaan

Setelah pembukaan panjang, muncul pertanyaan yang tak terhindarkan. Metode mana (menurut Anda) yang terbaik?

saffsd
sumber
10
Dalam Anda contoh-contoh bagus Anda menggunakan variabel y3, tetapi kecuali y3 dinyatakan global, ini akan menghasilkan NameError: global name 'y3' is not definedmungkin hanya menggunakan 3?
hetepeperfan
11
Banyak pertanyaan hebat dengan jawaban luar biasa ditutup karena kata kunci 'opini' muncul. Anda bisa berpendapat bahwa seluruh SO didasarkan pada pendapat, tetapi pendapat tersebut diinformasikan oleh fakta, referensi, dan keahlian khusus. Hanya karena seseorang bertanya "menurut Anda yang terbaik" tidak berarti mereka meminta pendapat pribadi yang disarikan dari fakta dunia nyata, referensi, dan keahlian khusus. Mereka hampir pasti meminta dengan tepat pendapat semacam itu, jenis yang didasari sepenuhnya, dan didokumentasikan dengan, fakta, referensi, dan keahlian khusus yang digunakan orang tersebut untuk membentuk opini tersebut.
NeilG
@hetepeperfan tidak perlu mengubah 3, dan tidak ada yang mendefinisikan y3 di global, Anda juga bisa menggunakan nama lokal y3, yang akan melakukan pekerjaan yang sama juga.
okie

Jawaban:

637

Tuple tuple ditambahkan dalam 2.6 untuk tujuan ini. Juga lihat os.stat untuk contoh bawaan serupa.

>>> import collections
>>> Point = collections.namedtuple('Point', ['x', 'y'])
>>> p = Point(1, y=2)
>>> p.x, p.y
1 2
>>> p[0], p[1]
1 2

Dalam versi terbaru Python 3 (3,6+, saya pikir), typingperpustakaan baru membuat NamedTuplekelas untuk membuat tuple bernama lebih mudah untuk dibuat dan lebih kuat. Mewarisi dari typing.NamedTuplememungkinkan Anda menggunakan dokumen, nilai default, dan ketik anotasi.

Contoh (Dari dokumen):

class Employee(NamedTuple):  # inherit from typing.NamedTuple
    name: str
    id: int = 3  # default value

employee = Employee('Guido')
assert employee.id == 3
A. Coady
sumber
68
Ini hanya jawaban yang benar karena itu adalah satu-satunya struktur kanonik yang tidak dipertimbangkan OP dan karena itu mengatasi masalahnya dalam mengelola tupel panjang. Harus ditandai sebagai diterima.
serangan udara
7
Nah, alasan desain namedtupleadalah memiliki jejak memori yang lebih kecil untuk hasil massal (daftar panjang tupel, seperti hasil permintaan DB). Untuk setiap item (jika fungsi yang dimaksud tidak sering dipanggil) kamus dan kelas juga baik-baik saja. Tapi namedtuples adalah solusi yang bagus / lebih bagus dalam hal ini juga.
Lutz Prechelt
8
@ wanita: Jangan lakukan ini. Python tidak berusaha untuk menyatukan namedtupledefinisi (setiap panggilan membuat yang baru), membuat namedtuplekelas relatif mahal baik dalam CPU dan memori, dan semua definisi kelas secara intrinsik melibatkan referensi siklik (jadi pada CPython, Anda sedang menunggu siklus siklik GC bagi mereka untuk dirilis). Itu juga membuat tidak mungkin untuk picklekelas (dan karena itu, tidak mungkin untuk menggunakan contoh dengan multiprocessingdalam kebanyakan kasus). Setiap pembuatan kelas pada 3,6,4 x64 saya mengkonsumsi ~ 0,337 ms, dan menempati hanya di bawah 1 KB memori, membunuh tabungan contoh apa pun.
ShadowRanger
3
Saya akan perhatikan, Python 3.7 meningkatkan kecepatan membuat namedtuplekelas baru ; biaya CPU turun kira-kira faktor 4x , tetapi mereka masih kira-kira 1000x lebih tinggi daripada biaya untuk membuat instance, dan biaya memori untuk setiap kelas tetap tinggi (saya salah dalam komentar terakhir saya tentang "di bawah 1 KB" untuk kelas, _sourcedengan sendirinya biasanya 1,5 KB; _sourcedihapus dalam 3,7, sehingga kemungkinan lebih dekat dengan klaim asli di bawah 1 KB per pembuatan kelas).
ShadowRanger
4
@SergeStroobandt - Ini adalah bagian dari perpustakaan standar, hanya saja bukan builtin. Anda tidak perlu khawatir bahwa itu mungkin tidak diinstal pada sistem lain dengan Python> = 2.6. Atau apakah Anda hanya keberatan dengan baris kode tambahan?
Justin
234

Untuk proyek kecil saya merasa paling mudah untuk bekerja dengan tuple. Ketika itu menjadi terlalu sulit untuk dikelola (dan bukan sebelumnya) saya mulai mengelompokkan hal-hal ke dalam struktur logis, namun saya pikir Anda menyarankan penggunaan kamus dan ReturnValueobjek adalah salah (atau terlalu sederhana).

Kembali kamus dengan tombol "y0", "y1", "y2", dll tidak menawarkan keuntungan apapun atas tupel. Mengembalikan ReturnValuecontoh dengan sifat .y0, .y1, .y2, dll tidak menawarkan keuntungan apapun atas tupel baik. Anda harus mulai memberi nama hal-hal jika Anda ingin pergi ke mana saja, dan Anda bisa melakukannya menggunakan tuple:

def get_image_data(filename):
    [snip]
    return size, (format, version, compression), (width,height)

size, type, dimensions = get_image_data(x)

IMHO, satu-satunya teknik yang baik di luar tuple adalah mengembalikan objek nyata dengan metode dan properti yang tepat, seperti yang Anda dapatkan dari re.match()atau open(file).

terlalu banyak php
sumber
6
Pertanyaan - apakah ada perbedaan antara size, type, dimensions = getImageData(x)dan (size, type, dimensions) = getImageData(x)? Yaitu, apakah membungkus sisi kiri dari tugas tupled ada bedanya?
Reb.Cabin
11
@ Reb.Cabin Tidak ada perbedaan. Tuple diidentifikasi dengan koma dan penggunaan tanda kurung hanya untuk mengelompokkan hal-hal bersama. Misalnya (1)adalah int sementara (1,)atau 1,tupel.
phil
19
Kembali "mengembalikan kamus dengan kunci y0, y1, y2 dll tidak menawarkan keuntungan apa pun selain tuple": kamus memiliki keuntungan karena Anda dapat menambahkan bidang ke kamus yang dikembalikan tanpa melanggar kode yang ada.
ostrokach
Kembali "mengembalikan kamus dengan kunci y0, y1, y2 dll tidak menawarkan keuntungan apa pun selain tuple": kamus ini juga lebih mudah dibaca dan lebih sedikit kesalahan saat Anda mengakses data berdasarkan namanya daripada posisi.
Denis Dollfus
204

Banyak jawaban menyarankan Anda perlu mengembalikan koleksi, seperti kamus atau daftar. Anda dapat mengabaikan sintaks tambahan dan cukup menuliskan nilai-nilai pengembalian, dipisahkan dengan koma. Catatan: ini secara teknis mengembalikan tupel.

def f():
    return True, False
x, y = f()
print(x)
print(y)

memberi:

True
False
Joseph Hansen
sumber
24
Anda masih mengembalikan koleksi. Itu adalah tuple. Saya lebih suka tanda kurung untuk membuatnya lebih eksplisit. Coba ini: type(f())kembali <class 'tuple'>.
Igor
20
@Igor: Tidak ada alasan untuk membuat tupleaspek eksplisit; itu tidak benar-benar penting bahwa Anda mengembalikan a tuple, ini adalah ungkapan untuk mengembalikan periode beberapa nilai. Alasan yang sama Anda menghilangkan parens dengan idiom swap x, y = y, x,, inisialisasi berganda x, y = 0, 1, dll .; tentu, itu membuat tuples di bawah tenda, tetapi tidak ada alasan untuk membuat itu eksplisit, karena tuples bukan intinya sama sekali. Tutorial Python memperkenalkan banyak penugasan jauh sebelum bahkan menyentuh pada tuples.
ShadowRanger
@ShadowRanger setiap urutan nilai yang dipisahkan oleh koma di sisi kanan =adalah tuple dengan Python dengan atau tanpa tanda kurung di sekitarnya. Jadi sebenarnya tidak ada eksplisit atau implisit di sini. a, b, c sebanyak tupel (a, b, c). Juga tidak ada pembuatan tuple "di bawah tenda" ketika Anda mengembalikan nilai seperti itu, karena itu hanya tuple sederhana. OP sudah menyebutkan tupel sehingga sebenarnya tidak ada perbedaan antara apa yang ia sebutkan dan apa yang ditunjukkan jawaban ini. Tidak ada
Ken4scholars
2
Ini adalah opsi pertama yang disarankan dalam pertanyaan
endolith
1
@ endolith Dua kali pria itu mengajukan pertanyaan ("Bagaimana cara mengembalikan beberapa nilai?" dan "Bagaimana Anda mengembalikan beberapa nilai?") dijawab oleh jawaban ini. Teks pertanyaan terkadang berubah. Dan ini pertanyaan berbasis opini.
Joseph Hansen
74

Saya memilih kamus.

Saya menemukan bahwa jika saya membuat fungsi yang mengembalikan lebih dari 2-3 variabel, saya akan melipatnya dalam kamus. Kalau tidak, saya cenderung lupa urutan dan isi dari apa yang saya kembalikan.

Juga, memperkenalkan struktur 'khusus' membuat kode Anda lebih sulit untuk diikuti. (Orang lain harus mencari melalui kode untuk mencari tahu apa itu)

Jika Anda khawatir tentang mengetikkan pencarian, gunakan kunci kamus deskriptif, misalnya, 'daftar nilai x'.

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }
monkut
sumber
5
setelah bertahun-tahun pemrograman, saya cenderung ke arah apa pun struktur data dan fungsi yang diperlukan. Berfungsi terlebih dahulu, Anda selalu bisa refactor sesuai kebutuhan.
monkut
Bagaimana kita mendapatkan nilai-nilai di dalam kamus tanpa memanggil fungsi beberapa kali? Misalnya, apakah saya ingin menggunakan y1 dan y3 dalam fungsi yang berbeda?
Matt
3
tetapkan hasilnya ke variabel terpisah. result = g(x); other_function(result)
monkut
1
@monkut ya. Cara ini juga memungkinkan untuk meneruskan hasil ke beberapa fungsi, yang mengambil argumen berbeda dari hasil, tanpa harus secara khusus merujuk bagian-bagian tertentu dari hasil setiap kali.
Gnudiff
38

Opsi lain akan menggunakan generator:

>>> def f(x):
        y0 = x + 1
        yield y0
        yield x * 3
        yield y0 ** 4


>>> a, b, c = f(5)
>>> a
6
>>> b
15
>>> c
1296

Meskipun tuple IMHO biasanya yang terbaik, kecuali dalam kasus di mana nilai yang dikembalikan adalah kandidat untuk enkapsulasi di kelas.

rlms
sumber
1
Ini tampaknya menjadi solusi terbersih, dan memiliki sintaksis bersih. Apakah ada kerugian untuk ini? Jika Anda tidak menggunakan semua pengembalian, apakah ada imbal hasil yang 'tidak dihabiskan' menunggu untuk menyakiti Anda?
Jiminion
24
Ini mungkin "bersih", tetapi sepertinya tidak intuitif sama sekali. Bagaimana seseorang yang belum pernah menemukan pola ini sebelumnya tahu bahwa melakukan pembongkaran tuple otomatis akan memicu masing-masing yield?
coredumperror
1
@CoreDumpError, generator hanya itu ... generator. Tidak ada perbedaan eksternal antara def f(x): …; yield b; yield a; yield rvs (g for g in [b, a, r]), dan keduanya akan mudah dikonversi ke daftar atau tuple, dan dengan demikian akan mendukung tuple membongkar. Bentuk generator tuple mengikuti pendekatan fungsional, sedangkan bentuk fungsi sangat penting dan akan memungkinkan kontrol aliran dan penugasan variabel.
sleblanc
30

Saya lebih suka menggunakan tuple setiap kali tuple terasa "alami"; koordinat adalah contoh khas, di mana objek yang terpisah dapat berdiri sendiri, misalnya dalam perhitungan penskalaan hanya satu sumbu, dan urutannya penting. Catatan: jika saya bisa mengurutkan atau mengacak item tanpa efek buruk pada arti dari grup, maka saya mungkin tidak boleh menggunakan tuple.

Saya menggunakan kamus sebagai nilai kembali hanya ketika objek yang dikelompokkan tidak selalu sama. Pikirkan tajuk email opsional.

Untuk sisa kasus, di mana objek yang dikelompokkan memiliki makna yang melekat di dalam grup atau objek yang lengkap dengan metode sendiri diperlukan, saya menggunakan kelas.

tzot
sumber
29

Saya lebih memilih:

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }

Sepertinya yang lainnya hanyalah kode tambahan untuk melakukan hal yang sama.

UnkwnTech
sumber
22
Tuples lebih mudah diurai: y0, y1, y2 = g () dengan dict yang harus Anda lakukan: result = g () y0, y1, y2 = result.get ('y0'), result.get ('y1' ), result.get ('y2') yang agak jelek. Setiap solusi memiliki 'kelebihan' dan 'kekurangan'.
Oli
27
>>> def func():
...    return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3
WebQube
sumber
@ edouard Tidak, itu mengembalikan tuple bukan daftar.
Simon Hibbs
1
destructuring adalah yang argumen untuk kembali daftar menurut saya
semiomant
21

Secara umum, "struktur khusus" sebenarnya adalah keadaan saat objek yang masuk akal, dengan metode sendiri.

class Some3SpaceThing(object):
  def __init__(self,x):
    self.g(x)
  def g(self,x):
    self.y0 = x + 1
    self.y1 = x * 3
    self.y2 = y0 ** y3

r = Some3SpaceThing( x )
r.y0
r.y1
r.y2

Saya suka mencari nama untuk struktur anonim jika memungkinkan. Nama yang bermakna membuat segalanya lebih jelas.

S.Lott
sumber
20

Tuple, dikts, dan objek Python menawarkan program pertukaran yang mulus antara formalitas dan kenyamanan untuk struktur data kecil ("barang"). Bagi saya, pilihan bagaimana mewakili sesuatu ditentukan terutama oleh bagaimana saya akan menggunakan struktur. Di C ++, ini adalah konvensi yang umum digunakan structuntuk item data saja dan classuntuk objek dengan metode, meskipun Anda dapat menggunakan metode secara legal struct; kebiasaan saya mirip dengan Python, dengan dictdantuple di tempatstruct .

Untuk set koordinat, saya akan menggunakan tupledaripada titik classatau dict(dan perhatikan bahwa Anda dapat menggunakan tuplesebagai kunci kamus, jadidict buatlah array multidimensi yang jarang).

Jika saya akan mengulangi daftar hal-hal, saya lebih suka membongkar tuplepada iterasi:

for score,id,name in scoreAllTheThings():
    if score > goodScoreThreshold:
        print "%6.3f #%6d %s"%(score,id,name)

... karena versi objek lebih berantakan untuk dibaca:

for entry in scoreAllTheThings():
    if entry.score > goodScoreThreshold:
        print "%6.3f #%6d %s"%(entry.score,entry.id,entry.name)

... apalagi itu dict.

for entry in scoreAllTheThings():
    if entry['score'] > goodScoreThreshold:
        print "%6.3f #%6d %s"%(entry['score'],entry['id'],entry['name'])

Jika hal itu banyak digunakan, dan Anda menemukan diri Anda melakukan operasi non-sepele yang serupa di beberapa tempat dalam kode, maka biasanya bermanfaat untuk menjadikannya objek kelas dengan metode yang tepat.

Akhirnya, jika saya akan bertukar data dengan komponen sistem non-Python, saya paling sering menyimpannya di dictkarena itu paling cocok untuk serialisasi JSON.

Russell Borogove
sumber
19

Beri +1 pada saran S.Lott tentang kelas wadah bernama.

Untuk Python 2.6 dan yang lebih tinggi, tuple yang diberi nama menyediakan cara yang berguna untuk dengan mudah membuat kelas kontainer ini, dan hasilnya "ringan dan tidak memerlukan lebih banyak memori daripada tupel biasa".

John Fouhy
sumber
4

Dalam bahasa seperti Python, saya biasanya menggunakan kamus karena melibatkan lebih sedikit overhead daripada membuat kelas baru.

Namun, jika saya menemukan diri saya terus-menerus mengembalikan set variabel yang sama, maka itu mungkin melibatkan kelas baru yang saya akan faktor keluar.

fluffels
sumber
4

Saya akan menggunakan dict untuk meneruskan dan mengembalikan nilai dari suatu fungsi:

Gunakan formulir variabel seperti yang didefinisikan dalam formulir .

form = {
    'level': 0,
    'points': 0,
    'game': {
        'name': ''
    }
}


def test(form):
    form['game']['name'] = 'My game!'
    form['level'] = 2

    return form

>>> print(test(form))
{u'game': {u'name': u'My game!'}, u'points': 0, u'level': 2}

Ini adalah cara paling efisien untuk saya dan untuk unit pemrosesan.

Anda harus melewati hanya satu pointer dan mengembalikan hanya satu pointer.

Anda tidak perlu mengubah argumen fungsi (ribuan dari mereka) setiap kali Anda membuat perubahan dalam kode Anda.

Elis Byberi
sumber
Dikte bisa berubah. Jika Anda meneruskan dikt ke fungsi dan fungsi itu mengedit dikt, perubahan akan tercermin di luar cakupan fungsi itu. Memiliki fungsi mengembalikan dict pada akhirnya mungkin menyiratkan bahwa fungsi tersebut tidak memiliki efek samping, oleh karena itu nilainya tidak boleh dikembalikan, membuatnya menjadi jelas bahwa testakan langsung mengubah nilainya. Bandingkan dengan ini dict.update, yang tidak mengembalikan nilai.
sleblanc
@sleblanc "Memiliki fungsi mengembalikan dict pada akhirnya mungkin menyiratkan bahwa fungsi tidak memiliki efek samping". Itu tidak menyiratkan bahwa karena, seperti yang Anda katakan, dikt bisa berubah. Namun, kembali formtidak merusak keterbacaan atau kinerja. Dalam kasus di mana Anda mungkin perlu memformat ulang form, mengembalikannya [formulir] memastikan bahwa yang terakhir formdikembalikan karena Anda tidak akan melacak perubahan formulir di mana pun.
Elis Byberi
3

"Terbaik" adalah keputusan yang sebagian subjektif. Gunakan tuple untuk set return kecil dalam kasus umum di mana immutable dapat diterima. Sebuah tuple selalu lebih disukai daripada daftar ketika mutabilitas bukan keharusan.

Untuk nilai pengembalian yang lebih kompleks, atau untuk kasus di mana formalitas bernilai (yaitu kode nilai tinggi), tuple bernama lebih baik. Untuk kasus yang paling kompleks, objek biasanya adalah yang terbaik. Namun, sebenarnya situasi itulah yang penting. Jika masuk akal untuk mengembalikan objek karena itulah yang secara alami Anda miliki di akhir fungsi (misalnya pola Pabrik), maka kembalikan objek tersebut.

Seperti kata orang bijak:

Optimalisasi prematur adalah akar dari semua kejahatan (atau setidaknya sebagian besar) dalam pemrograman.

joel3000
sumber
1
Donald Knuth mengatakannya (tahun 1974).
Peter Mortensen