[pyar] función con valores de retorno distintos

Daniel Moisset dmoisset en machinalis.com
Mar Jul 29 15:14:57 ART 2014


Mirá, el tema es complicado, y como todos los temas complicados la
respuesta a la pregunta es "depende". Depende esencialmente de que la
funcion sea comoda de usar para el que la invoca.

Por un lado, la idea de "tipos distintos" en python es difusa. Alguna
funcion puede estar devolviendo a veces un generator object, un generator
expression, o un iterador de listas, y probablemente nadie se da cuenta,
porque todo lo que hacen afuera es iterarlo. Entonces si bien son tipos
distintos (en el sentido de que type(f(x)) da distintos valores), son el
mismo tipo en el sentido "abstracto" de tipado (tienen tipo "cosas que se
pueden iterar"). Ese tipo de variación es super normal. No solo con listas,
sino con cualquieras dos cosas que tengan interfaces parecidas.

En este caso hay que tener cuidado con lo que documentas que se devuelvo.
Una funcion que a veces devuelve un iterador, y otras veces una lista sin
un criterio claro, corre el riesgo de que el llamador ponga x =
mifuncion()[17], y a veces funcione y otras no.

si el criterio esta claro no jode. por ej. si la funcion tiene un argumento
"iterable" que cuando le pasas False te garantiza que te da una lista, esta
todo ok.

A veces los tipos de retorno posibles no tienen nada que ver, pero estan
claros para el llamador. Fiajte por ejemplo el builtin max de python:

max(1, 36, -7) == 36
max("pan", "salame", "queso") == "salame"

si bien cadenas y enteros son tipos muy incompatibles, no hay ninguna
confusion desde el codigo de que esperar en el resultado (en los casos
"normales", en python 2 podes poner max('x','y',37) y da algun resultado).
En este ejemplo tenes una variacion de tipos mas similar a genericidad que
a multiples implementaciones de una interfaz.

Con las cantidades es parecido. Si tenes una funcion que se puede usar

value = get_some_value(...)
value, metadata = get_some_value(..., with_metadata=True)

es bastante razonable. Me parece que es parecido a tu caso, vos sabes
cuantos header_lines (adivino que usualmente es una constante en la
llamada), aunque tu caso tiene algunas particularidades.

En general, si tenes un if "innecesario" despues de la llamada para
enterarte de que carancho devolvió, puede que estes en uno de los casos
"malos". Si en tu ejemplo, header_lines tipicamente viene de un archivo de
configuracion, va s a tener que hacer

header, *lines = _get_lines(infile, config.header_lines)
if header is None: ...
elif isinstance(header, list): ...
else: ...

Fijate que acá ya se pone bastante "turbio" el codigo del llamador. Sobre
todo porque es medio dificil distinguir entre una lista y una cadena
(tienen interfaces muy parecidas). Pero todo depende, recien puse el
"innecesario" a lado del if, porque una funcion puede devolver o bien un
objeto o bien None en situaciones bien distintas, y ahi vas a tener dos
casos que por la naturaleza del problema conviene manejar distinto. Igual
si encontras una generalización te suele acabar ahorrando trabajo

El resumen es: los tipos pueden variar, las cantidades pueden variar, pero
usar la funcion tiene que ser "comodo"

Volviendo a tu ejemplo, yo capaz hubiera hecho:


return ret[:header_lines], ret[header_lines:]

y luego de eso lo uso así:

_, lines = _get_lines(infile, header_lines=0)
[header], lines = _get_lines(infile, header_lines=1)
[header1, header2], lines = _get_lines(infile, header_lines=2)

Y en el caso general

headers, lines = _get_lines(infile, header_lines=expresion que sale de
algun lado desconocido)
for h in headers: ...

Pero estoy adivinando, y todo depende no de la funcion sino del lugar donde
se usa. Si es un problema superaacotado y vos estas segurisisisisimo de que
a header_lines= le pazas siempre una constante, tu approach esta barbaro.

Saludos,
    D.




2014-07-29 14:33 GMT-03:00 Matias Graña <matias.alejo en gmail.com>:

> Gracias!
> Pensé en algo así y está lindo, pero todo el programa es cortito y creo
> que no vale la pena extenderlo con nuevas clases. Esta es para mí LA
> solución en algo más complejo.
>
> El punto central para mí es: es buena o mala práctica tener un método
> cuyos valores de retorno cambian (cambia la cantidad y/o los tipos) en
> función de los parámetros?
>
> -- Matías Graña
>
>
> 2014-07-29 11:39 GMT-03:00 Andres Riancho <andres.riancho en gmail.com>:
>
> Capaz podes meter todo dentro de un objeto "Linea", devolver siempre
>> Linea(s) y sobre ellas hacer:
>>
>> linea.get_header() => None, lista, string
>> linea.get_header_type() => Alguno de una lista de [NO_HEADER,
>> HEADER_LIST, HEADER_STRING]
>>
>> Y despues podrías en tu codigo tener algo que en base al tipo y al
>> header lo procese.
>>
>> FMAP = {NO_HEADER: funcion_a,
>>               HEADER_LIST: funcion_b,
>>               HEADER_STRING: funcion_c}
>>
>> def procesa_lineas(linea):
>>     return FMAP[linea.get_header_type()](linea)
>>
>>
>> 2014-07-29 11:28 GMT-03:00 Matias Graña <matias.alejo en gmail.com>:
>> > Hola;
>> > tengo una función que lee un archivo, hace alguna cosa y devuelve el
>> > contenido separado por líneas y, eventualmente, separando una línea (o
>> más)
>> > de headers.
>> > Se puede hacer así (versión simplificada):
>> >
>> > def _get_lines(infile, header_lines=1):
>> >     with open(infile, 'r') as data:
>> >         ret = [blabla(line) for line in data]
>> >     header = None if not header_lines else ret[0] if header_lines == 1
>> else
>> > ret[:header_lines]
>> >     return header, ret[header_lines:]
>> >
>> > Eso se llama con
>> > header, lines = _get_lines(infile)
>> > El punto es que header puede ser None, un str o una lista de str, en
>> función
>> > del valor de header_lines. La pregunta es (y es más amplia que este
>> ejemplo
>> > puntual) si es razonable que los valores de retorno de una función
>> cambien
>> > el tipo de esta manera.
>> > Esta forma de hacerlo tiene la ventaja de que los casos de uso típico
>> van a
>> > ser con header_lines = 0 o 1 y entonces en esos casos es cómodo. Pero
>> si en
>> > algún momento cambia algo y header_lines debe ser > 1, hay que cambiar
>> el
>> > código un poco más que si en el return tuviera
>> >
>> > return ret[:header_lines], ret[header_lines:]
>> >
>> > Otra opción, que tiene sus pros y sus contras también, es
>> >
>> > return tuple(ret[:header_lines]) + (ret[header_lines:],)
>> >
>> > Eso hace que deba llamarse con
>> >
>> > lines = _get_lines(infile, header_lines=0)
>> > header, lines = _get_lines(infile, header_lines=1)
>> > header1, header2, lines = _get_lines(infile, header_lines=2)
>> >
>> > etc. Acá lo que cambia es la cantidad de datos a devolver, y también
>> puede
>> > ser propenso a errores. En python3 sería un poco más sencillo, porque
>> puedo
>> > devolver directamente tuple(ret), sin calentarme por header_lines, y
>> obligar
>> > al llamado a que sea del estilo de
>> >
>> > header, *lines = _get_lines(infile)
>> >
>> > Lamentablemente, python3 no es una posibilidad acá. Imagino que no hay
>> una
>> > única manera correcta de hacer las cosas, pero argumentos a favor o
>> encontra
>> > de estas opciones son bienvenidos.
>> >
>> > Gracias!
>> >
>> > -- Matías Graña
>> >
>> > _______________________________________________
>> > pyar mailing list pyar en python.org.ar
>> > http://listas.python.org.ar/listinfo/pyar
>> >
>> > PyAr - Python Argentina - Sitio web: http://www.python.org.ar/
>> >
>> > La lista de PyAr esta Hosteada en USLA - Usuarios de Software Libre de
>> > Argentina - http://www.usla.org.ar
>>
>>
>>
>> --
>> Andrés Riancho
>> Project Leader at w3af - http://w3af.org/
>> Web Application Attack and Audit Framework
>> Twitter: @w3af
>> GPG: 0x93C344F3
>> _______________________________________________
>> pyar mailing list pyar en python.org.ar
>> http://listas.python.org.ar/listinfo/pyar
>>
>> PyAr - Python Argentina - Sitio web: http://www.python.org.ar/
>>
>> La lista de PyAr esta Hosteada en USLA - Usuarios de Software Libre de
>> Argentina - http://www.usla.org.ar
>>
>
>
> _______________________________________________
> pyar mailing list pyar en python.org.ar
> http://listas.python.org.ar/listinfo/pyar
>
> PyAr - Python Argentina - Sitio web: http://www.python.org.ar/
>
> La lista de PyAr esta Hosteada en USLA - Usuarios de Software Libre de
> Argentina - http://www.usla.org.ar
>
------------ próxima parte ------------
Se ha borrado un adjunto en formato HTML...
URL: <http://listas.python.org.ar/pipermail/pyar/attachments/20140729/238f64d7/attachment.html>


More information about the pyar mailing list