En Python, comment déterminer si un objet est itérable?


1085

Existe-t-il une méthode comme isiterable? La seule solution que j'ai trouvée jusqu'à présent est d'appeler

hasattr(myObj, '__iter__')

Mais je ne sais pas à quel point c'est infaillible.


18
__getitem__est également suffisant pour rendre un objet itérable
Kos

4
FWIW: iter(myObj)réussit si isinstance(myObj, dict), donc si vous regardez un myObjqui pourrait être une séquence de dicts ou un seul dict, vous réussirez dans les deux cas. Une subtilité qui est importante si vous voulez savoir ce qu'est une séquence et ce qui ne l'est pas. (en Python 2)
Ben Mosher

7
__getitem__est également suffisant pour rendre un objet itérable ... s'il commence à un index nul .
Carlos A. Gómez

Réponses:


28

J'ai étudié ce problème assez récemment. Sur cette base, ma conclusion est que c'est aujourd'hui la meilleure approche:

from collections.abc import Iterable   # drop `.abc` with Python 2.7 or lower

def iterable(obj):
    return isinstance(obj, Iterable)

Ce qui précède a été recommandé déjà plus tôt, mais le consensus général est que l'utilisation iter()serait préférable:

def iterable(obj):
    try:
        iter(obj)
    except Exception:
        return False
    else:
        return True

Nous avons également utilisé iter()notre code à cette fin, mais j'ai récemment commencé à être de plus en plus agacé par des objets qui ne sont __getitem__considérés que comme itérables. Il y a des raisons valables d'avoir __getitem__dans un objet non itérable et avec eux le code ci-dessus ne fonctionne pas bien. Comme exemple réel, nous pouvons utiliser Faker . Le code ci-dessus signale qu'il est itérable mais en réalité, essayer de l'itérer provoque un AttributeError(testé avec Faker 4.0.2):

>>> from faker import Faker
>>> fake = Faker()
>>> iter(fake)    # No exception, must be iterable
<iterator object at 0x7f1c71db58d0>
>>> list(fake)    # Ooops
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/.../site-packages/faker/proxy.py", line 59, in __getitem__
    return self._factory_map[locale.replace('-', '_')]
AttributeError: 'int' object has no attribute 'replace'

Si nous l'utilisions insinstance(), nous ne considérerions pas accidentellement les instances de Faker (ou tout autre objet n'ayant que __getitem__) comme étant itérables:

>>> from collections.abc import Iterable
>>> from faker import Faker
>>> isinstance(Faker(), Iterable)
False

Les réponses précédentes ont indiqué que l'utilisation iter()est plus sûre car l'ancienne méthode d'implémentation de l'itération en Python était basée sur __getitem__et l' isinstance()approche ne détectait pas cela. Cela peut avoir été vrai avec les anciennes versions de Python, mais basé sur mes tests assez exhaustifs isinstance()fonctionne très bien de nos jours. Le seul cas où cela isinstance()n'a pas fonctionné mais l' iter()a été avec l' UserDictutilisation de Python 2. Si cela est pertinent, il est possible de l'utiliser isinstance(item, (Iterable, UserDict))pour le couvrir.


1
Est également typing.Dictconsidéré comme itérable par iter(Dict)mais list(Dict)échoue avec une erreur TypeError: Parameters to generic types must be types. Got 0.. Comme prévu isinstance(Dict, Iterable)retourne faux.
Pekka Klärck

1
Je suis arrivé à la même conclusion, mais pour des raisons différentes. L'utilisation a iterfait ralentir inutilement une partie de notre code qui utilisait la "pré-mise en cache". Si le __iter__code est lent, il en sera de même pour l'appel iter... chaque fois que vous voulez juste voir si quelque chose est itérable.
thorwhalen

843
  1. Vérifier les __iter__travaux sur les types de séquence, mais cela échouerait par exemple sur les chaînes en Python 2 . Je voudrais aussi connaître la bonne réponse, jusque-là, voici une possibilité (qui fonctionnerait aussi sur les chaînes):

    from __future__ import print_function
    
    try:
        some_object_iterator = iter(some_object)
    except TypeError as te:
        print(some_object, 'is not iterable')

    La fonction iterintégrée vérifie la __iter__méthode ou, dans le cas de chaînes, la __getitem__méthode.

  2. Une autre approche pythonique générale consiste à supposer un itérable, puis à échouer gracieusement s'il ne fonctionne pas sur l'objet donné. Le glossaire Python:

    Style de programmation pythonique qui détermine le type d'un objet en inspectant sa méthode ou sa signature d'attribut plutôt que par une relation explicite avec un objet de type ("S'il ressemble à un canard et quacks comme un canard , ce doit être un canard .") En mettant l'accent sur les interfaces plutôt que des types spécifiques, un code bien conçu améliore sa flexibilité en permettant la substitution polymorphe. Le typage canard évite les tests utilisant type () ou isinstance (). Au lieu de cela, il utilise généralement le style de programmation EAFP (Easier to Ask Forgiveness than Permission).

    ...

    try:
       _ = (e for e in my_object)
    except TypeError:
       print my_object, 'is not iterable'
  3. Le collectionsmodule fournit des classes de base abstraites, qui permettent de demander aux classes ou aux instances si elles fournissent des fonctionnalités particulières, par exemple:

    from collections.abc import Iterable
    
    if isinstance(e, Iterable):
        # e is iterable

    Cependant, cela ne vérifie pas les classes qui sont itérables via __getitem__.


34
[e for e in my_object]peut déclencher une exception pour d'autres raisons, c'est my_object-à- dire des bogues non définis ou possibles dans l' my_objectimplémentation.
Nick Dandoulakis

37
Une chaîne est une séquence ( isinstance('', Sequence) == True) et comme toute séquence, elle est itérable ( isinstance('', Iterable)). Bien hasattr('', '__iter__') == Falseque cela puisse être déroutant.
jfs

82
Si elle my_objectest très grande (disons infinie comme itertools.count()) votre compréhension de la liste prendra beaucoup de temps / mémoire. Mieux vaut faire un générateur, qui n'essaiera jamais de construire une liste (potentiellement infinie).
Chris Lutz

14
Que se passe-t-il si some_object lance également TypeError causé par une autre raison (bogues, etc.)? Comment pouvons-nous le distinguer de la "TypeError non itérable"?
Shaung

54
Notez que dans Python 3: hasattr(u"hello", '__iter__')retoursTrue
Carlos

573

Dactylographie de canard

try:
    iterator = iter(theElement)
except TypeError:
    # not iterable
else:
    # iterable

# for obj in iterator:
#     pass

Vérification de type

Utilisez les classes de base abstraites . Ils ont besoin d'au moins Python 2.6 et ne fonctionnent que pour les classes de nouveau style.

from collections.abc import Iterable   # import directly from collections for Python < 3.3

if isinstance(theElement, Iterable):
    # iterable
else:
    # not iterable

Cependant, iter()est un peu plus fiable comme décrit dans la documentation :

La vérification isinstance(obj, Iterable)détecte les classes enregistrées comme Iterable ou qui ont une __iter__()méthode, mais elle ne détecte pas les classes qui itèrent avec la __getitem__() méthode. Le seul moyen fiable de déterminer si un objet est itérable est d'appeler iter(obj).


18
Extrait de "Fluent Python" de Luciano Ramalho: Depuis Python 3.4, la façon la plus précise de vérifier si un objet x est itérable est d'appeler iter (x) et de gérer une exception TypeError si ce n'est pas le cas. C'est plus précis que d'utiliser isinstance (x, abc.Iterable), car iter (x) prend également en compte la méthode getitem héritée, contrairement à ABC itératif .
RdB

Dans le cas où vous pensez "oh je vais juste isinstance(x, (collections.Iterable, collections.Sequence))au lieu de iter(x)", notez que cela ne détectera toujours pas un objet itérable qui implémente seulement __getitem__mais pas __len__. Utilisez iter(x)et attrapez l'exception.
Dale

Votre deuxième réponse ne fonctionne pas. A PyUNO si je le fais iter(slide1), tout se passe bien, cependant les isinstance(slide1, Iterable)lancers TypeError: issubclass() arg 1 must be a class.
Hi-Angel

@ Hi-Angel sonne comme un bug dans PyUNONotez que votre message d'erreur dit issubclass()au lieu de isinstance().
Georg Schölly

2
Appeler iter () sur un objet peut être une opération coûteuse (voir DataLoader dans Pytorch, qui forge / génère plusieurs processus sur iter ()).
szali

126

Je voudrais jeter un peu plus de lumière sur l'interaction entre iter, __iter__et __getitem__et ce qui se passe derrière les rideaux. Armé de ces connaissances, vous serez en mesure de comprendre pourquoi le mieux que vous puissiez faire est

try:
    iter(maybe_iterable)
    print('iteration will probably work')
except TypeError:
    print('not iterable')

Je vais d'abord énumérer les faits et ensuite faire un rappel rapide de ce qui se passe lorsque vous utilisez une forboucle en python, suivi d'une discussion pour illustrer les faits.

Faits

  1. Vous pouvez obtenir un itérateur à partir de n'importe quel objet oen appelant iter(o)si au moins une des conditions suivantes est remplie:

    a) opossède une __iter__méthode qui renvoie un objet itérateur. Un itérateur est un objet avec une méthode __iter__et une méthode __next__(Python 2 next:).

    b) oa une __getitem__méthode.

  2. La vérification d'une instance de Iterableou Sequence, ou la vérification de l'attribut __iter__ne suffit pas.

  3. Si un objet oimplémente uniquement __getitem__, mais pas __iter__, iter(o)il construira un itérateur qui essaiera d'extraire des éléments à partir od'un index entier, en commençant à l'index 0. L'itérateur interceptera toutes les IndexErrorerreurs (mais aucune autre erreur) qui seront levées, puis se lèvera StopIterationlui-même.

  4. Dans le sens le plus général, il n'y a aucun moyen de vérifier si l'itérateur retourné par iterest sain d'esprit autre que de l'essayer.

  5. Si un objet oimplémente __iter__, la iterfonction s'assurera que l'objet retourné par __iter__est un itérateur. Il n'y a pas de contrôle d'intégrité si un objet est uniquement implémenté __getitem__.

  6. __iter__gagne. Si un objet oimplémente les deux __iter__et __getitem__, iter(o)appellera __iter__.

  7. Si vous souhaitez rendre vos propres objets itérables, implémentez toujours la __iter__méthode.

for boucles

Pour suivre, vous devez comprendre ce qui se passe lorsque vous utilisez une forboucle en Python. N'hésitez pas à passer directement à la section suivante si vous le savez déjà.

Lorsque vous utilisez for item in opour un objet itérable o, Python appelle iter(o)et attend un objet itérateur comme valeur de retour. Un itérateur est tout objet qui implémente une méthode __next__(ou nexten Python 2) et une __iter__méthode.

Par convention, la __iter__méthode d'un itérateur doit retourner l'objet lui-même (ie return self). Python appelle ensuite nextl'itérateur jusqu'à ce qu'il StopIterationsoit levé. Tout cela se produit implicitement, mais la démonstration suivante le rend visible:

import random

class DemoIterable(object):
    def __iter__(self):
        print('__iter__ called')
        return DemoIterator()

class DemoIterator(object):
    def __iter__(self):
        return self

    def __next__(self):
        print('__next__ called')
        r = random.randint(1, 10)
        if r == 5:
            print('raising StopIteration')
            raise StopIteration
        return r

Itération sur un DemoIterable:

>>> di = DemoIterable()
>>> for x in di:
...     print(x)
...
__iter__ called
__next__ called
9
__next__ called
8
__next__ called
10
__next__ called
3
__next__ called
10
__next__ called
raising StopIteration

Discussion et illustrations

Aux points 1 et 2: obtenir un itérateur et des contrôles peu fiables

Considérez la classe suivante:

class BasicIterable(object):
    def __getitem__(self, item):
        if item == 3:
            raise IndexError
        return item

L'appel iteravec une instance de BasicIterableretournera un itérateur sans aucun problème car BasicIterableimplémente __getitem__.

>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>

Cependant, il est important de noter qu'il bn'a pas l' __iter__attribut et n'est pas considéré comme une instance de Iterableou Sequence:

>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False

C'est pourquoi Fluent Python de Luciano Ramalho recommande d'appeler iteret de gérer le potentiel TypeErrorcomme le moyen le plus précis de vérifier si un objet est itérable. Citant directement du livre:

Depuis Python 3.4, la façon la plus précise de vérifier si un objet xest itérable est d'appeler iter(x)et de gérer une TypeErrorexception s'il ne l'est pas. Ceci est plus précis que l'utilisation isinstance(x, abc.Iterable), car iter(x)il considère également la __getitem__méthode héritée , Iterablecontrairement à l' ABC.

Au point 3: itération sur des objets qui ne fournissent que __getitem__, mais pas__iter__

Itération sur une instance de BasicIterabletravaux comme prévu: Python construit un itérateur qui essaie de récupérer des éléments par index, en commençant à zéro, jusqu'à ce que an IndexErrorsoit levé. La __getitem__méthode de l'objet de démonstration renvoie simplement celui itemqui a été fourni comme argument __getitem__(self, item)par l'itérateur renvoyé par iter.

>>> b = BasicIterable()
>>> it = iter(b)
>>> next(it)
0
>>> next(it)
1
>>> next(it)
2
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Notez que l'itérateur déclenche StopIterationlorsqu'il ne peut pas retourner l'élément suivant et que celui IndexErrorqui est déclenché item == 3est géré en interne. C'est pourquoi le bouclage sur un BasicIterableavec une forboucle fonctionne comme prévu:

>>> for x in b:
...     print(x)
...
0
1
2

Voici un autre exemple afin de ramener à la maison le concept de la façon dont l'itérateur renvoyé par itertente d'accéder aux éléments par index. WrappedDictn'hérite pas de dict, ce qui signifie que les instances n'auront pas de __iter__méthode.

class WrappedDict(object): # note: no inheritance from dict!
    def __init__(self, dic):
        self._dict = dic

    def __getitem__(self, item):
        try:
            return self._dict[item] # delegate to dict.__getitem__
        except KeyError:
            raise IndexError

Notez que les appels à __getitem__sont délégués dict.__getitem__pour lesquels la notation entre crochets est simplement un raccourci.

>>> w = WrappedDict({-1: 'not printed',
...                   0: 'hi', 1: 'StackOverflow', 2: '!',
...                   4: 'not printed', 
...                   'x': 'not printed'})
>>> for x in w:
...     print(x)
... 
hi
StackOverflow
!

Aux points 4 et 5: iterrecherche un itérateur lorsqu'il appelle__iter__ :

Quand iter(o)est appelé pour un objet o, iters'assurera que la valeur de retour de __iter__, si la méthode est présente, est un itérateur. Cela signifie que l'objet renvoyé doit implémenter __next__(ou nexten Python 2) et __iter__. iterne peut effectuer aucun contrôle d'intégrité pour les objets qui fournissent uniquement __getitem__, car il n'a aucun moyen de vérifier si les éléments de l'objet sont accessibles par index entier.

class FailIterIterable(object):
    def __iter__(self):
        return object() # not an iterator

class FailGetitemIterable(object):
    def __getitem__(self, item):
        raise Exception

Notez que la construction d'un itérateur à partir d' FailIterIterableinstances échoue immédiatement, tandis que la construction d'un itérateur à partir de FailGetItemIterableréussit, mais lèvera une exception lors du premier appel à __next__.

>>> fii = FailIterIterable()
>>> iter(fii)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'object'
>>>
>>> fgi = FailGetitemIterable()
>>> it = iter(fgi)
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/path/iterdemo.py", line 42, in __getitem__
    raise Exception
Exception

Au point 6: __iter__gagne

Celui-ci est simple. Si un objet implémente __iter__et __getitem__, iterappellera __iter__. Considérez la classe suivante

class IterWinsDemo(object):
    def __iter__(self):
        return iter(['__iter__', 'wins'])

    def __getitem__(self, item):
        return ['__getitem__', 'wins'][item]

et la sortie lors du bouclage sur une instance:

>>> iwd = IterWinsDemo()
>>> for x in iwd:
...     print(x)
...
__iter__
wins

Au point 7: vos classes itérables devraient implémenter __iter__

Vous pourriez vous demander pourquoi la plupart des séquences intégrées comme listimplémenter une __iter__méthode __getitem__sont suffisantes.

class WrappedList(object): # note: no inheritance from list!
    def __init__(self, lst):
        self._list = lst

    def __getitem__(self, item):
        return self._list[item]

Après tout, l'itération sur les instances de la classe ci-dessus, qui délègue les appels __getitem__à list.__getitem__(en utilisant la notation entre crochets), fonctionnera correctement:

>>> wl = WrappedList(['A', 'B', 'C'])
>>> for x in wl:
...     print(x)
... 
A
B
C

Les raisons pour lesquelles vos itérables personnalisés doivent implémenter __iter__sont les suivantes:

  1. Si vous implémentez __iter__, les instances seront considérées comme itérables et isinstance(o, collections.abc.Iterable)reviendront True.
  2. Si l'objet renvoyé par __iter__n'est pas un itérateur, iteréchouera immédiatement et déclenchera a TypeError.
  3. La gestion spéciale de __getitem__existe pour des raisons de compatibilité descendante. Citant à nouveau de Fluent Python:

C'est pourquoi toute séquence Python est itérable: ils implémentent tous __getitem__. En fait, les séquences standard implémentent également __iter__, et la vôtre devrait aussi, car la gestion spéciale de __getitem__existe pour des raisons de compatibilité descendante et peut être supprimée à l'avenir (bien qu'elle ne soit pas déconseillée au moment où j'écris ceci).


il est donc sûr de définir un prédicat is_iterableen revenant Truedans le trybloc et Falsedans le except TypeErrorbloc?
alancalvitti

C'est une excellente réponse. Je pense que cela met en évidence la nature peu intuitive et malheureuse du protocole getitem. Il n'aurait jamais dû être ajouté.
Neil G

31

Ce n'est pas suffisant: l'objet renvoyé par __iter__doit implémenter le protocole d'itération (ie nextméthode). Voir la section appropriée dans la documentation .

En Python, une bonne pratique consiste à "essayer de voir" au lieu de "vérifier".


9
"taper du canard" je crois? :)
willem

9
@willem: ou "ne demandez pas la permission mais le pardon" ;-)
jldupont

14
@willem Les styles "permission" et "pardon" sont tous deux considérés comme du type canard. Si vous demandez ce qu'un objet peut faire plutôt que ce qu'il est , c'est du typage de canard. Si vous utilisez l'introspection, c'est "permission"; si vous essayez juste de le faire et voyez si cela fonctionne ou non, c'est "le pardon".
Mark Reed

22

En Python <= 2.5, vous ne pouvez pas et ne devriez pas - iterable était une interface "informelle".

Mais depuis Python 2.6 et 3.0, vous pouvez exploiter la nouvelle infrastructure ABC (classe de base abstraite) ainsi que certains ABC intégrés qui sont disponibles dans le module collections:

from collections import Iterable

class MyObject(object):
    pass

mo = MyObject()
print isinstance(mo, Iterable)
Iterable.register(MyObject)
print isinstance(mo, Iterable)

print isinstance("abc", Iterable)

Maintenant, que cela soit souhaitable ou fonctionne réellement, ce n'est qu'une question de conventions. Comme vous pouvez le voir, vous pouvez enregistrer un objet non itérable comme Iterable - et il lèvera une exception lors de l'exécution. Par conséquent, isinstance acquiert une "nouvelle" signification - il vérifie simplement la compatibilité de type "déclaré", ce qui est une bonne façon de procéder en Python.

D'un autre côté, si votre objet ne satisfait pas l'interface dont vous avez besoin, qu'allez-vous faire? Prenons l'exemple suivant:

from collections import Iterable
from traceback import print_exc

def check_and_raise(x):
    if not isinstance(x, Iterable):
        raise TypeError, "%s is not iterable" % x
    else:
        for i in x:
            print i

def just_iter(x):
    for i in x:
        print i


class NotIterable(object):
    pass

if __name__ == "__main__":
    try:
        check_and_raise(5)
    except:
        print_exc()
        print

    try:
        just_iter(5)
    except:
        print_exc()
        print

    try:
        Iterable.register(NotIterable)
        ni = NotIterable()
        check_and_raise(ni)
    except:
        print_exc()
        print

Si l'objet ne satisfait pas ce que vous attendez, vous lancez simplement une TypeError, mais si le bon ABC a été enregistré, votre vérification est inutile. Au contraire, si la __iter__méthode est disponible, Python reconnaîtra automatiquement l'objet de cette classe comme étant itérable.

Donc, si vous vous attendez à un itérable, parcourez-le et oubliez-le. D'un autre côté, si vous devez faire des choses différentes selon le type d'entrée, vous pourriez trouver l'infrastructure ABC assez utile.


13
n'utilisez pas bare except:dans l'exemple de code pour les débutants. Il favorise les mauvaises pratiques.
jfs

JFS: Je ne le ferais pas, mais je devais passer par plusieurs codes déclencheurs d'exceptions et je ne voulais pas intercepter l'exception spécifique ... Je pense que le but de ce code est assez clair.
Alan Franzoni

21
try:
  #treat object as iterable
except TypeError, e:
  #object is not actually iterable

N'effectuez pas de vérifications pour voir si votre canard est vraiment un canard pour voir s'il est itérable ou non, traitez-le comme s'il l'était et plaignez-vous s'il ne l'est pas.


3
Techniquement, au cours de l'itération, votre calcul pourrait jeter un TypeErroret vous jeter ici, mais fondamentalement oui.
Chris Lutz

6
@willem: Veuillez utiliser timeit pour effectuer un benchmark. Les exceptions Python sont souvent plus rapides que les instructions if. Ils peuvent emprunter un chemin légèrement plus court à travers l'interprète.
S.Lott

2
@willem: IronPython a des exceptions lentes (par rapport à CPython).
jfs

2
Un essai de travail: la déclaration est vraiment rapide. Donc, si vous avez quelques exceptions, essayez-sauf est rapide. Si vous vous attendez à de nombreuses exceptions, «si» peut être plus rapide.
Arne Babenhauserheide

2
Est-ce que l'objet d'exception ne doit pas être capturé en ajoutant " as e" après TypeErrorau lieu d'ajouter " , e"?
HelloGoodbye

21

Depuis Python 3.5, vous pouvez utiliser le module de saisie de la bibliothèque standard pour des choses liées au type:

from typing import Iterable

...

if isinstance(my_item, Iterable):
    print(True)

18

La meilleure solution que j'ai trouvée jusqu'à présent:

hasattr(obj, '__contains__')

qui vérifie essentiellement si l'objet implémente l' inopérateur.

Avantages (aucune des autres solutions n'a les trois):

  • c'est une expression (fonctionne comme un lambda , par opposition à l' essai ... sauf variante)
  • il est (devrait être) implémenté par tous les itérables, y compris les chaînes (par opposition à __iter__)
  • fonctionne sur n'importe quel Python> = 2.5

Remarques:

  • la philosophie Python de "demander pardon, pas autorisation" ne fonctionne pas bien lorsque, par exemple, dans une liste, vous avez à la fois des itérables et des non-itérables et que vous devez traiter chaque élément différemment selon son type (traiter les itérables à l'essai et non iterables sur l' exception ne fonctionne, mais il regarderaient bout à bout laid et trompeur)
  • des solutions à ce problème qui tentent d'itérer réellement sur l'objet (par exemple [x pour x dans obj]) pour vérifier s'il est itérable peuvent induire des pénalités de performances importantes pour les grands itérables (surtout si vous avez juste besoin des premiers éléments de l'itérable, par exemple exemple) et doit être évité

3
Bien, mais pourquoi ne pas utiliser le module collections comme proposé dans stackoverflow.com/questions/1952464/… ? Cela me semble plus expressif.
Dave Abrahams

1
Il est plus court (et ne nécessite pas d'importations supplémentaires) sans perdre de clarté: avoir une méthode "contient" semble être un moyen naturel de vérifier si quelque chose est une collection d'objets.
Vlad

46
Ce n'est pas parce que quelque chose peut contenir quelque chose qu'il est itérable. Par exemple, un utilisateur peut vérifier si un point se trouve dans un cube 3D, mais comment voulez-vous parcourir cet objet?
Casey Kuball

13
Ceci est une erreur. Un itérable lui-même ne prend pas en charge contient , au moins avec Python 3.4.
Peter Shinners

15

Vous pouvez essayer ceci:

def iterable(a):
    try:
        (x for x in a)
        return True
    except TypeError:
        return False

Si nous pouvons créer un générateur qui itère dessus (mais n'utilisez jamais le générateur pour qu'il ne prenne pas de place), c'est itérable. On dirait une sorte de chose "duh". Pourquoi avez-vous besoin de déterminer si une variable est itérable en premier lieu?


Et alors iterable(itertools.repeat(0))? :)
badp

5
@badp, (x for x in a)crée juste un générateur, il ne fait aucune itération sur a.
catchmeifyoutry

5
Essayer est-il (x for x in a)exactement équivalent à essayer iterator = iter(a)? Ou y a-t-il des cas où les deux sont différents?
max

N'est-ce pas for _ in a: breakplus simple? Est-ce plus lent?
Mr_and_Mrs_D

2
@Mr_and_Mrs_D c'est mauvais si l'objet testé est un itérateur qui est itéré par la suite (il y aura 1 élément court car sa position ne peut pas être réinitialisée), la création de générateurs de déchets n'itère pas sur l'objet car ils ne sont pas itérés, bien que je ne sois pas certain qu'il soulèvera 100% une TypeError s'il n'est pas itérable.
Tcll

13

J'ai trouvé une bonne solution ici :

isiterable = lambda obj: isinstance(obj, basestring) \
    or getattr(obj, '__iter__', False)

11

Selon le glossaire Python 2 , les itérables sont

tous les types de séquence (tels que list,, stret tuple) et certains types de non-séquence comme dictet fileet les objets de toutes les classes que vous définissez avec une méthode __iter__()ou __getitem__(). Les itérables peuvent être utilisés dans une boucle for et dans de nombreux autres endroits où une séquence est nécessaire (zip (), map (), ...). Lorsqu'un objet itérable est passé en tant qu'argument à la fonction intégrée iter (), il renvoie un itérateur pour l'objet.

Bien sûr, étant donné le style de codage général pour Python basé sur le fait qu'il est "plus facile de demander pardon que permission.", L'attente générale est d'utiliser

try:
    for i in object_in_question:
        do_something
except TypeError:
    do_something_for_non_iterable

Mais si vous devez le vérifier explicitement, vous pouvez tester un itérable par hasattr(object_in_question, "__iter__") or hasattr(object_in_question, "__getitem__"). Vous devez vérifier les deux, car les strs n'ont pas de __iter__méthode (du moins pas en Python 2, en Python 3 ils en ont) et parce que les generatorobjets n'ont pas de __getitem__méthode.


8

Je trouve souvent pratique, dans mes scripts, de définir une iterablefonction. (Comprend maintenant la simplification suggérée par Alfe):

import collections

def iterable(obj):
    return isinstance(obj, collections.Iterable):

afin que vous puissiez tester si un objet est itérable sous une forme très lisible

if iterable(obj):
    # act on iterable
else:
    # not iterable

comme vous le feriez avec lecallable fonction

EDIT: si vous avez installé numpy, vous pouvez simplement faire: from numpy import iterable, qui est simplement quelque chose comme

def iterable(obj):
    try: iter(obj)
    except: return False
    return True

Si vous n'avez pas numpy, vous pouvez simplement implémenter ce code, ou celui ci-dessus.


3
Chaque fois que vous aimez if x: return True else: return False(en xétant booléen), vous pouvez écrire ceci comme return x. Dans votre cas return isinstance(…)sans aucun if.
Alfe

Puisque vous reconnaissez que la solution d'Alfe est meilleure, pourquoi n'avez-vous pas modifié votre réponse pour simplement dire cela? Au lieu de cela, vous avez maintenant les deux versions dans votre réponse. Verbosité inutile. Soumettre une modification pour résoudre ce problème.
ToolmakerSteve

2
Vous devez intercepter "TypeError" dans la ligne `except: return False`. Tout attraper est un mauvais schéma.
Mariusz Jamro

Sache que. J'ai traduit ce morceau de code à partir de la bibliothèque NumPy, qui utilise l'exception générique.
fmonegaglia

Ce n'est pas parce qu'un code est extrait de NumPy qu'il est bon ... modèle ou non, que la seule fois où tout doit être fait doit être si vous gérez explicitement les erreurs dans votre programme.
Tcll

5

a une fonction intégrée comme ça:

from pandas.util.testing import isiterable

cependant, cela regarde simplement s'il y a des __iter__séquences et similaires et ne s'en soucie pas vraiment.
ead

4

Il m'a toujours échappé pourquoi le python a callable(obj) -> boolmais pas iterable(obj) -> bool...
c'est sûrement plus facile à fairehasattr(obj,'__call__') même s'il est plus lent.

Étant donné qu'à peu près toutes les autres réponses recommandent d'utiliser try/ except TypeError, où le test des exceptions est généralement considéré comme une mauvaise pratique dans n'importe quelle langue, voici une implémentation de que iterable(obj) -> boolj'apprécie et utilise souvent:

Pour l'amour de python 2, je vais utiliser un lambda juste pour augmenter les performances supplémentaires ...
(en python 3, peu importe ce que vous utilisez pour définir la fonction, defa à peu près la même vitesse que lambda)

iterable = lambda obj: hasattr(obj,'__iter__') or hasattr(obj,'__getitem__')

Notez que cette fonction s'exécute plus rapidement pour les objets avec __iter__car elle ne teste pas __getitem__.

La plupart des objets itérables doivent se baser sur l' __iter__endroit où se replient les objets spéciaux __getitem__, bien que l'un ou l'autre soit requis pour qu'un objet soit itérable.
(et comme c'est standard, cela affecte également les objets C)


il ne fournit pas de code de travail, sans parler des performances de python ... bien que cette réponse soit vraiment juste pour plus de commodité comme je l'ai vu à plusieurs reprises ici.
Tcll

3
def is_iterable(x):
    try:
        0 in x
    except TypeError:
        return False
    else:
        return True

Cela dira oui à toutes sortes d'objets itérables, mais cela dira non aux chaînes en Python 2 . (C'est ce que je veux par exemple lorsqu'une fonction récursive peut prendre une chaîne ou un conteneur de chaînes. Dans cette situation, demander pardon peut conduire à un obfuscode, et il vaut mieux demander la permission d'abord.)

import numpy

class Yes:
    def __iter__(self):
        yield 1;
        yield 2;
        yield 3;

class No:
    pass

class Nope:
    def __iter__(self):
        return 'nonsense'

assert is_iterable(Yes())
assert is_iterable(range(3))
assert is_iterable((1,2,3))   # tuple
assert is_iterable([1,2,3])   # list
assert is_iterable({1,2,3})   # set
assert is_iterable({1:'one', 2:'two', 3:'three'})   # dictionary
assert is_iterable(numpy.array([1,2,3]))
assert is_iterable(bytearray("not really a string", 'utf-8'))

assert not is_iterable(No())
assert not is_iterable(Nope())
assert not is_iterable("string")
assert not is_iterable(42)
assert not is_iterable(True)
assert not is_iterable(None)

De nombreuses autres stratégies ici diront oui aux chaînes. Utilisez-les si c'est ce que vous voulez.

import collections
import numpy

assert isinstance("string", collections.Iterable)
assert isinstance("string", collections.Sequence)
assert numpy.iterable("string")
assert iter("string")
assert hasattr("string", '__getitem__')

Remarque: is_iterable () dira oui aux chaînes de type byteset bytearray.

  • bytesles objets en Python 3 sont itérables True == is_iterable(b"string") == is_iterable("string".encode('utf-8'))Il n'y a pas un tel type en Python 2.
  • bytearray les objets en Python 2 et 3 sont itérables True == is_iterable(bytearray(b"abc"))

L' hasattr(x, '__iter__')approche OP dira oui aux chaînes en Python 3 et non en Python 2 (que ce soit ''ou b''ou u''). Merci à @LuisMasuelli d'avoir remarqué qu'il vous laissera également tomber sur un buggy __iter__.


2

Le moyen le plus simple, en respectant le typage canard de Python , est de rattraper l'erreur (Python sait parfaitement ce qu'il attend d'un objet pour devenir un itérateur):

class A(object):
    def __getitem__(self, item):
        return something

class B(object):
    def __iter__(self):
        # Return a compliant iterator. Just an example
        return iter([])

class C(object):
    def __iter__(self):
        # Return crap
        return 1

class D(object): pass

def iterable(obj):
    try:
        iter(obj)
        return True
    except:
        return False

assert iterable(A())
assert iterable(B())
assert iterable(C())
assert not iterable(D())

Remarques :

  1. Peu importe que l'objet ne soit pas itérable, ou qu'un buggy __iter__ait été implémenté, si le type d'exception est le même: de toute façon, vous ne pourrez pas itérer l'objet.
  2. Je pense que je comprends votre préoccupation: comment callableexiste- t- il comme vérification si je pouvais également compter sur la saisie de canard pour élever un AttributeErrorif qui __call__n'est pas défini pour mon objet, mais ce n'est pas le cas pour la vérification itérable?

    Je ne connais pas la réponse, mais vous pouvez soit implémenter la fonction que j'ai (et d'autres utilisateurs) donnée, soit simplement intercepter l'exception dans votre code (votre implémentation dans cette partie sera comme la fonction que j'ai écrite - assurez-vous simplement d'isoler le création d'itérateur à partir du reste du code afin que vous puissiez capturer l'exception et la distinguer d'une autre TypeError.


1

La isiterablefonction du code suivant retourne Truesi l'objet est itérable. si ce n'est pas des retours itérablesFalse

def isiterable(object_):
    return hasattr(type(object_), "__iter__")

exemple

fruits = ("apple", "banana", "peach")
isiterable(fruits) # returns True

num = 345
isiterable(num) # returns False

isiterable(str) # returns False because str type is type class and it's not iterable.

hello = "hello dude !"
isiterable(hello) # returns True because as you know string objects are iterable

2
tant de réponses détaillées ci-dessus avec beaucoup de votes positifs et vous jetez une réponse inexpliquée ... meh
Nrzonline

Veuillez ne pas publier de code nu. Incluez également une explication de ce que cela fait.
Jonathan Mee

1

Au lieu de vérifier l' __iter__attribut, vous pouvez vérifier l' __len__attribut, qui est implémenté par chaque itérable intégré python, y compris les chaînes.

>>> hasattr(1, "__len__")
False
>>> hasattr(1.3, "__len__")
False
>>> hasattr("a", "__len__")
True
>>> hasattr([1,2,3], "__len__")
True
>>> hasattr({1,2}, "__len__")
True
>>> hasattr({"a":1}, "__len__")
True
>>> hasattr(("a", 1), "__len__")
True

Les objets non itérables ne l'implémentent pas pour des raisons évidentes. Cependant, il n'attrape pas les itérables définis par l'utilisateur qui ne l'implémentent pas, ni les expressions de générateur, qui iterpeuvent les gérer. Cependant, cela peut être fait en ligne, et l'ajout d'une orexpression simple vérifiant les générateurs résoudrait ce problème. (Notez que l'écriture type(my_generator_expression) == generatordonnerait un NameError. Reportez - vous plutôt à cette réponse.)

Vous pouvez utiliser GeneratorType à partir de types:

>>> import types
>>> types.GeneratorType
<class 'generator'>
>>> gen = (i for i in range(10))
>>> isinstance(gen, types.GeneratorType)
True

--- réponse acceptée par utdemir

(Cela le rend utile pour vérifier si vous pouvez appeler lenl'objet cependant.)


Malheureusement, tous les objets itérables ne sont pas utilisés __len__... dans ce cas, c'est généralement une mauvaise utilisation du calcul de la distance entre 2 objets. où obj.dist()pourrait être facilement substitué.
Tcll

Ouais. La plupart des itérables définis par l'utilisateur implémentent iter et getitem mais pas len. Cependant, les types intégrés le font, et si vous voulez vérifier si vous pouvez appeler la fonction len dessus, la vérification de len est plus sécurisée. Mais tu as raison.
DarthCadeus

0

Pas vraiment "correct" mais peut servir de vérification rapide des types les plus courants comme les cordes, les tuples, les flotteurs, etc ...

>>> '__iter__' in dir('sds')
True
>>> '__iter__' in dir(56)
False
>>> '__iter__' in dir([5,6,9,8])
True
>>> '__iter__' in dir({'jh':'ff'})
True
>>> '__iter__' in dir({'jh'})
True
>>> '__iter__' in dir(56.9865)
False

0

Un peu tard pour la fête mais je me suis posé cette question et j'ai vu cela puis j'ai pensé à une réponse. Je ne sais pas si quelqu'un a déjà posté ça. Mais essentiellement, j'ai remarqué que tous les types itérables ont __getitem __ () dans leur dict. C'est ainsi que vous vérifieriez si un objet est un itérable sans même essayer. (Jeu de mots volontaire)

def is_attr(arg):
    return '__getitem__' in dir(arg)

Malheureusement, ce n'est pas fiable. Exemple
timgeb

1
Les objets de set sont un autre contre-exemple.
Raymond Hettinger
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.