Comment renvoyer plusieurs valeurs à partir d'une fonction ?

La manière canonique de renvoyer des valeurs multiples dans les langages qui la prennent en charge est souvent le [tupling][1].

Option #### : Utiliser un tuple Considérons cet exemple trivial :

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

Cependant, cela devient rapidement problématique lorsque le nombre de valeurs renvoyées augmente. Que faire si vous voulez renvoyer quatre ou cinq valeurs ? Bien sûr, vous pouvez continuer à les regrouper, mais il est facile d'oublier quelle valeur se trouve à quel endroit. De plus, il est plutôt laid de les déballer là où vous voulez les recevoir.

Option : Utiliser un dictionnaire

La prochaine étape logique semble être d'introduire une sorte de 'notation d'enregistrement&#39 ;. En Python, la façon la plus évidente de le faire est d'utiliser un dict.

Prenons l'exemple suivant :

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

(Pour être clair, y0, y1 et y2 ne sont que des identifiants abstraits. Comme indiqué, en pratique, vous utiliserez des identifiants significatifs).

Maintenant, nous avons un mécanisme par lequel nous pouvons projeter un membre particulier de l'objet retourné. Par exemple,

result['y0']

Option : Utiliser une classe

Cependant, il existe une autre option. Nous pourrions plutôt retourner une structure spécialisée. J'ai présenté cela dans le contexte de Python, mais je suis sûr que cela s'applique également à d'autres langages. En effet, si vous travaillez en C, cela pourrait très bien être votre seule option. C'est parti :

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)

En Python, les deux précédentes sont peut-être très similaires en termes de plomberie - après tout { y0, y1, y2 } finissent juste par être des entrées dans le __dict__ interne de la ReturnValue.

Il y a cependant une fonctionnalité supplémentaire fournie par Python pour les petits objets, l'attribut __slots__. La classe pourrait être exprimée comme suit :

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

Extrait du [Manuel de référence de Python][2] :

La déclaration __slots__ prend une séquence de variables d'instance et réserve juste assez d'espace dans chaque instance pour contenir une valeur pour chaque variable. L'espace est économisé car __dict__ n'est pas créé pour chaque instance.

Option #### : Utiliser une [dataclass][3] (Python 3.7+)

En utilisant les nouvelles dataclasses de Python 3.7, renvoyez une classe avec des méthodes spéciales ajoutées automatiquement, un typage et d'autres outils utiles :

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

Option : Utilisation d'une liste

Une autre suggestion que j&#8217avais négligée vient de Bill le Lézard :

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

C'est cependant la méthode que je préfère le moins. Je suppose que mon exposition à Haskell a pu m'influencer, mais l'idée de listes de types mixtes m'a toujours mis mal à l'aise. Dans cet exemple particulier, la liste n'est -pas- de type mixte, mais il est concevable qu'elle puisse l'être.

Pour autant que je sache, une liste utilisée de cette manière ne gagne rien par rapport au tuple. La seule véritable différence entre les listes et les tuples en Python est que les listes sont [mutables][4], alors que les tuples ne le sont pas.

Personnellement, j'ai tendance à reprendre les conventions de la programmation fonctionnelle : utiliser des listes pour un nombre quelconque d'éléments du même type, et des tuples pour un nombre fixe d'éléments de types prédéterminés.

Question

Après ce long préambule, vient l'inévitable question. Quelle est (selon vous) la meilleure méthode ?

J&#8217ai généralement choisi la méthode du dictionnaire parce qu&#8217elle implique moins de travail de mise en place. Cependant, d'un point de vue typographique, il est peut-être préférable d'opter pour la méthode des classes, car cela peut vous éviter de confondre ce que représente un dictionnaire.

D'un autre côté, certains membres de la communauté Python pensent que [les interfaces implicites devraient être préférées aux interfaces explicites][5], auquel cas le type de l'objet n'est pas vraiment pertinent, puisque vous vous fiez essentiellement à la convention selon laquelle le même attribut aura toujours la même signification.

Alors, comment renvoyer plusieurs valeurs en Python ?

[1] : https://stackoverflow.com/questions/38508/whats-the-best-way-to-return-multiple-values-from-a-function-in-python [2] : http://www.network-theory.co.uk/docs/pylang/__slots__.html [3] : https://docs.python.org/3/library/dataclasses.html [4] : http://docs.python.org/library/stdtypes.html#typesseq-mutable [5] : http://www.canonical.org/~kragen/isinstance/

Je préfère :

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

Il semble que tout le reste ne soit que du code supplémentaire pour faire la même chose.

Commentaires (1)

En général, la "structure spécialisée" EST en fait un état actuel sensible d'un objet, avec ses propres méthodes.

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

J'aime trouver des noms pour les structures anonymes quand c'est possible. Des noms significatifs rendent les choses plus claires.

Commentaires (0)

Dans des langages comme Python, j'utilise généralement un dictionnaire car cela implique moins de frais que la création d'une nouvelle classe.

Cependant, si je me retrouve à renvoyer constamment le même ensemble de variables, cela implique probablement une nouvelle classe que je ne prendrai pas en compte.

Commentaires (0)