Pregunta ¿Cómo devuelves valores múltiples en Python?


La forma canónica de devolver múltiples valores en idiomas que la soportan es a menudo tupling.

Opción: usar una tupla

Considera este ejemplo trivial:

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

Sin embargo, esto se vuelve problemático rápidamente a medida que aumenta el número de valores devueltos. ¿Qué pasa si quieres devolver cuatro o cinco valores? Claro, podrías seguir tupling, pero es fácil olvidar qué valor es dónde. También es bastante feo descomprimirlos donde quiera que los reciba.

Opción: usar un diccionario

El siguiente paso lógico parece ser introducir algún tipo de 'notación de registro'. En Python, la forma obvia de hacerlo es mediante un dict.

Considera lo siguiente:

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

(edit- Solo para ser claros, y0, y1 e y2 solo son identificadores abstractos. Como se señaló, en la práctica se usarían identificadores significativos)

Ahora, tenemos un mecanismo mediante el cual podemos proyectar un miembro en particular del objeto devuelto. Por ejemplo,

result['y0']

Opción: usar una clase

Sin embargo, hay otra opción. En su lugar, podríamos devolver una estructura especializada. Enmarqué esto en el contexto de Python, pero estoy seguro de que se aplica a otros idiomas también. De hecho, si estuvieras trabajando en C, esta podría ser tu única opción. Aquí va:

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, los dos anteriores son quizás muy similares en términos de fontanería. Después de todo { y0, y1, y2 } acaba de terminar siendo entradas en el interno __dict__ del ReturnValue.

Sin embargo, hay una característica adicional proporcionada por Python para objetos pequeños, la __slots__ atributo. La clase podría expresarse como:

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

Desde el Manual de referencia de Python:

los __slots__ declaración toma una secuencia de variables de instancia y reserva suficiente espacio en cada instancia para mantener un valor para cada variable. El espacio se salva porque __dict__ no se crea para cada instancia.

Opción: usar una lista

Otra sugerencia que había pasado por alto viene de Bill el Lagarto:

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

Este es mi método menos favorito, sin embargo. Supongo que estoy contaminado por la exposición a Haskell, pero la idea de las listas de tipos mixtos siempre me ha parecido incómoda. En este ejemplo particular, la lista es -no-tipo mixto, pero posiblemente podría serlo. Una lista usada de esta manera realmente no gana nada con respecto a la tupla hasta donde yo sé. La única diferencia real entre listas y tuplas en Python es que las listas son mudable, cuando las tuplas no lo son Yo personalmente tiendo a trasladar las convenciones de la programación funcional: use listas para cualquier cantidad de elementos del mismo tipo y tuplas para una cantidad fija de elementos de tipos predeterminados.

Pregunta

Después del extenso preámbulo, surge la inevitable pregunta. ¿Qué método (crees) es el mejor?

Por lo general, me he encontrado yendo por la ruta del diccionario porque implica menos trabajo de configuración. Desde una perspectiva de tipos, sin embargo, es mejor que vayas por la ruta de clase, ya que eso puede ayudarte a evitar confundir lo que representa un diccionario. Por otro lado, hay algunos en la comunidad de Python que sienten las interfaces implícitas deberían preferirse a las interfaces explícitas, en ese punto, el tipo de objeto realmente no es relevante, ya que básicamente se basa en la convención de que el mismo atributo siempre tendrá el mismo significado.

Entonces, ¿cómo devuelves valores múltiples en Python?


804
2017-12-10 01:55


origen


Respuestas:


Tuplas nombradas se agregaron en 2.6 para este propósito. Ver también os.stat para un ejemplo incorporado similar.

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



Para proyectos pequeños, me resulta más fácil trabajar con tuplas. Cuando eso se vuelve demasiado difícil de manejar (y no antes) empiezo a agrupar las cosas en estructuras lógicas, sin embargo, creo que su uso sugerido de diccionarios y objetos ReturnValue es incorrecto (o demasiado simplista).

Devolver un diccionario con las claves y0, y1, y2, etc. no ofrece ninguna ventaja sobre las tuplas. Devolver una instancia de ReturnValue con propiedades .y0 .y1 .y2 etc. tampoco ofrece ninguna ventaja sobre las tuplas. Debes comenzar a nombrar cosas si quieres llegar a algún lado, y puedes hacerlo usando tuplas de todos modos:

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

En mi humilde opinión, la única buena técnica más allá de las tuplas es devolver objetos reales con los métodos y propiedades adecuados, como se obtiene de re.match() o open(file).


164
2017-12-10 02:22



Muchas de las respuestas sugieren que debe devolver una colección de algún tipo, como un diccionario o una lista. Puede dejar fuera la sintaxis adicional y simplemente escribir los valores de retorno, separados por comas. Nota: esto técnicamente devuelve una tupla.

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

da:

True
False

122
2018-04-14 20:08



Yo voto por el diccionario.

Encuentro que si hago una función que devuelve algo más de 2 o 3 variables, las plegaré en un diccionario. De lo contrario, tiendo a olvidar el orden y el contenido de lo que estoy devolviendo.

Además, la introducción de una estructura "especial" hace que su código sea más difícil de seguir. (Alguien más tendrá que buscar el código para averiguar qué es)

Si le preocupa el tipo, busque claves descriptivas del diccionario, por ejemplo, 'lista de valores de 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



Otra opción sería usar generadores:

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

Aunque las tuplas en mi humilde opinión suelen ser las mejores, excepto en los casos en que los valores que se devuelven son candidatos para la encapsulación en una clase.


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



Prefiero usar tuplas cuando una tupla se siente "natural"; las coordenadas son un ejemplo típico, donde los objetos separados pueden sostenerse solos, p. en cálculos de escalado de un solo eje, y el orden es importante. Nota: si puedo ordenar o mezclar los elementos sin un efecto adverso sobre el significado del grupo, entonces probablemente no debería usar una tupla.

Uso diccionarios como valor de retorno solo cuando los objetos agrupados no son siempre iguales. Piensa en encabezados de correo electrónico opcionales.

Para el resto de los casos, cuando los objetos agrupados tienen un significado inherente dentro del grupo o se necesita un objeto totalmente desarrollado con sus propios métodos, utilizo una clase.


22
2017-12-10 02:40