La forma canónica de devolver múltiples valores en los lenguajes que lo soportan suele ser tupling.
Considere este ejemplo trivial:
def f(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return (y0, y1, y2)
Sin embargo, esto se vuelve rápidamente problemático a medida que aumenta el número de valores devueltos. ¿Qué pasa si quiere devolver cuatro o cinco valores? Claro, usted podría seguir tupling ellos, pero se hace fácil olvidar qué valor es donde. Además, es bastante feo desempaquetarlos donde quiera recibirlos.
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
.
Considere lo siguiente:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0': y0, 'y1': y1 ,'y2': y2}
(Para que quede claro, y0, y1, e y2 son sólo identificadores abstractos. Como se ha señalado, en la práctica, usted's utilizar los identificadores significativos).
Ahora, tenemos un mecanismo por el cual podemos proyectar un miembro particular del objeto devuelto. Por ejemplo,
result['y0']
Sin embargo, hay otra opción. Podríamos devolver una estructura especializada. He enmarcado esto en el contexto de Python, pero estoy seguro de que se aplica a otros lenguajes también. De hecho, si estuvieras trabajando en C esta podría ser perfectamente tu única opción. Aquí va:
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 los dos anteriores son quizás muy similares en términos de fontanería - después de todo { y0, y1, y2 }
sólo terminan siendo entradas en el __dict__
interno del ReturnValue
.
Hay una característica adicional proporcionada por Python para los objetos pequeños, el atributo __slots__
. 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
Del Manual de Referencia de Python:
La declaración __slots__
toma una secuencia de variables de instancia y reserva el espacio suficiente en cada instancia para mantener un valor para cada variable. Se ahorra espacio porque el __dict__
no se crea para cada instancia.
Usando las nuevas clases de datos de Python 3.7' devuelve una clase con métodos especiales añadidos automáticamente, tipado y otras herramientas útiles:
@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)
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 el método que menos me gusta. Supongo que estoy contaminado por la exposición a Haskell, pero la idea de las listas de tipo mixto siempre me ha parecido incómoda. En este ejemplo particular, la lista es -no- de tipo mixto, pero podría serlo.
Una lista utilizada de esta manera realmente no gana nada con respecto a la tupla, por lo que puedo decir. La única diferencia real entre las listas y las tuplas en Python es que las listas son mutables, mientras que las tuplas no lo son.
Personalmente, tiendo a mantener las convenciones de la programación funcional: usar listas para cualquier número de elementos del mismo tipo, y tuplas para un número fijo de elementos de tipos predeterminados.
Después del largo preámbulo, viene la pregunta inevitable. ¿Qué método (crees) que es mejor?
Normalmente me he decantado por la vía del diccionario porque implica menos trabajo de configuración. Sin embargo, desde el punto de vista de los tipos, podría ser mejor optar por la ruta de las clases, ya que eso puede ayudar a evitar la confusión de lo que representa un diccionario.
Por otro lado, hay algunos en la comunidad de Python que piensan que las interfaces implícitas deberían ser preferidas a las interfaces explícitas, en cuyo caso el tipo del objeto realmente no es relevante, ya que básicamente estás confiando en la convención de que el mismo atributo siempre tendrá el mismo significado.
Así que, ¿cómo puedes devolver múltiples valores en Python?
Generalmente, la "estructura especializada" en realidad ES un estado actual sensible de un objeto, con sus propios métodos.
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
Me gusta encontrar nombres para las estructuras anónimas siempre que sea posible. Los nombres significativos hacen las cosas más claras.
En lenguajes como Python, suelo utilizar un diccionario ya que implica menos sobrecarga que crear una nueva clase.
Sin embargo, si me encuentro devolviendo constantemente el mismo conjunto de variables, entonces eso probablemente implique una nueva clase que voy a factorizar.