2009-09-13 18 views
11

Tengo una función cuyo argumento de entrada puede ser un elemento o una lista de elementos. Si este argumento es un elemento único, lo pongo en una lista para que pueda iterar sobre la entrada de manera consistente.manera pitónica de convertir la variable a la lista

Actualmente tengo unas pocas cosas:

def my_func(input): 
    if not isinstance(input, list): input = [input] 
    for e in input: 
     ... 

estoy trabajando con una API existente, de modo que no puede cambiar los parámetros de entrada. Usar isinstance() se siente raro, ¿así que hay una forma correcta de para hacer esto?

+2

re: proper. En Python, el término usual es 'Pythonic'. Reconoce que hay muchas maneras de hacer algo, pero que algunas están más en el espíritu del lenguaje. –

Respuesta

6

Me gusta la sugerencia de Andrei Vajna de hasattr(var,'__iter__'). Tenga en cuenta estos resultados de algunos tipos de Python típicos:

>>> hasattr("abc","__iter__") 
False 
>>> hasattr((0,),"__iter__") 
True 
>>> hasattr({},"__iter__") 
True 
>>> hasattr(set(),"__iter__") 
True 

Esto tiene la ventaja añadida de tratar una cadena como un no iterable - cadenas son una zona gris, como a veces se quiere tratar como un elemento, otras veces como una secuencia de personajes.

Tenga en cuenta que en Python 3 del str tipo hace tienen el atributo __iter__ y esto no funciona:

>>> hasattr("abc", "__iter__") 
True 
+0

¿No te refieres a mi sugerencia? – Unknown

+0

Mi comentario sobre la respuesta de Peter fue primero. ¡Decir ah! :PAG –

-1

Esto parece una forma razonable de hacerlo. Estás queriendo probar si el elemento es una lista, y esto lo hace directamente. Se vuelve más complicado si quieres apoyar a otros tipos de datos 'lista-como', también, por ejemplo:

isinstance(input, (list, tuple)) 

o, más generalmente, abstraer la pregunta:

def iterable(obj): 
    try: 
    len(obj) 
    return True 
    except TypeError: 
    return False 

pero de nuevo, en resumen, tu método es simple y correcto, ¡lo cual me parece bien!

+2

Creo que puedes usar algo como 'hasattr (a, '__iter __')' para ver si se trata de un tipo de datos 'similar a una lista'. –

+1

-1. Los Iterables no necesariamente tienen un __len__, ni son necesariamente finitos. – Triptych

0

Puede hacer comparaciones de tipo directo usando type().

def my_func(input): 
    if not type(input) is list: 
     input = [input] 
    for e in input: 
     # do something 

Sin embargo, la forma en que lo tienes permitirá cualquier tipo derivado de tipo list a pasar a través. Por lo tanto, evita que los tipos derivados se envuelvan accidentalmente.

+0

este tipo de comparación directa no es una gran idea: si un usuario subclasifica 'lista', por ejemplo, su comparación basada en' tipo() 'se romperá inmediatamente. 'isinstance()' está más cerca de lo que quiere el OP. – Peter

+0

Exactamente, estaba dando alternativas, pero mi observación "sin embargo" le dice que su camino es aún mejor. – Soviut

0

Su enfoque parece correcto para mí.

Es similar a la forma en que usa atom? en Lisp cuando itera sobre listas y comprueba el elemento actual para ver si es una lista o no, porque si es una lista también quiere procesar sus elementos.

Así que, sí, no veo nada de malo en eso.

2

Usted puede poner * antes de que su argumento, así que siempre obtendrá una tupla:

def a(*p): 
    print type(p) 
    print p 

a(4) 
>>> <type 'tuple'> 
>>> (4,) 

a(4, 5) 
>>> <type 'tuple'> 
>>> (4,5,) 

Pero eso le obligará a llamar a su función con parámetros variables, que no sé si eso s aceptable para ti

+0

Esto no funciona para él porque dijo que no puede cambiar los argumentos y que alguien podría ingresar [1,2] y estaría doblemente envuelto como ([1,2],) – Unknown

+0

Sí, lo siento, no lo hice vio la parte donde dijo que estaba trabajando en una API. mi culpa. – attwad

11

Por lo general, las cadenas (plano y Unicode) son los únicos iterables que desee tener en cuenta, sin embargo, como "elementos individuales" - existe la basestring incorporado específicamente para permitir que se prueba para uno u otro tipo de cuerdas con isinstance, así que es muy ONU -grotty para ese caso especial ;-).

Así que mi enfoque sugerido para el caso más general es:

if isinstance(input, basestring): input = [input] 
    else: 
    try: iter(input) 
    except TypeError: input = [input] 
    else: input = list(input) 

Ésta es la manera de tratar a cada iterables excepto las series como una lista directamente, cadenas y números y otros no iterables como escalares (siendo normalizado en listas de elementos individuales).

Estoy haciendo explícitamente una lista de cada tipo de iterable para que sepa que puede realizar CUALQUIER tipo de truco de lista: ordenar, iterar más de una vez, agregar o eliminar elementos para facilitar la iteración, etc., todo sin alterando la lista de entrada REAL (si la lista era ;-). Si todo lo que necesita es un solo bucle sencillo for entonces ese último paso no es necesario (y de hecho poco útil si, por ejemplo entrada es un archivo abierto enormes) y me gustaría sugerir un generador auxiliar en su lugar:

def justLoopOn(input): 
    if isinstance(input, basestring): 
    yield input 
    else: 
    try: 
     for item in input: 
     yield item 
    except TypeError: 
     yield input 

ahora en todos y cada uno una de sus funciones que necesitan dicha normalización argumento, sólo tiene que utilizar:

for item in justLoopOn(input): 

puede utilizar un normalizador-función auxiliar incluso en el otro caso (donde se necesita una lista de bienes a efectos más nefastos); En realidad, en tales casos (raros), sólo puede hacer:

thelistforme = list(justLoopOn(input)) 

por lo que la lógica de normalización (inevitablemente) un poco peludo es en un solo lugar, como debe ser -)

+0

Llamar 'iter (input)' e ignorar su resultado no es un desperdicio de recursos? ¿No sería mejor si solo comprueba la existencia del atributo '__iter__'? –

+0

@Cristian, no creo que sea costoso, puede ser una llamada a función que hará casi lo mismo que usted sugiere y tampoco se basa en comprobar el atributo mágico '__iter__' –

+3

' try'/'iter (x) '/' except' no es especialmente costoso. Y, 'iter (x)' tiene éxito para algún objeto w/o '__iter__' (por ejemplo, un objeto w/a adecuado' __getitem__' que acepta claves numéricas pero no '__iter__'). –

0

! Esa es una buena forma de hacerlo (no te olvides de incluir tuplas).

Sin embargo, también es posible que desee considerar si el argumento tiene un __iter__ method o __getitem__ method. (Tenga en cuenta que las cadenas han __getitem__ en lugar de __iter__.)

hasattr(arg, '__iter__') or hasattr(arg, '__getitem__') 

Este es probablemente el requisito más general para un tipo que sólo se lista como la comprobación del tipo.

3

En primer lugar, no existe un método general que podría decirle a un "solo elemento" de la "lista de elementos "ya que por lista de definición puede ser un elemento de otra lista.

yo diría que es necesario definir qué tipo de datos que tenga, por lo que es posible que tenga:

  • cualquier descendiente de list contra cualquier otra cosa
    • prueba con isinstance(input, list) (por lo que su ejemplo es correcto)
  • cualquier secuencia excepto cadenas (basestring en Python 2.x, str en Python 3.x)
    • Uso secuencia metaclase: isinstance(myvar, collections.Sequence) and not isinstance(myvar, str)
  • algún tipo de secuencia en contra de los casos conocidos, como int, str, MyClass
    • de prueba con isinstance(input, (int, str, MyClass))
  • cualquier iterable excepto cadenas:
    • prueba con

.

try: 
     input = iter(input) if not isinstance(input, str) else [input] 
    except TypeError: 
     input = [input] 
Cuestiones relacionadas