Pergunta

Eu tenho uma função cujo argumento de entrada pode ser um elemento ou uma lista de elementos. Se esse argumento é um único elemento, então eu colocá-lo em uma lista para que eu possa interagir sobre a entrada de uma maneira consistente.

Atualmente eu tenho este:

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

Eu estou trabalhando com uma API existente, então não posso mudar os parâmetros de entrada. Usando isinstance () sente hacky, assim há uma correta maneira de fazer isso?

Foi útil?

Solução

Eu como sugestão de hasattr(var,'__iter__') de Andrei Vajna. Nota estes resultados de alguns tipos típicos Python:

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

Isto tem a vantagem de tratar uma string como um não-iterable -. Cordas são uma área cinzenta, como às vezes você quer tratá-los como um elemento, outras vezes como uma seqüência de caracteres

Note que em Python 3 do tipo str faz ter o atributo __iter__ e isso não funciona:

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

Outras dicas

Normalmente, cordas (simples e unicode) são a única iterables que você quer considerar, no entanto, como "elementos isolados" - o builtin basestring existe especificamente para permitir que você teste para qualquer tipo de cordas com isinstance, por isso é muito UN- ofensivo para esse caso especial; -).

Assim, a minha abordagem sugerida para o caso mais geral é:

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

Esta é a maneira de tratar cada iterable EXCETO cadeias como uma lista diretamente, cordas e números e outros não-iterables como escalares (para ser normalizada em listas de item único).

Eu estou fazendo explicitamente uma lista de cada tipo de iterable assim que você sabe que pode mais adiante realizar todo o tipo de truque lista - classificação, repetindo mais de uma vez, adicionando ou removendo itens para facilitar a iteração, etc, tudo sem alterar a lista de entrada real (se a lista de fato era ;-). Se tudo que você precisa é de um único laço for simples, em seguida, a última etapa é desnecessária (e certamente inútil se por exemplo de entrada é um enorme arquivo aberto) e eu sugiro um gerador auxiliar em vez disso:

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

Agora, em cada uma das suas funções que necessitam essa normalização argumento, basta usar:

 for item in justLoopOn(input):

Você pode usar uma função normalizadora auxiliar ainda no outro caso (onde você precisa de uma lista real para outros fins nefastos); na verdade, em tais (raros) casos, você pode simplesmente fazer:

 thelistforme = list(justLoopOn(input))

para que a lógica de normalização (inevitavelmente) pouco-peludo é apenas em um lugar, tal como deve ser -!)

Em primeiro lugar, não existe um método geral que poderia dizer um "elemento único" de "lista de elementos", uma vez por lista de definição pode ser um elemento de uma outra lista.

Eu diria que você precisa definir que tipos de dados que você pode ter, de modo que você pode ter:

  • qualquer descendente de list contra qualquer outra coisa
    • Teste com isinstance(input, list) (para que o seu exemplo é correto)
  • qualquer tipo de sequência, excepto cordas (basestring em Python 2.x, str em Python 3.x)
    • Use sequência metaclass: isinstance(myvar, collections.Sequence) and not isinstance(myvar, str)
  • algum tipo seqüência contra casos conhecidos, como int, str, MyClass
    • Teste com isinstance(input, (int, str, MyClass))
  • qualquer iterable exceto strings:
    • Teste com

.

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

Você pode colocar * antes de seu argumento, desta forma você sempre terá uma tupla:

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

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

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

Mas isso vai forçá-lo a chamar sua função com parâmetros variáveis, eu não sei se isso é aceitável para você.

Você pode fazer tipo comparações diretas usando type().

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

No entanto, a maneira que você tem que permitirá qualquer tipo derivado do tipo list a serem repassados. Assim, impedindo as quaisquer tipos de derivados a partir acidentalmente sendo embrulhado.

O seu aproach parece certo para mim.

É semelhante a como você usa atom? em Lisp quando você iterar listas e verifique o item atual para ver se ele é uma lista ou não, porque se for uma lista que você deseja processar seus itens também.

Então, sim, não vejo mal nada com isso.

Essa é uma maneira ok para fazê-lo (não se esqueça de incluir linhas).

No entanto, você pode também querer considerar se o argumento tem um método __iter__ ou __getitem__ método . (Note que as cordas têm __getitem__ vez de __iter __.)

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

Este é provavelmente o requisito mais geral para uma lista-like tipo do que apenas a verificação do tipo.

Esta parece ser uma maneira razoável de fazê-lo. Você está querendo testar se o elemento é uma lista, e isso leva a efeito aquele diretamente. Ele fica mais complicado se você quer apoiar 'lista-like' outros tipos de dados, também, por exemplo:

isinstance(input, (list, tuple))

ou, mais geralmente, abstrair a pergunta:

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

mas, novamente, em resumo, o método é simples e correta, o que soa bem para mim!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top