Alternativas para devolver múltiples valores desde una función de Python [cerrado]

Resuelto saffsd asked hace 15 años • 0 respuestas

La forma canónica de devolver múltiples valores en idiomas que lo admiten suele ser tuplar .

Opción: usar una tupla

Considere este ejemplo trivial:

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

Sin embargo, esto rápidamente se vuelve problemático a medida que aumenta el número de valores devueltos. ¿Qué pasa si quieres devolver cuatro o cinco valores? Claro, puedes seguir tuplicándolos, pero es fácil olvidar qué valor está dónde. También es bastante feo desembalarlos donde quieras recibirlos.

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 archivo dict.

Considera 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 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 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. He enmarcado esto en el contexto de Python, pero estoy seguro de que también se aplica a otros lenguajes. De hecho, si estuviera trabajando en C, esta podría ser su ú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 plomería; después de todo, { y0, y1, y2 }simplemente terminan siendo entradas en el interior __dict__del archivo ReturnValue.

Python proporciona una característica adicional para objetos pequeños: el __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

Del Manual de referencia de Python :

La __slots__declaración toma una secuencia de variables de instancia y reserva suficiente espacio en cada instancia para contener un valor para cada variable. Se ahorra espacio porque __dict__no se crea para cada instancia.

Opción: usar una clase de datos (Python 3.7+)

Usando las nuevas clases de datos de Python 3.7, devuelve una clase con métodos especiales agregados automáticamente, escritura 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)

Opción: usar una lista

Otra sugerencia que pasé por alto proviene de Bill the Lizard:

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

Sin embargo, este es mi método menos favorito. Supongo que estoy contaminado por la exposición a Haskell, pero la idea de listas de tipos mixtos siempre me ha resultado incómoda. En este ejemplo particular, la lista no es de tipo mixto, pero posiblemente podría serlo.

Hasta donde yo sé, una lista utilizada de esta manera realmente no gana nada con respecto a la tupla. La única diferencia real entre listas y tuplas en Python es que las listas son mutables , mientras que las tuplas no.

Personalmente tiendo a trasladar las convenciones de la programación funcional: uso listas para cualquier número de elementos del mismo tipo y tuplas para un número fijo de elementos de tipos predeterminados.

Pregunta

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

saffsd avatar Dec 10 '08 08:12 saffsd
Aceptado

Se agregaron tuplas con nombre en 2.6 para este propósito. Consulte también os.stat para ver un ejemplo integrado 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

En versiones recientes de Python 3 (creo que 3.6+), la nueva typingbiblioteca consiguió que la NamedTupleclase hiciera que las tuplas con nombre fueran más fáciles de crear y más potentes. Heredar de typing.NamedTuplele permite usar cadenas de documentación, valores predeterminados y anotaciones de tipo.

Ejemplo (de los documentos):

class Employee(NamedTuple):  # inherit from typing.NamedTuple
    name: str
    id: int = 3  # default value

employee = Employee('Guido')
assert employee.id == 3
A. Coady avatar Dec 10 '2008 16:12 A. Coady

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 cosas en estructuras lógicas; sin embargo, creo que el uso sugerido de diccionarios y ReturnValueobjetos es incorrecto (o demasiado simplista).

Devolver un diccionario con claves "y0", "y1", "y2", etc. no ofrece ninguna ventaja sobre las tuplas. Devolver una ReturnValueinstancia con propiedades .y0, .y1, .y2, etc. tampoco ofrece ninguna ventaja sobre las tuplas. Necesitas comenzar a nombrar cosas si quieres llegar a alguna parte, y puedes hacerlo usando tuplas de todos modos:

def get_image_data(filename):
    [snip]
    return size, (format, version, compression), (width,height)

size, type, dimensions = get_image_data(x)

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

too much php avatar Dec 10 '2008 02:12 too much php

Muchas de las respuestas sugieren que necesita devolver una colección de algún tipo, como un diccionario o una lista. Puede omitir 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
Joseph Hansen avatar Apr 14 '2016 20:04 Joseph Hansen

Yo voto por el diccionario.

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

Además, introducir una estructura "especial" hace que el código sea más difícil de seguir. (Alguien más tendrá que buscar en el código para saber qué es)

Si le preocupa la búsqueda de tipos, utilice claves de diccionario descriptivas, por ejemplo, 'lista de valores x'.

def g(x):
  y0 = x + 1
  y1 = x * 3
  y2 = y0 ** y3
  return {'y0':y0, 'y1':y1 ,'y2':y2 }
monkut avatar Dec 10 '2008 02:12 monkut

Otra opción sería utilizar 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 en mi humilde opinión las tuplas suelen ser las mejores, excepto en los casos en que los valores que se devuelven son candidatos para encapsularse en una clase.

rlms avatar Feb 23 '2014 15:02 rlms