como saber se uma variável é iterável, mas não uma string

88

Tenho uma função que recebe um argumento que pode ser um item único ou um item duplo:

def iterable(arg)
    if #arg is an iterable:
        print "yes"
    else:
        print "no"

de modo a:

>>> iterável (("f", "f"))
sim

>>> iterável (["f", "f"])
sim

>>> iterável ("ff")
não

O problema é que a string é tecnicamente iterável, então não posso simplesmente capturar o ValueError ao tentar arg[1]. Não quero usar isinstance (), porque essa não é uma boa prática (ou pelo menos foi o que me disseram).

sacerdote
fonte
1
Qual versão do Python? Acredito que a resposta seja diferente entre 2. * e 3
Kathy Van Stone
4
Disseram-lhe incorretamente que isinstance não é uma prática ruim.
Lennart Regebro
3
Oh, espere, talvez ele se refira ao princípio de que é ruim verificar um tipo de objeto, e que isso é uma indicação de que o programa está quebrado? Isso é verdade em princípio (mas nem sempre na prática). Este pode ou não ser esse o caso. Mas não é a função e instância que é o problema, é o hábito de verificar os tipos.
Lennart Regebro
@Lennart: canonical.org/~kragen/isinstance pode estar desatualizado
priestc
@up Isso não menciona a sobrecarga de função baseada em tipo, e isinstanceé a maneira de fazer isso em linguagens com tipos dinâmicos. Uma coisa que não deve ser usada todos os dias, mas OK em casos justificados.
Kos

Respostas:

50

Use isinstance (não vejo por que é uma prática ruim)

import types
if not isinstance(arg, types.StringTypes):

Observe o uso de StringTypes. Isso garante que não nos esqueçamos de algum tipo obscuro de string.

Por outro lado, isso também funciona para classes de string derivadas.

class MyString(str):
    pass

isinstance(MyString("  "), types.StringTypes) # true

Além disso, você pode querer dar uma olhada nesta questão anterior .

Felicidades.


NB: o comportamento mudou no Python 3 StringTypese basestringnão está mais definido. Dependendo de suas necessidades, você pode substituí-los isinstancepor str, ou uma tupla de subconjunto de (str, bytes, unicode), por exemplo, para usuários Cython. Como @Theron Luhn mencionou, você também pode usar six.

Scvalex
fonte
Legal, scvalex. Estou removendo meu -1 agora e tornando-o +1 :-).
Tom
2
Acho que a ideia da má prática se deve ao princípio da digitação do pato . Ser membro de uma classe específica não significa que seja o único objeto que pode ser usado nem que os métodos esperados estejam disponíveis. Mas acho que às vezes você simplesmente não consegue inferir o que o método faz, mesmo se estiver presente, então isinstancepode ser a única maneira.
estani
2
Observação: types.StringTypes não está disponível em Python 3. Como há apenas um tipo de string em py3k, acho que é seguro do isinstance(arg, str). Para uma versão compatível com versões anteriores, considere usar pythonhosted.org/six/#six.string_types
Theron Luhn
Eu uso estritamente Python3 e percebi que types.StringTypesnão está disponível em Python3. Qual é o valor em Python2?
kevinarpe
2
2017 : Esta resposta não é mais válida, consulte stackoverflow.com/a/44328500/99834 para ver aquele que funciona com todas as versões do Python.
Sorin
26

A partir de 2017, aqui está uma solução portátil que funciona com todas as versões do Python:

#!/usr/bin/env python
import collections
import six


def iterable(arg):
    return (
        isinstance(arg, collections.Iterable) 
        and not isinstance(arg, six.string_types)
    )


# non-string iterables    
assert iterable(("f", "f"))    # tuple
assert iterable(["f", "f"])    # list
assert iterable(iter("ff"))    # iterator
assert iterable(range(44))     # generator
assert iterable(b"ff")         # bytes (Python 2 calls this a string)

# strings or non-iterables
assert not iterable(u"ff")     # string
assert not iterable(44)        # integer
assert not iterable(iterable)  # function
Sorin
fonte
Existem algumas pequenas inconsistências entre 2/3 com bytestrings, mas se você usar a "string" nativa, ambas são falsas
Nick T
16

Desde Python 2.6, com a introdução de classes de base abstratas isinstance(usadas em ABCs, não em classes concretas) agora é considerado perfeitamente aceitável. Especificamente:

from abc import ABCMeta, abstractmethod

class NonStringIterable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is NonStringIterable:
            if any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

Esta é uma cópia exata (mudando apenas o nome da classe) de Iterableconforme definido em _abcoll.py(um detalhe de implementação de collections.py) ... a razão pela qual isso funciona como você deseja, enquanto collections.Iterablenão funciona, é que o último vai além para garantir que as strings sejam considerado iterável, chamando Iterable.register(str)explicitamente logo após essa classinstrução.

Claro que é fácil aumentar __subclasshook__retornando Falseantes da anychamada para outras classes que você deseja excluir especificamente de sua definição.

Em qualquer caso, depois de ter importado este novo módulo como myiter, isinstance('ciao', myiter.NonStringIterable)será Falsee isinstance([1,2,3], myiter.NonStringIterable)será True, exatamente como você solicitou - e no Python 2.6 e posteriores esta é considerada a maneira adequada de incorporar tais verificações ... defina uma classe base abstrata e verifique isinstanceisso.

Alex Martelli
fonte
Em Python 3 isinstance('spam', NonStringIterable)retorna True.
Nick T
1
(...) e no Python 2.6 e posterior, isso é considerado a maneira adequada de incorporar tais verificações (...) Como abusar de um conceito bem conhecido de classe abstrata dessa forma poderia ser considerado da maneira adequada está além da minha compreensão. A maneira adequada seria introduzir algum operador semelhante .
Piotr Dobrogost
Alex, você pode responder à afirmação de Nick de que isso não funciona no Python 3? Gosto da resposta, mas gostaria de ter certeza de que estou escrevendo um código à prova de futuro.
Merlyn Morgan-Graham
@ MerlynMorgan-Graham, correto, porque agora __iter__ está implementado em strings no Python 3. Portanto, meu parágrafo "fácil de aumentar" torna-se aplicável e, por exemplo, if issublass(cls, str): return Falseprecisa ser adicionado no início de __subclasshook__(bem como quaisquer outras classes que definam, __iter__mas em seu mentalidade não deve ser aceita como "iteráveis ​​não-string").
Alex Martelli
@AlexMartelli Para Python 3, você não quer dizer que if issublass(C, str): return Falsedeve ser adicionado?
Rob Smallshire
4

Sei que este é um post antigo, mas pensei que valeria a pena adicionar minha abordagem para a posteridade da Internet. A função abaixo parece funcionar para mim na maioria das circunstâncias com Python 2 e 3:

def is_collection(obj):
    """ Returns true for any iterable which is not a string or byte sequence.
    """
    try:
        if isinstance(obj, unicode):
            return False
    except NameError:
        pass
    if isinstance(obj, bytes):
        return False
    try:
        iter(obj)
    except TypeError:
        return False
    try:
        hasattr(None, obj)
    except TypeError:
        return True
    return False

Isso verifica se há uma string não iterável por (mis) usando o embutido hasattrque irá gerar a TypeErrorquando seu segundo argumento não for uma string ou uma string Unicode.

Nigel Small
fonte
3

Ao combinar as respostas anteriores, estou usando:

import types
import collections

#[...]

if isinstance(var, types.StringTypes ) \
    or not isinstance(var, collections.Iterable):

#[Do stuff...]

Não é 100% à prova de idiotas, mas se um objeto não for iterável, você ainda pode deixá-lo passar e voltar a digitar.


Editar: Python3

types.StringTypes == (str, unicode). O equivalente Phython3 é:

if isinstance(var, str ) \
    or not isinstance(var, collections.Iterable):
xvan
fonte
Sua declaração de importação deve ser 'tipos' e não 'tipo'
PaulR
3

2.x

Eu teria sugerido:

hasattr(x, '__iter__')

ou em vista do comentário de David Charles ajustando isso para Python3, que tal:

hasattr(x, '__iter__') and not isinstance(x, (str, bytes))

3.x

o basestringtipo abstrato embutido foi removido . Use em seu strlugar. Os tipos stre bytesnão têm funcionalidade suficiente em comum para garantir uma classe base compartilhada.

roedor de microfone
fonte
3
Talvez porque __iter__existe strings no Python 3?
davidrmcharles
@DavidCharles Oh, é mesmo? Foi mal. Eu sou um usuário Jython e Jython não tem atualmente a versão 3.
mike roedor
Isso não é realmente uma resposta, é mais um comentário / pergunta, e é errado para 3.x. Você poderia limpar isso? Você pode adicionar justificativa para alegar "Os tipos 'str' e 'bytes' não têm funcionalidade suficiente em comum para garantir uma classe base compartilhada." Um dos pontos-chave do 3.x era tornar os bytes Unicode um cidadão de primeira classe.
smci
Não tenho idéia de por que escrevi qualquer uma das opções acima. Proponho excluir todo o texto em "3.x" ... embora você já tenha editado minha resposta. Edite mais se quiser.
roedor de microfone
0

Como você observou corretamente, uma única string é uma sequência de caracteres.

Portanto, o que você realmente deseja fazer é descobrir que tipo de sequência argé usando isinstance ou type (a) == str.

Se você deseja realizar uma função que leva uma quantidade variável de parâmetros, você deve fazer assim:

def function(*args):
    # args is a tuple
    for arg in args:
        do_something(arg)

function ("ff") e function ("ff", "ff") funcionarão.

Não consigo ver um cenário em que uma função isiterable () como a sua seja necessária. Não é isinstance () que é um estilo ruim, mas situações em que você precisa usar isinstance ().

Otto Allmendinger
fonte
4
O uso type(a) == strdeve ser evitado. É uma má prática porque não leva em consideração tipos semelhantes ou tipos derivados de str. typenão sobe na hierarquia de tipo, mas isinstancesim, portanto, é melhor usar isinstance.
AkiRoss de
0

Para expandir explicitamente o excelente hack de Alex Martelli collections.pye abordar algumas das questões em torno dele: A solução de trabalho atual em python 3.6+ é

import collections
import _collections_abc as cabc
import abc


class NonStringIterable(metaclass=abc.ABCMeta):

    __slots__ = ()

    @abc.abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, c):
        if cls is NonStringIterable:
            if issubclass(c, str):
                return False
            return cabc._check_methods(c, "__iter__")
        return NotImplemented

e demonstrado

>>> typs = ['string', iter(''), list(), dict(), tuple(), set()]
>>> [isinstance(o, NonStringIterable) for o in typs]
[False, True, True, True, True, True]

Se você deseja adicionar iter('')às exclusões, por exemplo, modifique a linha

            if issubclass(c, str):
                return False

ser estar

            # `str_iterator` is just a shortcut for `type(iter(''))`*
            if issubclass(c, (str, cabc.str_iterator)):
                return False

para obter

[False, False, True, True, True, True]
Alexander McFarlane
fonte