cómo saber que una variable es iterable pero no una cadena

88

Tengo una función que toma un argumento que puede ser un solo elemento o un elemento doble:

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

así que eso:

>>> iterable (("f", "f"))
si

>>> iterable (["f", "f"])
si

>>> iterable ("ff")
No

El problema es que la cadena es técnicamente iterable, por lo que no puedo detectar el ValueError cuando lo intento arg[1]. No quiero usar isinstance (), porque esa no es una buena práctica (o eso me dijeron).

sacerdotec
fuente
1
¿Qué versión de Python? Creo que la respuesta es diferente entre 2. * y 3
Kathy Van Stone
4
Se le dijo incorrectamente que esta instancia no es una mala práctica.
Lennart Regebro
3
Oh, espere, ¿tal vez se refiere al principio de que es malo verificar un tipo de objeto y que esto es una indicación de que el programa está roto? Esto es cierto en principio (pero no siempre en la práctica). Este puede ser o no un caso así. Pero no es la función es la instancia el problema, es el hábito de buscar tipos.
Lennart Regebro
@Lennart: canonical.org/~kragen/isinstance, aunque puede estar desactualizado
Priestc
@up Sin embargo, esto no menciona la sobrecarga de funciones basadas en tipos, y isinstancees la forma de hacerlo en lenguajes de tipado dinámico. Algo que no debe usarse todos los días, pero está bien en casos justificados.
Kos

Respuestas:

50

Use isinstance (no veo por qué es una mala práctica)

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

Tenga en cuenta el uso de StringTypes. Asegura que no nos olvidemos de algún tipo oscuro de cadena.

Por el lado positivo, esto también funciona para clases de cadenas derivadas.

class MyString(str):
    pass

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

Además, es posible que desee echar un vistazo a esta pregunta anterior .

Salud.


NB: el comportamiento cambió en Python 3 ya que StringTypesy basestringya no están definidos. Dependiendo de sus necesidades, puede reemplazarlos isinstancepor str, o una tupla de subconjunto de (str, bytes, unicode), por ejemplo, para los usuarios de Cython. Como mencionó @Theron Luhn , también puede usar six.

scvalex
fuente
Bien, scvalex. Voy a eliminar mi -1 ahora y convertirlo en un +1 :-).
Tom
2
Creo que la idea de una mala práctica se debe al principio de tipificación de pato . Ser miembro de una clase en particular no significa que sea el único objeto que se puede usar ni que los métodos esperados estén disponibles. Pero creo que a veces no se puede inferir lo que hace el método incluso si está presente, por lo que isinstancepodría ser la única forma.
estani
2
Nota: types.StringTypes no está disponible en Python 3. Como solo hay un tipo de cadena en py3k, creo que es seguro hacerlo do isinstance(arg, str). Para obtener una versión compatible con versiones anteriores, considere usar pythonhosted.org/six/#six.string_types
Theron Luhn
Uso estrictamente Python3 y noté types.StringTypesque no está disponible en Python3. ¿Cuál es el valor en Python2?
kevinarpe
2
2017 : esta respuesta ya no es válida, consulte stackoverflow.com/a/44328500/99834 para ver una que funcione con todas las versiones de Python.
sorin
26

A partir de 2017, aquí hay una solución portátil que funciona con todas las versiones de 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
fuente
Hay algunas inconsistencias menores entre 2/3 con cadenas de bytes, pero si usa la "cadena" nativa, ambas son falsas
Nick T
16

Desde Python 2.6, con la introducción de clases base abstractas, isinstance(usado en ABC, no en clases concretas) ahora se considera perfectamente aceptable. Específicamente:

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 es una copia exacta (cambiando solo el nombre de la clase) de Iterablecomo se define en _abcoll.py(un detalle de implementación de collections.py) ... la razón por la que esto funciona como desea, aunque collections.Iterableno lo hace, es que este último hace un esfuerzo adicional para garantizar que las cadenas sean considerado iterable, llamando Iterable.register(str)explícitamente justo después de esta classdeclaración.

Por supuesto, es fácil aumentar __subclasshook__regresando Falseantes de la anyconvocatoria para otras clases que desea excluir específicamente de su definición.

En cualquier caso, después de haber importado este nuevo módulo como myiter, isinstance('ciao', myiter.NonStringIterable)será Falsey isinstance([1,2,3], myiter.NonStringIterable)será True, tal como lo solicita, y en Python 2.6 y posteriores, esta se considera la forma adecuada de incorporar tales comprobaciones ... defina una clase base abstracta y comprobarlo isinstance.

Alex Martelli
fuente
En Python 3 isinstance('spam', NonStringIterable)vuelve True.
Nick T
1
(...) y en Python 2.6 y posteriores, esta se considera la forma correcta de incorporar tales comprobaciones (...) Cómo abusar del conocido concepto de clase abstracta de tal manera podría considerarse la manera correcta está más allá de mi comprensión. La forma correcta sería introducir algún operador de apariencia en su lugar.
Piotr Dobrogost
Alex, ¿puedes abordar la afirmación de Nick de que esto no funciona en Python 3? Me gusta la respuesta, pero me gustaría asegurarme de que estoy escribiendo un código preparado para el futuro.
Merlyn Morgan-Graham
@ MerlynMorgan-Graham, eso es correcto, porque ahora __iter__ está implementado en cadenas en Python 3. Por lo tanto, mi párrafo "fácil de aumentar" se vuelve aplicable y, por ejemplo if issublass(cls, str): return False, debe agregarse al principio de __subclasshook__(así como cualquier otra clase que defina __iter__pero en su la mentalidad no debe aceptarse como "iterables sin cadenas").
Alex Martelli
@AlexMartelli Para Python 3, ¿no quiere decir que if issublass(C, str): return Falsedebería agregarse?
Rob Smallshire
4

Me doy cuenta de que es una publicación antigua, pero pensé que valía la pena agregar mi enfoque para la posteridad de Internet. La función a continuación parece funcionar para mí en la mayoría de las circunstancias con Python 2 y 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

Esto verifica una no cadena iterable por (mis) usando el incorporado hasattrque generará un TypeErrorcuando su segundo argumento no sea una cadena o una cadena Unicode.

Nigel Pequeño
fuente
3

Combinando respuestas anteriores, estoy usando:

import types
import collections

#[...]

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

#[Do stuff...]

No es 100% a prueba de tontos, pero si un objeto no es iterable, aún puede dejarlo pasar y volver a escribir pato.


Editar: Python3

types.StringTypes == (str, unicode). El equivalente de Phython3 es:

if isinstance(var, str ) \
    or not isinstance(var, collections.Iterable):
xvan
fuente
Su declaración de importación debe ser 'tipos' no 'tipo'
PaulR
3

2.x

Habría sugerido:

hasattr(x, '__iter__')

o en vista del comentario de David Charles que modifica esto para Python3, ¿qué pasa con:

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

3.x

se eliminó elbasestring tipo de resumen incorporado . Úselo en su lugar. Los tipos y no tienen suficiente funcionalidad en común para garantizar una clase base compartida.strstrbytes

mike roedor
fuente
3
¿Quizás porque __iter__hay cadenas en Python 3?
davidrmcharles
@DavidCharles Oh, ¿en serio? Culpa mía. Soy un usuario de Jython y Jython actualmente no tiene la versión 3.
Mike
Esto no es realmente una respuesta, más un comentario / pregunta, y está mal para 3.x. ¿Podrías limpiarlo? ¿Puede agregar una justificación para afirmar que "Los tipos 'str' y 'bytes' no tienen la funcionalidad suficiente en común para garantizar una clase base compartida". Uno de los puntos clave de 3.x fue convertir a los bytes Unicode en un ciudadano de primera clase.
smci
No tengo ni idea de por qué escribí nada de lo anterior. Propongo eliminar todo el texto de "3.x" ... aunque ya ha editado mi respuesta. Edítelo más si lo desea.
Mike rodent
0

Como señala correctamente, una sola cadena es una secuencia de caracteres.

Entonces, lo que realmente quieres hacer es averiguar qué tipo de secuencia arges usando isinstance o type (a) == str.

Si desea realizar una función que toma una cantidad variable de parámetros, debe hacerlo así:

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

function ("ff") y function ("ff", "ff") funcionarán.

No puedo ver un escenario en el que se necesite una función isiterable () como la suya. No es isinstance () lo que es de mal estilo, sino situaciones en las que necesita usar isinstance ().

Otto Allmendinger
fuente
4
El uso type(a) == strse evitará. Es una mala práctica porque no tiene en cuenta tipos similares o tipos derivados de str. typeno sube en la jerarquía de tipos, mientras que isinstancesí, por lo tanto, es mejor usar isinstance.
AkiRoss
0

Para expandir explícitamente el excelente truco de Alex Martelli collections.pyy abordar algunas de las preguntas que lo rodean: la solución de trabajo actual en python 3.6+ es

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

y demostrado

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

Si desea agregar iter('')a las exclusiones, por ejemplo, modifique la línea

            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

Llegar

[False, False, True, True, True, True]
Alexander McFarlane
fuente