Bir işlevin birden çok değer döndürmesi pitonik mi?


81

Python'da, bir işlevin birden çok değer döndürmesini sağlayabilirsiniz. İşte uydurma bir örnek:

def divide(x, y):
    quotient = x/y
    remainder = x % y
    return quotient, remainder  

(q, r) = divide(22, 7)

Bu çok kullanışlı görünüyor, ama aynı zamanda kötüye kullanılabilecek gibi görünüyor ("Peki..fonksiyon X, ihtiyacımız olanı bir ara değer olarak hesaplıyor. X'in de bu değeri döndürmesini sağlayalım").

Çizgiyi ne zaman çizmeli ve farklı bir yöntem tanımlamalısınız?

Yanıtlar:


107

Kesinlikle (verdiğiniz örnek için).

Tuples, Python'da birinci sınıf vatandaşlardır

divmod()Tam olarak bunu yapan yerleşik bir işlev var.

q, r = divmod(x, y) # ((x - x%y)/y, x%y) Invariant: div*y + mod == x

Başka örnekler de vardır: zip, enumerate, dict.items.

for i, e in enumerate([1, 3, 3]):
    print "index=%d, element=%s" % (i, e)

# reverse keys and values in a dictionary
d = dict((v, k) for k, v in adict.items()) # or 
d = dict(zip(adict.values(), adict.keys()))

BTW, çoğu zaman parantez gerekli değildir. Python Kitaplığı Referansından Alıntı :

Tuplelar birkaç şekilde oluşturulabilir:

  • Boş demeti belirtmek için bir çift parantez kullanma: ()
  • Tekli bir demet için sondaki virgül kullanma: a, veya (a,)
  • Öğeleri virgülle ayırma: a, b, c veya (a, b, c)
  • Tuple () yerleşik olarak kullanma: tuple () veya tuple (yinelenebilir)

Fonksiyonlar tek bir amaca hizmet etmelidir

Bu nedenle tek bir nesne döndürmeleri gerekir. Sizin durumunuzda bu nesne bir demettir. Demeti geçici bir bileşik veri yapısı olarak düşünün. Hemen hemen her işlevin birden çok değer döndürdüğü diller vardır (Lisp'te liste).

Bazen (x, y)yerine dönmek yeterlidir Point(x, y).

Adlandırılmış gruplar

Python 2.6'da adlandırılmış tupleların kullanılmaya başlanmasıyla birlikte, çoğu durumda düz demetler yerine adlandırılmış tuple'ları döndürmek tercih edilir.

>>> import collections
>>> Point = collections.namedtuple('Point', 'x y')
>>> x, y = Point(0, 1)
>>> p = Point(x, y)
>>> x, y, p
(0, 1, Point(x=0, y=1))
>>> p.x, p.y, p[0], p[1]
(0, 1, 0, 1)
>>> for i in p:
...   print(i)
...
0
1

1
Tuple'ları böyle kullanma yeteneği çok kullanışlıdır. Java'da bu yeteneği gerçekten özlüyorum.
MBCook

3
İsimli demetlerden bahsettiğiniz için çok teşekkürler. Bunun gibi bir şey arıyordum.
vobject

sınırlı döndürülen değerler için, dict () daha basit / daha hızlı olabilir (özellikle işlev daha sonra REST API ve JSON için kullanılırsa). sistemimde, def test_nt (): Nokta = adlandırılmıştuple ('Nokta', ['x', 'y']) p = Nokta (10.5, 11.5) json.dumps (p._asdict ()) 819 μs / döngü alır , def test_dict (): d = {'x': 10.5, 'y': 11.5} json.dumps (d) 15.9μs / döngü alır .... bu yüzden> 50x daha hızlı
comte

@comte: Evet, Point(10.5, 11.5)6 kat daha yavaştır {'x': 10.5, 'y':11.5}. Mutlak zamanlar 635 ns +- 26 nsvs105 ns +- 4 ns . Her ikisinin de uygulamada bir darboğaz olması olası değildir - profilciniz aksini söylemediği sürece optimize etmeyin. Neden ihtiyaç duyduğunuzu bilmiyorsanız, işlev düzeyinde sınıflar oluşturmayın. API'niz dictkullanımdan fazlasını gerektiriyorsa dict- performansla ilgisi yoktur.
jfs

27

İlk olarak, Python'un aşağıdakilere izin verdiğini unutmayın (paranteze gerek yoktur):

q, r = divide(22, 7)

Sorunuzla ilgili olarak, her iki şekilde de sert ve hızlı bir kural yoktur. Basit (ve genellikle uydurulmuş) örnekler için, belirli bir işlevin tek bir amaca sahip olması her zaman mümkün gibi görünebilir, bu da tek bir değerle sonuçlanır. Bununla birlikte, Python'u gerçek dünya uygulamaları için kullanırken, birden çok değer döndürmenin gerekli olduğu birçok durumla hızla karşılaşırsınız ve sonuçta daha temiz kod elde edilir.

Yani, mantıklı olanı yapın ve yapay bir sözleşmeye uymaya çalışmayın derim. Python birden çok dönüş değerini destekler, bu nedenle uygun olduğunda kullanın.


4
virgül kayıt düzeni bu nedenle ifadeyi oluşturur: q, r olan bir tanımlama grubu.
jfs

Vinko, bir örnek olarak standart kitaplıktaki tempfile.mkstemp () 'dir ve bir dosya tanıtıcısı ve mutlak bir yol içeren bir demet döndürür. JFS, evet, haklısın.
Jason Etheridge

Ancak bu tek bir amaçtır ... Tek amaçlı ve birden çok dönüş değerine sahip olabilirsiniz
Vinko Vrsalovic

Diğer dillerin birden fazla dönüş türü olsaydı, referanslar ve 'out' parametreleri ile takılıp kalmazdım.
orip

Birden çok değer döndürmek, python'daki en büyük özelliklerden biridir!
Martin Beckett

13

Verdiğiniz örnek aslında bir python yerleşik işlevidir divmod. Yani birisi, zamanın bir noktasında, temel işlevselliğe dahil edecek kadar pitonik olduğunu düşündü.

Bana göre, kodu daha temiz hale getiriyorsa, pitoniktir. Bu iki kod bloğunu karşılaştırın:

seconds = 1234
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)

seconds = 1234
minutes = seconds / 60
seconds = seconds % 60
hours = minutes / 60
minutes = minutes % 60

3

Evet, birden çok değer döndürmek (yani bir demet) kesinlikle pitoniktir. Diğerlerinin de belirttiği gibi, Python standart kitaplığında ve saygın Python projelerinde çok sayıda örnek var. İki ek yorum:

  1. Birden çok değer döndürmek bazen çok, çok kullanışlıdır. Örneğin, isteğe bağlı olarak bir olayı işleyen (bunu yaparken bir değer döndüren) ve aynı zamanda başarı veya başarısızlık döndüren bir yöntemi ele alalım. Bu bir sorumluluk zinciri modelinde ortaya çıkabilir. Diğer durumlarda, verilen örnekte olduğu gibi, birbiriyle yakından bağlantılı birden çok veri parçası döndürmek istersiniz. Bu ayarda, birden çok değer döndürmek, birden çok üye değişkenle anonim bir sınıfın tek bir örneğini döndürmeye benzer.
  2. Python'un yöntem argümanlarını ele alması, birden çok değeri doğrudan döndürme yeteneğini gerektirir. C ++ 'da, örneğin, yöntem bağımsız değişkenleri başvuruya göre iletilebilir, böylece biçimsel dönüş değerine ek olarak bunlara çıktı değerleri atayabilirsiniz. Python'da, bağımsız değişkenler "referans olarak" iletilir (ancak Java anlamında, C ++ değil). Yöntem bağımsız değişkenlerine yeni değerler atayamaz ve bunların yöntem kapsamının dışında yansıtılmasını sağlayamazsınız. Örneğin:

    // C++
    void test(int& arg)
    {
        arg = 1;
    }
    
    int foo = 0;
    test(foo); // foo is now 1!
    

    İle karşılaştırmak:

    # Python
    def test(arg):
        arg = 1
    
    foo = 0
    test(foo) # foo is still 0
    

"isteğe bağlı olarak bir olayı işleyen ve aynı zamanda başarı veya başarısızlık döndüren bir yöntem" - İstisnalar bunun içindir.
Nathan

İstisnaların yalnızca "istisnai" koşullar için olduğu, yalnızca başarı veya başarısızlık için olmadığı yaygın olarak kabul edilmektedir. Tartışmanın bir kısmı için Google "hata kodları ve istisnalar".
zweiterlinde

1

Kesinlikle pitonik. Bir yere geri döndüğünüz her tür kombinasyonu için bir yapı tanımlamanız gereken C gibi bir dilde ortak metinden birden fazla değer döndürebileceğiniz gerçeği.

Bununla birlikte, tek bir işlevden 10 değer gibi çılgın bir şey döndürdüğünüz noktaya ulaşırsanız, bunları bir sınıfta bir araya getirmeyi ciddiye almalısınız çünkü bu noktada hantal hale gelir.



1

OT: RSRE'den Algol68 meraklı "/: =" operatörüne sahip. Örneğin.

INT quotient:=355, remainder;
remainder := (quotient /:= 113);

3'lük bir bölüm ve 16'nın kalan kısmı verilir.

Not: tipik olarak "(x /: = y)" değeri, "x" bölümü referans tarafından atandığından atılır, ancak RSRE durumunda döndürülen değer kalan değerdir.

cf Tamsayı Aritmetik - Algol68


0

Gibi basit işlevler için bir demet kullanarak birden çok değer döndürmek sorun değil divmod . Kodu okunabilir hale getiriyorsa, Pythonic'tir.

Dönüş değeri kafa karıştırmaya başlarsa, işlevin çok fazla şey yapıp yapmadığını kontrol edin ve varsa bölün. Bir nesne gibi büyük bir demet kullanılıyorsa, onu bir nesne yapın. Ayrıca, Python 2.6'daki standart kitaplığın parçası olacak adlandırılmış tuplelar kullanmayı düşünün .


0

Python'da oldukça yeniyim, ancak tuple tekniği bana çok pitonik görünüyor. Ancak, okunabilirliği artırabilecek başka bir fikrim var. Sözlük kullanmak, farklı değerlere konumdan ziyade isme göre erişime izin verir. Örneğin:

def divide(x, y):
    return {'quotient': x/y, 'remainder':x%y }

answer = divide(22, 7)
print answer['quotient']
print answer['remainder']

2
Bunu yapma. Sonucu tek satırlık olarak atayamazsınız, bu çok hantal. (Sonucu saklamak istemiyorsanız, bu durumda bunu bir yönteme koymak daha iyidir.) Eğer dikteyle amacınız dönüş değerlerini kendi kendine belgelemekse, o zaman a) fn'ye makul ölçüde açıklayıcı bir ad verin (" divmod ") ve b) açıklamanın geri kalanını bir dokümana (onları koymak için Pythonic yeridir) koyun; Bu docstring iki satırdan daha uzun gerektiriyorsa, bu, işlevin tematik olarak aşırı yüklendiğini gösterir ve kötü bir fikir olabilir.
smci
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.