Le moyen canonique de renvoyer plusieurs valeurs dans les langues qui le supportent est souvent tupling .
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 lorsque le nombre de valeurs renvoyées augmente. Et si vous voulez renvoyer quatre ou cinq valeurs? Bien sûr, vous pouvez continuer à les multiplier, mais il est facile d’oublier quelle valeur est où. Il est également plutôt moche de les décompresser où vous voulez les recevoir.
La prochaine étape logique semble être d'introduire une sorte de "notation d'enregistrement". En Python, le moyen évident de le faire consiste à utiliser 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}
(Juste pour être clair, y0, y1 et y2 ne sont que des identificateurs abstraits. Comme indiqué, dans la pratique, vous utiliseriez des identificateurs significatifs.)
Nous disposons maintenant d'un mécanisme permettant de projeter un membre particulier de l'objet renvoyé. Par exemple,
result['y0']
Cependant, il existe une autre option. Nous pourrions plutôt renvoyer une structure spécialisée. J'ai encadré cela dans le contexte de Python, mais je suis sûr que cela s'applique également à d'autres langages. En effet, si vous travailliez en C, cela pourrait très bien être votre seule option. Voici:
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)
Dans Python, les deux précédents sont peut-être très similaires en termes de plomberie - après tout, { y0, y1, y2 }
finit par être des entrées dans le __dict__
interne de ReturnValue
.
Il existe une fonctionnalité supplémentaire fournie par Python bien que pour les objets minuscules, l'attribut __slots__
. La classe pourrait être exprimée comme:
class ReturnValue(object):
__slots__ = ["y0", "y1", "y2"]
def __init__(self, y0, y1, y2):
self.y0 = y0
self.y1 = y1
self.y2 = y2
À partir du Manuel de référence Python :
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 enregistré car__dict__
n'est pas créé pour chaque instance.
En utilisant les nouvelles classes de données de Python 3.7, restituez une classe avec des méthodes spéciales, la frappe et d'autres outils utiles ajoutés automatiquement:
@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)
Une autre suggestion que j'avais 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
Ceci est ma méthode la moins préférée cependant. J'imagine que j'ai été exposé à Haskell, mais l'idée de listes de types mixtes m'a toujours semblé inconfortable. Dans cet exemple particulier, la liste est de type non mélangé, mais ce pourrait être le cas.
Une liste utilisée de cette manière ne rapporte rien au tuple, pour autant que je sache. La seule différence réelle entre les listes et les tuples dans Python est que les listes sont mutables , 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 n-uplets pour un nombre fixe d'éléments de types prédéterminés.
Après le long préambule, il y a l'inévitable question. Quelle méthode (pensez-vous) est la meilleure?
Je me suis généralement retrouvé dans la voie des dictionnaires, car cela implique moins de travail d'installation. Cependant, du point de vue des types, il vaudrait peut-être mieux que vous suiviez la voie des cours, car cela peut vous aider à éviter de confondre ce que représente un dictionnaire.
D'autre part, il y en a dans la communauté Python qui se sent les interfaces implicites devraient être préférées aux interfaces explicites , à quel point le type de l'objet n'est plus vraiment pertinent, puisque vous vous appuyez essentiellement sur la convention voulant que le même attribut ait toujours la même signification.
Alors, comment peut-on renvoyer plusieurs valeurs en Python?
tuples nommés ont été ajoutés dans 2.6 à cette fin. Voir 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
Dans les versions récentes de Python 3 (3.6+, je pense), la nouvelle bibliothèque typing
a obtenu la classe NamedTuple
qui permet de créer des nuplets nommés plus faciles et plus puissants. Hériter de typing.NamedTuple
vous permet d'utiliser des docstrings, des valeurs par défaut et des annotations.
Exemple (à partir de la documentation):
class Employee(NamedTuple): # inherit from collections.NamedTuple
name: str
id: int = 3 # default value
employee = Employee('Guido')
assert employee.id == 3
Pour les petits projets, il est plus facile de travailler avec des n-uplets. Lorsque cela devient trop difficile à gérer (et pas avant), je commence à regrouper les éléments dans des structures logiques. Cependant, 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 par rapport aux n-uplets. Renvoyer une instance ReturnValue avec les propriétés .y0, .y1, .y2, etc. n'offre aucun avantage par rapport aux n-uplets. Vous devez commencer à nommer des choses si vous voulez aller n'importe où, et vous pouvez le faire en utilisant des n-uplets de toute façon:
def getImageData(filename):
[snip]
return size, (format, version, compression), (width,height)
size, type, dimensions = getImageData(x)
IMHO, la seule bonne technique à part les tuples est de retourner des objets réels avec les méthodes et propriétés appropriées, comme vous obtenez de re.match()
ou open(file)
.
Un grand nombre de réponses suggèrent que vous deviez renvoyer une collection quelconque, comme un dictionnaire ou une liste. Vous pouvez ne pas utiliser la syntaxe supplémentaire et simplement écrire les valeurs de retour, séparées par des virgules. Remarque: techniquement, cela retourne un tuple.
def f():
return True, False
x, y = f()
print(x)
print(y)
donne:
True
False
Je vote pour le dictionnaire.
Je trouve que si je crée une fonction qui renvoie plus de 2 ou 3 variables, je les replie dans un dictionnaire. Sinon, j'ai tendance à oublier l'ordre et le contenu de ce que je retourne.
De plus, l'introduction d'une structure "spéciale" rend votre code plus difficile à suivre. (Quelqu'un d'autre devra chercher dans le code pour savoir ce que c'est)
Si vous êtes préoccupé par le type recherché, utilisez des clés de dictionnaire descriptif, 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 }
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 nuplets IMHO soient généralement les meilleurs, sauf dans les cas où les valeurs renvoyées sont des candidats pour l’encapsulation dans une classe.
Je préfère utiliser des n-uplets chaque fois qu'un tuple se sent "naturel"; les coordonnées sont un exemple typique, où les objets séparés peuvent être autonomes, par ex. dans les calculs de mise à l'échelle sur un axe uniquement, et l'ordre est important. Remarque: si je peux trier ou mélanger les éléments sans nuire à la signification du groupe, je ne devrais probablement pas utiliser de 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 si un objet à part entière avec ses propres méthodes est nécessaire, j'utilise une classe.
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 soit simplement du code supplémentaire pour faire la même chose.
>>> def func():
... return [1,2,3]
...
>>> a,b,c = func()
>>> a
1
>>> b
2
>>> c
3
Généralement, la "structure spécialisée" IS est 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 dans la mesure du possible. Les noms significatifs rendent les choses plus claires.
Les nuplets, les dict et les objets de Python offrent au programmeur un bon compromis entre formalité et commodité pour les petites structures de données ("choses"). Pour moi, le choix de la manière de représenter une chose est dicté principalement par la manière dont je vais utiliser la structure. En C++, il est courant d'utiliser struct
pour les éléments contenant uniquement des données et class
pour les objets avec des méthodes, même si vous pouvez légalement placer des méthodes sur un struct
; mon habitude est similaire en Python, avec dict
et Tuple
à la place de struct
.
Pour les ensembles de coordonnées, je vais utiliser un Tuple
plutôt qu'un point class
ou un dict
(et notez que vous pouvez utiliser un Tuple
comme clé de dictionnaire, afin que dict
crée de grands tableaux multidimensionnels épars).
Si je vais parcourir une liste de choses, je préfère décompresser Tuple
s sur l'itération:
for score,id,name in scoreAllTheThings():
if score > goodScoreThreshold:
print "%6.3f #%6d %s"%(score,id,name)
... car la version de l'objet est plus encombrée à lire:
for entry in scoreAllTheThings():
if entry.score > goodScoreThreshold:
print "%6.3f #%6d %s"%(entry.score,entry.id,entry.name)
... et encore moins le dict
.
for entry in scoreAllTheThings():
if entry['score'] > goodScoreThreshold:
print "%6.3f #%6d %s"%(entry['score'],entry['id'],entry['name'])
Si la chose est largement utilisée et que vous vous trouvez à effectuer des opérations similaires non triviales à plusieurs endroits dans le code, il est généralement intéressant d'en faire un objet de classe avec les méthodes appropriées.
Enfin, si je vais échanger des données avec des composants système non Python, je les conserverai le plus souvent dans un dict
, car cela convient mieux à la sérialisation JSON.
+1 sur la suggestion de S.Lott d'une classe de conteneur nommée.
Pour Python 2.6 et versions ultérieures, un nommé Tuple fournit un moyen utile de créer facilement ces classes de conteneur. Les résultats sont "légers et ne nécessitent pas plus de mémoire que des n-uplets standard".
Dans des langages tels que Python, j'utiliserais généralement un dictionnaire, car il implique moins de temps système que la création d'une nouvelle classe.
Cependant, si je retourne constamment le même ensemble de variables, cela implique probablement une nouvelle classe que je vais factoriser.
J'utiliserais un dict pour transmettre et renvoyer les valeurs d'une fonction:
Utilisez une forme variable telle que définie dans forme .
form = {
'level': 0,
'points': 0,
'game': {
'name': ''
}
}
def test(form):
form['game']['name'] = 'My game!'
form['level'] = 2
return form
>>> print(test(form))
{u'game': {u'name': u'My game!'}, u'points': 0, u'level': 2}
C'est le moyen le plus efficace pour moi et pour l'unité de traitement.
Vous devez passer un seul pointeur et renvoyer un seul pointeur.
Vous n'êtes pas obligé de changer les arguments des fonctions (des milliers) à chaque fois que vous modifiez votre code.
"Best" est une décision partiellement subjective. Utilisez des n-uplets pour les petits ensembles de retour dans le cas général où une immuable est acceptable. Un tuple est toujours préférable à une liste lorsque la mutabilité n'est pas requise.
Pour des valeurs de retour plus complexes, ou dans le cas où la formalité est précieuse (c'est-à-dire un code de valeur élevée), un Tuple nommé est préférable. Pour les cas les plus complexes, un objet est généralement préférable. Cependant, c'est vraiment la situation qui compte. S'il est logique de retourner un objet parce que c'est ce que vous avez naturellement à la fin de la fonction (par exemple, un modèle d'usine), renvoyez-le.
Comme l'a dit le sage:
L'optimisation prématurée est la racine de tous les maux (ou du moins la majeure partie) de la programmation.