2010-03-20 9 views
5

Supongamos que tengo una función que puede tomar un iterable/iterador o un iterable como argumento. Iterability se comprueba con try: iter(arg).Método de Python para eliminar la iterabilidad

Dependiendo de si la entrada es iterable o no, el resultado del método será diferente. No cuando quiero pasar una entrada no iterable como iterable, es fácil de hacer: simplemente lo envolveré con una tupla.

¿Qué hago cuando quiero pasar un iterable (una cadena por ejemplo) pero quiero que la función lo tome como si no fuera iterable? P.ej. hacer que iter(str) falle.

Editar - mi intención original:

quería generalizar la función zip en que se pueda comprimir iterables con los no iterables. El no iterable sería entonces repeat mismo tan a menudo como los otros iterables no han terminado.

La única solución general para mí parece ser ahora, que no debería verificar dentro de la función general_zip (debido a los problemas de cadena); pero en su lugar tendré que agregar el iterador repeat al argumento antes de llamando al zip. (Esto en realidad me evita inventar la función general_zip, aunque aún podría hacerlo porque con una entrada no iterable sería inequívoco sin la repetición adicional.)

+3

La forma de Python es para que la persona que llama sea explícita, y convierta el elemento no iterable en iterable. 'zip (mi_lista, itertools.repeat (42))' Esto es lo mismo que tener que escribir '42 + int ('100')' cuando desee agregar un int y una cadena. Agregar conversiones mágicas conduce a adivinar y confundir. –

+0

Sí, claro. Pero necesito llamar a esta función muchas veces y luego tendría que hacer la comprobación cada vez antes de llamarla. Esto parece un poco redundante. - Entonces, la razón por la que necesito esta función es bastante estrecha y bien definida, sin embargo, me gustaría que las capacidades potenciales de esa función sean lo más generales posible. – Debilski

Respuesta

3

Cuanto más lo pienso, parece que no es posible prescindir de verificar tipos o pasarle argumentos a la función.

Sin embargo, dependiendo de la intención de la función, una manera de manejar la situación podría ser:

from itertools import repeat 
func(repeat(string_iterable)) 

func sigue viendo un iterable pero no va a repetir por los charaters de la propia cadena. Y efectivamente, el argumento funciona como si fuera una constante no iterable.

+0

'repeat (string_iterable)' devolverá la cadena sin fin. ¿Quisiste decir '[string_iterable]' (devolverá la cadena solo una vez)? – jfs

+0

No, para mi problema [string_iterable] sería la solución incorrecta. La analogía debería ser la del punto en una dimensión que, cuando la expandes en dos dimensiones, corresponde a una línea completa de puntos y no a una sola. – Debilski

0

Especialícela.

def can_iter(arg): 
    if isinstance(arg, str): 
    return False 
    try: 
    ... 
+0

Pero esto significa que necesito decidir dentro de la función y no puedo tomar una nueva decisión de una ocasión a otra. – Debilski

2

Whoo! Parece que quieres poder pasar iterables como iterables, iterables como no elegibles, no heredables como iterables, y noniterables como no heredables. ya que desea ser capaz de manejar todas las posibilidades, y el ordenador no puede (aún) leer la mente, ustedes van a tener que decirle a la función de cómo desea que el argumento para ser manejado:

def foo_iterable(iterable): 
    ... 
def foo_noniterable(noniterable): 
    ... 

def foo(thing,isiterable=True): 
    if isiterable: 
     foo_iterable(thing) 
    else: 
     foo_noniterable(thing) 

Aplicar foo a un iterable

foo(iterable) 

Aplicar foo a un iterable como noniterable:

foo_noniterable(iterable)  # or 
foo(iterable, isiterable=False) 

Aplicar foo a un noniterable como noniterable:

foo_noniterable(noniterable)  # or 
foo(noniterable,isiterable=False) 

Aplicar foo a un noniterable como un iterable:

foo((noniterable,)) 

PS. Soy un creyente en pequeñas funciones que hacen bien un solo trabajo. Son más fáciles de depurar y de realizar pruebas unitarias. En general, aconsejaría evitar las funciones monolíticas que se comportan de manera diferente según el tipo. Sí, le impone al desarrollador una pequeña carga adicional para llamar exactamente a la función que se pretende, pero creo que las ventajas en términos de depuración y pruebas unitarias más que lo compensan.

+0

El problema es que la función puede tener varios argumentos; se complicaría un poco entonces. – Debilski

+0

@Debilski: ¿Por qué no agregar varios argumentos a 'foo'? Tal vez no sé lo suficiente acerca de su situación. ¿Por qué es complicado? – unutbu

+0

No lo sé. Parece que esto terminaría así: 'foo ([1,2,3], [1,2,3]," abc ", isiterable1 = True, isiterable2 = True, isiterable3 = False)'. O con más argumentos, incluso. Creo que su solución estaría bien con solo uno o dos argumentos fijos; mi situación es que me gustaría que sea más general. (De lo contrario, creo que no me hubiera importado preguntar de todos modos y lo hubiera hecho más o menos como usted propuso). – Debilski

0

Bueno, un enfoque para decirle a la función cómo le gustaría tratar sus argumentos es tener valores predeterminados razonables (haciendo que la función lo trate todo por su tipo original por defecto), mientras que puede especificar los ajustes que desee con comodidad (es decir, con un corto y ausente por defecto cadena fmt), como:

def smart_func(*args, **kw): 
    """If 'kw' contains an 'fmt' parameter, 
    it must be a list containing positions of arguments, 
    that should be treated as if they were of opposite 'kind' 
    (i.e. iterables will be treated as non-iterables and vise-versa) 

    The 'kind' of a positional argument (i.e. whether it as an iterable) 
    is inferred by trying to call 'iter()' on the argument. 
    """ 

    fmt = kw.get('fmt', []) 

    def is_iter(it): 
     try: 
      iter(it) 
      return True 
     except TypeError: 
      return False 

    for i,arg in enumerate(args): 
     arg_is_iterable = is_iter(arg) 
     treat_arg_as_iterable = ((not arg_is_iterable) 
           if (i in fmt) else arg_is_iterable) 
     print arg, arg_is_iterable, treat_arg_as_iterable 

esto da:

>>> smart_func() 
>>> smart_func(1, 2, []) 
1 False False 
2 False False 
[] True True 
>>> smart_func(1, 2, [], fmt=[]) 
1 False False 
2 False False 
[] True True 
>>> smart_func(1, 2, [], fmt=[0]) 
1 False True 
2 False False 
[] True True 
>>> smart_func(1, 2, [], fmt=[0,2]) 
1 False True 
2 False False 
[] True False 

Ampliando esta función (la búsqueda de la longitud de la iterables más largo, etc), una puede construir un smart-zip de los que está hablando.

[PS] Otra forma será la de llamar a la función de la siguiente manera:

smart_func(s='abc', 1, arr=[0,1], [1,2], fmt={'s':'non-iter','some_arr':'iter'}) 

y tienen la función coincide con los nombres de los argumentos que se suministra ('s' y 'arr', nota, hay no hay nombres en la firma de funciones, ya que es el mismo que el anterior) al 'fmt' "sugerencias de tipo" (es decir, 'iter' hace un argumento considerado como iterable, y 'non-iter' como no iterable). Este enfoque puede, por supuesto, combinarse con el anterior "tipo palanca".

+0

No hay ninguna razón para usar funciones anidadas allí. Las funciones anidadas son útiles para hacer cierres, pero usarlas para definir funciones constantes localmente es una tontería. –

+1

@Mike Graham El motivo es cumplir el contrato de la función (especificado en su documento por cierto). Es cierto que la función en cuestión se puede mover al exterior (por ejemplo, para que sea reutilizable), ya que en realidad no depende de argumentos locales. Pero si es tonto ... bueno, que todos lo elijan por sí mismo, señor :) – mlvljr

+0

Bueno, ahora veo que 'smart_func' es posiblemente demasiado inteligente [para ser votado a favor] :)) – mlvljr

0

No verificar la iterabilidad. Es un error tener una función que compruebe cosas sobre sus tipos de elementos/capacidades para que una sola función realice tareas diferentes. Si quieres hacer dos cosas diferentes, realiza dos funciones diferentes.

Suena como que han llegado a esta conclusión a sí mismo ya está proporcionando una API consistente, en el que hacer

from itertools import repeat 
zip([1, 2, 3], repeat(5), "bar") 

Tenga en cuenta que es casi siempre inútil para hacer esto ya que sólo se podría hacer

five = 5 
for number, letter in zip([1, 2, 3], "bar") 
    # Just use five here since it never changes 

A menos que por supuesto esté alimentando esto a algo que ya usa zip.

+0

La entrada que me está dando' cinco 'bien podría ser una lista. Y esto ocurre en varios puntos de mi código, por lo que no quiero comprobar esto todo el tiempo antes de llamar a la función de compresión, sino realizar la verificación dentro. Mi problema era cómo lidiar con casos extremos. – Debilski

+0

@Debilski Si desea que su función sea "preempaquetada" con lógica de inspección iterable/no iterable, así como también para proporcionar la capacidad de "anular"/intercambiar la forma en que se procesa un argumento, ¿por qué no utilizar un simple (y vaciar por predeterminado) 'format' specifier? – mlvljr

+0

@Debilski, Derecha, por supuesto. Y claramente has descubierto la manera de pasar lo que realmente necesitas, un iterable que produce el mismo valor una y otra vez. –

Cuestiones relacionadas