Was sind die Unterschiede zwischen type () und isinstance ()?

1248

Was sind die Unterschiede zwischen diesen beiden Codefragmenten?

Verwenden von type():

import types

if type(a) is types.DictType:
    do_something()
if type(b) in types.StringTypes:
    do_something_else()

Verwenden von isinstance():

if isinstance(a, dict):
    do_something()
if isinstance(b, str) or isinstance(b, unicode):
    do_something_else()
Abt
quelle
Hinweis: Wenn dies nicht der Fall ist strund unicode(wo Sie nur prüfen können basestring), können Sie ein Tupel zum Prüfen auf mehrere Typen verwenden. Um zu überprüfen, ob es somethingist intoder strverwenden isinstance(something, (int, str)).
xuiqzy

Antworten:

1271

Um den Inhalt anderer (bereits guter!) Antworten zusammenzufassen, wird isinstancedie Vererbung berücksichtigt (eine Instanz einer abgeleiteten Klasse ist auch eine Instanz einer Basisklasse), während auf Gleichheit geprüft typewird (dies erfordert die Identität von Typen und lehnt Instanzen ab) von Subtypen, AKA-Unterklassen).

Normalerweise möchten Sie in Python natürlich, dass Ihr Code die Vererbung unterstützt (da die Vererbung so praktisch ist, wäre es schlecht, die Verwendung Ihres Codes zu verhindern!), Also isinstanceweniger schlecht als die Überprüfung der Identität von types, da sie nahtlos unterstützt Erbe.

Es ist nicht das isinstanceist gut , wohlgemerkt-es ist nur weniger schlecht als Gleichheit der Arten zu überprüfen. Die normale, pythonische, bevorzugte Lösung ist fast immer "Ententypisierung": Versuchen Sie, das Argument so zu verwenden, als ob es von einem bestimmten gewünschten Typ wäre, und führen Sie es in einer try/ except-Anweisung aus, wobei alle Ausnahmen erfasst werden, die auftreten könnten, wenn das Argument nicht tatsächlich vorhanden wäre Geben Sie ein (oder einen anderen Typ, der es gut nachahmt ;-), und exceptversuchen Sie in der Klausel etwas anderes (mit dem Argument "als ob" es von einem anderen Typ wäre).

basestring Dies ist jedoch ein ganz besonderer Fall - ein integrierter Typ, der nur zur Verwendung durch Sie isinstance(sowohl strals auch unicodeUnterklasse basestring) vorhanden ist. Strings sind Sequenzen (Sie können sie durchlaufen, indizieren, in Scheiben schneiden, ...), aber Sie möchten sie im Allgemeinen als "skalare" Typen behandeln - es ist etwas unpraktisch (aber ein ziemlich häufiger Anwendungsfall), alle Arten von zu behandeln Zeichenfolgen (und möglicherweise andere Skalartypen, dh solche, auf denen Sie keine Schleife ausführen können) auf eine Weise, alle Container (Listen, Mengen, Diktate usw.) auf eine andere Weise und basestringplus isinstancehelfen Ihnen dabei - die Gesamtstruktur davon Redewendung ist so etwas wie:

if isinstance(x, basestring)
  return treatasscalar(x)
try:
  return treatasiter(iter(x))
except TypeError:
  return treatasscalar(x)

Man könnte sagen, dass dies basestringeine abstrakte Basisklasse ("ABC") ist - sie bietet Unterklassen keine konkrete Funktionalität, sondern existiert als "Marker", hauptsächlich zur Verwendung mit isinstance. Das Konzept in Python wächst offensichtlich, da PEP 3119 , das eine Verallgemeinerung einführt, akzeptiert wurde und ab Python 2.6 und 3.0 implementiert wurde.

Das PEP macht deutlich, dass ABCs zwar häufig die Ententypisierung ersetzen können, dies jedoch im Allgemeinen keinen großen Druck darstellt (siehe hier ). ABCs, wie sie in neueren Python-Versionen implementiert wurden, bieten jedoch zusätzliche Extras: isinstance(und issubclass) können jetzt mehr als nur "[eine Instanz] einer abgeleiteten Klasse" bedeuten (insbesondere kann jede Klasse bei einem ABC "registriert" werden, so dass dies der Fall ist show als Unterklasse und ihre Instanzen als Instanzen des ABC); und ABCs können auch tatsächlichen Unterklassen auf sehr natürliche Weise über Entwurfsmusteranwendungen für Vorlagenmethoden zusätzlichen Komfort bieten (siehe hier und hier [[Teil II]] für weitere Informationen zum TM DP im Allgemeinen und speziell in Python, unabhängig von ABCs). .

Die zugrunde liegenden Mechanismen der ABC-Unterstützung, wie sie in Python 2.6 angeboten werden, finden Sie hier . Für ihre Version 3.1, sehr ähnlich, siehe hier . In beiden Versionen Standard - Bibliothek - Modul Sammlungen (das ist die Version 3.1-für die sehr ähnliche Version 2.6 finden Sie hier ) bietet einige nützlichen ABCs.

Für diese Antwort ist es wichtig, ABCs beizubehalten (abgesehen von einer wohl natürlicheren Platzierung der TM DP-Funktionalität im Vergleich zur klassischen Python-Alternative von Mixin-Klassen wie UserDict.DictMixin ), dass sie viel mehr machen isinstance(und issubclass) attraktiver und allgegenwärtiger (in Python 2.6 und in Zukunft) als früher (in 2.5 und früher), und daher ist die Überprüfung der Typgleichheit in neueren Python-Versionen im Gegensatz dazu bereits eine noch schlechtere Praxis als früher.

Alex Martelli
quelle
9
„Es ist nicht so, dass die Instanz gut ist, wohlgemerkt - es ist nur weniger schlecht, als die Gleichheit der Typen zu überprüfen. Die normale, pythonische, bevorzugte Lösung ist fast immer "Ententypisierung". Dies ist eine eher eingeschränkte Ansicht: Es gibt sehr gute Fälle für die Verwendung von isinstance (), beispielsweise in einem Interpreter, in dem die Typen die Grammatik widerspiegeln. "Pythonic" zu sein ist nicht alles!
Gene Callahan
2
basestring ist in Python 3 nicht verfügbar.
erobertc
@GeneCallahan, weil es sehr gute Fälle gibt, bedeutet nicht, dass das, was gesagt wurde, keine gute allgemeine Regel ist. Ich bin damit einverstanden, dass die Überprüfung des Typs im Voraus definitiv seinen Platz hat, aber das Entenquaken der Enten sollte die meisten Fälle flexibler und effizienter abdecken .
Eric Ed Lohmar
@erobertc, gemäß den Neuerungen in Python 3.0 : "Der integrierte abstrakte Basisring-Typ wurde entfernt. Verwenden Sie stattdessen str."
Neurit
345

Hier ist ein Beispiel, wo isinstanceetwas erreicht wird, typedas nicht kann:

class Vehicle:
    pass

class Truck(Vehicle):
    pass

In diesem Fall ist ein LKW-Objekt ein Fahrzeug, aber Sie erhalten Folgendes:

isinstance(Vehicle(), Vehicle)  # returns True
type(Vehicle()) == Vehicle      # returns True
isinstance(Truck(), Vehicle)    # returns True
type(Truck()) == Vehicle        # returns False, and this probably won't be what you want.

Mit anderen Worten, isinstancegilt auch für Unterklassen.

Siehe auch: Wie vergleiche ich den Typ eines Objekts in Python?

Peter
quelle
143
Da es einen Fall gibt, in dem Sie das isInstance-Verhalten nicht wollen, würde ich argumentieren, dass es kein "besseres" gibt. Sie machen einfach etwas anderes.
Philgo20
27
-1, weil "isinstance ist besser als type" ein irreführender Kommentar ist. es wird auf den ersten Blick so verstanden, als ob " typeveraltet ist, isinstancestattdessen verwenden". Ich wollte zum Beispiel genau type()nachsehen, wurde aber aus diesem Grund für kurze Zeit in die Irre geführt (und musste ein wenig debuggen).
Zeremonie
8
Es ist ein gutes Beispiel dafür, wie sie anders funktionieren, aber ich bin gerade auf einen Fall gestoßen, den ich speziell brauchte type()und nicht isinstance(). Man ist nicht besser; Sie sind für verschiedene Dinge.
EL_DON
103

Unterschiede zwischen isinstance()und type()in Python?

Typprüfung mit

isinstance(obj, Base)

ermöglicht Instanzen von Unterklassen und mehrere mögliche Basen:

isinstance(obj, (Base1, Base2))

wohingegen Typprüfung mit

type(obj) is Base

unterstützt nur den Typ, auf den verwiesen wird.


Als Nebenbemerkung isist wahrscheinlich angemessener als

type(obj) == Base

weil Klassen Singletons sind.

Vermeiden Sie die Typprüfung - verwenden Sie Polymorphismus (Ententypisierung)

In Python möchten Sie normalerweise einen beliebigen Typ für Ihre Argumente zulassen, ihn wie erwartet behandeln. Wenn sich das Objekt nicht wie erwartet verhält, wird ein entsprechender Fehler ausgegeben. Dies ist als Polymorphismus bekannt, auch als Ententypisierung bekannt.

def function_of_duck(duck):
    duck.quack()
    duck.swim()

Wenn der obige Code funktioniert, können wir davon ausgehen, dass unser Argument eine Ente ist. So können wir in anderen Dingen tatsächliche Untertypen von Enten übergeben:

function_of_duck(mallard)

oder das funktioniert wie eine Ente:

function_of_duck(object_that_quacks_and_swims_like_a_duck)

und unser Code funktioniert immer noch.

Es gibt jedoch einige Fälle, in denen eine explizite Typprüfung wünschenswert ist. Vielleicht haben Sie vernünftige Dinge mit verschiedenen Objekttypen zu tun. Beispielsweise kann das Pandas Dataframe-Objekt aus Diktaten oder Datensätzen erstellt werden. In einem solchen Fall muss Ihr Code wissen, welche Art von Argument er erhält, damit er richtig damit umgehen kann.

Um die Frage zu beantworten:

Unterschiede zwischen isinstance()und type()in Python?

Lassen Sie mich den Unterschied demonstrieren:

type

Angenommen, Sie müssen ein bestimmtes Verhalten sicherstellen, wenn Ihre Funktion eine bestimmte Art von Argument erhält (ein häufiger Anwendungsfall für Konstruktoren). Wenn Sie nach einem solchen Typ suchen:

def foo(data):
    '''accepts a dict to construct something, string support in future'''
    if type(data) is not dict:
        # we're only going to test for dicts for now
        raise ValueError('only dicts are supported for now')

Wenn wir versuchen, ein Diktat zu übergeben, das eine Unterklasse von dict(wie wir sollten, wenn wir erwarten, dass unser Code dem Prinzip der Liskov-Substitution folgt , dass Subtypen durch Typen ersetzt werden können), bricht unser Code!:

from collections import OrderedDict

foo(OrderedDict([('foo', 'bar'), ('fizz', 'buzz')]))

löst einen Fehler aus!

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in foo
ValueError: argument must be a dict

isinstance

Aber wenn wir verwenden isinstance, können wir Liskov Substitution unterstützen!:

def foo(a_dict):
    if not isinstance(a_dict, dict):
        raise ValueError('argument must be a dict')
    return a_dict

foo(OrderedDict([('foo', 'bar'), ('fizz', 'buzz')]))

kehrt zurück OrderedDict([('foo', 'bar'), ('fizz', 'buzz')])

Abstrakte Basisklassen

In der Tat können wir es noch besser machen. collectionsbietet abstrakte Basisklassen, die minimale Protokolle für verschiedene Typen erzwingen. Wenn wir in unserem Fall nur das MappingProtokoll erwarten , können wir Folgendes tun, und unser Code wird noch flexibler:

from collections import Mapping

def foo(a_dict):
    if not isinstance(a_dict, Mapping):
        raise ValueError('argument must be a dict')
    return a_dict

Antwort auf Kommentar:

Es sollte beachtet werden, dass der Typ verwendet werden kann, um mit mehreren Klassen zu vergleichen type(obj) in (A, B, C)

Ja, Sie können die Gleichheit der Typen testen, aber anstelle der oben genannten verwenden Sie die mehreren Basen für den Kontrollfluss, es sei denn, Sie erlauben ausdrücklich nur diese Typen:

isinstance(obj, (A, B, C))

Der Unterschied besteht wiederum darin, dass isinstanceUnterklassen unterstützt werden, die das übergeordnete Element ersetzen können, ohne das Programm anderweitig zu beschädigen. Diese Eigenschaft wird als Liskov-Substitution bezeichnet.

Noch besser ist es jedoch, Ihre Abhängigkeiten umzukehren und überhaupt nicht nach bestimmten Typen zu suchen.

Fazit

Da wir das Ersetzen von Unterklassen unterstützen möchten, möchten wir in den meisten Fällen die Typprüfung mit vermeiden typeund die Typprüfung mit bevorzugen isinstance- es sei denn, Sie müssen die genaue Klasse einer Instanz wirklich kennen.

Aaron Hall
quelle
Wenn Sie Ihre_Module.py haben, in der Sie suchen isinstance(instance, y)und verwenden from v.w.x import y, und diese Prüfung importieren, aber wenn Sie sie instanziieren instance, from x import yanstatt wie y in Ihre_Module.py importiert wurde, schlägt die Prüfung der Instanz fehl, obwohl es sich um dieselbe Klasse handelt.
ToonarmyCaptain
64

Letzteres wird bevorzugt, da Unterklassen ordnungsgemäß behandelt werden. In der Tat kann Ihr Beispiel noch einfacher geschrieben werden, da isinstance()der zweite Parameter ein Tupel sein kann:

if isinstance(b, (str, unicode)):
    do_something_else()

oder mit der basestringabstrakten Klasse:

if isinstance(b, basestring):
    do_something_else()
John Millikin
quelle
9

Ein praktischer Unterschied in der Verwendung besteht darin, wie sie behandelt werden booleans:

Trueund Falsesind nur Schlüsselwörter, die bedeuten 1und 0in Python. Somit,

isinstance(True, int)

und

isinstance(False, int)

beide kehren zurück True. Beide Booleschen Werte sind eine Instanz einer Ganzzahl. type()ist jedoch klüger:

type(True) == int

kehrt zurück False.

Alec Alameddine
quelle
0

Für die wirklichen Unterschiede können wir es in finden code, aber ich kann das Gerät des Standardverhaltens des nicht finden isinstance().

Wir können jedoch den ähnlichen abc .__ instancecheck__ gemäß __instancecheck__ erhalten .

Von oben abc.__instancecheck__nach dem Test unten:

# file tree
# /test/__init__.py
# /test/aaa/__init__.py
# /test/aaa/aa.py
class b():
pass

# /test/aaa/a.py
import sys
sys.path.append('/test')

from aaa.aa import b
from aa import b as c

d = b()

print(b, c, d.__class__)
for i in [b, c, object]:
    print(i, '__subclasses__',  i.__subclasses__())
    print(i, '__mro__', i.__mro__)
    print(i, '__subclasshook__', i.__subclasshook__(d.__class__))
    print(i, '__subclasshook__', i.__subclasshook__(type(d)))
print(isinstance(d, b))
print(isinstance(d, c))

<class 'aaa.aa.b'> <class 'aa.b'> <class 'aaa.aa.b'>
<class 'aaa.aa.b'> __subclasses__ []
<class 'aaa.aa.b'> __mro__ (<class 'aaa.aa.b'>, <class 'object'>)
<class 'aaa.aa.b'> __subclasshook__ NotImplemented
<class 'aaa.aa.b'> __subclasshook__ NotImplemented
<class 'aa.b'> __subclasses__ []
<class 'aa.b'> __mro__ (<class 'aa.b'>, <class 'object'>)
<class 'aa.b'> __subclasshook__ NotImplemented
<class 'aa.b'> __subclasshook__ NotImplemented
<class 'object'> __subclasses__ [..., <class 'aaa.aa.b'>, <class 'aa.b'>]
<class 'object'> __mro__ (<class 'object'>,)
<class 'object'> __subclasshook__ NotImplemented
<class 'object'> __subclasshook__ NotImplemented
True
False

Ich bekomme diese Schlussfolgerung, Für type:

# according to `abc.__instancecheck__`, they are maybe different! I have not found negative one 
type(INSTANCE) ~= INSTANCE.__class__
type(CLASS) ~= CLASS.__class__

Für isinstance:

# guess from `abc.__instancecheck__`
return any(c in cls.__mro__ or c in cls.__subclasses__ or cls.__subclasshook__(c) for c in {INSTANCE.__class__, type(INSTANCE)})

Übrigens: Besser nicht mischen relative and absolutely import, verwenden absolutely importvon project_dir (hinzugefügt von sys.path)

Cheney
quelle