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

444

C'est ce que je fais normalement pour vérifier que l'entrée est un list/ tuple- mais pas un str. Parce que plusieurs fois, je suis tombé sur des bugs où une fonction passe un strobjet par erreur, et la fonction cible for x in lstsuppose que lstc'est en fait un listou tuple.

assert isinstance(lst, (list, tuple))

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

Sridhar Ratnakumar
la source
9
type (lst) est list?
jackalope
1
not isinstance (key, six.string_types)
wyx

Réponses:

332

En python 2 uniquement (pas en python 3):

assert not isinstance(lst, basestring)

C'est en fait ce que vous voulez, sinon vous passerez à côté de beaucoup de choses qui agissent comme des listes, mais ne sont pas des sous-classes de listou tuple.

Nick Craig-Wood
la source
91
Oui, c'est la bonne réponse. En Python 3, basestringc'est parti, et il suffit de vérifier isinstance(lst, str).
steveha
5
Il y a beaucoup de choses que vous pouvez parcourir comme les listes, par exemple set, les expressions de générateur, les itérateurs. Il y a des choses exotiques comme mmap, des choses moins exotiques arrayqui agissent à peu près comme des listes, et probablement beaucoup plus que j'ai oubliées.
Nick Craig-Wood
50
Il convient de noter que cela ne garantit pas qu'il lstest itérable, tandis que l'original l'a fait (par exemple, un int passerait cette vérification)
Peter Gibson
11
@PeterGibson - Une combinaison des deux fournira une vérification valide et plus restrictive et garantira 1) lst est itérable, 2) lst n'est pas une chaîne. assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring)
strongMA
4
Eh bien, cette solution ne vérifie que les types dérivés de chaînes, mais qu'en est-il des entiers, des doubles ou de tout autre type non itérable?
Eneko Alonso
171

N'oubliez pas qu'en Python, nous voulons utiliser le "typage du 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, voyez simplement si elle agit comme une liste.

Mais les chaînes agissent également 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, recherchez explicitement une chaîne, mais utilisez ensuite la frappe de canard.

Voici une fonction que j'ai écrite pour le plaisir. Il s'agit d'une version spéciale 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 que fait ce isinstance()chèque là-bas? C'est une sorte de hack. Mais c'est essentiel.

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

Les fonctions comme celle-ci, qui dépendent de chaque appel récursif décomposant le travail à effectuer, doivent avoir des chaînes de cas spécial - car vous ne pouvez pas décomposer une chaîne en dessous du niveau d'une chaîne à un caractère, et même une La chaîne de caractères agit comme une liste.

Remarque: le try/ exceptest le moyen le plus propre d'exprimer nos intentions. Mais si ce code était en quelque sorte critique dans le temps, nous pourrions vouloir le remplacer par une sorte de test pour voir s'il args'agit d'une séquence. Plutôt que de tester le type, nous devrions probablement tester les comportements. S'il a une .strip()méthode, c'est une chaîne, alors ne la considérez pas comme une séquence; sinon, si elle est indexable ou itérable, c'est 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: J'ai écrit à l'origine ce qui précède avec une vérification, __getslice__()mais j'ai remarqué que dans la collectionsdocumentation du module, la méthode intéressante est __getitem__(); c'est logique, c'est ainsi que vous indexez un objet. Cela semble plus fondamental que __getslice__()j'ai donc changé ce qui précède.

steveha
la source
2
@stantonk, merci de l'avoir dit, mais je pense qu'il y avait déjà une réponse acceptée lorsque j'ai écrit ceci et je ne m'attends pas vraiment à ce que la réponse acceptée soit modifiée.
steveha
@steveha: sreprest une idée très intéressante. Mais j'ai une opinion différente de la vôtre sur la nécessité de cas particulier str. Oui, strest de loin l'itérable le plus évident et le plus courant qui provoquerait une récursion infinie dans srepr. Mais je peux facilement imaginer des itérables définis par l'utilisateur qui se comportent de la même manière (avec ou sans raison valable). Plutôt que de cas particulier str, nous devons admettre que cette approche peut se heurter à une récursion infinie et convenir d'une manière de la gérer. Je posterai ma suggestion dans une réponse.
max
1
Je pense que c'est définitivement la bonne voie. Cependant, pour gérer le cas spécial (de la chaîne dans ce scénario), je pense que nous ferions mieux de poser la question "comment un humain ferait-il la différence?" Par exemple, considérons un argument de fonction qui peut être une liste d'adresses e-mail ou une seule adresse e-mail (en gardant à l'esprit qu'une chaîne est simplement une liste de caractères). Donnez cette variable à un humain. Comment pourrait-il dire de quoi il s'agit? La façon la plus simple à laquelle je peux penser est de voir combien de caractères se trouvent dans chaque élément de la liste. S'il est supérieur à 1, l'argument ne peut certainement pas être une liste de caractères.
Josh
1
J'y ai réfléchi un peu et j'en ai discuté avec quelques autres personnes, et je pense que ça srepr()va comme ça . Nous avons besoin d'une fonction récursive pour gérer des choses comme une liste imbriquée dans une autre liste; mais pour les chaînes, nous préférons les faire imprimer comme "foo"plutôt que comme <'f', 'o', 'o'>. Donc, une vérification explicite d'une chaîne a du sens ici. De plus, il n'y a vraiment pas d'autres exemples de types de données où l'itération retourne toujours un itérable et la récursivité provoquera toujours un débordement de pile, nous n'avons donc pas besoin d'une propriété spéciale pour tester cela ("La praticité bat la pureté").
steveha
1
Cela ne fonctionne pas en Python 3, car les chaînes ont une __iter__()méthode en Python 3, mais pas en Python 2. Il vous manque des parenthèses dans is_sequence(), il devrait lire:return (not hasattr(arg, "strip") and (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__")))
MiniQuark
124
H = "Hello"

if type(H) is list or type(H) is tuple:
    ## Do Something.
else
    ## Do Something.
Mani Shankar Venkankatachalam
la source
11
Sauf qu'il n'utilise pas l'idiome Python de la frappe de canard comme d'autres commentateurs l'ont souligné (bien qu'il réponde à la question directement et proprement).
Soren Bjornstad
7
Cette réponse est moins acceptable que d'autres car elle ne permet pas de taper du canard et elle échoue également dans le cas simple du sous-classement (un exemple typique est la classe namedtuple).
Philippe Gauthier
11
"Ne pas autoriser la frappe de canard" ne rend pas la réponse moins acceptable, d'autant plus que cette réponse répond effectivement à la question.
Petri
4
J'ai voté contre cette réponse, mais elle if isinstance( H, (list, tuple) ): ...est plus courte et plus claire.
shahar_m
2
Syntaxe alternative:if type(H) in [list, tuple]:
Štefan Schindler
77

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")

Modifié dans la version 3.3: Classes de base abstraites des collections déplacées vers le module collections.abc. Pour des raisons de compatibilité descendante, ils continueront à être visibles dans ce module également jusqu'à la version 3.8 où il cessera de fonctionner.

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"
suzanshakya
la source
5
Hou la la! Cela fonctionne très bien et est beaucoup plus succinct que toutes les autres bonnes réponses. Je n'avais aucune idée que les types intégrés héritent de collections.Sequencemais je l'ai testé et je vois qu'ils le font. Il en va de même xrange. Encore mieux, ce test exclut dict, qui a à la fois __getitem__et __iter__.
Neil Mayhew
Une idée pourquoi le résultat de inspect.getmro(list)ne comprend pas Sequence? Comment sommes-nous censés comprendre ce que nous pouvons faire isinstancequand getmrone montre pas tout?
Steve Jorgensen
@SteveJorgensen Method Resolution Order définit le chemin de recherche de classe utilisé par Python pour rechercher la bonne méthode à utiliser dans les classes. Sequenceest une classe abstraite.
suzanshakya
3
En Python3, vous pouvez remplacer isinstance (obj, basestring) par isinstance (obj, str), et cela devrait fonctionner.
Adrian Keister
2
dans Python 3, vous avez besoin et non de l'instance (obj, octets) ... si vous voulez une liste de choses, et pas seulement pour énumérer les octets ...
Erik Aronesty
35

Python avec saveur PHP:

def is_array(var):
    return isinstance(var, (list, tuple))
César
la source
6
Python est un langage de type canard, vous devriez donc vraiment vérifier si var a un attribut __getitem__. Le nom est également trompeur, car il existe également un module de tableau. Et le var pourrait également être un numpy.ndarray ou tout autre type, qui a __getitem__. Voir stackoverflow.com/a/1835259/470560 pour la bonne réponse.
peterhil
9
@peterhil a straussi __getitem__donc votre chèque n'exclut passtr
erikbwork
9
Un dicton aussi. Vérifier __getitem__ici est un mauvais conseil.
Petri
10

D'une manière générale, le fait qu'une fonction qui itère sur un objet fonctionne aussi bien sur les chaînes que sur les tuples et les listes est plus caractéristique que bug. Vous pouvez certainement utiliser isinstanceou taper du texte pour vérifier un argument, mais pourquoi 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 vrai problème, pas au problème perçu. Pourquoi est-ce un bug quand une chaîne est passée à la fonction? Aussi: si c'est un bug quand une chaîne est passée à cette fonction, est-ce aussi un bug si un autre itérable non list / tuple lui est passé? Pourquoi ou pourquoi pas?

Je pense que la réponse la plus courante à 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 existe probablement des circonstances 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 sur les caractères d'une chaîne. Mais j'y réfléchirais longuement et longuement en premier.

Robert Rossney
la source
16
"Mais j'y réfléchirais longuement et longuement en premier." Je ne le ferais pas. Si la fonction est censée être une fonction list-y, alors oui, elle devrait les traiter de la même manière (c'est-à-dire, étant donné une liste, la recracher à l'envers, des choses comme ça). Cependant, s'il s'agit d'une fonction où l'un des arguments peut être une chaîne ou une liste de chaînes (ce qui est un besoin assez courant), forcer le développeur utilisant cette fonction à toujours entrer son paramètre à l'intérieur d'un tableau semble un peu trop . Réfléchissez également à la façon dont vous géreriez, disons, l'entrée JSON. Vous voudriez certainement traiter une liste d'objets différente d'une chaîne.
Jordan Reiter
8

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 cela aide.

Om Prakash
la source
Python 3.6.5:AttributeError: module 'types' has no attribute 'ListType'
Juha Untinen
1
En Python 3, c'est: from typing import List-> isinstance([1, 2, 3], List= True, et isinstance("asd", List)= False
Juha Untinen
5

L' strobjet n'a pas d' __iter__attribut

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

afin que vous puissiez faire un contrôle

assert hasattr(x, '__iter__')

et cela soulèvera également une belle AssertionErrorpour 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

Moe
la source
8
Attention: en Python 3 hasattr('','__iter__')revient True. Et bien sûr, cela a du sens puisque vous pouvez parcourir une chaîne.
Tim Pietzcker
1
Vraiment? Je ne le savais pas. J'ai toujours pensé que c'était une solution élégante au problème, eh bien.
Moe
1
Ce test n'a pas fonctionné sur pyodbc.Row. Il n'a pas d' itère __ () mais il se comporte plus ou moins comme une liste (il définit même "__setitem "). Vous pouvez très bien itérer ses éléments. La fonction len () fonctionne et vous pouvez indexer ses éléments. J'ai du mal à trouver la bonne combinaison qui attrape tous les types de liste, mais exclut les chaînes. Je pense que je vais me contenter d'un contrôle sur " getitem " et " len " tout en excluant explicitement la chaîne de base.
haridsv
5

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

J'ai été très intéressé par la réponse de @steveha ci-dessus, qui semblait donner un exemple où la frappe de canard semble casser. À la réflexion, cependant, son exemple suggère que le typage du canard est difficile à respecter, mais il ne suggère pas qu'il strmérite une manipulation particulière.

Après tout, un non- strtype (par exemple, un type défini par l'utilisateur qui conserve certaines structures récursives compliquées) peut provoquer une sreprfonction @steveha provoquant une récursion infinie. Bien que cela soit certes assez improbable, nous ne pouvons pas ignorer cette possibilité. Par conséquent, plutôt que de cuvelage spécial strdans srepr, nous devons clarifier ce que nous voulons sreprfaire quand un résultat de récursion infinie.

Il peut sembler qu'une approche raisonnable consiste simplement à briser la récursivité dans sreprl'instant 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 où il list(arg) == [arg]ne se produit jamais. Par conséquent, bien que la vérification ci-dessus soit utile, elle n'est pas suffisante. Nous avons besoin de quelque chose comme une limite stricte sur la profondeur de récursivité.

Mon point est que si vous envisagez de gérer des types d'arguments arbitraires, la manipulation strvia la frappe de canard est beaucoup, beaucoup plus facile que la gestion des types plus généraux que vous pouvez (théoriquement) rencontrer. Donc, si vous ressentez le besoin d'exclure des strinstances, vous devez plutôt demander que l'argument soit une instance de l'un des rares types que vous spécifiez explicitement.

max
la source
1
Hmm, j'aime ta façon de penser. Je pense que vous ne pouvez pas prétendre que mon code est pratique: il y a exactement un cas commun str, que le code de cas spécial gère. Mais il devrait peut-être y avoir une nouvelle propriété standard que le code peut inspecter, .__atomic__disons, qui signale que quelque chose ne peut plus être décomposé. Il est probablement trop tard pour ajouter une autre fonction intégrée atomic()à Python, mais nous pouvons peut-être ajouter from collections import atomicquelque chose.
steveha
5

Je trouve une telle fonction nommé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é qu'il répond à vos besoins.

Lerner Zhang
la source
2

Je le fais dans mes tests.

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» s'il est passé dans un générateur, ce qui peut visser les choses en aval. Mais là encore, c'est un «unittest»

FlipMcF
la source
2

De manière "typée canard", que diriez-

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

ou

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

respectivement. Cela évite le truc isinstance/ hasattrintrospection.

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", +=aucun TypeErrorne serait soulevé dans tous les cas s'il lsts'agit d'une liste (et non d'un tuple ). C'est pourquoi la mission se fait de cette façon. Peut-être que quelqu'un peut nous expliquer pourquoi.

utobi
la source
1

manière la plus simple ... en utilisant anyetisinstance

>>> 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
abarik
la source
1

Une autre version du typage canard pour aider à distinguer les objets de type chaîne des autres objets de type séquence.

La représentation sous forme de chaîne des objets de type chaîne est la chaîne elle-même, vous pouvez donc vérifier si vous récupérez un objet égal du strconstructeur:

# If a string was passed, convert it to a single-element sequence
if var == str(var):
    my_list = [var]

# All other iterables
else: 
    my_list = list(var)

Cela devrait fonctionner pour tous les objets compatibles avec stret pour toutes sortes d'objets itérables.

stevepastelan
la source
0

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 tuples, ce serait:

from typing import List, Tuple

def isit(value):
    return isinstance(value, List) or isinstance(value, Tuple)
Juha Untinen
la source
0
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"
ersh
la source
2
est-ce une bonne façon de procéder?
ersh
1
Bienvenue chez SO. Une explication de la raison pour laquelle ce code répond à la question serait utile.
Nick
Oui, bien sûr, j'utilise des méthodes similaires à cela, car le canal est traité comme un ou si vous affirmez que le type doit être une liste ou un tuple de type produisant une erreur de message personnalisé pour la gestion des erreurs. Je crois que cela répond à la question, mais j'étais curieux comme si c'était un moyen efficace de le faire car j'essaie toujours de comprendre le code le plus optimisé. Je ne suis pas sûr cependant si ce code manque de choses qui peuvent agir comme des listes / tuples mais ne sont pas des sous-classes non plus, comme la façon dont la réponse acceptée répond à cette possibilité. Merci!
ersh
-1

Fais juste ça

if type(lst) in (list, tuple):
    # Do stuff
ATOzTOA
la source
5
isinstance (lst, (list, tuple))
Davi Lima
@DaviLima OK, c'est une autre façon. Mais type () est recommandé pour les types intégrés et est utilisé pour les classes.
ATOzTOA
-1

en python> 3,6

import collections
isinstance(set(),collections.abc.Container)
True
isinstance([],collections.abc.Container)
True
isinstance({},collections.abc.Container)
True
isinstance((),collections.abc.Container)
True
isinstance(str,collections.abc.Container)
False
Jean Doux
la source
2
Lors de la dernière vérification, vous utilisez un type str, pas une chaîne. Essayez isinstance('my_string', collections.abc.Container)et vous verrez qu'il reviendra True. C'est parce que abc.Containerfournit la __contains__méthode, et les chaînes l'ont, bien sûr.
Georgy
-6

J'ai tendance à faire ça (si je devais vraiment, vraiment le faire):

for i in some_var:
   if type(i) == type(list()):
       #do something with a list
   elif type(i) == type(tuple()):
       #do something with a tuple
   elif type(i) == type(str()):
       #here's your string
DrBloodmoney
la source
5
Vous ne devriez presque jamais faire ça. Que se passe-t-il si je suis some_varune instance d'une classe qui est une sous-classe de list()? Votre code n'aura aucune idée de ce qu'il faut en faire, même s'il fonctionnera parfaitement sous le code "faire quelque chose avec une liste". Et vous avez rarement besoin de vous soucier de la différence entre une liste et un tuple. Désolé, -1.
steveha
1
Pas besoin d'écrire type(tuple())- tuplefera l'affaire. Idem pour la liste. Aussi, les deux stret unicodeétendent basestring, qui est le vrai type de chaîne, vous devez donc vérifier cela à la place.
traitez bien vos mods le
@DrBloodmoney: downvote accidentel. Veuillez (trivialement) modifier votre réponse pour me permettre de supprimer le downvote.
SabreWolfy
L'égalité ne me semble pas être une comparaison significative pour les types. Je l' identité à la place tester: type(i) is list. En outre, type(list())c'est juste listlui - même ... Enfin, cela ne fonctionne pas correctement avec les sous-classes. Si iest en fait et OrderedDict, ou une sorte de namedtuple, ce code traitera comme une chaîne.
bukzor