it-swarm.com.de

Wie prüfe ich, ob ein Objekt eine Liste oder ein Tuple ist (aber keine Zeichenfolge)?

Dies ist, was ich normalerweise mache, um festzustellen, dass die Eingabe eine list/Tuple ist, aber keine str. Viele Male bin ich auf Fehler gestoßen, bei denen eine Funktion versehentlich ein str-Objekt übergibt und die Zielfunktion for x in lst voraussetzt, dass lst tatsächlich eine list oder Tuple ist.

assert isinstance(lst, (list, Tuple))

Meine Frage ist: Gibt es einen besseren Weg, um dies zu erreichen?

400

Nur in Python 2 (nicht Python 3):

assert not isinstance(lst, basestring)

Ist eigentlich das, was Sie wollen, sonst werden Sie viele Dinge verpassen, die sich wie Listen verhalten, aber keine Unterklassen von list oder Tuple sind.

315
Nick Craig-Wood

Denken Sie daran, dass wir in Python "duck typing" verwenden möchten. Alles, was sich wie eine Liste verhält, kann also als Liste behandelt werden. Also nicht nach dem Typ einer Liste suchen, sondern nur nachsehen, ob sie sich wie eine Liste verhält.

Aber auch Strings wirken wie eine Liste, und oft wollen wir das nicht. Es gibt Zeiten, in denen es sogar ein Problem ist! Suchen Sie also explizit nach einer Zeichenfolge, verwenden Sie dann die Eingabetaste.

Hier ist eine Funktion, die ich zum Spaß geschrieben habe. Es ist eine spezielle Version von repr(), die jede Sequenz in spitzen Klammern ('<', '>') druckt.

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

Dies ist insgesamt sauber und elegant. Aber was macht der isinstance()-Check dort? Das ist eine Art Hack. Aber es ist wichtig.

Diese Funktion ruft sich rekursiv auf alles auf, was sich wie eine Liste verhält. Wenn wir den String nicht speziell behandeln, wird er wie eine Liste behandelt und ein Zeichen nach dem anderen aufgeteilt. Aber dann würde der rekursive Aufruf versuchen, jeden Charakter als Liste zu behandeln - und es würde funktionieren! Sogar eine Zeichenfolge mit einem Zeichen funktioniert als Liste! Die Funktion ruft sich selbst solange rekursiv auf, bis der Stapel überläuft.

Funktionen wie diese, die davon abhängig sind, dass jeder rekursive Aufruf die durchzuführende Arbeit unterbricht, haben Sonderzeichenfolgen - da Sie eine Zeichenfolge nicht unter der Ebene einer ein Zeichen langen Zeichenfolge oder sogar einer Zeichenfolge aufteilen können -Zeichenzeichenfolge verhält sich wie eine Liste.

Hinweis: Die try/except ist der sauberste Weg, unsere Absichten auszudrücken. Wenn dieser Code jedoch zeitkritisch ist, möchten wir ihn möglicherweise durch einen Test ersetzen, um zu sehen, ob arg eine Sequenz ist. Anstatt den Typ zu testen, sollten wir wahrscheinlich Verhalten testen. Wenn es eine .strip()-Methode hat, ist es eine Zeichenfolge, betrachten Sie sie also nicht als Sequenz. Andernfalls, wenn es indizierbar oder iterierbar ist, handelt es sich um eine Sequenz:

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)

BEARBEITEN: Ich habe das Obige ursprünglich mit einer Überprüfung für __getslice__() geschrieben, aber ich habe festgestellt, dass in der collections-Dokumentation die interessante Methode __getitem__() ist. Das macht Sinn, so dass Sie ein Objekt indizieren. Das scheint grundlegender als __getslice__() zu sein, also habe ich die obigen Angaben geändert.

168
steveha
H = "Hello"

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

Für 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"

Für 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")

In Version 3.3 geändert: Abstrakte Basisklassen für Sammlungen in das Modul collection.abc verschoben. Aus Gründen der Abwärtskompatibilität sind sie auch in diesem Modul bis Version 3.8 sichtbar, wo sie nicht mehr funktionieren.

57
suzanshakya

Python mit PHP - Geschmack:

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

Im Allgemeinen ist die Tatsache, dass eine Funktion, die ein Objekt durchläuft, sowohl für Zeichenfolgen als auch für Tupel und Listen geeignet ist, mehr ein Feature als ein Fehler. Sie können sicherlich können verwenden Sie isinstance oder duck tippen, um ein Argument zu prüfen, aber warum sollten Sie das tun?

Das klingt nach einer rhetorischen Frage, ist es aber nicht. Die Antwort auf "Warum sollte ich den Typ des Arguments überprüfen?" wird wahrscheinlich eine Lösung für das eigentliche Problem vorschlagen, nicht das wahrgenommene Problem. Warum ist es ein Fehler, wenn ein String an die Funktion übergeben wird? Außerdem: Wenn es sich um einen Fehler handelt, wenn ein String an diese Funktion übergeben wird, ist es auch ein Fehler, wenn ein anderer nicht-list/Tuple-Iterer an sie übergeben wird? Warum oder warum nicht?

Ich denke, dass die häufigste Antwort auf die Frage wahrscheinlich ist, dass Entwickler, die f("abc") schreiben, erwarten, dass sich die Funktion so verhält, als hätten sie f(["abc"]) geschrieben. Es gibt wahrscheinlich Umstände, in denen es sinnvoller ist, Entwickler vor sich selbst zu schützen, als wenn der Anwendungsfall der Iteration der Zeichen in einer Zeichenfolge unterstützt wird. Aber ich würde lange darüber nachdenken.

9
Robert Rossney

Versuchen Sie dies zur besseren Lesbarkeit und Best Practices:

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

Ich hoffe es hilft.

7
Om Prakash

Das str-Objekt hat kein __iter__-Attribut

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

so kannst du einen check machen

assert hasattr(x, '__iter__')

und dies wird auch ein Nice AssertionError für jedes andere nicht wiederholbare Objekt erzeugen.

Edit: Wie Tim in den Kommentaren erwähnt, funktioniert dies nur in Python 2.x, nicht in 3.x

6
Moe

Dies ist nicht dazu gedacht, das OP direkt zu beantworten, aber ich wollte einige verwandte Ideen teilen.

Ich war sehr interessiert an der @ steveha-Antwort oben, die ein Beispiel zu geben schien, bei dem das Tippen von Enten zu brechen scheint. Auf den zweiten Blick deutet sein Beispiel jedoch darauf hin, dass das Tippen von Enten schwer zu befolgen ist, aber es deutet nicht an , dass str etwas Besonderes verdient Handhabung.

Immerhin kann ein Typ, der nicht str ist (z. B. ein benutzerdefinierter Typ, der einige komplizierte rekursive Strukturen beibehält), dazu führen, dass die Funktion @steveha srepr eine unendliche Rekursion verursacht. Obwohl dies zugegebenermaßen eher unwahrscheinlich ist, können wir diese Möglichkeit nicht ignorieren. Daher sollten wir anstelle der Sonderform str in srepr klarstellen, was srepr tun soll, wenn eine unendliche Rekursion auftritt.

Es mag so aussehen, als ob ein vernünftiger Ansatz darin besteht, die Rekursion in srepr in dem Moment list(arg) == [arg] zu unterbrechen. Dies würde in der Tat das Problem mit str ohne isinstance vollständig lösen.

Eine wirklich komplizierte rekursive Struktur kann jedoch eine Endlosschleife verursachen, in der list(arg) == [arg] niemals vorkommt. Daher ist die obige Prüfung zwar nützlich, aber nicht ausreichend. Wir brauchen so etwas wie eine harte Grenze für die Rekursionstiefe.

Mein Punkt ist, dass, wenn Sie vorhaben, mit beliebigen Argumenttypen umzugehen, die Handhabung von str über das Duck-Typing weitaus einfacher ist als die Handhabung der allgemeineren Typen, denen Sie (theoretisch) begegnen können. Wenn Sie also die Notwendigkeit haben, str Instanzen auszuschließen, sollten Sie stattdessen verlangen, dass das Argument eine Instanz eines der wenigen Typen ist, die Sie explizit angeben.

5
max

Ich finde eine solche Funktion namens is_sequence in 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))

Und ich habe bestätigt, dass es Ihren Bedürfnissen entspricht. 

3
lerner

Ich mache das in meinen Testfällen.

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

Ungeprüft an Generatoren, ich glaube, Sie bleiben beim nächsten "Ertrag", wenn Sie in einen Generator geleitet werden, der die Dinge stromabwärts vermasseln könnte. Aber andererseits ist dies ein "unittest"

2
FlipMcF

In der Art von "duck typing", wie wäre es damit

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

oder

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

beziehungsweise. Dies vermeidet das isinstance/hasattr-Introspektions-Zeug.

Sie könnten auch umgekehrt prüfen:

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

Alle Varianten ändern nicht tatsächlich den Inhalt der Variablen, sondern implizieren eine Neuzuweisung. Ich bin nicht sicher, ob dies unter Umständen unerwünscht ist.

Interessanterweise würde mit der Zuweisung "in place" += auf jeden Fall keine TypeError ausgelöst werden, wenn lst eine list ist (keine Tuple ). Deshalb erfolgt die Zuweisung auf diese Weise. Vielleicht kann jemand beleuchten, warum das so ist.

1
utobi

einfachste Möglichkeit ... mit any und 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 hat folgendes:

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

Um sowohl nach Listen als auch nach Tupeln zu suchen, wäre dies:

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

Tu das einfach

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