La manière canonique de renvoyer plusieurs valeurs dans les langues qui la prennent en charge est souvent le couplage .
Option: utiliser 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. Et si vous souhaitez 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 assez 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}
(Pour être clair, y0, y1 et y2 ne sont que des identifiants 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é 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)
En Python, les deux précédents sont peut-être très similaires en termes de plomberie - après tout, ils { y0, y1, y2 }
finissent par être des entrées à l'intérieur __dict__
du ReturnValue
.
Il y a une fonctionnalité supplémentaire fournie par Python pour les petits objets, l' __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
Dans le manuel de référence Python :
La
__slots__
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 enregistré car il__dict__
n'est pas créé pour chaque instance.
Option: utilisation d'une classe de données (Python 3.7+)
En utilisant les nouvelles classes de données de Python 3.7, renvoyez une classe avec des méthodes spéciales, des saisies 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)
Option: utiliser une liste
Une autre suggestion que j'avais ignoré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 entaché par l'exposition à Haskell, mais l'idée de listes de types mixtes m'a toujours fait mal à l'aise. Dans cet exemple particulier, la liste est de type non-mixte, mais elle pourrait l'être.
Pour autant que je sache, une liste utilisée de cette manière n'a vraiment rien à voir avec le tuple. La seule vraie différence entre les listes et les tuples en Python est que les listes sont mutables , contrairement aux tuples.
Personnellement, j'ai tendance à reprendre 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?
y3
, mais à moins que Y3 est déclarée mondiale, ce céderaisNameError: global name 'y3' is not defined
peut - être juste utiliser3
?y3
, qui fera le même travail aussi.Réponses:
Des tuples nommés ont été ajoutés dans 2.6 à cet effet. Voir également os.stat pour un exemple intégré similaire.
Dans les versions récentes de Python 3 (3.6+, je pense), la nouvelle
typing
bibliothèque a obtenu laNamedTuple
classe pour rendre les tuples nommés plus faciles à créer et plus puissants. L'héritage detyping.NamedTuple
vous permet d'utiliser des docstrings, des valeurs par défaut et des annotations de type.Exemple (extrait des documents):
la source
namedtuple
est d'avoir une empreinte mémoire plus petite pour les résultats de masse (longues listes de tuples, telles que les résultats des requêtes DB). Pour les éléments individuels (si la fonction en question n'est pas souvent appelée), les dictionnaires et les classes sont également très bien. Mais les couples nommés sont également une solution agréable / plus agréable dans ce cas.namedtuple
définitions (chaque appel en crée une nouvelle), la création de lanamedtuple
classe est relativement coûteuse en CPU et en mémoire, et toutes les définitions de classe impliquent intrinsèquement des références cycliques (ainsi sur CPython, vous attendez une exécution GC cyclique leur libération). Cela le rend également impossible pourpickle
la classe (et donc, impossible d'utiliser des instances avecmultiprocessing
dans la plupart des cas). Chaque création de la classe sur mon 3.6.4 x64 consomme ~ 0,337 ms et occupe un peu moins de 1 Ko de mémoire, tuant ainsi toute économie d'instance.namedtuple
classes ; les coûts du processeur baissent d'environ un facteur 4x , mais ils sont toujours environ 1000 fois plus élevés que le coût de création d'une instance, et le coût de la mémoire pour chaque classe reste élevé (j'avais tort dans mon dernier commentaire sur "moins de 1 Ko" pour la classe,_source
elle est généralement de 1,5 Ko; elle_source
est supprimée dans la version 3.7, elle est donc probablement plus proche de la revendication initiale d'un peu moins de 1 Ko par création de classe).Pour les petits projets, je trouve 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
ReturnValue
objets est incorrecte (ou trop simpliste).De retour un dictionnaire avec les touches
"y0"
,"y1"
,"y2"
, etc. n'offre aucun avantage sur tuples. De retour unReturnValue
exemple avec des propriétés.y0
,.y1
,.y2
, etc. n'offre aucun avantage sur tuples non plus . Vous devez commencer à nommer les choses si vous voulez aller n'importe où, et vous pouvez quand même le faire en utilisant des tuples:À mon humble avis, la seule bonne technique au-delà des tuples est de renvoyer des objets réels avec des méthodes et des propriétés appropriées, comme vous obtenez de
re.match()
ouopen(file)
.la source
size, type, dimensions = getImageData(x)
et(size, type, dimensions) = getImageData(x)
? C'est-à-dire, le fait d'habiller le côté gauche d'une affectation tuplée fait-il une différence?(1)
est un entier int(1,)
ou1,
est un tuple.De nombreuses réponses suggèrent que vous devez renvoyer une collection quelconque, comme un dictionnaire ou une liste. Vous pouvez laisser la syntaxe supplémentaire et simplement écrire les valeurs de retour, séparées par des virgules. Remarque: cela renvoie techniquement un tuple.
donne:
la source
type(f())
retourne<class 'tuple'>
.tuple
aspect explicite; ce n'est pas vraiment important que vous retourniez untuple
, c'est l'idiome pour retourner une période de valeurs multiples. Même raison pour laquelle vous omettez les parens avec l'idiome de swapx, y = y, x
, l'initialisation multiplex, y = 0, 1
, etc .; bien sûr, cela fait dutuple
s sous le capot, mais il n'y a aucune raison de le rendre explicite, car letuple
s n'est pas du tout le point. Le didacticiel Python introduit plusieurs affectations bien avant qu'il ne touche même à l'tuple
art.=
est un tuple en Python avec ou sans parenthèses autour d'eux. Il n'y a donc en fait ni explicite ni implicite ici. a, b, c est autant un tuple que (a, b, c). Il n'y a pas non plus de création de tuple "sous le capot" lorsque vous retournez de telles valeurs, car il s'agit simplement d'un tuple simple. L'OP a déjà mentionné des tuples donc il n'y a en fait aucune différence entre ce qu'il a mentionné et ce que cette réponse montre. AucunJe vote pour le dictionnaire.
Je trouve que si je crée une fonction qui renvoie plus de 2-3 variables, je les replierai 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 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'.
la source
result = g(x); other_function(result)
Une autre option serait d'utiliser des générateurs:
Bien que les tuples IMHO soient généralement les meilleurs, sauf dans les cas où les valeurs renvoyées sont candidates à l'encapsulation dans une classe.
la source
yield
?def f(x): …; yield b; yield a; yield r
vs(g for g in [b, a, r])
et les deux seront facilement convertis en listes ou en tuples, et en tant que tels prendront en charge le décompactage de tuple. La forme du générateur de tuple suit une approche fonctionnelle, tandis que la forme de la fonction est impérative et permettra le contrôle de flux et l'affectation des variables.Je préfère utiliser des tuples chaque fois qu'un tuple semble "naturel"; les coordonnées sont un exemple typique, où les objets séparés peuvent se tenir seuls, par exemple dans des 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 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 courrier électronique facultatifs.
Pour le reste des cas, lorsque les objets groupés ont une signification inhérente à l'intérieur du groupe ou qu'un objet à part entière avec ses propres méthodes est nécessaire, j'utilise une classe.
la source
Je préfère:
Il semble que tout le reste ne soit qu'un code supplémentaire pour faire la même chose.
la source
la source
Généralement, la "structure spécialisée" EST en fait un état actuel sensible d'un objet, avec ses propres méthodes.
J'aime trouver des noms de structures anonymes dans la mesure du possible. Les noms significatifs rendent les choses plus claires.
la source
Les tuples, dict et objets de Python offrent au programmeur un compromis harmonieux entre formalité et commodité pour les petites structures de données («choses»). Pour moi, le choix de la façon de représenter une chose est dicté principalement par la façon dont je vais utiliser la structure. En C ++, c'est une convention courante à utiliser
struct
pour les éléments de données uniquement etclass
pour les objets avec des méthodes, même si vous pouvez légalement placer des méthodes sur unstruct
; mon habitude est similaire en Python, avecdict
ettuple
à la place destruct
.Pour les jeux de coordonnées, j'utiliserai
tuple
plutôt qu'un pointclass
ou undict
(et notez que vous pouvez utiliser untuple
comme clé de dictionnaire, doncdict
faites de grands tableaux multidimensionnels clairsemés).Si je vais parcourir une liste de choses, je préfère décompresser les
tuple
s sur l'itération:... car la version de l'objet est plus encombrée à lire:
... et encore moins le
dict
.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 vaut généralement la peine d'en faire un objet de classe avec des méthodes appropriées.
Enfin, si je vais échanger des données avec des composants système non Python, je les garderai le plus souvent dans un
dict
car c'est le mieux adapté à la sérialisation JSON.la source
+1 sur la suggestion de S.Lott d'une classe de conteneur nommée.
Pour Python 2.6 et versions ultérieures, 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 ordinaires".
la source
Dans des langages comme Python, j'utilisais généralement un dictionnaire car il implique moins de surcharge que la création d'une nouvelle classe.
Cependant, si je me retrouve constamment à renvoyer le même ensemble de variables, cela implique probablement une nouvelle classe que je factoriserai.
la source
J'utiliserais un dict pour passer et renvoyer des valeurs d'une fonction:
Utilisez un formulaire variable tel que défini dans le formulaire .
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'avez pas à modifier les arguments des fonctions (des milliers d'entre eux) chaque fois que vous apportez une modification à votre code.
la source
test
cela modifiera directement la valeur. Comparez cela avecdict.update
, qui ne renvoie pas de valeur.form
ne nuit pas à la lisibilité ni aux performances. Dans les cas où vous devrez peut-être le reformaterform
, le renvoyer [formulaire] garantit que le dernierform
est renvoyé car vous ne garderez aucune trace des modifications du formulaire.«Meilleur» est une décision partiellement subjective. Utilisez des tuples pour les petits jeux 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 une exigence.
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 le cas le plus complexe, un objet est généralement préférable. Cependant, c'est vraiment la situation qui compte. S'il est judicieux de renvoyer un objet parce que c'est ce que vous avez naturellement à la fin de la fonction (par exemple, motif d'usine), renvoyez l'objet.
Comme l'a dit le sage:
la source