¿Es pythonico que una función devuelva múltiples valores?

En Python, puede hacer que una función devuelva múltiples valores. Aquí hay un ejemplo artificial:

def divide(x, y): quotient = x/y remainder = x % y return quotient, remainder (q, r) = divide(22, 7) 

Esto parece muy útil, pero parece que también se puede abusar (“Bueno … la función X ya calcula lo que necesitamos como valor intermedio. Hagamos que X también devuelva ese valor”).

¿Cuándo debes dibujar la línea y definir un método diferente?

Absolutamente (por el ejemplo proporcionado).

Las tuplas son ciudadanos de primera clase en Python

Hay una función incorporada divmod() que hace exactamente eso.

 q, r = divmod(x, y) # ((x - x%y)/y, x%y) Invariant: div*y + mod == x 

Hay otros ejemplos: zip , enumerate , dict.items .

 for i, e in enumerate([1, 3, 3]): print "index=%d, element=%s" % (i, e) # reverse keys and values in a dictionary d = dict((v, k) for k, v in adict.items()) # or d = dict(zip(adict.values(), adict.keys())) 

Por cierto, los paréntesis no son necesarios la mayor parte del tiempo. Referencia de la biblioteca de Python :

Las tuplas se pueden construir de varias maneras:

  • Usando un par de paréntesis para denotar la tupla vacía: ()
  • Uso de una coma al final de una tupla singleton: a, o (a,)
  • Separar elementos con comas: a, b, c o (a, b, c)
  • Uso de la tupla () incorporada: tupla () o tupla (iterable)

Las funciones deben servir un solo propósito

Por lo tanto, deben devolver un solo objeto. En tu caso este objeto es una tupla. Considere la tupla como una estructura de datos compuesta ad-hoc. Hay idiomas donde casi todas las funciones devuelven múltiples valores (lista en Lisp).

A veces es suficiente devolver (x, y) lugar de Point(x, y) .

Tuplas con nombre

Con la introducción de tuplas con nombre en Python 2.6, es preferible en muchos casos devolver tuplas con nombre en lugar de tuplas sin formato.

 >>> import collections >>> Point = collections.namedtuple('Point', 'x y') >>> x, y = Point(0, 1) >>> p = Point(x, y) >>> x, y, p (0, 1, Point(x=0, y=1)) >>> px, py, p[0], p[1] (0, 1, 0, 1) >>> for i in p: ... print(i) ... 0 1 

En primer lugar, tenga en cuenta que Python permite lo siguiente (sin necesidad de paréntesis):

 q, r = divide(22, 7) 

Respecto a tu pregunta, no hay una regla dura y rápida de ninguna manera. Para ejemplos simples (y generalmente artificiales), puede parecer que siempre es posible que una función determinada tenga un solo propósito, lo que resulta en un solo valor. Sin embargo, al usar Python para aplicaciones del mundo real, rápidamente te encuentras en muchos casos en los que es necesario devolver varios valores y se obtiene un código más limpio.

Entonces, yo diría que hacer lo que tenga sentido, y no tratar de ajustarme a una convención artificial. Python admite varios valores de retorno, así que utilícelo cuando sea apropiado.

El ejemplo que das es en realidad una función incorporada de python, llamada divmod . Así que alguien, en algún momento en el tiempo, pensó que era lo suficientemente python para incluirlo en la funcionalidad principal.

Para mí, si hace que el código sea más limpio, es pythonic. Compara estos dos bloques de código:

 seconds = 1234 minutes, seconds = divmod(seconds, 60) hours, minutes = divmod(minutes, 60) seconds = 1234 minutes = seconds / 60 seconds = seconds % 60 hours = minutes / 60 minutes = minutes % 60 

Sí, devolver múltiples valores (es decir, una tupla) es definitivamente pythonic. Como han señalado otros, hay muchos ejemplos en la biblioteca estándar de Python, así como en proyectos Python muy respetados. Dos comentarios adicionales:

  1. Devolver múltiples valores es a veces muy, muy útil. Tome, por ejemplo, un método que opcionalmente maneja un evento (devolviendo algo de valor al hacerlo) y también devuelve el éxito o el fracaso. Esto podría surgir en un patrón de cadena de responsabilidad. En otros casos, desea devolver múltiples datos estrechamente vinculados, como en el ejemplo dado. En esta configuración, devolver varios valores es similar a devolver una instancia única de una clase anónima con varias variables miembro.
  2. El manejo de los argumentos de los métodos por parte de Python requiere la capacidad de devolver directamente varios valores. En C ++, por ejemplo, los argumentos de los métodos se pueden pasar por referencia, por lo que puede asignarles valores de salida, además del valor de retorno formal. En Python, los argumentos se pasan “por referencia” (pero en el sentido de Java, no C ++). No puede asignar nuevos valores a los argumentos de método y hacer que se reflejen fuera del scope del método. Por ejemplo:

     // C++ void test(int& arg) { arg = 1; } int foo = 0; test(foo); // foo is now 1! 

    Comparar con:

     # Python def test(arg): arg = 1 foo = 0 test(foo) # foo is still 0 

Definitivamente es un python. El hecho de que puede devolver varios valores desde una función que tenía en un lenguaje como C, donde debe definir una estructura para cada combinación de tipos que devuelva en algún lugar.

Sin embargo, si alcanza el punto en el que está devolviendo algo loco como 10 valores desde una sola función, debería considerar seriamente agruparlos en una clase porque en ese momento se vuelve difícil de manejar.

Devolver una tupla es genial. También tenga en cuenta la nueva stack con nombre que se agregó en Python 2.6 que puede hacer esto más aceptable para usted: http://docs.python.org/dev/library/collections.html#collections.namedtuple

OT: Algol68 de RSRE tiene el curioso operador “/: =”. p.ej.

 INT quotient:=355, remainder; remainder := (quotient /:= 113); 

Dando un cociente de 3, y un rest de 16.

Nota: por lo general, el valor de “(x /: = y)” se descarta ya que el cociente “x” se asigna por referencia, pero en el caso de RSRE el valor devuelto es el rest.

cf Aritmética de enteros – Algol68

Está bien devolver múltiples valores usando una tupla para funciones simples como divmod . Si hace que el código sea legible, es Pythonic.

Si el valor de retorno comienza a ser confuso, verifique si la función está haciendo demasiado y divídala si es así. Si una tupla grande se usa como un objeto, conviértalo en un objeto. Además, considere usar tuplas con nombre , que formarán parte de la biblioteca estándar en Python 2.6.

Soy bastante nuevo en Python, pero la técnica de la tupla me parece muy pythonica. Sin embargo, he tenido otra idea que puede mejorar la legibilidad. El uso de un diccionario permite acceder a los diferentes valores por nombre en lugar de por posición. Por ejemplo:

 def divide(x, y): return {'quotient': x/y, 'remainder':x%y } answer = divide(22, 7) print answer['quotient'] print answer['remainder']