¿Existe una convención para devolver múltiples artículos?

10

En Python específicamente (no sé si esto se generaliza), ¿hay una "mejor" forma de devolver múltiples elementos de una función?

def func1():
    return a,b #equivalent to (a,b)

def func2():
    return[a,b]

def func3():
    return{"valueA":a,"valueB":b}

Lo primero es lo que veo más generalmente, pero siento que el último crea un código más legible porque ha nombrado salidas, pero ¿podría estar perdiendo algún tipo de sobrecarga creada por este método?

Devon Muraoka
fuente
No estás reajustando múltiples argumentos; En todos estos ejemplos, está devolviendo un único resultado que resulta ser un tipo de datos compuesto (una tupla, una lista y un diccionario, respectivamente). El tipo de datos a devolver depende de su función y de cómo debe usarse.
cabeza de jardín
Interesante, nunca lo pensé de esa manera, si respondes eso, puedo aceptarlo
Devon Muraoka

Respuestas:

13

Creo que las opciones deben considerarse estrictamente desde el punto de vista de la persona que llama: ¿qué es lo más probable que tenga que hacer el consumidor?

¿Y cuáles son las características más destacadas de cada colección?

  • Se accede a la tupla en orden e inmutable.
  • Se accede a la lista en orden y mutable
  • Se accede al dict por clave

La lista y la tupla son equivalentes para el acceso, pero la lista es mutable. Bueno, eso no me importa a la persona que llama si voy a desempaquetar inmediatamente los resultados:

score, top_player = play_round(players)
# or
idx, record = find_longest(records)

No hay ninguna razón para que me importe si es una lista o una tupla, y la tupla es más simple en ambos lados.

Por otro lado, si la colección devuelta se mantendrá completa y se usará como colección :

points = calculate_vertices(shape)
points.append(another_point)
# Make a new shape

entonces podría tener sentido que el regreso sea mutable. La homogeneidad también es un factor importante aquí. Digamos que ha escrito una función para buscar una secuencia de patrones repetidos. La información que obtengo es el índice en la secuencia de la primera instancia del patrón, el número de repeticiones y el patrón en sí. Esos no son los mismos tipos de cosas. Aunque podría mantener las piezas juntas, no hay razón para que quiera mutar la colección . Esto no es un list.

Ahora para el diccionario.

el último crea un código más legible porque ha nombrado salidas

Sí, tener claves para los campos hace que los datos heterogéneos sean más explícitos, pero también conlleva cierta molestia. Nuevamente, para el caso de "Solo voy a desempacar las cosas", esto

round_results = play_round(players)
score, top_player = round_results["score"], round_results["top_player"]

(incluso si evita las cadenas literales para las claves), es un trabajo ocupado innecesario en comparación con la versión de tupla.

La pregunta aquí es triple: ¿qué tan compleja es la colección, cuánto tiempo se mantendrá la colección unida y vamos a necesitar usar este mismo tipo de colección en un montón de lugares diferentes?

Sugeriría que un valor de retorno de acceso con clave comienza a tener más sentido que una tupla cuando hay más de unos tres miembros, y especialmente cuando hay anidamiento:

shape["transform"]["raw_matrix"][0, 1] 
# vs.
shape[2][4][0, 1]

Eso lleva a la siguiente pregunta: ¿la colección va a dejar este alcance intacto, en algún lugar alejado de la llamada que lo creó? El acceso clave allí ayudará absolutamente a la comprensión.

La tercera pregunta, reutilizar, apunta a un tipo de datos personalizado simple como una cuarta opción que no presentó.

¿La estructura es propiedad exclusiva de esta función? ¿O estás creando el mismo diseño de diccionario en muchos lugares? ¿Es necesario que muchas otras partes del programa operen en esta estructura? Un diseño de diccionario repetido debe factorizarse en una clase. La ventaja es que puede adjuntar el comportamiento: tal vez algunas de las funciones que operan en los datos se encapsulan como métodos.

Una quinta buena opción, ligera, es namedtuple(). Esta es, en esencia, la forma inmutable del valor de retorno del diccionario.

jscs
fuente
¿Hay alguna razón por la que no menciona instancias de clases definidas por el usuario? Esa debería ser la solución goto en cualquier lenguaje OO.
Esben Skov Pedersen
1
Eso está cubierto en el penúltimo y el penúltimo párrafo, @EsbenSkovPedersen. "Un diseño de diccionario repetido se debe tener en cuenta para una clase".
jscs
1

No pienses en las funciones que devuelven múltiples argumentos. Conceptualmente, es mejor pensar en las funciones como recibir y devolver un solo argumento. Una función que parece aceptar múltiples argumentos en realidad recibe un solo argumento de tipo tupla (formalmente producto ). Del mismo modo, una función que devuelve múltiples argumentos es simplemente devolver una tupla.

En Python:

def func(a, b, c):
  return b, c

podría reescribirse como

def func(my_triple):
  return (my_triple[1], my_triple[2])

para hacer la comparación obvia.

El primer caso es simplemente azúcar sintáctico para el segundo; ambos reciben un triple como argumento, pero el primer patrón coincide en su argumento para realizar la desestructuración automática en sus componentes constituyentes. Por lo tanto, incluso los lenguajes sin coincidencia de patrones generales completos admiten alguna forma de coincidencia de patrones básicos en algunos de sus tipos (Python admite la coincidencia de patrones tanto en los tipos de productos como de registro).


Para volver a la pregunta en cuestión: no hay una respuesta única a su pregunta, porque sería como preguntar "¿cuál debería ser el tipo de retorno de una función arbitraria"? Depende de la función y el caso de uso. Y, por cierto, si los "valores de retorno múltiple" son realmente independientes, entonces probablemente deberían calcularse mediante funciones separadas.

cabeza de jardín
fuente