Kanonickým spôsobom vrátenia viacerých hodnôt v jazykoch, ktoré to podporujú, je často tupling.
Zoberme si tento triviálny príklad:
def f(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return (y0, y1, y2)
Tento postup sa však rýchlo stáva problematickým, keď sa zvyšuje počet vrátených hodnôt. Čo ak chcete vrátiť štyri alebo päť hodnôt? Iste, mohli by ste ich ďalej tuplovať, ale ľahko by ste zabudli, ktorá hodnota je kde. Takisto je dosť škaredé ich rozbaľovať, kamkoľvek ich chcete dostať.
Ďalším logickým krokom sa zdá byť zavedenie nejakého 'zápisu záznamu'. V jazyku Python je zrejmým spôsobom, ako to urobiť, použitie slovníka dict
.
Uvažujme nasledujúce:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0': y0, 'y1': y1 ,'y2': y2}
(Aby bolo jasné, y0, y1 a y2 sú myslené len ako abstraktné identifikátory. Ako bolo zdôraznené, v praxi by ste použili zmysluplné identifikátory.)
Teraz máme mechanizmus, pomocou ktorého môžeme premietnuť konkrétny člen vráteného objektu. Napríklad,
result['y0']
Existuje však aj iná možnosť. Namiesto toho by sme mohli vrátiť špecializovanú štruktúru. Uviedol som to v kontexte jazyka Python, ale som si istý, že to platí aj pre iné jazyky. Ak by ste pracovali v jazyku C, mohla by to byť skutočne jediná možnosť. Takto:
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)
V jazyku Python sú predchádzajúce dve možnosti možno veľmi podobné z hľadiska inštalácie - veď { y0, y1, y2 }
sú nakoniec len položky v internom __dict__
ReturnValue
.
Pre drobné objekty však Python poskytuje jednu dodatočnú funkciu, atribút __slots__
. Trieda by mohla byť vyjadrená ako:
class ReturnValue(object):
__slots__ = ["y0", "y1", "y2"]
def __init__(self, y0, y1, y2):
self.y0 = y0
self.y1 = y1
self.y2 = y2
Z Referenčnej príručky jazyka Python:
Deklarácia __slots__
preberá postupnosť inštančných premenných a v každej inštancii vyhradzuje práve toľko miesta, aby sa do nej zmestila hodnota pre každú premennú. Miesto sa ušetrí, pretože __dict__
sa nevytvára pre každú inštanciu.
Použitie nových dátových tried Pythonu 3.7'vráti triedu s automaticky pridanými špeciálnymi metódami, typovaním a ďalšími užitočnými nástrojmi:
@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)
Ďalší návrh, ktorý som prehliadol, pochádza od Billa the Lizarda:
def h(x):
result = [x + 1]
result.append(x * 3)
result.append(y0 ** y3)
return result
Toto je však moja najmenej obľúbená metóda. Asi som poznačený skúsenosťami s Haskellom, ale myšlienka zmiešaných typových zoznamov mi vždy bola nepríjemná. V tomto konkrétnom príklade zoznam nie je zmiešaného typu, ale pravdepodobne by mohol byť.
Takto použitý zoznam naozaj nič nezískava vzhľadom na tuple, pokiaľ viem. Jediný skutočný rozdiel medzi zoznamami a tuplami v jazyku Python je ten, že zoznamy sú mutovateľné, zatiaľ čo tuply nie.
Ja osobne sa prikláňam k tomu, aby som preniesol konvencie z funkcionálneho programovania: zoznamy používam pre ľubovoľný počet prvkov rovnakého typu a tuply pre pevný počet prvkov vopred určených typov.
Po dlhšej úvodnej časti prichádza nevyhnutná otázka. Ktorá metóda (podľa vás) je najlepšia?
Zvyčajne som sa rozhodol pre slovníkovú metódu, pretože si vyžaduje menej práce s prípravou. Z hľadiska typov však možno bude lepšie ísť cestou triedy, pretože vám to môže pomôcť vyhnúť sa zámene toho, čo slovník predstavuje.
Na druhej strane, v komunite Pythonu sú ľudia, ktorí sa domnievajú, že implicitné rozhrania by sa mali uprednostňovať pred explicitnými rozhraniami, pričom v takom prípade typ objektu naozaj nie je dôležitý, pretože sa v podstate spoliehate na konvenciu, že rovnaký atribút bude mať vždy rovnaký význam.
Ako teda v Pythone vrátite viacero hodnôt?
Všeobecne platí, že "špecializovaná štruktúra" v skutočnosti JE rozumný aktuálny stav objektu s vlastnými metódami.
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
Rád hľadám názvy pre anonymné štruktúry, kde je to možné. Významné názvy robia veci prehľadnejšími.
V jazykoch, ako je Python, by som zvyčajne použil slovník, pretože to znamená menšiu réžiu ako vytváranie novej triedy.
Ak však zistím, že neustále vraciam tú istú sadu premenných, potom to pravdepodobne zahŕňa novú triedu, ktorú budem vylučovať.