Hogyan adhatok vissza több értéket egy függvényből?

A többszörös értékek visszaadásának kanonikus módja az ezt támogató nyelvekben gyakran a tupling.

Lehetőség: Tuple használata

Tekintsük ezt a triviális példát:

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

Ez azonban gyorsan problémássá válik, ahogy a visszaadott értékek száma növekszik. Mi van akkor, ha négy vagy öt értéket szeretne visszaadni? Persze, folytathatod a többszörözést, de könnyen elfelejted, hogy melyik érték hol van. Ráadásul elég csúnya dolog kicsomagolni őket oda, ahová kapni akarod őket.

Lehetőség: Szótár használata

A következő logikus lépésnek tűnik valamilyen 'rekordjegyzetelés' bevezetése. Pythonban ennek kézenfekvő módja a dict.

Tekintsük a következőt:

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

(Csak hogy világos legyen, y0, y1 és y2 csak absztrakt azonosítóként értendő. Mint rámutattunk, a gyakorlatban értelmes azonosítókat használnánk).

Most már van egy olyan mechanizmusunk, amellyel kivetíthetjük a visszaadott objektum egy adott tagját. Például,

result['y0']

Lehetőség: Osztály használata

Van azonban egy másik lehetőség is. Ehelyett visszaadhatunk egy speciális struktúrát. Ezt a Python kontextusában fogalmaztam meg, de biztos vagyok benne, hogy más nyelvekre is alkalmazható. Sőt, ha C nyelven dolgoznánk, akkor ez lehet az egyetlen lehetőségünk. Itt is van:

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)

Pythonban az előző kettő talán nagyon hasonló a vízvezeték-szerelés szempontjából - elvégre { y0, y1, y2 } végül is csak a ReturnValue belső __dict__ bejegyzései.

Van azonban egy további funkció, amit a Python biztosít az apró objektumok számára, a __slots__ attribútum. Az osztály kifejezhető így:

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

A Python Reference Manual című kézikönyvből:

A __slots__ deklaráció egy sor példányváltozót vesz fel, és minden példányban csak annyi helyet foglal el, hogy minden egyes változónak egy értéket tudjon tárolni. A helyet azért takarítjuk meg, mert a __dict__ nem jön létre minden egyes példányhoz.

Opció: dataclass használata (Python 3.7+)

A Python 3.7'új dataclassok használatával egy olyan osztályt adhatunk vissza, amely automatikusan hozzáadott speciális metódusokkal, tipizálással és egyéb hasznos eszközökkel rendelkezik:

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

Opció: ### Opció: Egy lista használata

Egy másik javaslat, amit elnéztem, Bill the Lizardtól származik:

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

Bár ez a legkevésbé kedvenc módszerem. Gondolom, a Haskellnek való kitettségem miatt megfertőződtem, de a vegyes típusú listák ötlete mindig is kényelmetlen volt számomra. Ebben a konkrét példában a lista -nem- vegyes típusú, de elképzelhető, hogy lehetne.

Egy ilyen módon használt lista tényleg nem nyer semmit a tuple-hez képest, amennyire meg tudom ítélni. Az egyetlen valódi különbség a listák és a tuple-ok között Pythonban az, hogy a listák mutable, míg a tuple-ok nem.

Én személy szerint hajlamos vagyok átvenni a funkcionális programozásból származó konvenciókat: listákat használok tetszőleges számú azonos típusú elemre, és tuple-okat előre meghatározott típusú elemek fix számú elemére.

Kérdés

A hosszas bevezető után jön az elkerülhetetlen kérdés. Melyik módszer (szerinted) a legjobb?

Én jellemzően a szótári útvonalat választottam, mert ez kevesebb beállítási munkával jár. A típusok szempontjából azonban lehet, hogy jobban jársz az osztályos útvonalon, mivel így elkerülheted, hogy összekeveredjen, hogy mit képvisel egy szótár.

Másrészt vannak a Python közösségben olyanok, akik úgy gondolják, hogy az explicit interfészekkel szemben előnyben kell részesíteni a hallgatólagos interfészeket, és ekkor az objektum típusa nem igazán releváns, mivel alapvetően arra a konvencióra támaszkodsz, hogy ugyanaz az attribútum mindig ugyanazt a jelentést fogja jelenteni.

Szóval, hogyan adhatsz vissza több értéket Pythonban?

Jobban szeretem:

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

Úgy tűnik, hogy minden más csak extra kód ugyanarra a célra.

Kommentárok (1)

Általában a "specializált struktúra" valójában egy objektum értelmes aktuális állapota, saját metódusokkal.

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

Szeretek neveket találni névtelen struktúráknak, ahol csak lehet. A jelentéses nevek egyértelműbbé teszik a dolgokat.

Kommentárok (0)

Az olyan nyelveken, mint a Python, általában szótárat használok, mivel ez kevesebb többletköltséggel jár, mint egy új osztály létrehozása.

Ha azonban úgy találom magam, hogy folyamatosan ugyanazt a változóhalmazt adom vissza, akkor ez valószínűleg egy új osztályt jelent, amelyet én'll faktorálok.

Kommentárok (0)