Déterminer le type d'un objet?

1791

Existe-t-il un moyen simple de déterminer si une variable est une liste, un dictionnaire ou autre chose? Je récupère un objet qui peut être de l'un ou de l'autre type et je dois pouvoir faire la différence.

Justin Ethier
la source
44
Bien qu'en général je sois d'accord avec vous, il y a des situations où il est utile de le savoir. Dans ce cas particulier, je faisais un piratage rapide que j'ai finalement annulé, donc vous avez raison cette fois. Mais dans certains cas - lors de l'utilisation de la réflexion, par exemple - il est important de savoir de quel type d'objet il s'agit.
Justin Ethier
67
@ S.Lott, je ne suis pas d'accord avec cela; en étant en mesure de connaître le type, vous pouvez gérer certaines entrées assez variées et toujours faire la bonne chose. Il vous permet de contourner les problèmes d'interface inhérents au fait de s'appuyer sur le typage pur canard (par exemple, la méthode .bark () sur un arbre signifie quelque chose de complètement différent de sur un chien.) Par exemple, vous pouvez créer une fonction qui fonctionne un fichier qui accepte une chaîne (par exemple, un chemin), un objet chemin ou une liste. Tous ont des interfaces différentes, mais le résultat final est le même: effectuez une opération sur ce fichier.
Robert P
22
@ S.Lott J'espérais qu'il serait évident que c'est un exemple artificiel; néanmoins, c'est un point majeur de la dactylographie de canard, et qui tryn'aide pas. Par exemple, si vous saviez qu'un utilisateur pouvait passer une chaîne ou un tableau, les deux sont indexables, mais cet index signifie quelque chose de complètement différent. S'appuyer simplement sur un essai dans ces cas échouera de manière inattendue et étrange. Une solution consiste à créer une méthode distincte, une autre à ajouter une petite vérification de type. Personnellement, je préfère le comportement polymorphe à plusieurs méthodes qui font presque la même chose ... mais c'est juste moi :)
Robert P
22
@ S.Lott, qu'en est-il des tests unitaires? Parfois, vous voulez que vos tests vérifient qu'une fonction renvoie quelque chose du bon type. Un exemple très réel est lorsque vous avez une usine de classe.
Elliot Cameron
17
Pour un exemple moins artificiel, considérez un sérialiseur / désérialiseur. Par définition, vous effectuez une conversion entre des objets fournis par l'utilisateur et une représentation sérialisée. Le sérialiseur doit déterminer le type d'objet que vous avez transmis, et vous ne disposez peut-être pas des informations adéquates pour déterminer le type désérialisé sans demander le temps d'exécution (ou à tout le moins, vous pouvez en avoir besoin pour vérifier la validité afin d'attraper les mauvaises données avant qu'elles n'entrent. votre système!)
Karl

Réponses:

1977

Il existe deux fonctions intégrées qui vous aident à identifier le type d'un objet. Vous pouvez utiliser type() si vous avez besoin du type exact d'un objet et isinstance()pour comparer le type d'un objet avec quelque chose. Habituellement, vous souhaitez utiliser la isistance()plupart du temps car il est très robuste et prend également en charge l'héritage de types.


Pour obtenir le type réel d'un objet, vous utilisez la type()fonction intégrée. Le passage d'un objet comme seul paramètre renvoie l'objet type de cet objet:

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

Bien sûr, cela fonctionne également pour les types personnalisés:

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

Notez que type()ne renverra que le type immédiat de l'objet, mais ne pourra pas vous parler de l'héritage de type.

>>> type(b) is Test1
False

Pour couvrir cela, vous devez utiliser la isinstancefonction. Bien sûr, cela fonctionne également pour les types intégrés:

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

isinstance()est généralement le moyen préféré pour garantir le type d'un objet car il accepte également les types dérivés. Donc, sauf si vous avez réellement besoin de l'objet type (pour une raison quelconque), l'utilisation isinstance()est préférable à type().

Le deuxième paramètre de isinstance()accepte également un tuple de types, il est donc possible de vérifier plusieurs types à la fois. isinstanceretournera alors true, si l'objet est de l'un de ces types:

>>> isinstance([], (tuple, list, set))
True
poussée
la source
68
Je pense qu'il est plus clair d'utiliser isau lieu de ==car les types sont des singletons
John La Rooy
18
@gnibbler, Dans les cas où vous feriez une vérification typographique (ce que vous ne devriez pas faire pour commencer), isinstanceest de toute façon la forme préférée, donc ni l'un ==ni l' autre ne isdoit être utilisé.
Mike Graham
23
@Mike Graham, il y a des moments où typeest la meilleure réponse. Il y a des moments où isinstanceest la meilleure réponse et il y a des moments où la frappe de canard est la meilleure réponse. Il est important de connaître toutes les options afin de pouvoir choisir celle qui convient le mieux à la situation.
John La Rooy
6
@gnibbler, C'est peut-être, même si je n'ai pas encore rencontré la situation où ce type(foo) is SomeTypeserait mieux que isinstance(foo, SomeType).
Mike Graham
5
@poke: Je suis totalement d'accord sur PEP8, mais vous attaquez un homme de paille ici: la partie importante de l'argument de Sven n'était pas PEP8, mais que vous pouvez également utiliser isinstancepour votre cas d'utilisation (vérifier une gamme de types), et avec aussi une syntaxe propre, qui a le grand avantage que vous pouvez capturer des sous-classes. quelqu'un utilisant OrderedDictdétesterait que votre code échoue car il accepte simplement les dict purs.
vol de moutons
166

Vous pouvez le faire en utilisant type():

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

Il pourrait être plus Pythonic d'utiliser un bloc try.... exceptDe cette façon, si vous avez une classe qui caquette comme une liste, ou charlatans comme un dict, il se comportera correctement quel que soit son type vraiment est.

Pour clarifier, la méthode préférée pour "faire la différence" entre les types de variables est avec quelque chose appelé typage du canard : tant que les méthodes (et les types de retour) auxquels une variable répond sont celles attendues par votre sous-programme, traitez-la comme ce que vous attendez d'elle être. Par exemple, si vous avez une classe qui surcharge les opérateurs de parenthèses avec getattret setattr, mais utilise un schéma interne amusant, il serait approprié qu'elle se comporte comme un dictionnaire si c'est ce qu'elle essaie d'émuler.

L'autre problème avec la type(A) is type(B)vérification est que si Aest une sous-classe de B, elle évalue falsequand, par programme, vous espérez qu'elle le soit true. Si un objet est une sous-classe d'une liste, il devrait fonctionner comme une liste: la vérification du type tel que présenté dans l'autre réponse évitera cela. ( isinstancefonctionnera cependant).

Seth Johnson
la source
16
Taper du canard ne signifie pas vraiment faire la différence. Il s'agit d'utiliser une interface commune.
Justin Ethier
5
Soyez prudent - la plupart des guides de style de codage recommandent de ne pas utiliser la gestion des exceptions dans le cadre du flux de contrôle normal du code, généralement parce que cela rend le code difficile à lire. try... exceptest une bonne solution lorsque vous souhaitez gérer les erreurs, mais pas lorsque vous décidez d'un comportement basé sur le type.
Rens van der Heijden
34

Sur les instances d'objet, vous avez également:

__class__

attribut. Voici un exemple extrait de la 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'>

Attention, dans python 3.x et dans les classes New-Style (disponibles en option à partir de Python 2.6), la classe et le type ont été fusionnés et cela peut parfois conduire à des résultats inattendus. Principalement pour cette raison, ma façon préférée de tester les types / classes est la fonction intégrée intégrée.

Lorenzo Persichetti
la source
2
Votre point à la fin est très important. type (obj) is Class ne fonctionnait pas correctement, mais c'est l'instance qui a fait l'affaire. Je comprends que cette instance est de toute façon préférée, mais c'est plus avantageux que de simplement vérifier les types dérivés, comme suggéré dans la réponse acceptée.
mstbaum
__class__est généralement OK sur Python 2.x, les seuls objets en Python qui n'ont pas d' __class__attribut sont les classes anciennes AFAIK. Soit dit en passant, je ne comprends pas votre préoccupation concernant Python 3 - sur une telle version, juste chaque objet a un __class__attribut qui pointe vers la classe appropriée.
Alan Franzoni
21

Déterminer le type d'un objet Python

Déterminez le type d'un objet avec type

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

Bien que cela fonctionne, évitez les attributs de soulignement double comme __class__- ils ne sont pas sémantiquement publics, et, même si ce n'est peut-être pas le cas dans ce cas, les fonctions intégrées ont généralement un meilleur comportement.

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

vérification de type

Existe-t-il un moyen simple de déterminer si une variable est une liste, un dictionnaire ou autre chose? Je récupère un objet qui peut être de l'un ou de l'autre type et je dois pouvoir faire la différence.

Eh bien, c'est une question différente, n'utilisez pas de type - utilisez 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)

Cela couvre le cas où votre utilisateur pourrait faire quelque chose d'intelligent ou de sensible en sous str- classant - selon le principe de la substitution de Liskov, vous voulez pouvoir utiliser des instances de sous-classe sans casser votre code - etisinstance prend en charge cela.

Utiliser des abstractions

Encore mieux, vous pouvez rechercher une classe de base abstraite spécifique à partir de collectionsou 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)

Ou tout simplement ne pas vérifier explicitement le type

Ou, peut-être mieux encore, utilisez la frappe de canard et ne vérifiez pas explicitement votre code. Duck-typing soutient Liskov Substitution avec plus d'élégance et moins de 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)

Conclusion

  • Utilisation type d'obtenir réellement la classe d'une instance.
  • Utilisation isinstance de vérifier explicitement les sous-classes réelles ou les abstractions enregistrées.
  • Et évitez simplement la vérification de type là où cela a du sens.
Aaron Hall
la source
Il y a toujours try/ exceptau lieu de vérifier explicitement.
toonarmycaptain
Vraisemblablement, c'est ce que l'utilisateur fera s'il n'est pas sûr des types qu'il transmettra. Je n'aime pas encombrer une implémentation correcte avec la gestion des exceptions, sauf si j'ai quelque chose de très bien à faire avec l'exception. L'exception soulevée devrait être suffisante pour informer l'utilisateur qu'il doit corriger son utilisation.
Aaron Hall
13

Vous pouvez utiliser type()ou isinstance().

>>> type([]) is list
True

Soyez averti que vous pouvez clobber listou tout autre type en affectant une variable dans la portée actuelle du même nom.

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

Ci-dessus, nous voyons que cela dictest réaffecté à une chaîne, donc le test:

type({}) is dict

...échoue.

Pour contourner cela et utiliser type()avec plus de prudence:

>>> import __builtin__
>>> the_d = {}
>>> type({}) is dict
True
>>> dict =""
>>> type({}) is dict
False
>>> type({}) is __builtin__.dict
True
deed02392
la source
2
Je ne suis pas sûr qu'il soit nécessaire de souligner que l'observation du nom d'un type de données intégré est mauvaise dans ce cas. Votre dictchaîne échouera également pour de nombreux autres codes, comme dict([("key1", "value1"), ("key2", "value2")]). La réponse pour ce genre de problèmes est "Alors ne fais pas ça" . N'obscurcissez pas les noms de types intégrés et ne vous attendez pas à ce que les choses fonctionnent correctement.
Blckknght
3
Je suis d'accord avec vous sur la partie "ne faites pas ça". Mais en effet, pour dire à quelqu'un de ne pas faire quelque chose, vous devez au moins expliquer pourquoi et je me suis dit que c'était une occasion pertinente de le faire. Je voulais que la méthode prudente soit laide et illustre pourquoi ils pourraient ne pas vouloir le faire, en les laissant décider.
deed02392
type () ne fonctionne pas comme prévu sur Python 2.x pour les instances classiques.
Alan Franzoni
5

Bien que les questions soient assez anciennes, je suis tombé dessus tout en trouvant moi-même un moyen approprié, et je pense qu'il faut encore clarifier, au moins pour Python 2.x (n'a pas vérifié Python 3, mais puisque le problème se pose avec les classes classiques qui ont disparu sur une telle version, cela n'a probablement pas d'importance).

Ici, j'essaie de répondre à la question du titre: comment puis-je déterminer le type d'un objet arbitraire ? D'autres suggestions sur l'utilisation ou la non-utilisation de l'instance sont bonnes dans de nombreux commentaires et réponses, mais je ne réponds pas à ces préoccupations.

Le principal problème avec l' type()approche est qu'elle ne fonctionne pas correctement avec les instances à l'ancienne :

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'exécution de cet extrait donnerait:

Are o and t instances of the same class? True

Ce qui, selon moi, n'est pas ce à quoi la plupart des gens s'attendraient.

L' __class__approche est la plus proche de l'exactitude, mais elle ne fonctionnera pas dans un cas crucial: lorsque l'objet transmis est une classe à l' ancienne (pas une instance!), Car ces objets n'ont pas un tel attribut.

C'est le plus petit extrait de code auquel je pourrais penser qui répond à une telle question légitime de manière cohérente:

#!/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
la source
5

soyez prudent en utilisant isinstance

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

mais tapez

type(True) == bool
True
>>> type(True) == int
False
tnusraddinov
la source
3

En marge des réponses précédentes, il convient de mentionner l'existence de collections.abcplusieurs classes de base abstraites (ABC) qui complètent le typage canard.

Par exemple, au lieu de vérifier explicitement si quelque chose est une liste avec:

isinstance(my_obj, list)

vous pourriez, si vous êtes seulement intéressé à voir si l'objet que vous avez permet d'obtenir des objets, utilisez collections.abc.Sequence:

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

si vous êtes strictement intéressé par les objets qui permettent d'obtenir, de définir et de supprimer des éléments (c'est-à-dire des séquences mutables ), vous opterez pour collections.abc.MutableSequence.

Beaucoup d' autres y sont définis ABCs, Mappingpour les objets qui peuvent être utilisés comme cartes, Iterable, Callable, et ainsi de suite. Une liste complète de tous ces éléments peut être consultée dans la documentation de collections.abc.

Dimitris Fasarakis Hilliard
la source
1

En général, vous pouvez extraire une chaîne d'un objet avec le nom de classe,

str_class = object.__class__.__name__

et l'utiliser à des fins de comparaison,

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

Dans de nombreux cas pratiques, au lieu d'utiliser typeou isinstancevous pouvez également utiliser @functools.singledispatch, qui est utilisé pour définir des fonctions génériques ( fonction composée de plusieurs fonctions implémentant la même opération pour différents types ).

En d'autres termes, vous voudriez l'utiliser lorsque vous avez un code comme celui-ci:

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

Voici un petit exemple de son fonctionnement:

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

De plus, nous pouvons utiliser des classes abstraites pour couvrir plusieurs types à la fois:

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
la source
0

type()est une meilleure solution que isinstance(), notamment pour booleans:

Trueet ne Falsesont que des mots clés qui signifient 1et 0en python. Donc,

isinstance(True, int)

et

isinstance(False, int)

les deux reviennent True. Les deux booléens sont une instance d'un entier. type(), cependant, est plus intelligent:

type(True) == int

retourne False.

Alec Alameddine
la source