Bestimmen Sie den Typ eines Objekts?

1791

Gibt es eine einfache Möglichkeit, festzustellen, ob eine Variable eine Liste, ein Wörterbuch oder etwas anderes ist? Ich bekomme ein Objekt zurück, das von beiden Typen sein kann, und ich muss in der Lage sein, den Unterschied zu erkennen.

Justin Ethier
quelle
44
Obwohl ich Ihnen im Allgemeinen zustimme, gibt es Situationen, in denen es hilfreich ist, dies zu wissen. In diesem speziellen Fall habe ich ein schnelles Hacking durchgeführt, das ich schließlich zurückgesetzt habe, sodass Sie diesmal richtig liegen. In einigen Fällen - zum Beispiel bei der Verwendung von Reflexion - ist es wichtig zu wissen, um welche Art von Objekt es sich handelt.
Justin Ethier
67
@ S.Lott Ich würde dem nicht zustimmen. Wenn Sie den Typ kennen, können Sie mit einigen hübschen Varianteneingaben umgehen und trotzdem das Richtige tun. Auf diese Weise können Sie Schnittstellenprobleme umgehen, die mit der reinen Ententypisierung verbunden sind (z. B. bedeutet die .bark () -Methode für einen Baum etwas völlig anderes als für einen Hund). Sie können beispielsweise eine Funktion erstellen, an der einige Arbeiten ausgeführt werden Eine Datei, die eine Zeichenfolge (z. B. einen Pfad), ein Pfadobjekt oder eine Liste akzeptiert. Alle haben unterschiedliche Schnittstellen, aber das Endergebnis ist das gleiche: Führen Sie eine Operation an dieser Datei durch.
Robert P
22
@ S.Lott Ich hoffte, es wäre offensichtlich, dass es ein erfundenes Beispiel ist; Trotzdem ist es ein schwerwiegender Fehler beim Entenschreiben, tryder nicht hilft. Wenn Sie beispielsweise wussten, dass ein Benutzer eine Zeichenfolge oder ein Array übergeben kann, sind beide indexfähig, aber dieser Index bedeutet etwas völlig anderes. In diesen Fällen scheitert es einfach auf unerwartete und seltsame Weise, sich auf einen Versuch zu verlassen. Eine Lösung besteht darin, eine separate Methode zu erstellen, eine andere darin, eine kleine Typprüfung hinzuzufügen. Ich persönlich bevorzuge polymorphes Verhalten gegenüber mehreren Methoden, die fast dasselbe tun ... aber das bin nur ich :)
Robert P
22
@ S.Lott, was ist mit Unit-Tests? Manchmal möchten Sie, dass Ihre Tests überprüfen, ob eine Funktion etwas vom richtigen Typ zurückgibt. Ein sehr reales Beispiel ist, wenn Sie eine Klassenfabrik haben.
Elliot Cameron
17
Betrachten Sie für ein weniger ausgeklügeltes Beispiel einen Serializer / Deserializer. Per Definition konvertieren Sie zwischen vom Benutzer bereitgestellten Objekten und einer serialisierten Darstellung. Der Serializer muss den Typ des übergebenen Objekts ermitteln, und Sie verfügen möglicherweise nicht über ausreichende Informationen, um den deserialisierten Typ zu ermitteln, ohne die Laufzeit zu erfragen (oder zumindest zur Überprüfung der Integrität, um fehlerhafte Daten abzufangen, bevor sie eingegeben werden Ihr System!)
Karl

Antworten:

1976

Es gibt zwei integrierte Funktionen, mit denen Sie den Typ eines Objekts identifizieren können. Sie verwenden können , type() wenn Sie den genauen Typ eines Objekts benötigen, und isinstance()zu überprüfen , ein Objekt der Art gegen etwas. Normalerweise möchten Sie die isistance()meiste Zeit verwenden, da es sehr robust ist und auch die Typvererbung unterstützt.


Um den tatsächlichen Typ eines Objekts abzurufen, verwenden Sie die integrierte type()Funktion. Wenn Sie ein Objekt als einzigen Parameter übergeben, wird das Typobjekt dieses Objekts zurückgegeben:

>>> type([]) is list
True
>>> type({}) is dict
True
>>> type('') is str
True
>>> type(0) is int
True

Dies funktioniert natürlich auch für benutzerdefinierte Typen:

>>> class Test1 (object):
        pass
>>> class Test2 (Test1):
        pass
>>> a = Test1()
>>> b = Test2()
>>> type(a) is Test1
True
>>> type(b) is Test2
True

Beachten Sie, dass type()nur der unmittelbare Typ des Objekts zurückgegeben wird, Sie jedoch nicht über die Typvererbung informiert werden können.

>>> type(b) is Test1
False

Um dies abzudecken, sollten Sie die isinstanceFunktion verwenden. Dies funktioniert natürlich auch für eingebaute Typen:

>>> isinstance(b, Test1)
True
>>> isinstance(b, Test2)
True
>>> isinstance(a, Test1)
True
>>> isinstance(a, Test2)
False
>>> isinstance([], list)
True
>>> isinstance({}, dict)
True

isinstance()Dies ist normalerweise die bevorzugte Methode, um den Typ eines Objekts sicherzustellen, da auch abgeleitete Typen akzeptiert werden. Wenn Sie das Typobjekt nicht tatsächlich benötigen (aus welchem ​​Grund auch immer), wird die Verwendung der Verwendung isinstance()vorgezogen type().

Der zweite Parameter von isinstance()akzeptiert auch ein Tupel von Typen, sodass nach mehreren Typen gleichzeitig gesucht werden kann. isinstancegibt dann true zurück, wenn das Objekt einen dieser Typen hat:

>>> isinstance([], (tuple, list, set))
True
Sack
quelle
68
Ich denke , es klarer zu bedienen , isanstatt ==wie die Typen sind Singletons
John La Rooy
18
@gnibbler, In den Fällen, in denen Sie Typchecking durchführen würden (was Sie zunächst nicht tun sollten), isinstanceist sowieso die bevorzugte Form, daher wird keine ==oder iskeine verwendet.
Mike Graham
23
@ Mike Graham, es gibt Zeiten, in denen typedie beste Antwort ist. Es gibt Zeiten, in denen isinstancedie beste Antwort ist, und es gibt Zeiten, in denen das Tippen von Enten die beste Antwort ist. Es ist wichtig, alle Optionen zu kennen, damit Sie auswählen können, welche für die Situation besser geeignet ist.
John La Rooy
6
@gnibbler, das mag sein, obwohl ich noch nicht auf die Situation gestoßen bin, wo type(foo) is SomeTypees besser wäre als isinstance(foo, SomeType).
Mike Graham
5
@poke: Ich stimme PEP8 voll und ganz zu, aber Sie greifen hier einen Strohmann an: Der wichtige Teil von Svens Argument war nicht PEP8, sondern dass Sie es auch isinstancefür Ihren Anwendungsfall (Überprüfung auf eine Reihe von Typen) und mit verwenden können Ebenso eine saubere Syntax, die den großen Vorteil hat, dass Sie Unterklassen erfassen können. Jemand, OrderedDictder Ihren Code verwendet, würde es hassen , wenn er fehlschlägt, weil er nur reine Diktate akzeptiert.
fliegende Schafe
165

Sie können dies tun mit type():

>>> a = []
>>> type(a)
<type 'list'>
>>> f = ()
>>> type(f)
<type 'tuple'>
inkedmn
quelle
40

Es könnte pythonischer sein, einen try... exceptBlock zu verwenden. Auf diese Weise verhält sich eine Klasse, die wie eine Liste quakt oder wie ein Diktat quakt, unabhängig von ihrem tatsächlichen Typ ordnungsgemäß .

Zur Verdeutlichung besteht die bevorzugte Methode zum "Erkennen des Unterschieds" zwischen Variablentypen in der sogenannten Ententypisierung : Solange die Methoden (und Rückgabetypen), auf die eine Variable reagiert, den Erwartungen Ihres Unterprogramms entsprechen, behandeln Sie sie so, wie Sie es erwarten sein. Wenn Sie beispielsweise eine Klasse haben, die die Klammeroperatoren mit getattrund überlastet setattr, aber ein lustiges internes Schema verwendet, ist es angemessen, dass sie sich wie ein Wörterbuch verhält, wenn sie dies zu emulieren versucht.

Das andere Problem bei der type(A) is type(B)Überprüfung besteht darin, dass, wenn Aes sich um eine Unterklasse von handelt B, ausgewertet wird, falsewann dies programmgesteuert der Fall sein würde true. Wenn ein Objekt eine Unterklasse einer Liste ist, sollte es wie eine Liste funktionieren: Wenn Sie den in der anderen Antwort angegebenen Typ überprüfen, wird dies verhindert. ( isinstancewird jedoch funktionieren).

Seth Johnson
quelle
16
Beim Tippen beim Enten geht es jedoch nicht wirklich darum, den Unterschied zu erkennen. Es geht darum, eine gemeinsame Schnittstelle zu verwenden.
Justin Ethier
5
Seien Sie vorsichtig - die meisten Codierungsstil-Anleitungen empfehlen, die Ausnahmebehandlung nicht als Teil des normalen Code-Kontrollflusses zu verwenden, da dies normalerweise das Lesen von Code erschwert. try... exceptist eine gute Lösung, wenn Sie mit Fehlern umgehen möchten, aber nicht, wenn Sie sich für ein typbasiertes Verhalten entscheiden.
Rens van der Heijden
34

Auf Instanzen von Objekten haben Sie auch die:

__class__

Attribut. Hier ist ein Beispiel aus der Python 3.3-Konsole

>>> str = "str"
>>> str.__class__
<class 'str'>
>>> i = 2
>>> i.__class__
<class 'int'>
>>> class Test():
...     pass
...
>>> a = Test()
>>> a.__class__
<class '__main__.Test'>

Beachten Sie, dass in Python 3.x und in New-Style-Klassen (optional in Python 2.6 verfügbar) Klasse und Typ zusammengeführt wurden und dies manchmal zu unerwarteten Ergebnissen führen kann. Hauptsächlich aus diesem Grund ist meine Lieblingsmethode zum Testen von Typen / Klassen die in isinstance integrierte Funktion.

Lorenzo Persichetti
quelle
2
Ihr Punkt am Ende ist sehr wichtig. type (obj) is Class hat nicht richtig funktioniert, aber isinstance hat den Trick gemacht. Ich verstehe, dass isinstance sowieso bevorzugt wird, aber es ist vorteilhafter als nur abgeleitete Typen zu überprüfen, wie in der akzeptierten Antwort vorgeschlagen.
Mstbaum
__class__ist in Python 2.x meistens in Ordnung. Die einzigen Objekte in Python, die kein __class__Attribut haben , sind AFAIK-Klassen alten Stils. Ich verstehe Ihr Python 3-Problem übrigens nicht - in einer solchen Version hat nur jedes Objekt ein __class__Attribut, das auf die richtige Klasse verweist.
Alan Franzoni
21

Bestimmen Sie den Typ eines Python-Objekts

Bestimmen Sie den Typ eines Objekts mit type

>>> obj = object()
>>> type(obj)
<class 'object'>

Vermeiden Sie doppelte Unterstrichattribute wie __class__- sie sind nicht semantisch öffentlich, und obwohl dies in diesem Fall möglicherweise nicht der Fall ist, verhalten sich die integrierten Funktionen normalerweise besser.

>>> obj.__class__ # avoid this!
<class 'object'>

Typprüfung

Gibt es eine einfache Möglichkeit, festzustellen, ob eine Variable eine Liste, ein Wörterbuch oder etwas anderes ist? Ich bekomme ein Objekt zurück, das von beiden Typen sein kann, und ich muss in der Lage sein, den Unterschied zu erkennen.

Nun, das ist eine andere Frage, benutze keinen Typ - benutze isinstance:

def foo(obj):
    """given a string with items separated by spaces, 
    or a list or tuple, 
    do something sensible
    """
    if isinstance(obj, str):
        obj = str.split()
    return _foo_handles_only_lists_or_tuples(obj)

Dies deckt den Fall ab, in dem Ihr Benutzer durch Unterklassen möglicherweise etwas Kluges oder Sinnvolles tut str- gemäß dem Prinzip der Liskov-Substitution möchten Sie Unterklasseninstanzen verwenden können, ohne Ihren Code zu beschädigen - und isinstanceunterstützt dies.

Verwenden Sie Abstraktionen

Noch besser ist es, wenn Sie nach einer bestimmten abstrakten Basisklasse suchen von collectionsoder numbers:

from collections import Iterable
from numbers import Number

def bar(obj):
    """does something sensible with an iterable of numbers, 
    or just one number
    """
    if isinstance(obj, Number): # make it a 1-tuple
        obj = (obj,)
    if not isinstance(obj, Iterable):
        raise TypeError('obj must be either a number or iterable of numbers')
    return _bar_sensible_with_iterable(obj)

Oder einfach nicht explizit Typprüfung

Oder, vielleicht am besten, verwenden Sie die Enten-Typisierung und überprüfen Sie Ihren Code nicht explizit. Die Ententypisierung unterstützt die Liskov-Substitution mit mehr Eleganz und weniger Ausführlichkeit.

def baz(obj):
    """given an obj, a dict (or anything with an .items method) 
    do something sensible with each key-value pair
    """
    for key, value in obj.items():
        _baz_something_sensible(key, value)

Fazit

  • Verwenden Sie typediese Option, um die Klasse einer Instanz abzurufen.
  • Verwenden Sie isinstancediese Option, um explizit nach tatsächlichen Unterklassen oder registrierten Abstraktionen zu suchen.
  • Und vermeiden Sie einfach die Typprüfung, wo es Sinn macht.
Aaron Hall
quelle
Es gibt immer try/ exceptanstatt explizit zu prüfen.
ToonarmyCaptain
Vermutlich ist es das, was der Benutzer tun wird, wenn er sich nicht sicher ist, welche Typen er übergeben wird. Ich mag es nicht, eine korrekte Implementierung mit Ausnahmebehandlung zu überladen, es sei denn, ich habe etwas sehr Gutes mit der Ausnahme zu tun. Die ausgelöste Ausnahme sollte ausreichen, um den Benutzer darüber zu informieren, dass er seine Verwendung korrigieren muss.
Aaron Hall
13

Sie können type()oder verwenden isinstance().

>>> type([]) is list
True

Seien Sie gewarnt, dass Sie Clobber listoder einen anderen Typ verwenden können, indem Sie eine Variable im aktuellen Bereich mit demselben Namen zuweisen.

>>> the_d = {}
>>> t = lambda x: "aight" if type(x) is dict else "NOPE"
>>> t(the_d) 'aight'
>>> dict = "dude."
>>> t(the_d) 'NOPE'

Oben sehen wir, dass dicteine Zeichenfolge neu zugewiesen wird, daher der Test:

type({}) is dict

... schlägt fehl.

Um dies zu umgehen und type()vorsichtiger zu verwenden:

>>> import __builtin__
>>> the_d = {}
>>> type({}) is dict
True
>>> dict =""
>>> type({}) is dict
False
>>> type({}) is __builtin__.dict
True
deed02392
quelle
2
Ich bin nicht sicher, ob es notwendig ist, darauf hinzuweisen, dass das Abschatten des Namens eines eingebauten Datentyps für diesen Fall schlecht ist. Ihre dictZeichenfolge schlägt auch für viele andere Codes fehl, z dict([("key1", "value1"), ("key2", "value2")]). Die Antwort auf diese Art von Problemen lautet "Dann tu das nicht" . Schattieren Sie keine eingebauten Typnamen und erwarten Sie, dass die Dinge richtig funktionieren.
Blckknght
3
Ich stimme Ihnen in dem Teil "Mach das nicht" zu. Aber um jemandem zu sagen, dass er etwas nicht tun soll, sollte man zumindest erklären, warum nicht, und ich dachte, dies wäre eine relevante Gelegenheit, genau das zu tun. Ich wollte, dass die vorsichtige Methode hässlich aussieht und zeigt, warum sie es vielleicht nicht wollen, und dass sie entscheiden müssen.
deed02392
type () funktioniert unter Python 2.x für klassische Instanzen nicht wie erwartet.
Alan Franzoni
5

Obwohl die Fragen ziemlich alt sind, bin ich darauf gestoßen, als ich selbst einen richtigen Weg gefunden habe, und ich denke, es muss noch geklärt werden, zumindest für Python 2.x (habe Python 3 nicht überprüft, aber da das Problem bei klassischen Klassen auftritt welche auf solche version gegangen sind, spielt es wahrscheinlich keine rolle).

Hier versuche ich die Frage des Titels zu beantworten: Wie kann ich den Typ eines beliebigen Objekts bestimmen ? Andere Vorschläge zur Verwendung oder Nichtverwendung von isinstance sind in vielen Kommentaren und Antworten in Ordnung, aber ich gehe nicht auf diese Bedenken ein.

Das Hauptproblem bei diesem type()Ansatz ist, dass er mit Instanzen alten Stils nicht richtig funktioniert :

class One:
    pass

class Two:
    pass


o = One()
t = Two()

o_type = type(o)
t_type = type(t)

print "Are o and t instances of the same class?", o_type is t_type

Das Ausführen dieses Snippets würde ergeben:

Are o and t instances of the same class? True

Was, so argumentiere ich, nicht das ist, was die meisten Leute erwarten würden.

Der __class__Ansatz kommt der Korrektheit am nächsten, funktioniert jedoch in einem entscheidenden Fall nicht: Wenn das übergebene Objekt eine Klasse alten Stils ist (keine Instanz!), Da diesen Objekten ein solches Attribut fehlt.

Dies ist der kleinste Codeausschnitt, den ich mir vorstellen kann, der eine solche legitime Frage auf konsistente Weise erfüllt:

#!/usr/bin/env python
from types import ClassType
#we adopt the null object pattern in the (unlikely) case
#that __class__ is None for some strange reason
_NO_CLASS=object()
def get_object_type(obj):
    obj_type = getattr(obj, "__class__", _NO_CLASS)
    if obj_type is not _NO_CLASS:
        return obj_type
    # AFAIK the only situation where this happens is an old-style class
    obj_type = type(obj)
    if obj_type is not ClassType:
        raise ValueError("Could not determine object '{}' type.".format(obj_type))
    return obj_type
Alan Franzoni
quelle
5

Seien Sie vorsichtig mit isinstance

isinstance(True, bool)
True
>>> isinstance(True, int)
True

aber tippe

type(True) == bool
True
>>> type(True) == int
False
tnusraddinov
quelle
3

Neben den vorherigen Antworten ist zu erwähnen, dass es collections.abcmehrere abstrakte Basisklassen (ABCs) gibt, die die Ententypisierung ergänzen.

Anstatt beispielsweise explizit zu überprüfen, ob es sich bei einer Liste um eine Liste handelt mit:

isinstance(my_obj, list)

Wenn Sie nur sehen möchten, ob das Objekt, das Sie haben, das Abrufen von Elementen ermöglicht, können Sie Folgendes verwenden collections.abc.Sequence:

from collections.abc import Sequence
isinstance(my_obj, Sequence) 

Wenn Sie ausschließlich an Objekten interessiert sind, mit denen Elemente abgerufen , festgelegt und gelöscht werden können (dh veränderbare Sequenzen), entscheiden Sie sich für collections.abc.MutableSequence.

Viele andere ABCs sind dort definiert, Mappingfür Objekte , die als Karten verwendet werden können, Iterable, Callable, et cetera. Eine vollständige Liste aller dieser Elemente finden Sie in der Dokumentation zu collections.abc.

Dimitris Fasarakis Hilliard
quelle
1

Im Allgemeinen können Sie eine Zeichenfolge aus einem Objekt mit dem Klassennamen extrahieren.

str_class = object.__class__.__name__

und zum Vergleich verwenden,

if str_class == 'dict':
    # blablabla..
elif str_class == 'customclass':
    # blebleble..
José Crespo Barrios
quelle
1

In vielen praktischen Fällen anstelle von typeoder können isinstanceSie auch verwenden @functools.singledispatch, um allgemeine Funktionen zu definieren ( Funktion, die aus mehreren Funktionen besteht, die dieselbe Operation für verschiedene Typen implementieren ).

Mit anderen Worten, Sie möchten es verwenden, wenn Sie einen Code wie den folgenden haben:

def do_something(arg):
    if isinstance(arg, int):
        ... # some code specific to processing integers
    if isinstance(arg, str):
        ... # some code specific to processing strings
    if isinstance(arg, list):
        ... # some code specific to processing lists
    ...  # etc

Hier ist ein kleines Beispiel, wie es funktioniert:

from functools import singledispatch


@singledispatch
def say_type(arg):
    raise NotImplementedError(f"I don't work with {type(arg)}")


@say_type.register
def _(arg: int):
    print(f"{arg} is an integer")


@say_type.register
def _(arg: bool):
    print(f"{arg} is a boolean")
>>> say_type(0)
0 is an integer
>>> say_type(False)
False is a boolean
>>> say_type(dict())
# long error traceback ending with:
NotImplementedError: I don't work with <class 'dict'>

Zusätzlich können wir abstrakte Klassen verwenden , um mehrere Typen gleichzeitig abzudecken:

from collections.abc import Sequence


@say_type.register
def _(arg: Sequence):
    print(f"{arg} is a sequence!")
>>> say_type([0, 1, 2])
[0, 1, 2] is a sequence!
>>> say_type((1, 2, 3))
(1, 2, 3) is a sequence!
Georgy
quelle
0

type()ist eine bessere Lösung als isinstance()insbesondere für 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