¿Cómo devolver múltiples valores desde una función?

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

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, puedes seguir amontonándolos, pero es fácil olvidar qué valor es dónde. También es bastante feo desempaquetarlos 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 hacer esto es por medio de 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 y y2 son solo significantes como 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. Podríamos en cambio devolver una estructura especializada. He enmarcado esto en el contexto de Python, pero estoy seguro de que también se aplica a otros idiomas. 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 plomería. Después de todo { y0, y1, y2 } simplemente terminan siendo entradas en el __dict__ interno del ReturnValue .

    Python proporciona una característica adicional para los objetos pequeños, el atributo __slots__ . La clase podría expressse 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 statement __slots__ toma una secuencia de variables de instancia y reserva solo el espacio suficiente en cada instancia para mantener un valor para cada variable. El espacio se guarda porque __dict__ no se crea para cada instancia.

    Opción: usar una lista

    Otra sugerencia que había pasado por alto proviene de Bill the Lizard:

     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 listas de tipos mixtos siempre me ha resultado incómoda. En este ejemplo particular, la lista es de tipo no mixto, pero posiblemente 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 transferir las convenciones de la progtwigció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?

    Por lo general, me encontré yendo por la ruta del diccionario porque implica menos trabajo de configuración. Sin embargo, desde la perspectiva de los tipos, 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 consideran que las interfaces implícitas deberían preferirse a las interfaces explícitas , en cuyo punto el tipo de objeto realmente no es relevante, ya que básicamente confía en la convención de que el mismo atributo Siempre tendrá el mismo significado.

    Entonces, ¿cómo -you- devuelve múltiples valores en Python?

    Las tuplas nombradas se agregaron en 2.6 para este propósito. También vea os.stat para un ejemplo incorporado similar.

     >>> import collections >>> Point = collections.namedtuple('Point', ['x', 'y']) >>> p = Point(1, y=2) >>> px, py 1 2 >>> p[0], p[1] 1 2 

    En las versiones recientes de Python 3 (3.6+, creo), la nueva biblioteca de typing obtuvo la clase NamedTuple para que las tuplas con nombre sean más fáciles de crear y más poderosas. Heredar de typing.NamedTuple permite usar cadenas de documentos, valores predeterminados y anotaciones de tipo.

    Ejemplo (de la documentación):

     class Employee(NamedTuple): # inherit from collections.NamedTuple name: str id: int = 3 # default value employee = Employee('Guido') assert employee.id == 3 

    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) comienzo 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 teclas 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. Necesitas comenzar a nombrar cosas si quieres llegar a cualquier parte, 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 técnica buena más allá de las tuplas es devolver objetos reales con métodos y propiedades adecuados, como los que obtiene de re.match() o open(file) .

    Muchas de las respuestas sugieren que necesita devolver una colección de algún tipo, como un diccionario o una lista. Podría omitir la syntax 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 

    Yo voto por el diccionario.

    Encuentro que si hago una función que devuelve algo más de 2-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 en el código para averiguar qué es)

    Si le preocupa el tipo de búsqueda, use las claves del diccionario descriptivo, 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 } 

    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 IMHO son generalmente las mejores, excepto en los casos en que los valores que se devuelven son candidatos para la encapsulación en una clase.

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

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

    Para el rest de los casos, donde los objetos agrupados tienen un significado inherente dentro del grupo o se necesita un objeto de pleno derecho con sus propios métodos, uso una clase.

    yo prefiero

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

    Parece que todo lo demás es solo código extra para hacer lo mismo.

     >>> def func(): ... return [1,2,3] ... >>> a,b,c = func() >>> a 1 >>> b 2 >>> c 3 

    Las tuplas, los dados y los objetos de Python ofrecen al progtwigdor un intercambio sin contratiempos entre formalidad y conveniencia para estructuras de datos pequeñas (“cosas”). Para mí, la elección de cómo representar una cosa viene determinada principalmente por cómo voy a utilizar la estructura. En C ++, es una convención común usar struct para elementos de solo datos y class para objetos con métodos, aunque legalmente puede poner métodos en una struct ; Mi hábito es similar en Python, con dict y tuple en lugar de struct .

    Para los conjuntos de coordenadas, tuple una tuple lugar de una class puntos o un dict (y tenga en cuenta que puede usar una tuple como clave de diccionario, por lo que los dict son excelentes arreglos dispersos).

    Si voy a estar iterando sobre una lista de cosas, prefiero desempacar tuple s en la iteración:

     for score,id,name in scoreAllTheThings(): if score > goodScoreThreshold: print "%6.3f #%6d %s"%(score,id,name) 

    … como la versión del objeto está más abarrotada de leer:

     for entry in scoreAllTheThings(): if entry.score > goodScoreThreshold: print "%6.3f #%6d %s"%(entry.score,entry.id,entry.name) 

    … y mucho menos el dict .

     for entry in scoreAllTheThings(): if entry['score'] > goodScoreThreshold: print "%6.3f #%6d %s"%(entry['score'],entry['id'],entry['name']) 

    Si la cosa se usa ampliamente y se encuentra realizando operaciones no triviales similares en varios lugares del código, entonces generalmente vale la pena convertirlo en un objeto de clase con los métodos apropiados.

    Finalmente, si voy a intercambiar datos con componentes del sistema que no son de Python, los mantendré a menudo en un dict porque es lo más adecuado para la serialización JSON.

    En general, 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 estructuras anónimas donde sea posible. Los nombres significativos hacen las cosas más claras.

    +1 en la sugerencia de S.Lott de una clase de contenedor con nombre.

    Para Python 2.6 y versiones posteriores, una tupla con nombre proporciona una manera útil de crear fácilmente estas clases de contenedores, y los resultados son “ligeros y no requieren más memoria que las tuplas normales”.

    En lenguajes como Python, usualmente usaría un diccionario ya que implica menos gastos generales que crear una nueva clase.

    Sin embargo, si me encuentro constantemente devolviendo el mismo conjunto de variables, entonces eso probablemente involucre una nueva clase que voy a considerar.

    Usaría un dict para pasar y devolver valores desde una función:

    Use la forma variable como se define en la forma .

     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} 

    Esta es la forma más eficiente para mí y para la unidad de procesamiento.

    Debe pasar solo un puntero y devolver solo un puntero.

    No tiene que cambiar los argumentos de las funciones (miles de ellos) cada vez que realice un cambio en su código.

    “Lo mejor” es una decisión parcialmente subjetiva. Use tuplas para pequeños juegos de retorno en el caso general en el que es aceptable un inmutable. Una tupla siempre es preferible a una lista cuando la mutabilidad no es un requisito.

    Para valores de retorno más complejos, o en el caso de que la formalidad sea valiosa (es decir, código de alto valor), es mejor una tupla con nombre. Para el caso más complejo, un objeto suele ser el mejor. Sin embargo, es realmente la situación lo que importa. Si tiene sentido devolver un objeto porque eso es lo que naturalmente tiene al final de la función (por ejemplo, patrón de fábrica), entonces devuelva el objeto.

    Como dijo el sabio:

    La optimización prematura es la raíz de todo mal (o al menos la mayor parte) en la progtwigción.