La forma canónica de devolver múltiples valores en idiomas que lo admiten es a menudo tupling .
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 desea devolver cuatro o cinco valores? Claro, podrías seguir tuplándolos, pero es fácil olvidar qué valor es dónde. También es bastante feo desempacarlos 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 mediante a dict
.
Considera lo siguiente:
def g(x):
y0 = x + 1
y1 = x * 3
y2 = y0 ** y3
return {'y0': y0, 'y1': y1 ,'y2': y2}
(Para ser claros, y0, y1 e y2 son solo identificadores abstractos. Como se señaló, en la práctica se usarían identificadores significativos).
Ahora, tenemos un mecanismo por 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 cambio, podríamos devolver una estructura especializada. Lo he enmarcado en el contexto de Python, pero estoy seguro de que también se aplica a otros lenguajes. De hecho, si estuvieras trabajando en C, esta podría ser tu ú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 fontanería, después de todo, { y0, y1, y2 }
terminan siendo entradas en el interior __dict__
del 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 mantener 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, mecanografía y otras herramientas útiles agregadas automáticamente:
@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 había pasado por alto proviene de Bill el Lagarto:
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 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 listas y tuplas en Python es que las listas son mutables , mientras que las tuplas no lo son.
Personalmente, tiendo a transferir las convenciones de la programación funcional: use 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 inevitable pregunta. ¿Qué método (crees) es el mejor?
fuente
y3
, pero a menos que y3 se declare global, ¿esto produciríaNameError: global name 'y3' is not defined
tal vez solo uso3
?y3
, que también hará el mismo trabajo.Respuestas:
Se agregaron tuplas con nombre en 2.6 para este propósito. También vea os.stat para un ejemplo incorporado similar.
En versiones recientes de Python 3 (3.6+, creo), la nueva
typing
biblioteca consiguió que laNamedTuple
clase hiciera las tuplas con nombre más fáciles de crear y más poderosas. Heredar de letyping.NamedTuple
permite usar cadenas de documentos, valores predeterminados y anotaciones de tipo.Ejemplo (de los documentos):
fuente
namedtuple
es tener una huella de memoria más pequeña para resultados masivos (largas listas de tuplas, como los resultados de consultas DB). Para elementos individuales (si la función en cuestión no se llama con frecuencia), los diccionarios y las clases también están bien. Pero las tuplas con nombre también son una solución agradable / más agradable en este caso.namedtuple
definiciones (cada llamada crea una nueva), la creación de lanamedtuple
clase es relativamente costosa tanto en la CPU como en la memoria, y todas las definiciones de clase implican intrínsecamente referencias cíclicas (por lo tanto, en CPython, está esperando una ejecución cíclica de GC para que sean liberados). También hace que sea imposible parapickle
la clase (y, por lo tanto, imposible usar instanciasmultiprocessing
en la mayoría de los casos). Cada creación de la clase en mi 3.6.4 x64 consume ~ 0.337 ms, y ocupa poco menos de 1 KB de memoria, eliminando cualquier ahorro de instancia.namedtuple
clases ; los costos de CPU caen aproximadamente un factor de 4x , pero siguen siendo aproximadamente 1000x más altos que el costo de crear una instancia, y el costo de memoria para cada clase sigue siendo alto (me equivoqué en mi último comentario sobre "menos de 1 KB" para la clase,_source
en sí mismo suele ser de 1.5 KB;_source
se elimina en 3.7, por lo que es probable que esté más cerca del reclamo original de poco menos de 1 KB por creación de clase).Para proyectos pequeños, me resulta más fácil trabajar con tuplas. Cuando eso se vuelve demasiado difícil de administrar (y no antes) empiezo a agrupar cosas en estructuras lógicas, sin embargo, creo que su uso sugerido de diccionarios y
ReturnValue
objetos es incorrecto (o demasiado simplista).La devolución de un diccionario con las teclas
"y0"
,"y1"
,"y2"
, etc no ofrece ninguna ventaja sobre tuplas. Devolviendo unReturnValue
instancia con propiedades.y0
,.y1
,.y2
, etc no ofrece ninguna ventaja sobre tuplas tampoco. Necesitas comenzar a nombrar cosas si quieres llegar a cualquier parte, y puedes hacerlo usando tuplas de todos modos: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 obtienes de
re.match()
oopen(file)
.fuente
size, type, dimensions = getImageData(x)
y(size, type, dimensions) = getImageData(x)
? Es decir, ¿hace alguna diferencia envolver el lado izquierdo de una tarea tupulada?(1)
es un int while(1,)
o1,
es una tupla.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.
da:
fuente
type(f())
vuelve<class 'tuple'>
.tuple
explícito el aspecto; no es realmente importante que devuelva untuple
, este es el idioma para devolver el período de valores múltiples. La misma razón por la que omite los parens con el idioma de intercambiox, y = y, x
, inicialización múltiplex, y = 0, 1
, etc. claro, hacetuple
s bajo el capó, pero no hay razón para hacerlo explícito, ya que lostuple
s no son el punto en absoluto. El tutorial de Python introduce asignaciones múltiples mucho antes de que incluso toquetuple
s.=
es una tupla en Python con o sin paréntesis a su alrededor. Entonces, en realidad no hay explícito o implícito aquí. a, b, c es tanto una tupla como (a, b, c). Tampoco se hace una tupla "debajo del capó" cuando se devuelven dichos valores, porque es simplemente una tupla simple. El OP ya mencionó las tuplas, por lo que en realidad no hay diferencia entre lo que mencionó y lo que muestra esta respuesta. NingunoYo voto por el diccionario.
Me parece que si hago una función que devuelve más de 2-3 variables, las doblaré 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 a través del código para averiguar qué es)
Si le preocupa la búsqueda de tipos, use claves descriptivas del diccionario, por ejemplo, 'lista de valores x'.
fuente
result = g(x); other_function(result)
Otra opción sería usar generadores:
Aunque las tuplas de la OMI suelen ser mejores, excepto en los casos en que los valores que se devuelven son candidatos para la encapsulación en una clase.
fuente
yield
?def f(x): …; yield b; yield a; yield r
vs.(g for g in [b, a, r])
, y ambos se convertirán fácilmente en listas o tuplas, y como tal admitirán el desempaquetado de tuplas. La forma del generador de tuplas sigue un enfoque funcional, mientras que la forma de la función es imprescindible y permitirá el control del flujo y la asignación de variables.Prefiero usar tuplas cuando una tupla se siente "natural"; Las coordenadas son un ejemplo típico, donde los objetos separados pueden sostenerse por sí mismos, por ejemplo, en cálculos de escala de un solo eje, y el orden es importante. Nota: si puedo ordenar o barajar los elementos sin un efecto adverso al 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 iguales. Piensa en encabezados de correo electrónico opcionales.
Para el resto de los casos, donde los objetos agrupados tienen un significado inherente dentro del grupo o se necesita un objeto completo con sus propios métodos, utilizo una clase.
fuente
Yo prefiero:
Parece que todo lo demás es solo código extra para hacer lo mismo.
fuente
fuente
En general, la "estructura especializada" en realidad ES un estado actual sensible de un objeto, con sus propios métodos.
Me gusta encontrar nombres para estructuras anónimas siempre que sea posible. Los nombres significativos aclaran las cosas.
fuente
Las tuplas, los dictados y los objetos de Python le ofrecen al programador una compensación suave entre formalidad y conveniencia para pequeñas estructuras de datos ("cosas"). Para mí, la elección de cómo representar una cosa está dictada principalmente por cómo voy a usar la estructura. En C ++, es una convención común usar
struct
para elementos solo de datos yclass
para objetos con métodos, aunque legalmente puede poner métodos en unstruct
; mi hábito es similar en Python, condict
ytuple
en lugar destruct
.Para conjuntos de coordenadas, usaré un en
tuple
lugar de un puntoclass
o undict
(y tenga en cuenta que puede usar untuple
como clave de diccionario, por lo quedict
s hacen grandes conjuntos dispersos multidimensionales).Si voy a iterar sobre una lista de cosas, prefiero desempaquetar
tuple
s en la iteración:... ya que la versión del objeto está más abarrotada para leer:
... y mucho menos el
dict
.Si la cosa se usa ampliamente y te encuentras realizando operaciones similares no triviales en varios lugares del código, entonces generalmente vale la pena convertirlo en un objeto de clase con los métodos adecuados.
Finalmente, si voy a intercambiar datos con componentes del sistema que no son de Python, con mayor frecuencia los guardaré en un
dict
porque es lo más adecuado para la serialización JSON.fuente
+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 contenedor, y los resultados son "ligeros y no requieren más memoria que las tuplas normales".
fuente
En lenguajes como Python, normalmente usaría un diccionario ya que implica menos sobrecarga que crear una nueva clase.
Sin embargo, si me encuentro constantemente devolviendo el mismo conjunto de variables, entonces eso probablemente implique una nueva clase que factorizaré.
fuente
Usaría un dict para pasar y devolver valores de una función:
Use la forma variable como se define en la forma .
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 ellas) cada vez que realiza un cambio en su código.
fuente
test
modificará directamente el valor. Compare esto condict.update
, que no devuelve un valor.form
no perjudica la legibilidad ni el rendimiento. En los casos en que necesiteform
volver a formatear , devolverlo [formulario] se asegura de queform
se devuelva el último porque no realizará un seguimiento de los cambios en el formulario en ningún lugar."Lo mejor" es una decisión parcialmente subjetiva. Use tuplas para pequeños conjuntos de retorno en el caso general donde un inmutable es aceptable. Una tupla siempre es preferible a una lista cuando la mutabilidad no es un requisito.
Para valores de retorno más complejos, o para el caso donde la formalidad es valiosa (es decir, código de alto valor), una tupla nombrada es mejor. 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 tiene naturalmente al final de la función (p. Ej., Patrón de fábrica), devuelva el objeto.
Como dijo el sabio:
fuente