Hvordan returnerer jeg flere værdier fra en funktion?

Den kanoniske måde at returnere flere værdier på i sprog, der understøtter det, er ofte tupling.

Mulighed: Brug af en tuple

Overvej dette trivielle eksempel:

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

Dette bliver dog hurtigt problematisk, når antallet af returnerede værdier stiger. Hvad hvis du ønsker at returnere fire eller fem værdier? Selvfølgelig kan du blive ved med at tuple dem, men det bliver let at glemme, hvilken værdi der er hvor. Det er også ret grimt at pakke dem ud, hvor du vil modtage dem.

Mulighed: Brug af en ordbog

Det næste logiske skridt synes at være at indføre en slags 'record notation'. I Python er den oplagte måde at gøre dette på ved hjælp af en dict.

Overvej følgende:

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

(For at det skal være klart, er y0, y1 og y2 blot ment som abstrakte identifikatorer. Som påpeget vil man i praksis bruge meningsfulde identifikatorer).

Nu har vi en mekanisme, hvormed vi kan projicere et bestemt medlem af det returnerede objekt ud. For eksempel,

result['y0']

Mulighed: Brug af en klasse

Der er dog en anden mulighed. Vi kunne i stedet returnere en specialiseret struktur. Jeg har formuleret dette i forbindelse med Python, men jeg er sikker på, at det også gælder for andre sprog. Hvis du arbejdede i C, kunne dette faktisk meget vel være din eneste mulighed. Here goes:

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)

I Python ligner de to foregående måske meget hinanden med hensyn til lodtrækning - når alt kommer til alt er { y0, y1, y2 } bare poster i den interne __dict__ af ReturnValue.

Der er dog en ekstra funktion, som Python tilbyder for små objekter, nemlig __slots__-attributten. Klassen kan udtrykkes som:

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

Fra Python Reference Manual:

__slots__-deklarationen tager en sekvens af instansvariabler og reserverer lige nok plads i hver instans til at indeholde en værdi for hver variabel. Der spares plads, fordi __dict__ ikke oprettes for hver instans.

Mulighed: Brug af en dataklasse (Python 3.7+)

Brug Python 3.7's nye dataklasser, returner en klasse med automatisk tilføjede specielle metoder, typning og andre nyttige værktøjer:

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

Mulighed: Brug af en liste

Et andet forslag, som jeg havde overset, kommer fra Bill the Lizard:

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

Dette er dog min mindst foretrukne metode. Jeg formoder at jeg er smittet af udsættelse for Haskell, men ideen om mixed-type lister har altid føltes ubehagelig for mig. I dette eksempel er listen -ikke- af blandet type, men det kunne den muligvis være.

En liste, der bruges på denne måde, vinder ikke rigtig noget i forhold til tuplen, så vidt jeg kan se. Den eneste reelle forskel mellem lister og tupler i Python er, at lister er mutable, mens tupler ikke er det.

Personligt har jeg en tendens til at overføre konventionerne fra funktionel programmering: brug lister til et vilkårligt antal elementer af samme type og tupler til et fast antal elementer af forudbestemte typer.

Spørgsmål

Efter den lange præambel kommer det uundgåelige spørgsmål. Hvilken metode (synes du) er bedst?

Jeg har typisk valgt ordbogsmetoden, fordi den indebærer mindre opstillingsarbejde. Set ud fra et typeperspektiv er det dog måske bedre at gå klasse-ruten, da det kan hjælpe dig med at undgå at forvirre, hvad en ordbog repræsenterer.

På den anden side er der nogle i Python-fællesskabet, der mener, at implicitte interfaces bør foretrækkes frem for eksplicitte interfaces, hvor objektets type i virkeligheden ikke er relevant, da du dybest set stoler på konventionen om, at den samme attribut altid vil have samme betydning.

Så hvordan returnerer man -du- flere værdier i Python?

Jeg foretrækker:

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

Det ser ud til, at alt andet bare er ekstra kode til at gøre det samme.

Kommentarer (1)

Generelt er "specialiseret struktur" faktisk en fornuftig aktuel tilstand for et objekt med sine egne metoder.

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

Jeg kan godt lide at finde navne til anonyme strukturer, hvor det er muligt. Meningsfulde navne gør tingene mere overskuelige.

Kommentarer (0)

I sprog som Python ville jeg normalt bruge en ordbog, da det medfører mindre overhead end at oprette en ny klasse.

Men hvis jeg finder mig selv konstant returnerer det samme sæt variabler, så involverer det sandsynligvis en ny klasse, som jeg vil udelade.

Kommentarer (0)