Czy funkcja zwraca wiele wartości?

81

W Pythonie funkcja może zwracać wiele wartości. Oto wymyślony przykład:

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

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

Wydaje się to bardzo przydatne, ale wygląda na to, że może być również nadużywane („Cóż… funkcja X już oblicza to, czego potrzebujemy jako wartość pośrednia. Niech X również zwróci tę wartość”).

Kiedy należy wyznaczyć granicę i zdefiniować inną metodę?

Tylko czytać
źródło

Odpowiedzi:

107

Absolutnie (na przykład podany przez Ciebie).

Krotki są obywatelami pierwszej klasy w Pythonie

Jest wbudowana funkcja divmod(), która dokładnie to robi.

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

Są też inne przykłady: 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()))

Przy okazji, nawiasy nie są potrzebne przez większość czasu. Cytat z Python Library Reference :

Krotki można konstruować na wiele sposobów:

  • Używanie pary nawiasów do oznaczenia pustej krotki: ()
  • Używanie końcowego przecinka dla krotki pojedynczej: a lub (a,)
  • Oddzielanie elementów przecinkami: a, b, c lub (a, b, c)
  • Korzystanie z wbudowanej tuple (): tuple () lub tuple (iterable)

Funkcje powinny służyć jednemu celowi

Dlatego powinny zwrócić pojedynczy obiekt. W twoim przypadku ten obiekt jest krotką. Traktuj krotkę jako złożoną strukturę danych ad hoc. Istnieją języki, w których prawie każda funkcja zwraca wiele wartości (lista w Lisp).

Czasami wystarczy wrócić (x, y)zamiast Point(x, y).

Nazwane krotki

Wraz z wprowadzeniem nazwanych krotek w Pythonie 2.6 w wielu przypadkach preferuje się zwracanie nazwanych krotek zamiast zwykłych krotek.

>>> 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
jfs
źródło
1
Możliwość używania takich krotek jest bardzo przydatna. Czasami naprawdę brakuje mi tej umiejętności w Javie.
MBCook,
3
Wielkie dzięki za wspomnienie o nazwanych krotkach. Szukałem czegoś takiego.
vobject
dla ograniczonych zwracanych wartości, dict () może być prostsze / szybsze (zwłaszcza jeśli funkcja jest następnie używana dla REST API i JSON). w moim systemie def test_nt (): Point = namedtuple ('Point', ['x', 'y']) p = Point (10.5, 11.5) json.dumps (p._asdict ()) zajmuje 819 μs / pętlę , def test_dict (): d = {'x': 10.5, 'y': 11.5} json.dumps (d) zajmuje 15,9μs / loop .... więc> 50x szybciej
comte
@comte: Tak, Point(10.5, 11.5)jest 6 razy wolniejsze niż {'x': 10.5, 'y':11.5}. Absolutne czasy 635 ns +- 26 nsporównaniu105 ns +- 4 ns . Jest mało prawdopodobne, że którykolwiek z nich stanowiłby wąskie gardło w aplikacji - nie optymalizuj, chyba że profiler mówi inaczej. Nie twórz klas na poziomie funkcji, chyba że wiesz, dlaczego tego potrzebujesz. Jeśli twoje API wymaga dictniż używania dict- nie ma to nic wspólnego z wydajnością.
jfs
27

Po pierwsze, zauważ, że Python pozwala na następujące rzeczy (nie ma potrzeby stosowania nawiasów):

q, r = divide(22, 7)

Jeśli chodzi o twoje pytanie, tak czy inaczej nie ma sztywnej i szybkiej reguły. W przypadku prostych (i zwykle wymyślonych) przykładów może się wydawać, że zawsze jest możliwe, aby dana funkcja miała jeden cel, co daje jedną wartość. Jednak podczas korzystania z języka Python w rzeczywistych aplikacjach szybko napotykasz wiele przypadków, w których konieczne jest zwrócenie wielu wartości, co skutkuje czystszym kodem.

Więc powiedziałbym, rób wszystko, co ma sens i nie próbuj dostosowywać się do sztucznej konwencji. Python obsługuje wiele zwracanych wartości, więc używaj go w razie potrzeby.

Jason Etheridge
źródło
4
przecinek tworzy krotkę, dlatego wyrażenie: q, r jest krotką.
jfs
Vinko, przykładem jest tempfile.mkstemp () z biblioteki standardowej, która zwraca krotkę zawierającą uchwyt pliku i ścieżkę bezwzględną. JFS, tak, masz rację.
Jason Etheridge,
Ale to JEST jeden cel ... Możesz mieć jeden cel i wiele zwracanych wartości
Vinko Vrsalovic
Gdyby inne języki miały wiele typów zwracanych, nie utknąłbym z odwołaniami i parametrami „out”.
orip
Zwracanie wielu wartości to jedna z największych funkcji w Pythonie!
Martin Beckett,
13

Podany przykład to w rzeczywistości wbudowana funkcja Pythona o nazwie divmod. Ktoś więc w pewnym momencie pomyślał, że jest wystarczająco pythonowy, aby uwzględnić podstawową funkcjonalność.

Dla mnie, jeśli sprawia, że ​​kod jest czystszy, to jest Python. Porównaj te dwa bloki kodu:

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
Nathan Jones
źródło
3

Tak, zwracanie wielu wartości (np. Krotki) jest zdecydowanie pythonowe. Jak zauważyli inni, istnieje wiele przykładów w standardowej bibliotece Pythona, a także w szanowanych projektach Pythona. Dwie dodatkowe uwagi:

  1. Zwracanie wielu wartości jest czasami bardzo, bardzo przydatne. Weźmy na przykład metodę, która opcjonalnie obsługuje zdarzenie (zwracając przy tym pewną wartość), a także zwraca sukces lub niepowodzenie. Może to powstać w łańcuchu odpowiedzialności. W innych przypadkach chcesz zwrócić wiele, ściśle powiązanych ze sobą fragmentów danych - tak jak w podanym przykładzie. W tym ustawieniu zwracanie wielu wartości jest podobne do zwracania pojedynczego wystąpienia klasy anonimowej z kilkoma zmiennymi składowymi.
  2. Obsługa argumentów metod w Pythonie wymaga możliwości bezpośredniego zwracania wielu wartości. Na przykład w C ++ argumenty metody mogą być przekazywane przez odwołanie, dzięki czemu można przypisać im wartości wyjściowe oprócz formalnej wartości zwracanej. W Pythonie argumenty są przekazywane „przez referencję” (ale w sensie Javy, a nie C ++). Nie można przypisać nowych wartości do argumentów metody i mieć odzwierciedlenie poza zakresem metody. Na przykład:

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

    Porównać z:

    # Python
    def test(arg):
        arg = 1
    
    foo = 0
    test(foo) # foo is still 0
    
zweiterlinde
źródło
„metoda, która opcjonalnie obsługuje zdarzenie i zwraca również sukces lub niepowodzenie” - do tego służą wyjątki.
Nathan
Powszechnie przyjmuje się, że wyjątki dotyczą tylko „wyjątkowych” warunków, a nie zwykłego sukcesu lub porażki. „Kody błędów a wyjątki” Google w niektórych dyskusjach.
zweiterlinde
1

To zdecydowanie pytoniczne. Fakt, że możesz zwrócić wiele wartości z funkcji, która jest szablonem, który miałbyś w języku takim jak C, w którym musisz zdefiniować strukturę dla każdej kombinacji typów, które gdzieś zwracasz.

Jeśli jednak osiągniesz punkt, w którym zwracasz coś szalonego, na przykład 10 wartości z jednej funkcji, powinieneś poważnie rozważyć umieszczenie ich w klasie, ponieważ w tym momencie staje się nieporęczna.

wcięcie
źródło
1

OT: Algol68 RSRE ma ciekawy operator "/: =". na przykład.

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

Dając iloraz 3 i resztę 16.

Uwaga: zazwyczaj wartość „(x /: = y)” jest odrzucana, ponieważ iloraz „x” jest przypisywany przez odniesienie, ale w przypadku RSRE zwracaną wartością jest reszta.

cf Arytmetyka liczb całkowitych - Algol68

NevilleDNZ
źródło
0

Zwracanie wielu wartości przy użyciu krotki w przypadku prostych funkcji, takich jak divmod. Jeśli sprawia, że ​​kod jest czytelny, jest to Pythonic.

Jeśli zwracana wartość zaczyna być myląca, sprawdź, czy funkcja nie robi za dużo i podziel ją, jeśli tak jest. Jeśli duża krotka jest używana jak obiekt, uczyń ją obiektem. Rozważ również użycie nazwanych krotek , które będą częścią standardowej biblioteki w Pythonie 2.6.

Will Harris
źródło
0

Jestem całkiem nowy w Pythonie, ale technika krotki wydaje mi się bardzo pytoniczna. Jednak mam inny pomysł, który może poprawić czytelność. Korzystanie ze słownika umożliwia dostęp do różnych wartości według nazwy, a nie pozycji. Na przykład:

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

answer = divide(22, 7)
print answer['quotient']
print answer['remainder']
Fred Larson
źródło
2
Nie rób tego. Nie możesz przypisać wyniku w jednej linijce, jest bardzo niezgrabny. (Chyba że chcesz zapisać wynik, w takim przypadku lepiej umieścić to w metodzie.) Jeśli Twoim zamiarem jest samodzielne udokumentowanie zwracanych wartości, a) nadaj fn rozsądnie objaśniającą nazwę (np. " divmod ") i b) resztę wyjaśnienia umieść w docstringu (jest to miejsce w Pythonie, w którym można je umieścić); Jeśli ten ciąg dokumentów wymaga więcej niż dwóch wierszy, oznacza to, że funkcja jest przeciążona tematycznie i może być złym pomysłem.
smci