Wie man eine Variable erkennt, ist iterierbar, aber keine Zeichenfolge

86

Ich habe eine Funktion, die ein Argument akzeptiert, das entweder ein einzelnes oder ein doppeltes Element sein kann:

def iterable(arg)
    if #arg is an iterable:
        print "yes"
    else:
        print "no"

damit:

>>> iterable (("f", "f"))
Ja

>>> iterierbar (["f", "f"])
Ja

>>> iterable ("ff")
Nein

Das Problem ist, dass die Zeichenfolge technisch iterierbar ist, sodass ich den ValueError beim Versuch nicht einfach abfangen kann arg[1]. Ich möchte isinstance () nicht verwenden, da dies keine gute Praxis ist (oder wie mir gesagt wurde).

Priester
quelle
1
Welche Version von Python? Ich glaube, die Antwort ist zwischen 2. * und 3 unterschiedlich
Kathy Van Stone
4
Ihnen wurde falsch gesagt, dass Instanz keine schlechte Praxis ist.
Lennart Regebro
3
Oh, warte, vielleicht bezieht er sich auf das Prinzip, dass es schlecht ist, einen Objekttyp zu überprüfen, und dass dies ein Hinweis darauf ist, dass das Programm kaputt ist? Dies gilt im Prinzip (aber nicht immer in der Praxis). Dies kann ein solcher Fall sein oder nicht. Aber nicht die Funktion ist die Instanz, die das Problem darstellt, sondern die Gewohnheit, nach Typen zu suchen.
Lennart Regebro
@ Lennart: canonical.org/~kragen/isinstance es kann jedoch veraltet sein
priester
@up Dies erwähnt jedoch nicht die typbasierte Überladung von Funktionen und isinstanceist die Möglichkeit, dies in dynamisch typisierten Sprachen zu tun. Eine Sache, die nicht jeden Tag verwendet werden darf, aber in berechtigten Fällen in Ordnung ist.
Kos

Antworten:

49

Verwenden Sie isinstance (ich verstehe nicht, warum es schlechte Praxis ist)

import types
if not isinstance(arg, types.StringTypes):

Beachten Sie die Verwendung von StringTypes. Es stellt sicher, dass wir keine obskure Art von Zeichenfolge vergessen.

Auf der anderen Seite funktioniert dies auch für abgeleitete Zeichenfolgenklassen.

class MyString(str):
    pass

isinstance(MyString("  "), types.StringTypes) # true

Vielleicht möchten Sie auch einen Blick auf diese vorherige Frage werfen .

Prost.


NB: Das Verhalten wurde in Python 3 geändert StringTypesund basestringist nicht mehr definiert. Abhängig von Ihren Anforderungen können Sie sie isinstancedurch stroder ein Teilmengen-Tupel ersetzen (str, bytes, unicode), z. B. für Cython-Benutzer. Wie @Theron Luhn erwähnt hat, können Sie auch verwenden six.

scvalex
quelle
Schön, Scvalex. Ich entferne jetzt meine -1 und mache daraus eine +1 :-).
Tom
2
Ich denke, die Idee der schlechten Praxis beruht auf dem Prinzip der Ententypisierung . Ein Mitglied einer bestimmten Klasse zu sein bedeutet weder, dass es das einzige Objekt ist, das verwendet werden kann, noch dass die erwarteten Methoden verfügbar sind. Aber ich denke, manchmal kann man einfach nicht ableiten, was die Methode tut, selbst wenn sie vorhanden ist. Dies isinstancekönnte der einzige Weg sein.
Estani
2
Hinweis: types.StringTypes ist in Python 3 nicht verfügbar. Da es in py3k nur einen Zeichenfolgentyp gibt, ist dies meiner Meinung nach sicher do isinstance(arg, str). Für eine abwärtskompatible Version sollten Sie pythonhosted.org/six/#six.string_types
Theron Luhn am
Ich verwende ausschließlich Python3 und habe festgestellt types.StringTypes, dass es in Python3 nicht verfügbar ist. Was ist der Wert in Python2?
Kevinarpe
2
2017 : Diese Antwort ist nicht mehr gültig. Unter stackoverflow.com/a/44328500/99834 finden Sie eine Antwort , die mit allen Python-Versionen funktioniert.
Sorin
26

Ab 2017 ist hier eine tragbare Lösung, die mit allen Versionen von Python funktioniert:

#!/usr/bin/env python
import collections
import six


def iterable(arg):
    return (
        isinstance(arg, collections.Iterable) 
        and not isinstance(arg, six.string_types)
    )


# non-string iterables    
assert iterable(("f", "f"))    # tuple
assert iterable(["f", "f"])    # list
assert iterable(iter("ff"))    # iterator
assert iterable(range(44))     # generator
assert iterable(b"ff")         # bytes (Python 2 calls this a string)

# strings or non-iterables
assert not iterable(u"ff")     # string
assert not iterable(44)        # integer
assert not iterable(iterable)  # function
Sorin
quelle
Es gibt einige kleinere Inkonsistenzen zwischen 2/3 mit Bytestrings, aber wenn Sie die native "Zeichenfolge" verwenden, sind beide falsch
Nick T
16

Seit Python 2.6 mit der Einführung abstrakter Basisklassen isinstance(verwendet auf ABCs, keine konkreten Klassen) wird dies nun als vollkommen akzeptabel angesehen. Speziell:

from abc import ABCMeta, abstractmethod

class NonStringIterable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is NonStringIterable:
            if any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

Dies ist eine exakte Kopie (die nur den Klassennamen ändert) von Iterablewie in _abcoll.py(ein Implementierungsdetail von collections.py) definiert. Der Grund, warum dies so funktioniert, wie Sie es wünschen collections.Iterable, ist, dass letzteres die Extrameile geht, um sicherzustellen, dass Zeichenfolgen vorhanden sind wird als iterabel angesehen, indem Iterable.register(str)explizit direkt nach dieser classAnweisung aufgerufen wird .

Natürlich ist es einfach zu erweitern, __subclasshook__indem Sie Falsevor dem anyAufruf für andere Klassen zurückkehren, die Sie ausdrücklich von Ihrer Definition ausschließen möchten.

In jedem Fall , nachdem Sie dieses neue Modul als importiert haben myiter, isinstance('ciao', myiter.NonStringIterable)werden False, und isinstance([1,2,3], myiter.NonStringIterable)wird True, wie Sie es fordern - und in Python 2.6 und höher ist dies der richtige Weg , so auszugestalten, Kontrollen betrachtet ... definieren eine abstrakte Basisklasse und überprüfe isinstancees.

Alex Martelli
quelle
In Python 3 isinstance('spam', NonStringIterable)kehrt zurück True.
Nick T
1
(...) und in Python 2.6 und höher wird dies als der richtige Weg angesehen, solche Überprüfungen durchzuführen (...) Wie ein Missbrauch eines bekannten Konzepts der abstrakten Klasse auf diese Weise jemals als der richtige Weg angesehen werden könnte, ist für mich unverständlich. Der richtige Weg wäre, stattdessen einen ähnlichen Operator einzuführen .
Piotr Dobrogost
Alex, kannst du Nicks Behauptung ansprechen, dass dies in Python 3 nicht funktioniert? Ich mag die Antwort, möchte aber sicherstellen, dass ich zukunftssicheren Code schreibe.
Merlyn Morgan-Graham
@ MerlynMorgan-Graham, das ist richtig, weil __iter__ ist jetzt in Python 3 in Strings implementiert ist. Mein "einfach zu erweiternder" Absatz wird also anwendbar und muss zB if issublass(cls, str): return Falsezu Beginn hinzugefügt werden __subclasshook__(sowie alle anderen Klassen, die __iter__außer in Ihrem definieren Die Denkweise darf nicht als "Nicht-String-Iterable" akzeptiert werden.
Alex Martelli
@AlexMartelli Meinst du nicht, dass Python 3 if issublass(C, str): return Falsehinzugefügt werden sollte?
Rob Smallshire
4

Mir ist klar, dass dies ein alter Beitrag ist, aber ich dachte, es lohnt sich, meinen Ansatz für die Internet-Nachwelt hinzuzufügen. Die folgende Funktion scheint unter den meisten Umständen sowohl mit Python 2 als auch mit Python 3 für mich zu funktionieren:

def is_collection(obj):
    """ Returns true for any iterable which is not a string or byte sequence.
    """
    try:
        if isinstance(obj, unicode):
            return False
    except NameError:
        pass
    if isinstance(obj, bytes):
        return False
    try:
        iter(obj)
    except TypeError:
        return False
    try:
        hasattr(None, obj)
    except TypeError:
        return True
    return False

Dies prüft, ob eine Nicht-Zeichenfolge durch (mis) Verwendung der integrierten Zeichenfolge iterierbar ist, hasattrwodurch a ausgelöst wird, TypeErrorwenn das zweite Argument keine Zeichenfolge oder Unicode-Zeichenfolge ist.

Nigel Small
quelle
3

Durch das Kombinieren früherer Antworten verwende ich:

import types
import collections

#[...]

if isinstance(var, types.StringTypes ) \
    or not isinstance(var, collections.Iterable):

#[Do stuff...]

Kein 100% iger Narrenbeweis, aber wenn ein Objekt nicht iterierbar ist, können Sie es trotzdem passieren lassen und auf das Tippen von Enten zurückgreifen.


Bearbeiten: Python3

types.StringTypes == (str, unicode). Das Phython3-Äquivalent lautet:

if isinstance(var, str ) \
    or not isinstance(var, collections.Iterable):
xvan
quelle
Ihre Importanweisung sollte "Typen" sein, nicht "Typ"
PaulR
3

2.x.

Ich hätte vorgeschlagen:

hasattr( x, '__iter__' )

oder angesichts des Kommentars von David Charles, der dies für Python3 optimiert, was ist mit:

hasattr(x, '__iter__') and not isinstance(x, (str, bytes))

3.x.

Der eingebaute abstrakte Typ 'basestring' wurde entfernt ( https://docs.python.org/3.0/whatsnew/3.0.html ). Verwenden Sie stattdessen 'str'. Die Typen 'str' und 'bytes' haben nicht genügend gemeinsame Funktionen, um eine gemeinsam genutzte Basisklasse zu gewährleisten.

Mike Nagetier
quelle
3
Vielleicht, weil __iter__es in Python 3 Strings gibt?
Davidrmcharles
@ DavidCharles Oh, wirklich? Mein Fehler. Ich bin ein Jython-Benutzer und Jython hat derzeit keine Version 3.
Mike Nagetier
Dies ist nicht wirklich eine Antwort, eher ein Kommentar / eine Frage, und es ist falsch für 3.x. Könnten Sie es aufräumen? Können Sie eine Begründung für die Behauptung hinzufügen: "Die Typen 'str' und 'bytes' haben nicht genügend gemeinsame Funktionen, um eine gemeinsam genutzte Basisklasse zu gewährleisten." Einer der wichtigsten Punkte von 3.x war, Unicode-Bytes zu einem erstklassigen Bürger zu machen.
smci
Ich habe keine Ahnung, warum ich eines der oben genannten geschrieben habe. Ich schlage vor, den gesamten Text unter "3.x" zu löschen ... obwohl Sie meine Antwort bereits bearbeitet haben. Bearbeiten Sie es mehr, wenn Sie möchten.
Mike Nagetier
0

Wie Sie richtig hervorheben, ist eine einzelne Zeichenfolge eine Zeichenfolge.

Das, was Sie wirklich tun möchten, ist herauszufinden, welche Art von Sequenz arg ist, indem Sie isinstance oder type (a) == str verwenden.

Wenn Sie eine Funktion realisieren möchten, die eine variable Anzahl von Parametern akzeptiert, sollten Sie dies folgendermaßen tun:

def function(*args):
    # args is a tuple
    for arg in args:
        do_something(arg)

Funktion ("ff") und Funktion ("ff", "ff") funktionieren.

Ich kann kein Szenario sehen, in dem eine isiterable () - Funktion wie Ihre benötigt wird. Es ist nicht isinstance (), das einen schlechten Stil hat, sondern Situationen, in denen Sie isinstance () verwenden müssen.

Otto Allmendinger
quelle
3
Verwendung type(a) == strist zu vermeiden. Dies ist eine schlechte Praxis, da ähnliche oder abgeleitete Typen nicht berücksichtigt werden str. typeklettert nicht auf die Typhierarchie, während dies der isinstanceFall ist, daher ist es besser, sie zu verwenden isinstance.
AkiRoss
0

Um Alex Martellis hervorragenden Hack explizit zu erweitern collections.pyund einige der damit verbundenen Fragen zu beantworten: Die derzeit funktionierende Lösung in Python 3.6+ ist

import collections
import _collections_abc as cabc
import abc


class NonStringIterable(metaclass=abc.ABCMeta):

    __slots__ = ()

    @abc.abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, c):
        if cls is NonStringIterable:
            if issubclass(c, str):
                return False
            return cabc._check_methods(c, "__iter__")
        return NotImplemented

und demonstriert

>>> typs = ['string', iter(''), list(), dict(), tuple(), set()]
>>> [isinstance(o, NonStringIterable) for o in typs]
[False, True, True, True, True, True]

Wenn Sie iter('')beispielsweise Ausschlüsse hinzufügen möchten , ändern Sie die Zeile

            if issubclass(c, str):
                return False

sein

            # `str_iterator` is just a shortcut for `type(iter(''))`*
            if issubclass(c, (str, cabc.str_iterator)):
                return False

bekommen

[False, False, True, True, True, True]
Alexander McFarlane
quelle