web-dev-qa-db-fra.com

Comment vérifier si un objet est une liste ou un tuple (mais pas une chaîne)?

C’est ce que je fais normalement pour vérifier que l’entrée est une list/Tuple - mais pas une str. Plusieurs fois, j’ai trébuché sur des bogues où une fonction passe un objet str par erreur, et la fonction cible fait for x in lst en supposant que lst est en fait un list ou Tuple.

assert isinstance(lst, (list, Tuple))

Ma question est la suivante: existe-t-il un meilleur moyen d'y parvenir?

400
Sridhar Ratnakumar

En python 2 uniquement (pas python 3):

assert not isinstance(lst, basestring)

C’est réellement ce que vous voulez, sinon vous manquerez de nombreuses choses qui agissent comme des listes, mais ne sont pas des sous-classes de list ou Tuple.

315
Nick Craig-Wood

Rappelez-vous qu'en Python, nous voulons utiliser "la saisie de canard". Ainsi, tout ce qui agit comme une liste peut être traité comme une liste. Donc, ne vérifiez pas le type d'une liste, mais voyez si elle agit comme une liste.

Mais les chaînes de caractères agissent aussi comme une liste, et souvent ce n’est pas ce que nous voulons. Il y a des moments où c'est même un problème! Donc, vérifiez explicitement pour une chaîne, mais utilisez ensuite un type de canard.

Voici une fonction que j'ai écrite pour le plaisir. C'est une version spéciale de repr() qui imprime toute séquence entre crochets ('<', '>').

def srepr(arg):
    if isinstance(arg, basestring): # Python 3: isinstance(arg, str)
        return repr(arg)
    try:
        return '<' + ", ".join(srepr(x) for x in arg) + '>'
    except TypeError: # catch when for loop fails
        return repr(arg) # not a sequence so just return repr

C'est propre et élégant, dans l'ensemble. Mais qu'est-ce que la vérification isinstance() fait là? C'est une sorte de bidouille. Mais c'est essentiel.

Cette fonction s’appelle récursivement sur tout ce qui se comporte comme une liste. Si nous ne traitions pas spécialement la chaîne, elle serait traitée comme une liste et scindée un caractère à la fois. Mais alors l'appel récursif essaierait de traiter chaque caractère comme une liste - et cela fonctionnerait! Même une chaîne d'un caractère fonctionne comme une liste! La fonction continuerait à s’appeler de manière récursive jusqu’à débordement de pile.

Les fonctions telles que celle-ci, qui dépendent de chaque appel récursif décomposant le travail à effectuer, doivent comporter des chaînes de casse spéciales, car vous ne pouvez pas décomposer une chaîne sous le niveau d'une chaîne à un caractère Une chaîne de caractères agit comme une liste.

Remarque: la variable try/except est le moyen le plus propre d’exprimer nos intentions. Mais si ce code était d'une manière ou d'une autre critique du point de vue temporel, nous pourrions vouloir le remplacer par une sorte de test pour voir si arg est une séquence. Plutôt que de tester le type, nous devrions probablement tester les comportements. Si elle a une méthode .strip(), c'est une chaîne, alors ne la considérez pas comme une séquence; sinon, s'il est indexable ou itérable, il s'agit d'une séquence:

def is_sequence(arg):
    return (not hasattr(arg, "strip") and
            hasattr(arg, "__getitem__") or
            hasattr(arg, "__iter__"))

def srepr(arg):
    if is_sequence(arg):
        return '<' + ", ".join(srepr(x) for x in arg) + '>'
    return repr(arg)

EDIT: À l’origine, j’écrivais ce qui précède avec une vérification de __getslice__() mais j’ai remarqué que, dans la documentation du module collections, la méthode intéressante est __getitem__(); cela a du sens, c'est ainsi que vous indexez un objet. Cela semble plus fondamental que __getslice__() alors j'ai changé ce qui précède.

168
steveha
H = "Hello"

if type(H) is list or type(H) is Tuple:
    ## Do Something.
else
    ## Do Something.

Pour Python 2:

import collections

if isinstance(obj, collections.Sequence) and not isinstance(obj, basestring):
    print "obj is a sequence (list, Tuple, etc) but not a string or unicode"

Pour Python 3:

import collections.abc

if isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str):
    print("obj is a sequence (list, Tuple, etc) but not a string or unicode")

Modifié dans la version 3.3: Classes de base abstraites de Collections déplacées vers le module collections.abc. Pour assurer la compatibilité ascendante, ils resteront visibles dans ce module jusqu'à la version 3.8, où il ne fonctionnera plus.

57
suzanshakya

Python à la saveur PHP:

def is_array(var):
    return isinstance(var, (list, Tuple))
31
Cesar

D'une manière générale, le fait qu'une fonction qui itère sur un objet fonctionne à la fois sur les chaînes, les n-uplets et les listes est plus caractéristique que bogue. Vous utilisez certainement pouvez utiliser isinstance ou une frappe de canard pour vérifier un argument, mais pourquoi le devriez-vous?

Cela ressemble à une question rhétorique, mais ce n’est pas le cas. La réponse à "pourquoi devrais-je vérifier le type de l'argument?" va probablement suggérer une solution au problème réel, pas le problème perçu. Pourquoi est-ce un bug quand une chaîne est passée à la fonction? Aussi: s'il s'agit d'un bogue lorsqu'une chaîne est transmise à cette fonction, s'agit-il également d'un bogue si un autre élément non listable/Tuple iterable lui est transmis? Pourquoi ou pourquoi pas?

Je pense que la réponse la plus commune à la question est probablement que les développeurs qui écrivent f("abc") s'attendent à ce que la fonction se comporte comme s'ils avaient écrit f(["abc"]). Il y a probablement des cas où il est plus logique de protéger les développeurs contre eux-mêmes que de prendre en charge le cas d'utilisation de l'itération parmi les caractères d'une chaîne. Mais j'y réfléchirais longuement en premier.

9
Robert Rossney

Essayez ceci pour la lisibilité et les meilleures pratiques:

Python2

import types
if isinstance(lst, types.ListType) or isinstance(lst, types.TupleType):
    # Do something

Python3

import typing
if isinstance(lst, typing.List) or isinstance(lst, typing.Tuple):
    # Do something

J'espère que ça aide.

7
Om Prakash

L'objet str n'a pas d'attribut __iter__

>>> hasattr('', '__iter__')
False 

afin que vous puissiez faire un chèque

assert hasattr(x, '__iter__')

et cela soulèvera aussi une Nice AssertionError pour tout autre objet non-itérable.

Edit: Comme Tim le mentionne dans les commentaires, cela ne fonctionnera qu'en python 2.x, pas 3.x

6
Moe

Ce n’est pas destiné à répondre directement au PO, mais je voulais partager quelques idées connexes.

La réponse @steveha ci-dessus m'a beaucoup intéressée, ce qui semblait donner un exemple de cas où la frappe de canards semble rompre. À bien y penser, cependant, son exemple suggère qu'il est difficile de se conformer à la frappe à l'aide de canards, mais cela ne signifie pas que str mérite une manipulation particulière.

Après tout, un type non -str (par exemple, un type défini par l'utilisateur qui conserve des structures récursives compliquées) peut amener la fonction @steveha srepr à provoquer une récursion infinie. Bien que cela soit certes plutôt improbable, nous ne pouvons ignorer cette possibilité. Par conséquent, plutôt que des cas spéciaux str dans srepr, nous devrions clarifier ce que nous voulons que srepr fasse lorsque la récursion est infinie.

Il peut sembler qu'une solution raisonnable consiste simplement à interrompre la récursion en srepr le moment list(arg) == [arg]. En fait, cela résoudrait complètement le problème avec str, sans aucun isinstance.

Cependant, une structure récursive vraiment compliquée peut provoquer une boucle infinie dans laquelle list(arg) == [arg] ne se produit jamais. Par conséquent, bien que la vérification ci-dessus soit utile, elle ne suffit pas. Nous avons besoin de quelque chose comme une limite stricte sur la profondeur de récursivité.

Ce que je veux dire, c'est que si vous envisagez de gérer des types d'arguments arbitraires, il est beaucoup plus facile de manipuler str via le typage de canard que de manipuler les types plus généraux que vous pouvez (théoriquement) rencontrer. Par conséquent, si vous ressentez le besoin d'exclure des instances str, vous devriez plutôt demander que l'argument soit une instance de l'un des rares types que vous spécifiez explicitement.

5
max

Je trouve une telle fonction appelée is_sequence dans tensorflow

def is_sequence(seq):
  """Returns a true if its input is a collections.Sequence (except strings).
  Args:
    seq: an input sequence.
  Returns:
    True if the sequence is a not a string and is a collections.Sequence.
  """
  return (isinstance(seq, collections.Sequence)
and not isinstance(seq, six.string_types))

Et j'ai vérifié que cela répond à vos besoins. 

3
lerner

Je le fais dans mes cas de test.

def assertIsIterable(self, item):
    #add types here you don't want to mistake as iterables
    if isinstance(item, basestring): 
        raise AssertionError("type %s is not iterable" % type(item))

    #Fake an iteration.
    try:
        for x in item:
            break;
    except TypeError:
        raise AssertionError("type %s is not iterable" % type(item))

Non testé sur les générateurs, je pense que vous êtes laissé au prochain «rendement» si passé dans un générateur, ce qui peut foirer les choses en aval. Mais là encore, c'est un "unittest"

2
FlipMcF

En "tapant du canard", que diriez-vous de

try:
    lst = lst + []
except TypeError:
    #it's not a list

ou

try:
    lst = lst + ()
except TypeError:
    #it's not a Tuple

respectivement. Cela évite l'introspection isinstance/hasattr.

Vous pouvez également vérifier l'inverse:

try:
    lst = lst + ''
except TypeError:
    #it's not (base)string

Toutes les variantes ne modifient pas réellement le contenu de la variable, mais impliquent une réaffectation. Je ne sais pas si cela pourrait être indésirable dans certaines circonstances.

Fait intéressant, avec l'affectation "en place", += non, TypeError serait soulevé dans tous les cas si lst est une liste (pas un Tuple ). C'est pourquoi la mission est faite de cette façon. Peut-être que quelqu'un peut expliquer pourquoi.

1
utobi

manière la plus simple ... en utilisant any et isinstance

>>> console_routers = 'x'
>>> any([isinstance(console_routers, list), isinstance(console_routers, Tuple)])
False
>>>
>>> console_routers = ('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, Tuple)])
True
>>> console_routers = list('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, Tuple)])
True
1
abarik

Python 3 a ceci:

from typing import List

def isit(value):
    return isinstance(value, List)

isit([1, 2, 3])  # True
isit("test")  # False
isit({"Hello": "Mars"})  # False
isit((1, 2))  # False

Donc, pour vérifier les listes et les nuplets, ce serait:

from typing import List, Tuple

def isit(value):
    return isinstance(value, List) or isinstance(value, Tuple)
0
Juha Untinen
assert (type(lst) == list) | (type(lst) == Tuple), "Not a valid lst type, cannot be string"
0
ersh

Fais juste ça

if type(lst) in (list, Tuple):
    # Do stuff
0
ATOzTOA