Question Comment renvoyez-vous plusieurs valeurs en Python?


La manière canonique de renvoyer plusieurs valeurs dans les langues qui le supportent est souvent tupling.

Option: Utilisation d'un tuple

Considérez 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 à mesure que le nombre de valeurs renvoyées augmente. Que faire si vous voulez retourner quatre ou cinq valeurs? Bien sûr, vous pouvez continuer à les trier, mais il devient facile d'oublier quelle valeur est où. Il est également plutôt moche de les déballer où vous voulez les recevoir.

Option: Utiliser un dictionnaire

La prochaine étape logique semble être d'introduire une sorte de «notation d'enregistrement». En python, la manière évidente de le faire est au moyen d'un dict.

Considérer ce qui suit:

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

(edit - Pour être clair, y0, y1 et y2 sont simplement des identificateurs abstraits Comme indiqué, en pratique, vous utiliseriez 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 encadré ceci dans le contexte de Python, mais je suis sûr qu'il s'applique aussi à d'autres langues. En effet, si vous travailliez en C, cela pourrait très bien être votre seule option. Voici:

class ReturnValue(object):
  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édents sont peut-être très similaires en termes de plomberie- Après tout { y0, y1, y2 } juste finir par être des entrées dans le interne __dict__ du ReturnValue.

Il y a une fonctionnalité supplémentaire fournie par Python mais pour les objets minuscules, le __slots__ attribut. 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

Du Python Manuel de référence:

le __slots__ La déclaration 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 sauvé parce que __dict__ n'est pas créé pour chaque instance.

Option: Utiliser une liste

Une autre suggestion que j'avais oubliée vient de Bill the Lizard:

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

C'est ma méthode la moins préférée. Je suppose que je suis corrompu par l'exposition à Haskell, mais l'idée de listes mixtes m'a toujours semblé inconfortable. Dans cet exemple particulier, la liste est de type «non mixte», mais il est concevable qu'elle le soit. Une liste utilisée de cette manière ne gagne rien en ce qui concerne le tuple autant que je peux dire. La seule vraie différence entre les listes et les tuples dans Python est que les listes sont mutable, alors que les tuples ne le sont pas. J'ai personnellement tendance à reporter les conventions de la programmation fonctionnelle: utiliser des listes pour n'importe quel nombre 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 le long préambule, vient la question inévitable. Quelle méthode (pensez-vous) est la meilleure?

En général, je me suis retrouvé dans la voie du dictionnaire car cela impliquait moins de travail d'installation. Du point de vue des types cependant, vous feriez mieux d'aller sur la route de la classe, car cela pourrait vous aider à éviter de confondre ce qu'un dictionnaire représente. D'un autre côté, il y en a dans la communauté Python les interfaces implicites devraient être préférées aux interfaces explicites, à quel point le type de l'objet n'est vraiment pas pertinent, puisque vous vous basez essentiellement sur la convention selon laquelle le même attribut aura toujours le même sens.

Alors, comment -you- renvoyez-vous plusieurs valeurs dans Python?


804
2017-12-10 01:55


origine


Réponses:


Tuples nommés ont été ajoutés en 2.6 à cet effet. Regarde aussi os.stat pour un exemple intégré similaire.

>>> import collections
>>> Point = collections.namedtuple('Point', ['x', 'y'])
>>> p = Point(1, y=2)
>>> p.x, p.y
1 2
>>> p[0], p[1]
1 2

511
2017-12-10 16:36



Pour les petits projets, je trouve qu'il est plus facile de travailler avec des tuples. Lorsque cela devient trop difficile à gérer (et pas avant), je commence à regrouper les choses dans des structures logiques, mais je pense que votre utilisation suggérée des dictionnaires et des objets ReturnValue est fausse (ou trop simpliste).

Retourner un dictionnaire avec les clés y0, y1, y2 etc. n'offre aucun avantage sur les tuples. Renvoyer une instance de ReturnValue avec les propriétés .y0 .y1 .y2 etc n'offre pas non plus d'avantage sur les tuples. Vous devez commencer à nommer les choses si vous voulez aller n'importe où, et vous pouvez le faire en utilisant des tuples de toute façon:

def getImageData(filename):
  [snip]
  return size, (format, version, compression), (width,height)
size, type, dimensions = getImageData(x)

À mon humble avis, la seule bonne technique au-delà des tuples est de retourner des objets réels avec des méthodes et des propriétés appropriées, comme vous obtenez de re.match() ou open(file).


164
2017-12-10 02:22



Beaucoup de réponses suggèrent que vous devez retourner une collection quelconque, comme un dictionnaire ou une liste. Vous pouvez ignorer la syntaxe supplémentaire et simplement écrire les valeurs de retour, séparées par des virgules. Note: cela renvoie techniquement un tuple.

def f():
    return True, False
x, y = f()
print(x)
print(y)

donne:

True
False

122
2018-04-14 20:08



Je vote pour le dictionnaire.

Je trouve que si je fais une fonction qui retourne quelque chose de plus de 2-3 variables, je vais les replier dans un dictionnaire. Sinon, j'ai tendance à oublier l'ordre et le contenu de ce que je retourne.

En outre, l'introduction d'une structure «spéciale» rend votre code plus difficile à suivre. (Quelqu'un d'autre devra chercher dans le code pour savoir de quoi il s'agit)

Si vous êtes préoccupé par le type de recherche, utilisez des clés de dictionnaire descriptives, par exemple, 'liste de valeurs x'.

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

53
2017-12-10 02:42



Une autre option serait d'utiliser des générateurs:

>>> def f(x):
        y0 = x + 1
        yield y0
        yield x * 3
        yield y0 ** 4


>>> a, b, c = f(5)
>>> a
6
>>> b
15
>>> c
1296

Bien que les tuples à mon humble avis sont généralement les meilleurs, sauf dans les cas où les valeurs retournées sont des candidats pour l'encapsulation dans une classe.


31
2018-02-23 15:26



>>> def func():
...    return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3

22
2018-01-21 20:57



Je préfère utiliser des tuples chaque fois qu'un tuple se sent «naturel»; les coordonnées sont un exemple typique, où les objets séparés peuvent se suffire à eux-mêmes, par ex. dans les calculs de mise à l'échelle sur un seul axe, et l'ordre est important. Remarque: si je peux trier ou mélanger les éléments sans affecter la signification du groupe, je ne devrais probablement pas utiliser un tuple.

J'utilise les dictionnaires comme valeur de retour uniquement lorsque les objets groupés ne sont pas toujours les mêmes. Pensez aux en-têtes de messagerie facultatifs.

Pour le reste des cas, où les objets groupés ont une signification inhérente à l'intérieur du groupe ou un objet à part entière avec ses propres méthodes est nécessaire, j'utilise une classe.


22
2017-12-10 02:40



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 n'est que du code supplémentaire pour faire la même chose.


20
2017-12-10 02:00



+1 sur la suggestion de S.Lott d'une classe de conteneur nommée.

Pour python 2.6 et plus, un tuple nommé fournit un moyen utile de créer facilement ces classes de conteneur, et les résultats sont "légers et ne nécessitent pas plus de mémoire que les tuples standard".


13
2017-12-10 03:51