Determinare il tipo di un oggetto?


1791

Esiste un modo semplice per determinare se una variabile è un elenco, un dizionario o qualcos'altro? Ricevo un oggetto che può essere di entrambi i tipi e devo essere in grado di dire la differenza.


44
Mentre in generale sono d'accordo con te, ci sono situazioni in cui è utile sapere. In questo caso particolare stavo facendo degli hacker rapidi che alla fine ho eseguito il rollback, quindi questa volta hai ragione. Ma in alcuni casi, ad esempio quando si utilizza la riflessione, è importante sapere con quale tipo di oggetto si ha a che fare.
Justin Ethier,

67
@ S. Lott Non sarei d'accordo con quello; essendo in grado di conoscere il tipo, è possibile gestire alcuni input piuttosto varianti e fare comunque la cosa giusta. Ti consente di aggirare i problemi di interfaccia inerenti all'affidamento alla semplice tipizzazione anatra (ad esempio, il metodo .bark () su un albero significa qualcosa di completamente diverso rispetto a un cane.) Ad esempio, potresti fare una funzione che fa un po 'di lavoro su un file che accetta una stringa (ad es. un percorso), un oggetto percorso o un elenco. Tutti hanno interfacce diverse, ma il risultato finale è lo stesso: eseguire alcune operazioni su quel file.
Robert P,

22
@ S. Lott Speravo fosse ovvio che si tratta di un esempio inventato; tuttavia è un grave punto debole della digitazione delle anatre e trynon aiuta. Ad esempio, se sapevi che un utente potrebbe passare una stringa o un array, entrambi sono in grado di indicizzare, ma quell'indice significa qualcosa di completamente diverso. Fare semplicemente affidamento su una prova in quei casi fallirà in modi inaspettati e strani. Una soluzione è quella di creare un metodo separato, un altro per aggiungere un piccolo controllo del tipo. Personalmente preferisco il comportamento polimorfico rispetto a più metodi che fanno quasi la stessa cosa ... ma sono solo io :)
Robert P

22
@ S.Lott, che dire dei test unitari? A volte vuoi che i tuoi test verifichino che una funzione sta restituendo qualcosa del tipo giusto. Un esempio molto reale è quando hai una fabbrica di classe.
Elliot Cameron,

17
Per un esempio meno ingegnoso, considera un serializzatore / deserializzatore. Per definizione si sta convertendo tra oggetti forniti dall'utente e una rappresentazione serializzata. Il serializzatore deve determinare il tipo di oggetto che hai passato e potresti non disporre di informazioni adeguate per determinare il tipo deserializzato senza chiedere il runtime (o almeno, potrebbe essere necessario per il controllo di integrità per rilevare i dati errati prima che entri il tuo sistema!)
Karl,

Risposte:


1977

Esistono due funzioni integrate che consentono di identificare il tipo di un oggetto. È possibile utilizzare type() se è necessario il tipo esatto di un oggetto e isinstance()per verificare il tipo di un oggetto rispetto a qualcosa. Di solito, si desidera utilizzare la isistance()maggior parte delle volte poiché è molto robusto e supporta anche l'ereditarietà dei tipi.


Per ottenere il tipo effettivo di un oggetto, utilizzare la type()funzione integrata. Passare un oggetto come unico parametro restituirà il tipo oggetto di quell'oggetto:

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

Questo ovviamente funziona anche per tipi personalizzati:

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

Si noti che type()restituirà solo il tipo immediato dell'oggetto, ma non sarà in grado di parlarvi dell'ereditarietà dei tipi.

>>> type(b) is Test1
False

Per coprirlo, dovresti usare la isinstancefunzione. Questo ovviamente funziona anche per i tipi integrati:

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

isinstance()di solito è il modo preferito per garantire il tipo di un oggetto perché accetterà anche i tipi derivati. Quindi, a meno che tu non abbia effettivamente bisogno dell'oggetto type (per qualsiasi motivo), l'utilizzo isinstance()è preferito type().

Il secondo parametro isinstance()accetta anche una tupla di tipi, quindi è possibile verificare più tipi contemporaneamente. isinstancerestituirà quindi true, se l'oggetto è di uno di questi tipi:

>>> isinstance([], (tuple, list, set))
True

68
Penso che sia più chiaro da usare isinvece che ==come i tipi sono singoli
John La Rooy

18
@gnibbler, Nei casi in cui dovresti controllare il testo (cosa che non dovresti fare per cominciare), isinstanceè comunque la forma preferita, quindi nessuna delle due ==o isdeve essere usata.
Mike Graham,

23
@ Mike Graham, ci sono momenti in cui typeè la risposta migliore. Ci sono momenti in cui isinstanceè la risposta migliore e ci sono momenti in cui la battuta d'anatra è la risposta migliore. È importante conoscere tutte le opzioni in modo da poter scegliere quale è più appropriato per la situazione.
John La Rooy,

6
@gnibbler, Potrebbe essere, anche se non mi sono ancora imbattuto nella situazione in cui type(foo) is SomeTypesarebbe meglio di isinstance(foo, SomeType).
Mike Graham,

5
@poke: sono totalmente d'accordo su PEP8, ma stai attaccando un uomo di paglia qui: la parte importante dell'argomento di Sven non era PEP8, ma che puoi usare anche isinstanceper il tuo caso d' uso (controllando una gamma di tipi), e con anche una sintassi pulita, che ha il grande vantaggio di poter acquisire sottoclassi. qualcuno che usa OrderedDictodierebbe il tuo codice per fallire perché accetta solo dicts puri.
pecore volanti,

166

Puoi farlo usando type():

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

40

Potrebbe essere più Pythonic da usare un try... exceptblocco. In questo modo, se si dispone di una classe che fa qua qua come un elenco, o fa qua qua come un dizionario, si comporterà correttamente indipendentemente dal suo tipo in realtà è.

Per chiarire, il metodo preferito di "dire la differenza" tra tipi di variabili è con qualcosa chiamato tipizzazione anatra : fintanto che i metodi (e i tipi restituiti) a cui una variabile risponde sono ciò che la tua subroutine si aspetta, trattala come ti aspetti essere. Ad esempio, se si dispone di una classe che sovraccarica gli operatori di parentesi con getattre setattr, ma utilizza qualche schema interno divertente, sarebbe opportuno che si comportasse come un dizionario se questo è ciò che sta cercando di emulare.

L'altro problema con il type(A) is type(B)controllo è che se si Atratta di una sottoclasse di B, valuta falsequando, a livello di programmazione, si spera che lo sia true. Se un oggetto è una sottoclasse di un elenco, dovrebbe funzionare come un elenco: la verifica del tipo come presentato nell'altra risposta lo impedirà. ( isinstancefunzionerà, comunque).


16
Tuttavia, la digitazione di anatre non riguarda in realtà la differenza. Si tratta di utilizzare un'interfaccia comune.
Justin Ethier,

5
Fai attenzione: la maggior parte delle guide di stile di codifica consiglia di non utilizzare la gestione delle eccezioni come parte del normale flusso di controllo del codice, di solito perché rende il codice difficile da leggere. try... exceptè una buona soluzione quando si desidera affrontare gli errori, ma non quando si decide il comportamento in base al tipo.
Rens van der Heijden,

34

Sulle istanze dell'oggetto hai anche:

__class__

attributo. Ecco un esempio preso dalla console Python 3.3

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

Attenzione che in Python 3.xe nelle classi New-Style (disponibili opzionalmente da Python 2.6) la classe e il tipo sono stati uniti e questo a volte può portare a risultati inaspettati. Principalmente per questo motivo il mio modo preferito di testare tipi / classi è la funzione integrata.


2
Il tuo punto alla fine è molto importante. type (obj) is Class non funzionava correttamente, ma isinstance ha fatto il trucco. Capisco che l'istanza è comunque preferita, ma è più vantaggiosa del semplice controllo dei tipi derivati, come suggerito nella risposta accettata.
mstbaum,

__class__è principalmente OK su Python 2.x, gli unici oggetti in Python che non hanno __class__attributo sono le classi di vecchio stile AFAIK. A proposito, non capisco la tua preoccupazione per Python 3: in tale versione, ogni oggetto ha un __class__attributo che punta alla classe corretta.
Alan Franzoni,

21

Determina il tipo di un oggetto Python

Determina il tipo di un oggetto con type

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

Anche se funziona, evita i doppi attributi di sottolineatura come __class__- non sono semanticamente pubblici e, anche se forse non in questo caso, le funzioni integrate di solito hanno un comportamento migliore.

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

controllo del tipo

Esiste un modo semplice per determinare se una variabile è un elenco, un dizionario o qualcos'altro? Ricevo un oggetto che può essere di entrambi i tipi e devo essere in grado di dire la differenza.

Bene, questa è una domanda diversa, non usare il tipo - usa 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)

Questo riguarda il caso in cui l'utente potrebbe fare qualcosa di intelligente o sensato con la sottoclasse str- secondo il principio di Sostituzione Liskov, si desidera poter utilizzare istanze di sottoclasse senza infrangere il codice - eisinstance supporta.

Usa le astrazioni

Ancora meglio, potresti cercare una specifica classe base astratta da collectionso 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)

O semplicemente non controllare esplicitamente il tipo

O, forse meglio di tutto, usa la digitazione anatra e non controllare esplicitamente il codice. La tipizzazione anatra supporta la sostituzione di Liskov con più eleganza e meno verbosità.

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)

Conclusione

  • Utilizzare typeper ottenere effettivamente la classe di un'istanza.
  • Utilizzare isinstanceper verificare esplicitamente sottoclassi effettive o astrazioni registrate.
  • E basta evitare il controllo del tipo dove ha senso.

C'è sempre try/ exceptinvece di controllare esplicitamente.
toonarmycaptain

Presumibilmente è ciò che l'utente farà se non è sicuro dei tipi che passerà. Non mi piace ingombrare un'implementazione corretta con la gestione delle eccezioni a meno che non abbia qualcosa di molto buono da fare con l'eccezione. L'eccezione sollevata dovrebbe essere sufficiente per informare l'utente della necessità di correggerne l'utilizzo.
Aaron Hall

13

Puoi usare type()o isinstance().

>>> type([]) is list
True

Tieni presente che puoi bloccare listo qualsiasi altro tipo assegnando una variabile nell'ambito corrente con lo stesso nome.

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

Sopra vediamo che dictviene riassegnato a una stringa, quindi il test:

type({}) is dict

...non riesce.

Per aggirare questo problema e usare con type()più cautela:

>>> import __builtin__
>>> the_d = {}
>>> type({}) is dict
True
>>> dict =""
>>> type({}) is dict
False
>>> type({}) is __builtin__.dict
True

2
Non sono sicuro che sia necessario sottolineare che l'ombreggiatura del nome di un tipo di dati incorporato non è utile in questo caso. La tua dictstringa fallirà anche per molti altri codici, come dict([("key1", "value1"), ("key2", "value2")]). La risposta per questo tipo di problemi è "Quindi non farlo" . Non ombreggiare i nomi dei tipi predefiniti e aspettarti che le cose funzionino correttamente.
Blckknght,

3
Sono d'accordo con te sulla parte "non farlo". Ma davvero per dire a qualcuno di non fare qualcosa dovresti almeno spiegare perché no e ho pensato che questa fosse un'opportunità rilevante per fare proprio questo. Intendevo che il metodo cauto apparisse brutto e illustrasse perché non avrebbero voluto farlo, lasciandoli a decidere.
deed02392,

type () non funziona come previsto su Python 2.x per istanze classiche.
Alan Franzoni,

5

Mentre le domande sono piuttosto vecchie, mi sono imbattuto in questo mentre trovavo me stesso un modo corretto e penso che debba ancora essere chiarito, almeno per Python 2.x (non ha verificato su Python 3, ma poiché il problema sorge con le classi classiche che sono andati su tale versione, probabilmente non importa).

Qui sto cercando di rispondere alla domanda del titolo: come posso determinare il tipo di un oggetto arbitrario ? Altri suggerimenti su come usare o non usare isinstance vanno bene in molti commenti e risposte, ma non sto affrontando queste preoccupazioni.

Il problema principale con l' type()approccio è che non funziona correttamente con istanze vecchio stile :

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

L'esecuzione di questo frammento produrrebbe:

Are o and t instances of the same class? True

Il che, sostengo, non è quello che la maggior parte della gente si aspetterebbe.

L' __class__approccio è il più vicino alla correttezza, ma non funzionerà in un caso cruciale: quando l'oggetto passato è una classe vecchio stile (non un'istanza!), Poiché quegli oggetti mancano di tale attributo.

Questo è il più piccolo frammento di codice a cui potrei pensare che soddisfa una domanda così legittima in modo coerente:

#!/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

5

fai attenzione usando isinstance

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

ma digita

type(True) == bool
True
>>> type(True) == int
False

3

A parte le risposte precedenti, vale la pena ricordare che l'esistenza collections.abccontiene diverse classi di base astratte (ABC) che completano la tipizzazione delle anatre.

Ad esempio, invece di verificare esplicitamente se qualcosa è un elenco con:

isinstance(my_obj, list)

potresti, se sei interessato solo a vedere se l'oggetto che hai consente di ottenere oggetti, usa collections.abc.Sequence:

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

se sei strettamente interessato agli oggetti che consentono di ottenere, impostare ed eliminare elementi (ad esempio sequenze mutabili ), opteresti per collections.abc.MutableSequence.

Molti altri ABCs sono definite lì, Mappingper gli oggetti che possono essere utilizzati come mappe, Iterable, Callable, eccetera. Un elenco completo di tutti questi può essere visto nella documentazione per collections.abc.


1

In generale puoi estrarre una stringa dall'oggetto con il nome della classe,

str_class = object.__class__.__name__

e usandolo per il confronto,

if str_class == 'dict':
    # blablabla..
elif str_class == 'customclass':
    # blebleble..

1

In molti casi pratici invece di utilizzare typeo isinstanceè anche possibile utilizzare @functools.singledispatch, che viene utilizzato per definire funzioni generiche ( funzione composta da più funzioni che implementano la stessa operazione per tipi diversi ).

In altre parole, vorresti usarlo quando hai un codice come il seguente:

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

Ecco un piccolo esempio di come funziona:

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

Inoltre, possiamo usare le classi astratte per coprire diversi tipi contemporaneamente:

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!

0

type()è una soluzione migliore di isinstance(), in particolare per booleans:

Truee Falsesono solo parole chiave che significano 1e 0in Python. Così,

isinstance(True, int)

e

isinstance(False, int)

entrambi ritornano True. Entrambi i booleani sono un'istanza di un numero intero. type(), tuttavia, è più intelligente:

type(True) == int

ritorna False.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.