2009-09-22 15 views
7

(Título y contenido actualizados después de leer la respuesta de Alex)¿Es Pythonic que una función devuelva un iterable o no iterable dependiendo de su entrada?

En general, creo que se considera una mala forma (no pitthonic) para que una función a veces devuelva un elemento iterable y en ocasiones único en función de sus parámetros. Por ejemplo, struct.unpack siempre devuelve una tupla incluso si contiene solo un elemento.

Estoy tratando de finalizar el API para un módulo y tengo algunas funciones que pueden tener uno o más parámetros (a través de *args) como este:

a = s.read(10)  # reads 10 bits and returns a single item 
b, c = s.read(5, 5) # reads 5 bits twice and returns a list of two items. 

de modo que devuelve un solo elemento si hay solo un parámetro; de lo contrario, devuelve una lista. Ahora creo que esto está bien y para nada confuso, pero sospecho que otros pueden estar en desacuerdo.

El caso de uso más común para estas funciones sería sólo quieren un único artículo devuelto, por lo que siempre devolviendo una lista (o tupla) se siente mal:

a, = s.read(10)  # Prone to bugs when people forget to unpack the object 
a = s.read(10)[0]  # Ugly and it's not clear only one item is being returned 

Otra opción es tener dos funciones:

a = s.read(10) 
b, c = s.read_list(5, 5) 

lo cual está bien, pero estorba hasta la API y requiere que el usuario recuerde el doble de funciones sin añadir ningún valor.

Así que mi pregunta es: ¿A veces se devuelve un elemento iterable y, a veces un solo elemento confuso y no-Ptónico? Si es así, ¿cuál es la mejor opción?


actualización: Creo que el consenso general es que es muy travieso sólo devuelve un iterable veces. Creo que la mejor opción para la mayoría de los casos sería devolver siempre el iterable, incluso si contenía solo un elemento.

Habiendo dicho eso, en mi caso particular, creo que voy a dividirme en dos funciones (read(item)/readlist(*items)), y el razonamiento es que creo que el caso del artículo individual ocurrirá con mucha más frecuencia que el caso del artículo múltiple , por lo que es más fácil de usar y la API cambia menos problemática para los usuarios.

Gracias a todos.

Respuesta

12

Si se van a volver iteradores veces, y los objetos individuales en otros, yo diría que el retorno siempre un iterador, por lo que no tiene pensar en eso

Normalmente, usaría esa función en un contexto que espera un iterador, por lo que si tuviera que verificar si era una lista para iterar o un objeto para hacer solo una vez el trabajo, entonces es más fácil simplemente devuelve un iterador e itera siempre, incluso si es una vez.

Si necesita hacer algo diferente si le devuelven un elemento, solo use if len(var):.

Recuerde, la consistencia es un bien valioso.

Me inclino por devolver un objeto consistente, no necesariamente del mismo tipo, pero si devuelvo un iterable, siempre devuelvo un iterable.

+2

+1. A veces ser una cosa y, a veces ser una lista de cosas suele ser un error. Python lo hizo para% -formatting y esto es ampliamente considerado como un error y una trampa desagradable. – bobince

+0

Temía que la gente dijera esto: ¡se siente mal recibir una lista cuando claramente solo has pedido un artículo! –

+0

@Scot Griffiths: En mi humilde opinión, los posibles errores causados ​​por ser demasiado inteligente superan los problemas que una variable simple podría causar. ¿Por qué no usar un método como'def read (a_tuple): 'en vez de usar' * args'? – voyager

0

En las listas de Python son objetos :) Así que no hay coincidencia de tipos

+0

¡Cierto! He editado la pregunta para evitar confusiones. –

1

La única situación en la que haría esto es con una función o método parametrizado, donde uno o más de los parámetros que proporciona la persona que llama determina el tipo devuelto; por ejemplo, una función de "fábrica" ​​que devuelve uno de una familia lógica similar de objetos:

newCharacter = characterFactory("human", "male", "warrior") 

En el caso general, cuando la persona que llama no llega a especificar, yo evitaría la "caja de bombones "comportamiento" :)

+0

En mi caso particular, la cantidad de elementos devueltos es igual a la cantidad de elementos proporcionados en la llamada a la función, por lo que no creo que el usuario se sorprenda por lo que se devuelve. –

2

En general, tendría que decir que devolver dos tipos diferentes es una mala práctica.

Imagine el próximo desarrollador que viene a leer y mantener su código. Al principio él/ella leerá un método usando su función y pensará "Ah, read() devuelve un solo elemento".

Más tarde verán el código que trata el resultado de read() como una lista. En el mejor de los casos, esto simplemente los confundirá y los obligará a examinar el uso de read(). En el peor de los casos, podrían pensar que hay un error en la implementación usando read() e intentar arreglarlo.

Finalmente, una vez que entienden que read() devuelve dos tipos posibles, tendrán que preguntarse "¿hay posiblemente un tercer tipo de devolución para el que necesito estar listo?"

Esto me recuerda el dicho: "Código como si el siguiente tipo para mantener tu código es un maníaco homicida que sabe dónde vives".

1

Puede que no sea una cuestión de "pitonica" sino más bien una cuestión de "buen diseño". Si devuelve cosas diferentes Y nadie tiene que hacer comprobaciones de tipo en ellas, entonces probablemente esté bien. Eso es polimorfismo para ti. OTOH, si la persona que llama tiene que "perforar el velo", entonces tiene un problema de diseño, conocido como una violación del Principio de Sustitución de Liskov. Pythonic o no, claramente no es un diseño OO, lo que significa que será propenso a errores e inconvenientes de programación.

1

hubiera leído (número entero) y read_list (iterable).

De esta manera puede leer (10) y obtener un resultado único y read_list ([5, 5, 10, 5]) y obtener una lista de resultados. Esto es tanto más flexible y explícito.

2

Devolver un solo objeto o una iterable de objetos, dependiendo de los argumentos, es definitivamente difícil de tratar. Pero la pregunta en su título es mucho más general y la afirmación de que la función de la biblioteca estándar evita (o "sobre todo evita") devolver tipos diferentes en función de los argumentos es bastante incorrecta. Hay muchos contraejemplos.

Las funciones copy.copy y copy.deepcopy devuelven el mismo tipo que su argumento, por lo que, por supuesto, "devuelven diferentes tipos según el argumento". "Devolver el mismo tipo que la entrada" es MUY común: se puede clasificar aquí, también, "recuperar un objeto de un contenedor donde se colocó", aunque normalmente eso se hace con un método en lugar de una función ;-) . Pero también, en el mismo orden de ideas, considere itertools.repeat (una vez que usted repite en su iterador devuelto), o, por ejemplo, filter ...:

>>> filter(lambda x: x>'f', 'zaplepidop') 
'zplpiop' 
>>> filter(lambda x: x>'f', list('zaplepidop')) 
['z', 'p', 'l', 'p', 'i', 'o', 'p'] 

filtrar una cadena devuelve una cadena, filtrar una lista devuelve una lista.

Pero espere, hay más! -) Funciones pickle.loads y sus amigos (por ejemplo, en el módulo marshal objetos & c) Devolución de tipos totalmente dependientes del valor que está pasando como argumento. Lo mismo ocurre con la función incorporada eval (y de manera similar input, en Python 2. *). Este es el segundo patrón común: construir o reconstruir un objeto según lo dictado por el valor del argumento (s), de una variedad amplia (o incluso ubicua) de tipos posibles, y devolverlo.

No conozco ningún buen ejemplo del antipatrón específico que ha observado (y creo que es un anti-patrón, moderadamente, no por ninguna razón falsa, solo porque es molesto e inconveniente para tratar con;-). Tenga en cuenta que estos casos que he ejemplificado SON prácticos y prácticos: ¡esa es la discriminación de diseño real en la mayoría de los problemas estándar de la biblioteca! -)

+0

Tiene razón en que la pregunta está formulada de manera muy general, y realmente se trata solo de un problema intereable vs. no entretenido. ¡Supongo que si lo has llamado antipatrón, entonces es el toque de gracia para eso! -) –

Cuestiones relacionadas