coding-style plot - ¿Cómo devuelves múltiples valores en Python?




title plt (13)

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

La forma canónica de devolver múltiples valores en los idiomas que lo admiten es a menudo la 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 rápidamente problemático 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 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 }

(Editar: para que quede claro, y0, y1 y y2 solo son significados 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 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 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 mutable , mientras que las tuplas no lo son. 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?

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 , momento en el que 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?


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 implica una nueva clase que voy a factorizar.


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

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


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.


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

Use la forma variable como se define en la form .

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.


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)
>>> p.x, p.y
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 hacer 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

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 cálculos de escalamiento de un solo eje, y el orden es importante. Nota: si puedo clasificar o mezclar los elementos sin un efecto adverso para el significado del grupo, entonces probablemente no debería usar una tupla.

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

Para el resto 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.


+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 forma útil de crear fácilmente estas clases de contenedores, y los resultados son "ligeros y no requieren más memoria que las tuplas normales".


Las tuplas, los dados y los objetos de Python ofrecen al programador 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 la forma en que voy a usar la estructura. En C ++, es una convención común utilizar 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 desempaquetar 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 sean de Python, los mantendré a menudo en un dict porque es lo más adecuado para la serialización JSON.


"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 programación.


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 encapsulación en una clase.


Absolutamente, y es increíblemente fácil de entender.

general syntax : first_expression if bool_expression_is_true else second_expression

Example: x= 3 if 3 > 2 else 4 
# assigns 3 to x if the boolean expression evaluates to true or 4 if it is false




python coding-style return return-value