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?
źródło
y3
, ale jeśli y3 nie zostanie zadeklarowane jako globalne, może to daćNameError: global name 'y3' is not defined
po prostu użycie3
?y3
, która również wykona tę samą pracę.Odpowiedzi:
W tym celu dodano 2.6 nazwane krotki . Zobacz także os.stat, aby zobaczyć podobny wbudowany przykład.
W najnowszych wersjach Pythona 3 (wydaje mi się, że wersja 3.6+) nowa
typing
biblioteka maNamedTuple
klasę, dzięki której krotki nazwane są łatwiejsze do tworzenia i mają większą moc. Dziedziczenie ztyping.NamedTuple
pozwala używać ciągów dokumentów, wartości domyślnych i adnotacji.Przykład (z dokumentów):
źródło
namedtuple
jest 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.namedtuple
definicji (każde wywołanie tworzy nową), tworzenienamedtuple
klasy 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 topickle
klasie (i dlategomultiprocessing
w 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.namedtuple
klas ; 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_source
sam w sobie wynosi zwykle 1,5 KB;_source
jest usuwany w 3.7, więc prawdopodobnie jest bliższy pierwotnemu twierdzeniu wynoszącemu nieco poniżej 1 KB na stworzenie klasy).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
ReturnValue
obiektó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 doReturnValue
wystą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: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()
lubopen(file)
.źródło
size, type, dimensions = getImageData(x)
i(size, type, dimensions) = getImageData(x)
? Tj. Czy zawijanie lewej strony zlecenia kroczącego robi jakąkolwiek różnicę?(1)
jest int czasem(1,)
lub1,
krotką.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ę.
daje:
źródło
type(f())
zwraca<class 'tuple'>
.tuple
aspekt był wyraźny; to nie jest tak naprawdę ważne, że zwracasz atuple
, to jest idiom zwracania wielu wartości okresu. Ten sam powód, dla którego pomijasz pareny z idiomem wymianyx, y = y, x
, wielokrotną inicjalizacjąx, y = 0, 1
itp .; Jasne, to sprawia, żetuple
s jest pod maską, ale nie ma powodu, aby to wyjaśniać, ponieważtuple
nie ma to w ogóle sensu. Samouczek języka Python wprowadza wiele zadań na długo zanim dotknietuple
s.=
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ź. BrakGł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”.
źródło
result = g(x); other_function(result)
Inną opcją byłoby użycie generatorów:
Chociaż krotki IMHO są zwykle najlepsze, z wyjątkiem przypadków, w których zwracane wartości są kandydatami do enkapsulacji w klasie.
źródło
yield
?def f(x): …; yield b; yield a; yield r
vs.(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.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.
źródło
Wolę:
Wydaje się, że wszystko inne to tylko dodatkowy kod do robienia tego samego.
źródło
źródło
Ogólnie rzecz biorąc, „specjalistyczna struktura” faktycznie jest rozsądnym bieżącym stanem obiektu, z jego własnymi metodami.
W miarę możliwości lubię szukać nazw anonimowych struktur. Znaczące nazwy wyjaśniają wszystko.
źródło
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
struct
elementów tylko do danych iclass
obiektów z metodami, nawet jeśli można legalnie umieścić metody nastruct
; mój nawyk jest podobny w Pythonie, zdict
ituple
zamiaststruct
.Dla zestawów współrzędnych użyję
tuple
raczej niż punktuclass
lub adict
(i zauważ, że możesz użyćtuple
jako klucza słownika, więcdict
s twórz świetne rzadkie tablice wielowymiarowe).Jeśli mam zamiar iterować na liście rzeczy, wolę rozpakowywać
tuple
s na iteracji:... ponieważ wersja obiektu jest bardziej zagracona do czytania:
... nie mówiąc już o
dict
.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,
dict
ponieważ najlepiej nadają się do serializacji JSON.źródło
+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”.
źródło
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ę.
źródło
Używałbym dict do przekazywania i zwracania wartości z funkcji:
Użyj zmiennej postaci zdefiniowanej w formularzu .
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.
źródło
test
bezpośrednio zmodyfikuje wartość. Porównaj to zdict.update
, co nie zwraca wartości.form
nie szkodzi czytelności ani wydajności. W przypadkach, w których może być konieczne ponowne sformatowanieform
, zwrócenie go [formularz] gwarantuje, że ostatniform
zostanie zwrócony, ponieważ nigdzie nie będziesz śledzić zmian formularza.„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:
źródło