De canonieke manier om meerdere waarden terug te geven in talen die dat ondersteunen is vaak tupling.
Beschouw dit triviaal voorbeeld:
def f(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return (y0, y1, y2)
Dit wordt echter al snel problematisch als het aantal geretourneerde waarden toeneemt. Wat als je vier of vijf waarden wilt teruggeven? Zeker, je zou ze kunnen blijven tukelen, maar het wordt makkelijk om te vergeten welke waarde waar is. Het'is ook nogal lelijk om ze uit te pakken waar je ze wilt ontvangen.
De volgende logische stap lijkt te zijn om een soort 'record notatie' te introduceren. In Python is de voor de hand liggende manier om dit te doen door middel van een dict
.
Beschouw het volgende:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0': y0, 'y1': y1 ,'y2': y2}
(Voor alle duidelijkheid, y0, y1, en y2 zijn slechts bedoeld als abstracte identifiers. Zoals gezegd, in de praktijk zou je'zinvolle identifiers gebruiken).
Nu hebben we een mechanisme waarmee we een bepaald lid van het teruggegeven object kunnen projecteren. Bijvoorbeeld,
result['y0']
Er is echter nog een andere optie. We kunnen in plaats daarvan een gespecialiseerde structuur teruggeven. Ik'heb dit in de context van Python geplaatst, maar ik'ben er zeker van dat het ook voor andere talen geldt. Inderdaad, als je in C zou werken zou dit heel goed je enige optie kunnen zijn. Hier gaat het:
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)
In Python zijn de vorige twee misschien erg vergelijkbaar in termen van loodgieterswerk - tenslotte zijn { y0, y1, y2 }
gewoon ingangen in het interne __dict__
van de ReturnValue
.
Python biedt echter nog een extra mogelijkheid voor kleine objecten, het __slots__
attribuut. De klasse kan worden uitgedrukt als:
class ReturnValue(object):
__slots__ = ["y0", "y1", "y2"]
def __init__(self, y0, y1, y2):
self.y0 = y0
self.y1 = y1
self.y2 = y2
Uit het Python Referentie Handboek:
De
__slots__
declaratie neemt een reeks instance variabelen en reserveert in elke instance net genoeg ruimte om voor elke variabele een waarde te bevatten. Er wordt ruimte bespaard omdat__dict__
niet voor elke instantie wordt aangemaakt.
Met behulp van Python 3.7's nieuwe dataklassen, geef je een klasse terug met automatisch toegevoegde speciale methoden, typering en andere handige hulpmiddelen:
@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)
Een andere suggestie die ik over het hoofd had gezien, komt van Bill the Lizard:
def h(x):
result = [x + 1]
result.append(x * 3)
result.append(y0 ** y3)
return result
Dit is echter mijn minst favoriete methode. Ik veronderstel dat ik besmet ben door blootstelling aan Haskell, maar het idee van mixed-type lijsten heeft me altijd ongemakkelijk gevoeld. In dit specifieke voorbeeld is de lijst -niet- van het gemengde type, maar het zou wel kunnen.
Een lijst die op deze manier wordt gebruikt wint er echt niets bij ten opzichte van de tuple, voor zover ik kan nagaan. Het enige echte verschil tussen lijsten en tuples in Python is dat lijsten mutable zijn, terwijl tuples dat niet zijn.
Persoonlijk ben ik geneigd de conventies van functioneel programmeren over te nemen: gebruik lijsten voor een willekeurig aantal elementen van hetzelfde type, en tuples voor een vast aantal elementen van vooraf bepaalde types.
Na de lange preambule komt de onvermijdelijke vraag. Welke methode is (volgens jou) de beste?
Ik ga meestal voor de woordenboek-route, omdat die minder werk met zich meebrengt. Vanuit het oogpunt van de types zou het echter beter kunnen zijn de klassen-route te volgen, omdat dat kan helpen verwarring te voorkomen over wat een woordenboek voorstelt.
Anderzijds zijn er sommigen in de Python gemeenschap die vinden dat impliciete interfaces de voorkeur moeten krijgen boven expliciete interfaces, op welk punt het type van het object echt niet relevant is, omdat je in principe vertrouwt op de conventie dat hetzelfde attribuut altijd dezelfde betekenis zal hebben.
Dus, hoe geef je -je- meerdere waarden terug in Python?
Ik heb liever:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0':y0, 'y1':y1 ,'y2':y2 }
Het lijkt erop dat al het andere alleen maar extra code is om hetzelfde te doen.
In het algemeen IS de "gespecialiseerde structuur" eigenlijk een zinnige huidige toestand van een object, met zijn eigen methoden.
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
Ik vind het leuk om namen te vinden voor anonieme structuren waar mogelijk. Betekenisvolle namen maken dingen duidelijker.
In talen als Python zou ik meestal een dictionary gebruiken, omdat dat minder overhead met zich meebrengt dan het creëren van een nieuwe klasse.
Echter, als ik merk dat ik steeds dezelfde set variabelen terugzend, dan komt daar waarschijnlijk een nieuwe klasse bij kijken die ik er uit zal filteren.