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


Y a-t-il une méthode comme isiterable? La seule solution que j'ai trouvée jusqu'ici est d'appeler

hasattr(myObj, '__iter__')

Mais je ne suis pas sûr à quel point c'est infaillible.


802
2017-12-23 12:13


origine


Réponses:


  1. Vérification pour __iter__ fonctionne sur les types de séquence, mais il échouerait par ex. cordes en Python 2. Je voudrais connaître la bonne réponse aussi, jusque là, voici une possibilité (qui fonctionnerait aussi sur les cordes):

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

    le iter contrôles intégrés pour la __iter__ méthode ou dans le cas de chaînes le __getitem__ méthode.

  2. Une autre approche pythonique générale consiste à supposer une itérative, puis échouer gracieusement si elle ne fonctionne pas sur l'objet donné. Le glossaire Python:

    Style de programmation Pythonic 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 ("Si cela ressemble à un canard et les charlatans comme un canard, ça doit être un canardEn mettant l'accent sur les interfaces plutôt que sur des types spécifiques, un code bien conçu améliore sa flexibilité en permettant une substitution polymorphe.Le typage en canard évite les tests utilisant type () ou isinstance (). Au lieu de cela, il utilise généralement le style de programmation EAFP (plus facile à demander pardon que permission).

    ...

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

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

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


644
2017-12-23 12:16



Canard en train de taper

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

# for obj in iterator:
#     pass

Vérification de type

Utilisez le Classes de base abstraites. Ils ont besoin au moins de Python 2.6 et ne fonctionnent que pour les nouvelles classes.

import collections

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

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

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


491
2017-12-23 12:53



Je voudrais jeter un peu plus de lumière sur l'interaction de iter, __iter__ et __getitem__ et ce qui se passe derrière les rideaux. Armé de cette connaissance, vous serez en mesure de comprendre pourquoi le mieux que vous pouvez faire est

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

Je vais énumérer les faits d'abord, puis suivi avec un rappel rapide de ce qui se passe quand vous employez un for boucle en python, suivie d'une discussion pour illustrer les faits.

Faits

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

    une) o a un __iter__ méthode qui renvoie un objet itérateur. Un itérateur est un objet avec un __iter__ et un __next__ (Python 2: next) méthode.

    b) o a un __getitem__ méthode.

  2. Vérification d'une instance de Iterable ou Sequenceou en vérifiant la attribut __iter__ n'est pas assez.

  3. Si un objet o implémente seulement __getitem__, mais non __iter__, iter(o)construira un itérateur qui essaie d'aller chercher des objets o par indice entier, en commençant à l'index 0. L'itérateur captera IndexError (mais pas d'autres erreurs) qui est soulevée puis soulève StopIteration lui-même.

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

  5. Si un objet o met en oeuvre __iter__, la iter la fonction s'assurera que l'objet renvoyé par __iter__ est un itérateur. Il n'y a pas de vérification de santé mentale si un objet implémente seulement __getitem__.

  6. __iter__ gagne. Si un objet o met en œuvre les deux __iter__ et __getitem__, iter(o) appellera __iter__.

  7. Si vous voulez rendre vos propres objets itérables, mettez toujours en œuvre le __iter__ méthode.

for boucles

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

Lorsque vous utilisez for item in o pour un objet itérable o, Appels Python iter(o) et attend un objet itérateur comme valeur de retour. Un itérateur est un objet qui implémente un __next__ (ou next en Python 2) méthode et un __iter__ méthode.

Par convention, le __iter__ méthode d'un itérateur doit retourner l'objet lui-même (c.-à-d. return self). Python appelle ensuite next sur l'itérateur jusqu'à StopIteration est élevé. Tout ceci se passe 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

Sur les points 1 et 2: obtenir un itérateur et des contrôles non fiables

Considérez la classe suivante:

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

Appel iter avec une instance de BasicIterable retournera un itérateur sans aucun problème car BasicIterable met en oeuvre __getitem__.

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

Cependant, il est important de noter que b n'a pas le __iter__ attribut et n'est pas considéré comme une instance de Iterable ou Sequence:

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

C'est pourquoi Python courant par Luciano Ramalho recommande d'appeler iter et gérer le potentiel TypeError comme le moyen le plus précis de vérifier si un objet est itérable. Citant directement du livre:

A partir de Python 3.4, le moyen le plus précis de vérifier si un objet x est itérable est d'appeler iter(x) et gérer un TypeError exception si ce n'est pas le cas. C'est plus précis que d'utiliser isinstance(x, abc.Iterable) , car iter(x) considère également l'héritage __getitem__ méthode, tandis que Iterable ABC ne le fait pas.

Sur le point 3: Itérer sur des objets qui ne fournissent que __getitem__, mais non __iter__

Itérer sur une instance de BasicIterable fonctionne comme prévu: Python construit un itérateur qui tente d'extraire les éléments par index, en commençant à zéro, jusqu'à ce qu'un IndexError est élevé. L'objet de démonstration __getitem__ méthode renvoie simplement le item qui a été fourni comme l'argument de __getitem__(self, item) par l'itérateur retourné 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 lève StopIteration quand il ne peut pas retourner l'objet suivant et que le IndexError qui est levé pour item == 3 est géré en interne. C'est pourquoi la boucle sur un BasicIterable avec un for boucle 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 retourné par iteressaie d'accéder aux éléments par index. WrappedDict n'hérite pas de dict, ce qui signifie que les instances n'auront pas __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 laquelle la notation en carré 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: iter vérifie un itérateur lorsqu'il appelle __iter__:

Quand iter(o) est appelé pour un objet o, iter fera en sorte que la valeur de retour de __iter__, si la méthode est présente, est un itérateur. Cela signifie que l'objet retourné doit mettre en œuvre __next__ (ou next en Python 2) et __iter__. iter ne peut effectuer aucune vérification de santé pour les objets qui ne fournir __getitem__, car il n'a aucun moyen de vérifier si les éléments de l'objet sont accessibles par un 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 FailIterIterable instances échoue immédiatement, tout en construisant un itérateur FailGetItemIterable réussit, mais jettera une exception sur le 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

Sur le point 6: __iter__ gagne

Celui-ci est simple. Si un objet implémente __iter__ et __getitem__, iter appellera __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 de la boucle sur une instance:

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

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

Vous pourriez vous demander pourquoi la plupart des séquences intégrées comme list mettre en œuvre un __iter__ méthode quand __getitem__ serait suffisant.

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, itération sur les instances de la classe ci-dessus, qui délègue les appels à __getitem__ à list.__getitem__ (en utilisant la notation de crochet), fonctionnera bien:

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

Les raisons pour lesquelles vos iterables personnalisés doivent être implémentés __iter__ sont les suivants:

  1. Si vous implémentez __iter__, les instances seront considérées comme des itérables, et isinstance(o, collections.Iterable) reviendra True.
  2. Si l'objet retourné par __iter__ n'est pas un itérateur, iter échouera immédiatement et soulever un TypeError.
  3. La manipulation spéciale de __getitem__ existe pour des raisons de compatibilité ascendante. Citant à nouveau de Fluent Python:

C'est pourquoi toute séquence Python est itérable: ils mettent tous en œuvre __getitem__ . En réalité,   les séquences standard implémentent également __iter__, et le vôtre devrait aussi, parce que le   manipulation spéciale de __getitem__ existe pour des raisons de compatibilité descendante et peut être   allé à l'avenir (bien qu'il ne soit pas obsolète que j'écris ceci).


55
2018-04-04 16:04



Ce n'est pas suffisant: l'objet retourné par __iter__ doit mettre en œuvre le protocole d'itération (c.-à-d. next méthode). Voir la section pertinente dans le Documentation.

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


28
2017-12-23 12:17



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

Ne pas lancer les vérifications pour voir si votre canard est vraiment un canard pour voir si c'est itérable ou non, traitez-la comme si c'était et se plaindre si ce n'était pas.


18
2017-12-23 12:21



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

hasattr(obj, '__contains__')

qui vérifie essentiellement si l'objet met en œuvre le in opérateur.

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

  • c'est une expression (travaille comme lambda, par opposition à la essayer ... sauf une variante)
  • il est (devrait être) mis en œuvre par tous les itérables, y compris cordes (par opposition à __iter__)
  • fonctionne sur n'importe quel Python> = 2.5

Remarques:

  • la philosophie Python de «demander pardon, pas de permission» ne fonctionne pas bien quand, par ex. Dans une liste, vous avez à la fois des itérables et des non-iterables et vous devez traiter chaque élément différemment en fonction de son type (traiter les itérables à essayer et les non-itérables à l'exception de aurait travail, mais il semblerait laid et induisant en erreur)
  • Les solutions à ce problème qui tentent d'itérer réellement sur l'objet (par exemple [x pour x dans obj]) pour vérifier si c'est itératif peuvent induire des pénalités de performance significatives pour les grandes itérations (surtout si vous avez juste besoin des premiers éléments exemple) et devrait être évité

16
2017-11-09 16:40



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 tirer parti de la nouvelle infrastructure ABC (classe de base abstraite) avec certains ABC intégrés disponibles dans le module de 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, si cela est souhaitable ou fonctionne réellement, est juste une question de conventions. Comme vous pouvez le voir, vous pouvez enregistrer un objet non itérable comme Iterable - et il déclenchera une exception lors de l'exécution. Ainsi, isinstance acquiert une «nouvelle» signification - il vérifie simplement la compatibilité de type «déclaré», ce qui est un bon moyen d'aller 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 une erreur TypeError, mais si le bon ABC a été enregistré, votre vérification n'est pas utile. Au contraire, si le __iter__ méthode est disponible Python reconnaît automatiquement l'objet de cette classe comme étant Iterable.

Donc, si vous attendez juste un itérable, passez en revue et oubliez-le. D'un autre côté, si vous devez faire des choses différentes en fonction du type d'entrée, vous pourriez trouver l'infrastructure ABC très utile.


14
2017-12-23 13:35