Jak zwrócić wiele wartości z funkcji? [Zamknięte]


1067

Kanonicznym sposobem zwracania wielu wartości w językach, które go obsługują, jest często krotki .

Opcja: użycie krotki

Rozważ ten trywialny przykład:

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

Szybko staje się to jednak problematyczne w miarę wzrostu liczby zwracanych wartości. Co jeśli chcesz zwrócić cztery lub pięć wartości? Jasne, możesz je ciągnąć, ale łatwo zapomnieć, która wartość jest gdzie. Rozpakowywanie ich w dowolnym miejscu, w którym chcesz je otrzymać, jest raczej brzydkie.

Opcja: Korzystanie ze słownika

Kolejnym logicznym krokiem wydaje się wprowadzenie pewnego rodzaju „zapisu zapisu”. W Pythonie oczywistym sposobem na to jest użycie dict.

Rozważ następujące:

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

(Żeby być jasnym, y0, y1 i y2 są po prostu abstrakcyjnymi identyfikatorami. Jak wskazano, w praktyce używałbyś znaczących identyfikatorów).

Teraz mamy mechanizm, dzięki któremu możemy rzutować określonego członka zwracanego obiektu. Na przykład,

result['y0']

Opcja: Korzystanie z klasy

Istnieje jednak inna opcja. Zamiast tego moglibyśmy zwrócić specjalistyczną strukturę. Zrobiłem to w kontekście Pythona, ale jestem pewien, że dotyczy to również innych języków. Rzeczywiście, jeśli pracujesz w C, może to być Twoja jedyna opcja. Tutaj idzie:

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)

W Pythonie poprzednie dwa są prawdopodobnie bardzo podobny pod względem kanalizacji - w końcu { y0, y1, y2 }po prostu skończyć się wpisy w wewnętrznym __dict__z ReturnValue.

Istnieje jednak jedna dodatkowa funkcja zapewniana przez Python dla małych obiektów, __slots__atrybut. Klasa może być wyrażona jako:

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

Z podręcznika Python Reference Manual :

__slots__Deklaracja wykonuje sekwencję zmiennych instancji i rezerwy tylko wystarczająco dużo miejsca w każdym przypadku do przechowywania wartości dla każdej zmiennej. Miejsce jest oszczędzane, ponieważ __dict__nie jest tworzone dla każdej instancji.

Opcja: Korzystanie z klasy danych (Python 3.7+)

Korzystając z nowych klas danych Python 3.7, zwróć klasę z automatycznie dodanymi specjalnymi metodami, pisaniem i innymi przydatnymi narzędziami:

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

Opcja: za pomocą listy

Kolejna sugestia, którą przeoczyłem, pochodzi od Billa Jaszczurki:

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

Jest to jednak moja najmniej ulubiona metoda. Przypuszczam, że jestem skażony kontaktem z Haskellem, ale pomysł list mieszanych zawsze był dla mnie niewygodny. W tym konkretnym przykładzie lista nie jest mieszana, ale możliwe, że tak.

Tak użyta lista tak naprawdę nic nie zyskuje w stosunku do krotki. Jedyną prawdziwą różnicą między listami a krotkami w Pythonie jest to, że listy są zmienne , a krotki nie.

Osobiście mam tendencję do przenoszenia konwencji z programowania funkcjonalnego: używam list dla dowolnej liczby elementów tego samego typu, a krotek dla stałej liczby elementów z góry określonych typów.

Pytanie

Po długiej preambule pojawia się nieuniknione pytanie. Która metoda (według Ciebie) jest najlepsza?


10
W twoich doskonałych przykładach używasz zmiennej y3, ale jeśli y3 nie zostanie zadeklarowane jako globalne, może to dać NameError: global name 'y3' is not definedpo prostu użycie 3?
hetepeperfan

11
Wiele świetnych pytań z doskonałymi odpowiedziami jest zamkniętych, ponieważ pojawia się słowo kluczowe „opinia”. Można argumentować, że całe pisemne zgłoszenie zastrzeżeń opiera się na opinii, ale jest ona oparta na faktach, referencjach i specjalistycznej wiedzy specjalistycznej. To, że ktoś pyta „co według ciebie jest najlepsze”, nie oznacza, że ​​prosi o osobiste opinie wyodrębnione z faktów, referencji i specjalistycznej wiedzy z prawdziwego świata. Niemal na pewno proszą o taką właśnie opinię, taką opartą dokładnie i udokumentowaną faktami, referencjami i konkretną wiedzą fachową, z jakiej osoba ta sformułowała opinię.
NeilG

@hetepeperfan nie trzeba zmieniać 3, ani też nie definiuje y3 w globalnym, możesz również użyć lokalnej nazwy y3, która również wykona tę samą pracę.
okie

Odpowiedzi:


637

W tym celu dodano 2.6 nazwane krotki . Zobacz także os.stat, aby zobaczyć podobny wbudowany przykład.

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

W najnowszych wersjach Pythona 3 (wydaje mi się, że wersja 3.6+) nowa typingbiblioteka ma NamedTupleklasę, dzięki której krotki nazwane są łatwiejsze do tworzenia i mają większą moc. Dziedziczenie z typing.NamedTuplepozwala używać ciągów dokumentów, wartości domyślnych i adnotacji.

Przykład (z dokumentów):

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

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

68
To tylko poprawna odpowiedź, ponieważ jest to jedyna struktura kanoniczna, której OP nie wziął pod uwagę i ponieważ rozwiązuje on jego problem zarządzania długimi krotkami. Powinny być oznaczone jako zaakceptowane.
nalot lotniczy

7
Cóż, uzasadnieniem projektu namedtuplejest mniejszy ślad pamięci dla wyników masowych (długie listy krotek, takie jak wyniki zapytań DB). W przypadku pojedynczych elementów (jeśli dana funkcja nie jest często wywoływana) słowniki i klasy również są w porządku. Ale imiona nazw są również ładniejszym / ładniejszym rozwiązaniem w tym przypadku.
Lutz Prechelt

8
@wom: Nie rób tego. Python nie stara się ujednolicić namedtupledefinicji (każde wywołanie tworzy nową), tworzenie namedtupleklasy jest stosunkowo drogie zarówno pod względem procesora, jak i pamięci, a wszystkie definicje klas z natury wiążą się z cyklicznymi odwołaniami (tak więc na CPython czekasz na cykliczne uruchamianie GC do ich zwolnienia). Uniemożliwia to pickleklasie (i dlatego multiprocessingw większości przypadków nie można używać instancji ). Każde stworzenie klasy na moim 3.6.4 x64 zużywa ~ 0,337 ms i zajmuje nieco mniej niż 1 KB pamięci, zabijając wszelkie oszczędności instancji.
ShadowRanger

3
Zauważę, że Python 3.7 poprawił szybkość tworzenia nowych namedtupleklas ; koszty procesora spadają około czterokrotnie , ale wciąż są około 1000 razy wyższe niż koszty utworzenia instancji, a koszt pamięci dla każdej klasy pozostaje wysoki (myliłem się w moim ostatnim komentarzu na temat „poniżej 1 KB” dla klasy _sourcesam w sobie wynosi zwykle 1,5 KB; _sourcejest usuwany w 3.7, więc prawdopodobnie jest bliższy pierwotnemu twierdzeniu wynoszącemu nieco poniżej 1 KB na stworzenie klasy).
ShadowRanger

4
@SergeStroobandt - To część standardowej biblioteki, a nie wbudowana. Nie musisz się martwić, że może nie zostać zainstalowany w innym systemie z Pythonem> = 2.6. A może sprzeciwiasz się dodatkowej linii kodu?
Justin,

234

W przypadku małych projektów najłatwiej jest pracować z krotkami. Kiedy staje się to zbyt trudne do zarządzania (i nie wcześniej), zaczynam grupować rzeczy w logiczne struktury, jednak myślę, że sugerowane użycie słowników i ReturnValueobiektów jest nieprawidłowe (lub zbyt uproszczone).

Wracając do słownika za pomocą klawiszy "y0", "y1", "y2", itd. Nie daje żadnej przewagi nad krotek. Wracając do ReturnValuewystąpienia o właściwościach .y0, .y1, .y2, itd. Nie daje żadnej przewagi nad krotek albo. Musisz zacząć nazywać rzeczy, jeśli chcesz dostać się gdziekolwiek i możesz to zrobić za pomocą krotek:

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

size, type, dimensions = get_image_data(x)

IMHO, jedyną dobrą techniką poza krotkami jest zwracanie prawdziwych obiektów za pomocą odpowiednich metod i właściwości, takich jak otrzymywane z re.match()lub open(file).


6
Pytanie - czy jest jakaś różnica między size, type, dimensions = getImageData(x)i (size, type, dimensions) = getImageData(x)? Tj. Czy zawijanie lewej strony zlecenia kroczącego robi jakąkolwiek różnicę?
Reb.Cabin

11
@ Reb.Cabin Nie ma różnicy. Krotka jest oznaczona przecinkiem, a nawiasy służą do grupowania różnych rzeczy. Na przykład (1)jest int czasem (1,)lub 1,krotką.
phil

19
Re „zwrócenie słownika z kluczami y0, y1, y2 itd. Nie daje żadnej przewagi nad krotkami”: słownik ma tę zaletę, że można dodawać pola do zwracanego słownika bez przerywania istniejącego kodu.
ostrokach

Re „zwrócenie słownika z kluczami y0, y1, y2 itd. Nie daje żadnej przewagi nad krotkami”: jest również bardziej czytelny i mniej podatny na błędy, gdy uzyskujesz dostęp do danych na podstawie jego nazwy, a nie pozycji.
Denis Dollfus

204

Wiele odpowiedzi sugeruje, że musisz zwrócić jakąś kolekcję, na przykład słownik lub listę. Możesz zrezygnować z dodatkowej składni i po prostu zapisać zwracane wartości rozdzielone przecinkami. Uwaga: technicznie zwraca to krotkę.

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

daje:

True
False

24
Nadal zwracasz kolekcję. To krotka. Wolę nawias, aby był bardziej wyraźny. Spróbuj tego: type(f())zwraca <class 'tuple'>.
Igor

20
@Igor: Nie ma powodu, aby tupleaspekt był wyraźny; to nie jest tak naprawdę ważne, że zwracasz a tuple, to jest idiom zwracania wielu wartości okresu. Ten sam powód, dla którego pomijasz pareny z idiomem wymiany x, y = y, x, wielokrotną inicjalizacją x, y = 0, 1itp .; Jasne, to sprawia, że tuples jest pod maską, ale nie ma powodu, aby to wyjaśniać, ponieważ tuplenie ma to w ogóle sensu. Samouczek języka Python wprowadza wiele zadań na długo zanim dotknie tuples.
ShadowRanger

@ShadowRanger dowolna sekwencja wartości oddzielona przecinkiem po prawej stronie =jest krotką w Pythonie z nawiasami wokół nich lub bez nich. Tak więc nie ma tutaj żadnego wyraźnego ani dorozumianego. a, b, c jest krotką tyle co (a, b, c). Po zwróceniu takich wartości nie ma też robienia krotki „pod maską”, ponieważ jest to zwykła krotka. OP wspomniał już o krotkach, więc tak naprawdę nie ma różnicy między tym, o czym wspomniał, a tym, co pokazuje ta odpowiedź. Brak
Ken4scholars

2
Jest to dosłownie pierwsza opcja sugerowana w pytaniu
endolith

1
@endolith dwóch razy facet zadaje pytanie ( „Jak mogę zwracać wiele wartości?” i „Jak się pan zwracać wiele wartości?”) są odbierane przez tę odpowiedź. Tekst pytania czasami się zmieniał. I to pytanie oparte na opiniach.
Joseph Hansen

74

Głosuję na słownik.

Uważam, że jeśli utworzę funkcję, która zwraca coś więcej niż 2-3 zmienne, złożę je w słowniku. W przeciwnym razie często zapominam o kolejności i treści zwrotów.

Ponadto wprowadzenie „specjalnej” struktury utrudnia śledzenie kodu. (Ktoś inny będzie musiał przeszukać kod, aby dowiedzieć się, co to jest)

Jeśli martwisz się wyszukiwaniem typu, użyj opisowych kluczy słownika, na przykład „lista wartości x”.

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

5
po wielu latach programowania dążę do tego, aby zawsze wymagana była struktura danych i funkcji. Najpierw funkcja, zawsze możesz refaktoryzować w razie potrzeby.
monkut

Jak uzyskalibyśmy wartości w słowniku bez wielokrotnego wywoływania funkcji? Na przykład, jeśli chcę użyć y1 i y3 w innej funkcji?
Matt

3
przypisz wyniki do osobnej zmiennej. result = g(x); other_function(result)
monkut

1
@monkut tak. W ten sposób można również przekazać wynik do kilku funkcji, które pobierają różne argumenty z wyniku, bez konieczności każdorazowego odwoływania się do określonych części wyniku.
Gnudiff

38

Inną opcją byłoby użycie generatorów:

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

Chociaż krotki IMHO są zwykle najlepsze, z wyjątkiem przypadków, w których zwracane wartości są kandydatami do enkapsulacji w klasie.


1
To wydaje się być najczystszym rozwiązaniem i ma czystą składnię. Czy są w tym jakieś wady? Jeśli nie wykorzystasz wszystkich zwrotów, czy istnieją „niewydane” zyski, które mogą cię skrzywdzić?
Jiminion

24
Może to być „czyste”, ale wcale nie wydaje się intuicyjne. Skąd ktoś, kto nigdy wcześniej nie spotkał się z tym wzorcem, wiedziałby, że uruchomienie automatycznego rozpakowywania krotek wywołałoby każde z nich yield?
coredumperror

1
@CoreDumpError, generatory to po prostu… generatory. Nie ma żadnej zewnętrznej różnicy między def f(x): …; yield b; yield a; yield rvs. (g for g in [b, a, r]), i oba będą łatwo konwertować na listy lub krotki i jako takie będą wspierać rozpakowywanie krotek. Generator krotek działa zgodnie z podejściem funkcjonalnym, podczas gdy forma funkcyjna jest niezbędna i umożliwia kontrolę przepływu i przypisywanie zmiennych.
sleblanc

30

Wolę używać krotek, gdy krotka wydaje się „naturalna”; współrzędne są typowym przykładem, w którym oddzielne obiekty mogą stać samodzielnie, np. w obliczeniach skalowania tylko w jednej osi, a kolejność jest ważna. Uwaga: jeśli mogę posortować lub przetasować przedmioty bez negatywnego wpływu na znaczenie grupy, prawdopodobnie nie powinienem używać krotki.

Używam słowników jako wartości zwracanej tylko wtedy, gdy zgrupowane obiekty nie zawsze są takie same. Pomyśl o opcjonalnych nagłówkach wiadomości e-mail.

W pozostałych przypadkach, w których zgrupowane obiekty mają wewnętrzne znaczenie w grupie lub potrzebny jest pełnoprawny obiekt z własnymi metodami, używam klasy.


29

Wolę:

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

Wydaje się, że wszystko inne to tylko dodatkowy kod do robienia tego samego.


22
Krotki są łatwiejsze do rozpakowania: y0, y1, y2 = g () z dyktatorem, który musisz zrobić: wynik = g () y0, y1, y2 = wynik.get ('y0'), wynik.get ('y1' ), result.get („y2”), co jest nieco brzydkie. Każde rozwiązanie ma swoje „plusy” i „minusy”.
Oli,

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

@edouard Nie, nie zwraca, zwraca krotkę, a nie listę.
Simon Hibbs

1
rozpad jest argumentem dla powracających list moim zdaniem
semiomant

21

Ogólnie rzecz biorąc, „specjalistyczna struktura” faktycznie jest rozsądnym bieżącym stanem obiektu, z jego własnymi metodami.

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

W miarę możliwości lubię szukać nazw anonimowych struktur. Znaczące nazwy wyjaśniają wszystko.


20

Krotki, dykty i obiekty Pythona oferują programistom płynny kompromis między formalnością a wygodą dla małych struktur danych („rzeczy”). Dla mnie wybór sposobu przedstawienia rzeczy podyktowany jest głównie tym, jak zamierzam użyć tej struktury. W C ++ powszechną konwencją jest stosowanie structelementów tylko do danych i classobiektów z metodami, nawet jeśli można legalnie umieścić metody na struct; mój nawyk jest podobny w Pythonie, z dicti tuplezamiast struct.

Dla zestawów współrzędnych użyję tupleraczej niż punktu classlub a dict(i zauważ, że możesz użyć tuplejako klucza słownika, więc dicts twórz świetne rzadkie tablice wielowymiarowe).

Jeśli mam zamiar iterować na liście rzeczy, wolę rozpakowywać tuples na iteracji:

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

... ponieważ wersja obiektu jest bardziej zagracona do czytania:

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

... nie mówiąc już o dict.

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

Jeśli rzecz jest szeroko używana i okaże się, że wykonujesz na niej podobne nietrywialne operacje w wielu miejscach kodu, zwykle warto uczynić z niej obiekt klasy za pomocą odpowiednich metod.

Wreszcie, jeśli zamierzam wymieniać dane z komponentami systemu innymi niż Python, najczęściej trzymam je w, dictponieważ najlepiej nadają się do serializacji JSON.


19

+1 na sugestii S.Lott o nazwie klasy kontenera.

W Pythonie 2.6 i nowszych nazwana krotka zapewnia użyteczny sposób łatwego tworzenia tych klas kontenerów, a wyniki są „lekkie i nie wymagają więcej pamięci niż zwykłe krotki”.


4

W językach takich jak Python zwykle używałbym słownika, ponieważ wiąże się to z mniejszym nakładem pracy niż tworzenie nowej klasy.

Jeśli jednak ciągle zwracam ten sam zestaw zmiennych, prawdopodobnie dotyczy to nowej klasy, którą uwzględnię.


4

Używałbym dict do przekazywania i zwracania wartości z funkcji:

Użyj zmiennej postaci zdefiniowanej w formularzu .

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}

To dla mnie i dla procesora najbardziej efektywny sposób.

Musisz przekazać tylko jeden wskaźnik i zwrócić tylko jeden wskaźnik.

Nie musisz zmieniać argumentów funkcji (tysiące z nich) za każdym razem, gdy wprowadzasz zmiany w kodzie.


Dykty są zmienne. Jeśli przekażesz dyktę do funkcji i ta funkcja edytuje dykt, zmiany zostaną odzwierciedlone poza zakresem tej funkcji. Gdy funkcja zwróci dykt na końcu, może to oznaczać, że funkcja nie ma skutków ubocznych, dlatego wartość nie powinna być zwracana, co wyjaśnia, że testbezpośrednio zmodyfikuje wartość. Porównaj to z dict.update, co nie zwraca wartości.
sleblanc

@ sleblanc „Zwrócenie funkcji na końcu może oznaczać, że funkcja nie ma skutków ubocznych”. Nie oznacza to, że, jak powiedziałeś, dyktat jest zmienny. Jednak zwrot formnie szkodzi czytelności ani wydajności. W przypadkach, w których może być konieczne ponowne sformatowanie form, zwrócenie go [formularz] gwarantuje, że ostatni formzostanie zwrócony, ponieważ nigdzie nie będziesz śledzić zmian formularza.
Elis Byberi

3

„Najlepsza” to decyzja częściowo subiektywna. Użyj krotek do małych zestawów zwrotnych w ogólnym przypadku, w którym niezmienne jest dopuszczalne. Krotka jest zawsze lepsza niż lista, gdy zmienność nie jest wymagana.

W przypadku bardziej złożonych wartości zwracanych lub w przypadku, gdy formalność jest cenna (tj. Kod o wysokiej wartości), nazwana krotka jest lepsza. W najbardziej skomplikowanym przypadku obiekt jest zwykle najlepszy. Jednak tak naprawdę liczy się sytuacja. Jeśli zwrócenie obiektu ma sens, ponieważ naturalnie to masz na końcu funkcji (np. Wzorzec fabryczny), zwróć obiekt.

Jak powiedział mędrzec:

Przedwczesna optymalizacja jest źródłem wszelkiego zła (a przynajmniej większości) w programowaniu.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.