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?
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
.
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.
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.
Python à la saveur PHP:
def is_array(var):
return isinstance(var, (list, Tuple))
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.
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.
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
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.
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.
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"
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.
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
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)
assert (type(lst) == list) | (type(lst) == Tuple), "Not a valid lst type, cannot be string"
Fais juste ça
if type(lst) in (list, Tuple):
# Do stuff