Bir işlevden birden çok değeri nasıl döndürürüm? [kapalı]


1067

Bunu destekleyen dillerde birden çok değer döndürmenin standart yolu genellikle tupling .

Seçenek: Bir demet kullanma

Bu önemsiz örneği düşünün:

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

Ancak, döndürülen değerlerin sayısı arttıkça bu hızla sorunlu hale gelir. Dört veya beş değer döndürmek isterseniz ne olur? Elbette, onları yakalamaya devam edebilirsiniz, ancak hangi değerin nerede olduğunu unutmak kolaylaşır. Bunları almak istediğiniz her yerde açmak oldukça çirkin.

Seçenek: Sözlük kullanma

Bir sonraki mantıklı adım, bir çeşit 'kayıt gösterimi' tanıtmak gibi görünüyor. Python'da bunu yapmanın bariz yolu a dict.

Aşağıdakileri göz önünde bulundur:

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

(Açık olmak gerekirse, y0, y1 ve y2 sadece soyut tanımlayıcılar içindir. Belirtildiği gibi, pratikte anlamlı tanımlayıcılar kullanırsınız.)

Şimdi, iade edilen nesnenin belirli bir üyesini yansıtabileceğimiz bir mekanizmamız var. Örneğin,

result['y0']

Seçenek: Sınıf kullanma

Ancak başka bir seçenek daha var. Bunun yerine özel bir yapıya dönebiliriz. Bunu Python bağlamında çerçeveledim, ancak eminim diğer diller için de geçerlidir. Gerçekten, eğer C'de çalışıyorsanız, bu tek seçeneğiniz olabilir. İşte gidiyor:

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)

Python önceki iki belki sıhhi tesisat açısından çok benzerdir - sonuçta { y0, y1, y2 }sadece iç girişler olma sonunda __dict__bir ReturnValue.

Küçük nesneler için Python tarafından sağlanan bir ek özellik daha vardır __slots__. Sınıf şu şekilde ifade edilebilir:

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

Gönderen Python Reference Manual :

__slots__Bildirimi, her bir değişken için bir değeri saklamak için, her bir durumda, örneğin değişken ve rezerv yeterli alan bir dizi alır. __dict__Her örnek için oluşturulmadığı için boşluk kaydedilir .

Seçenek: Veri kullanma (Python 3.7+)

Python 3.7'nin yeni verilerini kullanarak, otomatik olarak eklenen özel yöntemlerle, yazarak ve diğer faydalı araçlarla bir sınıfa dönün:

@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)

Seçenek: Liste kullanma

Göz ardı ettiğim bir diğer öneri ise Kertenkele Bill'den geliyor:

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

Bu benim en sevdiğim yöntem olsa. Sanırım Haskell'e maruz kaldığım için rahatsız oldum, ancak karışık tip listeler fikri her zaman beni rahatsız etti. Bu özel örnekte liste -not- karışık tiptedir, ancak makul olabilir.

Bu şekilde kullanılan bir liste, tuple ile ilgili olarak anlayabildiğim kadarıyla hiçbir şey kazanmaz. Python'daki listeler ve tuples arasındaki tek gerçek fark, listelerin değiştirilebilir olması , tuples olmamasıdır.

Ben şahsen fonksiyonel programlamadan gelen kuralları yerine getirme eğilimindeyim: aynı türden herhangi bir eleman için listeler ve önceden belirlenmiş türlerde sabit sayıda eleman için tuples kullanın.

Soru

Uzun önsözden sonra kaçınılmaz soru geliyor. Hangi yöntem (sizce) en iyisidir?


10
Değişken kullandığınız mükemmel örneklerde y3, ancak y3 global olarak bildirilmedikçe, bu NameError: global name 'y3' is not definedsadece kullanım sağlar 3mı?
hetepeperfan

11
'Görüş' anahtar kelimesi ortaya çıktığından, harika yanıtları olan birçok harika soru kapanır. Tüm SO'nun görüşe dayandığını iddia edebilirsiniz, ancak gerçekler, referanslar ve özel uzmanlık tarafından bilgilendirilen görüştür. Birisinin "hangisinin en iyi olduğunu düşünüyorsunuz" diye sorması, gerçek dünyadaki gerçeklerden, referanslardan ve özel uzmanlıktan soyutlanmış kişisel görüşler istediği anlamına gelmez. Neredeyse kesinlikle kesinlikle bu tür bir görüş, kişinin görüş oluşturmak için kullandığı gerçeklere, referanslara ve spesifik uzmanlığa dayalı olan ve bunlarla tam olarak belgelenen türden bir fikir istemektedirler.
NeilG

@hetepeperfan 3 değiştirmeye gerek yoktur ve küresel olarak y3'ü tanımlamaz y3, aynı işi de yapacak olan yerel bir ad da kullanabilirsiniz .
okie

Yanıtlar:


637

Bu amaçla 2.6'da adlandırılmış kaplar eklendi. Benzer yerleşik örnek için os.stat'a da bakın .

>>> 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

Python 3'ün (3.6+, sanırım) son sürümlerinde, yeni typingkütüphane, NamedTupleadlandırılmış tupleslerin oluşturulmasını daha kolay ve daha güçlü hale getirmek için sınıfı aldı . Kaynağından devralma typing.NamedTupledocstring'leri, varsayılan değerleri ve yazım notlarını kullanmanıza izin verir.

Örnek (Dokümanlardan):

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

employee = Employee('Guido')
assert employee.id == 3

68
Bu sadece doğru cevaptır, çünkü OP'nin dikkate almadığı tek kanonik yapıdır ve uzun tupleleri yönetme sorununu ele alır. Kabul edilmiş olarak işaretlenmelidir.
hava saldırısı

7
Tasarım mantığı namedtuple, kitle sonuçları için daha küçük bir bellek ayak izine sahip olmaktır (DB sorgularının sonuçları gibi uzun grup listeleri). Tek tek öğeler için (söz konusu işlev sık sık çağrılmazsa) sözlükler ve sınıflar da iyidir. Ancak adlandırılmış gruplar da bu durumda güzel / hoş bir çözümdür.
Lutz Prechelt

8
@wom: Bunu yapma. Python namedtupletanımları ayırmak için hiçbir çaba sarf etmez (her çağrı yeni bir tane yaratır), namedtuplesınıfı oluşturmak hem CPU'da hem de bellekte nispeten pahalıdır ve tüm sınıf tanımları özünde döngüsel referanslar içerir (yani CPython'da döngüsel bir GC çalışması bekliyorsunuz serbest bırakılması için). Ayrıca picklesınıf için imkansız hale getirir (ve bu nedenle multiprocessingçoğu durumda örneklerin kullanılması imkansızdır ). 3.6.4 x64'ümdeki sınıfın her oluşturulması ~ 0.337 ms tüketir ve 1 KB belleğin biraz altında yer kaplar ve herhangi bir örnek tasarrufunu öldürür.
ShadowRanger

3
Python 3.7 , yeni namedtuplesınıflar oluşturma hızını artırdı ; CPU maliyetleri kabaca 4 kat azalır , ancak yine de bir örnek oluşturma maliyetinden yaklaşık 1000 kat daha yüksektir ve her sınıf için bellek maliyeti yüksek kalır ("1 KB altı" hakkındaki son yorumumda yanılmışım sınıf için, _sourcetek başına tipik olarak 1,5 KB; _source3.7'de kaldırılır, bu nedenle orijinal oluşturma, sınıf oluşturma başına 1 KB'nin biraz altındadır.
ShadowRanger

4
@SergeStroobandt - Bu standart kütüphanenin bir parçasıdır, sadece yerleşik değildir. Python> = 2.6 ile başka bir sisteme yüklenmeyebileceğinden endişelenmenize gerek yok. Yoksa sadece ekstra kod satırına itiraz ediyor musunuz?
Justin

234

Küçük projeler için tupllerle çalışmanın en kolay yolunu buluyorum. Bu, yönetilmesi çok zorlaştığında (ve daha önce değil) bir şeyleri mantıksal yapılarda gruplandırmaya başladım, ancak sözlük ve ReturnValuenesneleri önerilen kullanımınızın yanlış (veya çok basit) olduğunu düşünüyorum.

Tuşlarıyla bir sözlük dönersek "y0", "y1", "y2"vb küpe karşısında hiçbir avantaj sağlamamaktadır. Geri Gelen ReturnValueözelliklere sahip örneği .y0, .y1, .y2vb ya küpe karşısında hiçbir avantaj sağlamamaktadır. Herhangi bir yere ulaşmak istiyorsanız bir şeyleri adlandırmaya başlamanız gerekir ve bunu yine de tuples kullanarak yapabilirsiniz:

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

size, type, dimensions = get_image_data(x)

IMHO, tuple'lerin ötesinde tek iyi teknik, gerçek nesneleri re.match()veya gibi elde ettiğiniz uygun yöntem ve özelliklerle döndürmektir open(file).


6
Soru - arasında herhangi bir fark yoktur size, type, dimensions = getImageData(x)ve (size, type, dimensions) = getImageData(x)? Yani, tupled bir ödevin sol tarafına sarmak herhangi bir fark yaratıyor mu?
Reb.Cabin

11
@ Reb.Cabin Fark yok. Tuple virgülle tanımlanır ve parantez kullanımı sadece şeyleri birlikte gruplamaktır. Örneğin (1), bir int while (1,)veya 1,bir demettir.
phil

19
Re "y0, y1, y2 vb tuşları ile bir sözlük döndürmek tuples üzerinde herhangi bir avantaj sağlamaz": sözlük, mevcut kodu kırmadan döndürülen sözlüğe alanlar ekleyebilmeniz avantajına sahiptir.
ostrokach

Re "y0, y1, y2 vb tuşları ile bir sözlük döndürmek, tuples'e göre herhangi bir avantaj sağlamaz": ayrıca verilere konumdan ziyade adına göre erişilirken daha okunabilir ve daha az hata eğilimi olur.
Denis Dollfus

204

Yanıtların çoğu, sözlük veya liste gibi bir tür koleksiyonu geri döndürmeniz gerektiğini gösterir. Ek sözdizimini bırakıp virgülle ayrılmış dönüş değerlerini yazabilirsiniz. Not: Bu teknik olarak bir demet döndürür.

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

verir:

True
False

24
Hâlâ bir koleksiyon iade ediyorsunuz. Bir demet. Daha açık hale getirmek için parantez tercih ediyorum. Şunu deneyin: type(f())döner <class 'tuple'>.
Igor

20
@Igor: tupleYönü açık yapmak için hiçbir neden yoktur ; a döndürmeniz gerçekten önemli değil tuple, bu birden çok değer dönemi döndürmek için kullanılan deyimdir. Aynı sebeple takas deyim x, y = y, x, çoklu başlatma x, y = 0, 1, vb. elbette, tuplekaputun altındadır, ancak bunu açıkça belirtmek için bir neden yoktur, çünkü tuples hiç bir nokta değildir. Python öğreticisi, dokunmadan çok önce birden fazla ödev sunartuple .
ShadowRanger

@ShadowSağının sağ tarafında virgülle ayrılmış herhangi bir değer dizisi, =Python'da etraflarında parantez olan veya olmayan bir demettir . Yani aslında burada açık veya kapalı bir şey yok. a, b, c (a, b, c) kadar bir demettir. Ayrıca, bu tür değerleri döndürdüğünüzde "kaputun altında" tuple yapımı da yoktur, çünkü bu sadece basit bir basit tupledir. OP zaten tuples'ten bahsetti, bu yüzden bahsettikleri ve bu cevabın gösterdiği şeyler arasında hiçbir fark yok. None
Ken4scholars

2
Bu tam anlamıyla soruda önerilen ilk seçenektir
endolith

1
İki kez @endolith adam ( "nasıl "? ı birden çok değer dönmek nasıl" ve bir soru sorar Eğer birden çok değer döndürür?") bu cevap ile cevaplandırılır. Sorunun metni bazen değişti. Ve bu görüşe dayalı bir soru.
Joseph Hansen

74

Sözlüğe oy veriyorum.

2-3 değişkenten daha fazla bir şey döndüren bir işlev yaparsam, bunları bir sözlüğe katlayacağım. Aksi takdirde iade ettiğim şeyin sırasını ve içeriğini unutma eğilimindeyim.

Ayrıca, 'özel' bir yapının sunulması kodunuzun takip edilmesini zorlaştırır. (Başka birinin ne olduğunu öğrenmek için kodda arama yapması gerekir)

Türle ilgili endişeleriniz varsa, açıklayıcı sözlük anahtarları kullanın, örneğin, 'x-değerleri listesi'.

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

5
Uzun yıllar süren programlamadan sonra, veri ve fonksiyonun yapısının ne kadar gerekli olduğuna yöneliyorum. Önce fonksiyon, gerektiğinde her zaman yeniden düzenleyebilirsiniz.
monkut

İşlevi birkaç kez çağırmadan sözlük içindeki değerleri nasıl elde ederiz? Örneğin, y1 ve y3'ü farklı bir işlevde kullanmak istersem?
Matt

3
sonuçları ayrı bir değişkene atayın. result = g(x); other_function(result)
monkut

1
@monkut evet. Bu şekilde, sonucun her seferinde sonucun belirli kısımlarına özel olarak başvurmak zorunda kalmadan, sonuçtan farklı argümanlar alan çeşitli fonksiyonlara geçilmesine de izin verilir.
Gnudiff

38

Başka bir seçenek jeneratörleri kullanmak olabilir:

>>> 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

IMHO tuples genellikle en iyi olmasına rağmen, döndürülen değerlerin bir sınıfta kapsülleme adayları hariç, genellikle en iyisidir.


1
Bu en temiz çözüm gibi görünüyor ve temiz bir sözdizimine sahip. Bunun bir dezavantajı var mı? Tüm iadeleri kullanmazsanız, size zarar vermeyi bekleyen 'harcanmamış' verimler var mı?
Jiminion

24
Bu "temiz" olabilir, ama hiç de sezgisel görünmüyor. Bu kalıpla daha önce hiç karşılaşmamış biri, otomatik demet açma işleminin her birini tetikleyeceğini yieldnasıl bilebilirdi ?
coredumperror

1
@CoreDumpError, jeneratörler sadece… jeneratörlerdir. Orada arasında hiçbir dış farktır def f(x): …; yield b; yield a; yield rvs (g for g in [b, a, r])ve her iki kolayca listeleri veya dizilerini ve böyle irade desteği tuple'larının ambalajından çıkardıktan olarak dönüştürür. Tuple jeneratör formu fonksiyonel bir yaklaşımı takip ederken, fonksiyon formu zorunludur ve akış kontrolü ve değişken atamaya izin verecektir.
sleblanc

30

Bir tuple "doğal" hissettiğinde tuples kullanmayı tercih ederim; koordinatlar, ayrı nesnelerin kendi başlarına durabildiği tipik bir örnektir, örneğin yalnızca tek eksenli ölçeklendirme hesaplamalarında ve sıra önemlidir. Not: Grubun anlamı üzerinde olumsuz bir etkisi olmayan öğeleri sıralayabilir veya karıştırabilirsem, muhtemelen bir demet kullanmamalıyım.

Sözlükleri bir dönüş değeri olarak yalnızca gruplanmış nesneler her zaman aynı olmadığında kullanırım. İsteğe bağlı e-posta başlıklarını düşünün.

Gruplanmış nesnelerin grup içinde doğal bir anlama sahip oldukları veya kendi yöntemleriyle tam teşekküllü bir nesneye ihtiyaç duyulduğu diğer durumlarda, bir sınıf kullanıyorum.


29

Tercih ederim:

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

Görünüşe göre her şey aynı şeyi yapmak için ekstra bir kod.


22
Tuples paketini açmak daha kolaydır: yapmanız gereken bir dikte ile y0, y1, y2 = g (): sonuç = g () y0, y1, y2 = sonuç.get ('y0'), sonuç.get ('y1' ), sonuç.get ('y2') biraz çirkin. Her çözümün kendi artıları ve eksileri vardır.
Oli

27
>>> def func():
...    return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3

@ edouard Hayır, listeyi değil bir tuple döndürür.
Simon Hibbs

1
yıkım, bence listeleri döndürmek için bir argüman
semiomant

21

Genel olarak, "özelleşmiş yapı" aslında bir nesnenin kendi yöntemleriyle mantıklı bir güncel halidir.

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

Mümkün olduğunca anonim yapıların isimlerini bulmayı seviyorum. Anlamlı isimler işleri daha net hale getirir.


20

Python'un tuples, dikte ve nesneleri, programcıya küçük veri yapıları ("şeyler") için formalite ve kolaylık arasında düzgün bir denge sağlar. Benim için, bir şeyi nasıl temsil edeceğimin seçimi esas olarak yapıyı nasıl kullanacağım tarafından belirlenir. C ++ 'da, yöntemleri structyalnızca classyasal olarak koyabilseniz bile, yalnızca veri içeren öğeler ve yöntem içeren nesneler için kullanılan yaygın bir kuraldır struct; alışkanlığım Python'da dictvetuple yerinestruct .

Koordinat kümeleri için tuplebir nokta classveya a yerine bir nokta kullanacağım dict(ve a'yı tuplesözlük anahtarı olarak kullanabileceğinizi unutmayın.dict çok seyrek çok boyutlu diziler yapın).

Bir şeyler listesi üzerinde yineleme yapacaksam, yinelemede paketleri açmayı tercih ederim tuple:

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

... nesne sürümü daha okunaklı olduğu için:

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

... bırak gitsin dict.

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

Eğer şey yaygın olarak kullanılıyorsa ve kendinizi kodda birden fazla yerde benzer önemsiz işlemler yaparken bulursanız, genellikle uygun yöntemlerle bir sınıf nesnesi haline getirmeye değer.

Son olarak, Python olmayan sistem bileşenleri ile veri alışverişinde bulunacaksam, çoğu zaman bunları saklayacağım dictçünkü bu JSON serileştirmesine en uygun olanıdır .


19

S.Lott'un adlandırılmış bir kapsayıcı sınıfı önerisinde +1.

Python 2.6 ve üstü için, adlandırılmış bir grup bu kap sınıflarını kolayca oluşturmanın yararlı bir yolunu sunar ve sonuçlar "hafiftir ve normal gruplardan daha fazla bellek gerektirmez".


4

Python gibi dillerde, genellikle yeni bir sınıf oluşturmaktan daha az ek yük içerdiğinden sözlük kullanırım.

Ancak, kendimi sürekli olarak aynı değişkenleri döndürürken bulursam, bu muhtemelen hesaba katacağım yeni bir sınıfı içerir.


4

Bir fonksiyondan değerleri iletmek ve döndürmek için bir dikte kullanırdım:

'De tanımlandığı gibi değişken formu kullanarak formu .

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}

Bu benim ve işlem birimi için en etkili yoldur.

Sadece bir işaretçi geçirmeniz ve sadece bir işaretçi döndürmeniz gerekir.

Kodunuzda her değişiklik yaptığınızda işlevlerin (binlerce) bağımsız değişkenini değiştirmeniz gerekmez.


Dicts değiştirilebilir. Bir işleve bir dikseli iletirseniz ve bu işlev dikteyi düzenlerse, değişiklikler o işlevin kapsamı dışında yansıtılır. İşlevin sonunda dikteyi döndürmesi, işlevin herhangi bir yan etkisi olmadığını ima edebilir, bu nedenle değer döndürülmemelidir, bu da testdeğeri doğrudan değiştirecek şekilde netleştirilir . Bunu dict.updatebir değer döndürmeyen ile karşılaştırın .
sleblanc

@sleblanc "İşlevin sonunda dikteyi döndürmesi, işlevin yan etkisi olmadığını gösterebilir". Söylediğiniz gibi, diktenin değiştirilebilir olduğu anlamına gelmez. Ancak geri dönüş form, okunabilirliğe veya performansa zarar vermez. Yeniden biçimlendirmeniz gerekebilecek durumlarda form[form] döndürmek form, form değişikliklerini hiçbir yerde takip etmeyeceğiniz için sonun döndürülmesini sağlar.
Elis Byberi

3

"En iyi" kısmen öznel bir karardır. Değiştirilemez olan genel durumda küçük dönüş setleri için tuples kullanın. Değiştirilebilirlik şart olmadığında bir demet her zaman bir listeye tercih edilir.

Daha karmaşık dönüş değerleri veya formalitenin değerli olduğu durumlarda (yani yüksek değer kodu) adlandırılmış bir demet daha iyidir. En karmaşık durumda bir nesne genellikle en iyisidir. Ancak, gerçekten önemli olan durum budur. Bir nesneyi döndürmek mantıklıysa, çünkü fonksiyonun sonunda doğal olarak sahip olduğunuz şey budur (örn. Fabrika deseni), nesneyi iade edin.

Bilge adamın dediği gibi:

Erken optimizasyon, programlamadaki tüm kötülüklerin (veya en azından çoğunun) köküdür.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.