Определить тип объекта?

1792

Есть ли простой способ определить, является ли переменная списком, словарем или чем-то еще? Я возвращаю объект, который может быть любого типа, и я должен быть в состоянии отличить.

Джастин этир
источник
44
Хотя в целом я с вами согласен, бывают ситуации, когда это полезно знать. В этом конкретном случае у меня был быстрый взлом, который я в итоге откатил, так что в этот раз вы правы. Но в некоторых случаях - например, при использовании отражения - важно знать, с каким типом объекта вы имеете дело.
Джастин Этье
67
@ S.Lott Я бы не согласился с этим; будучи в состоянии знать тип, вы можете иметь дело с некоторыми красивыми вариантами ввода и все еще делать правильные вещи. Это позволяет вам обойти проблемы интерфейса, присущие положению чисто утиной печати (например, метод .bark () в Дереве означает нечто совершенно иное, чем в Собаке.) Например, вы можете сделать функцию, которая поработает над файл, который принимает строку (например, путь), объект пути или список. Все они имеют разные интерфейсы, но конечный результат один и тот же: выполните некоторую операцию с этим файлом.
Роберт П
22
@ S.Lott Я надеялся, что будет очевидно, что это надуманный пример; тем не менее, это основной недостаток при наборе утки, и тот, tryкоторый не помогает. Например, если вы знали, что пользователь может передать строку или массив, оба могут индексировать, но этот индекс означает нечто совершенно иное. Просто полагаться на попытку в таких случаях не получится неожиданным и странным образом. Одно из решений - создать отдельный метод, другое - добавить небольшую проверку типов. Лично я предпочитаю полиморфное поведение нескольким методам, которые делают почти одно и то же ... но это только я :)
Robert P
22
@ S.Lott, а как насчет юнит-тестирования? Иногда вы хотите, чтобы ваши тесты проверяли, что функция возвращает что-то правильного типа. Очень реальный пример - когда у вас есть фабрика классов.
Эллиот Кэмерон
17
Для менее надуманного примера рассмотрим сериализатор / десериализатор. По определению вы конвертируете между предоставленными пользователем объектами и сериализованным представлением. Сериализатору необходимо определить тип объекта, который вы передали, и у вас может не хватить информации для определения десериализованного типа без запроса времени выполнения (или, по крайней мере, он может понадобиться вам для проверки работоспособности, чтобы перехватывать неверные данные до их ввода ваша система!)
Карл

Ответы:

1978

Есть две встроенные функции, которые помогут вам определить тип объекта. Вы можете использовать , type() если вам нужен точный тип объекта, и isinstance()чтобы проверить тип объекта против чего - то. Обычно вы хотите использовать isistance()большую часть времени, так как он очень надежен и поддерживает наследование типов.


Чтобы получить фактический тип объекта, вы используете встроенную type()функцию. Передача объекта в качестве единственного параметра вернет объект типа этого объекта:

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

Это, конечно, также работает для пользовательских типов:

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

Обратите внимание, что он type()будет возвращать только непосредственный тип объекта, но не сможет рассказать вам о наследовании типов.

>>> type(b) is Test1
False

Чтобы покрыть это, вы должны использовать isinstanceфункцию. Это, конечно, также работает для встроенных типов:

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

isinstance()обычно является предпочтительным способом обеспечения типа объекта, поскольку он также будет принимать производные типы. Поэтому, если вам не нужен объект типа (по какой-либо причине), использование isinstance()предпочтительнее, чем type().

Второй параметр isinstance()также принимает кортеж типов, поэтому можно проверять сразу несколько типов. isinstanceзатем вернет true, если объект имеет любой из этих типов:

>>> isinstance([], (tuple, list, set))
True
совать
источник
68
Я думаю , что это понятнее использовать isвместо ==как типы одиночек
Джон Ла Rooy
18
@gnibbler, В тех случаях, когда вы будете проверять типы (что вам не следует делать с самого начала), isinstanceэто предпочтительная форма, так ==или иначе, или ее не isнужно использовать.
Майк Грэм
23
@ Майк Грэм, бывают времена, когда typeлучший ответ. Есть времена, когда isinstanceэто лучший ответ, и бывают случаи, когда утка - лучший ответ. Важно знать все варианты, чтобы вы могли выбрать наиболее подходящий для ситуации.
Джон Ла Рой
6
@gnibbler, это может быть, хотя я еще не столкнулся с ситуацией, где type(foo) is SomeTypeбыло бы лучше, чем isinstance(foo, SomeType).
Майк Грэм,
5
@poke: я полностью согласен с PEP8, но вы здесь нападаете на соломенного чучела: важной частью аргумента Свена был не PEP8, но то, что вы также можете использовать isinstanceдля своего сценария использования (проверка на диапазон типов), и с а также чистый синтаксис, который имеет большое преимущество в том, что вы можете захватывать подклассы. кто-то, пользующийся, OrderedDictбудет ненавидеть ваш код, потому что он просто принимает чистые слова.
летающие овцы
166

Вы можете сделать это используя type():

>>> a = []
>>> type(a)
<type 'list'>
>>> f = ()
>>> type(f)
<type 'tuple'>
inkedmn
источник
40

Это может быть более Pythonic, чтобы использовать try... exceptблок. Таким образом, если у вас есть класс , который крякает как список, или крякает как Dict, он будет вести себя должным образом , независимо от того, что его типа на самом деле является.

Чтобы уточнить, предпочтительный метод «определения различий» между типами переменных - это то, что называется утиной типизацией : пока методы (и возвращаемые типы), на которые отвечает переменная, - это то, что ожидает ваша подпрограмма, обрабатывайте ее так, как вы ожидаете быть. Например, если у вас есть класс, который перегружает операторы скобок с помощью getattrи setattr, но использует какую-то забавную внутреннюю схему, было бы целесообразно, чтобы он вел себя как словарь, если это то, что он пытается эмулировать.

Другая проблема, связанная с type(A) is type(B)проверкой, заключается в том, что если Aэто подкласс B, он оценивает, falseкогда программным путем вы бы надеялись, что так и будет true. Если объект является подклассом списка, он должен работать как список: проверка типа, представленного в другом ответе, предотвратит это. ( isinstanceбудет работать, однако).

Сет Джонсон
источник
16
Утиная печать, на самом деле, не говорит о разнице. Речь идет об использовании общего интерфейса.
Джастин Этьер
5
Будьте осторожны - большинство руководств по стилю кодирования рекомендуют не использовать обработку исключений как часть нормального потока управления кода, обычно потому, что это затрудняет чтение кода. try... exceptявляется хорошим решением, когда вы хотите иметь дело с ошибками, но не при выборе поведения на основе типа.
Ренс ван дер Хейден
34

На экземплярах объекта у вас также есть:

__class__

атрибут. Вот пример, взятый из консоли 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'>

Помните, что в Python 3.x и в классах New-Style (возможно, из Python 2.6) класс и тип были объединены, и это может иногда привести к неожиданным результатам. Главным образом по этой причине мой любимый способ тестирования типов / классов - встроенная функция isinstance .

Лоренцо Персикетти
источник
2
Ваша точка зрения в конце очень важна. type (obj) is Class не работал правильно, но isinstance сделал свое дело. Я понимаю, что в любом случае предпочтительнее isinstance, но это выгоднее, чем просто проверка производных типов, как предлагается в принятом ответе.
Mstbaum
__class__в основном работает нормально в Python 2.x, единственные объекты в Python, которые не имеют __class__атрибутов, это классы старого стиля AFAIK. Кстати, я не понимаю вашу озабоченность Python 3 - в такой версии просто у каждого объекта есть __class__атрибут, который указывает на соответствующий класс.
Алан Францони
21

Определить тип объекта Python

Определите тип объекта с помощью type

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

Хотя это работает, избегайте атрибутов двойного подчеркивания, таких как __class__- они не являются семантически открытыми, и, хотя, возможно, и не в этом случае, встроенные функции обычно ведут себя лучше.

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

проверка типа

Есть ли простой способ определить, является ли переменная списком, словарем или чем-то еще? Я возвращаю объект, который может быть любого типа, и я должен быть в состоянии отличить.

Ну, это другой вопрос, не используйте тип - используйте 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)

Это относится к случаю, когда ваш пользователь может делать что-то умное или разумное путем strсоздания подклассов - согласно принципу подстановки Лискова, вы хотите иметь возможность использовать экземпляры подкласса, не нарушая свой код, - и isinstanceподдерживает это.

Используйте Абстракции

Более того, вы можете искать конкретный абстрактный базовый класс из collectionsили 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)

Или просто явно не проверяйте тип

Или, может быть, лучше всего, использовать Duck-типирование и не проверять код явно. Duck-typing поддерживает Liskov Substitution более элегантно и менее многословно.

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)

Вывод

  • Используйте, typeчтобы фактически получить класс экземпляра.
  • Используется isinstanceдля явной проверки на наличие подклассов или зарегистрированных абстракций.
  • И просто избегайте проверки типов там, где это имеет смысл.
Аарон Холл
источник
Там всегда try/ exceptвместо проверки явно.
toonarmycaptain
Предположительно, это то, что пользователь будет делать, если он не уверен в типах, которые он будет передавать. Я не люблю загромождать правильную реализацию обработкой исключений, если у меня нет чего-то очень хорошего, чтобы сделать исключение. Возникшего исключения должно быть достаточно, чтобы проинформировать пользователя о том, что ему необходимо исправить свое использование.
Аарон Холл
13

Вы можете использовать type()или isinstance().

>>> type([]) is list
True

Имейте в listвиду, что вы можете использовать clobber или любой другой тип, назначая переменную в текущей области с тем же именем.

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

Выше мы видим, что dictпереназначается на строку, поэтому тест:

type({}) is dict

... не получается.

Чтобы обойти это и использовать type()более осторожно:

>>> import __builtin__
>>> the_d = {}
>>> type({}) is dict
True
>>> dict =""
>>> type({}) is dict
False
>>> type({}) is __builtin__.dict
True
deed02392
источник
2
Я не уверен, что необходимо указать, что дублирование имени встроенного типа данных в этом случае плохо. Ваша dictстрока также потерпит неудачу для большого количества другого кода, например dict([("key1", "value1"), ("key2", "value2")]). Ответ на такие вопросы: «Тогда не делай этого» . Не скрывайте встроенные имена типов и не ожидайте, что все будет работать правильно.
Blckknght
3
Я согласен с тобой в части "не делай этого". Но на самом деле, чтобы сказать кому-то не делать что-то, вы должны хотя бы объяснить, почему нет, и я подумал, что это была подходящая возможность сделать именно это. Я хотел, чтобы осторожный метод выглядел уродливо и иллюстрировал, почему они не хотят этого делать, оставляя им решать.
deed02392
type () не работает должным образом в Python 2.x для классических экземпляров.
Алан Францони
5

Хотя вопросы довольно старые, я наткнулся на это, когда сам нашел правильный путь, и я думаю, что он все еще нуждается в разъяснении, по крайней мере для Python 2.x (не проверял на Python 3, но так как проблема возникает с классическими классами которые ушли на такой версии, это, вероятно, не имеет значения).

Здесь я пытаюсь ответить на вопрос заголовка: как я могу определить тип произвольного объекта ? Другие предложения об использовании или неиспользовании isinstance хороши во многих комментариях и ответах, но я не рассматриваю эти проблемы.

Основная проблема этого type()подхода заключается в том, что он не работает должным образом с экземплярами старого стиля :

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

Выполнение этого фрагмента даст:

Are o and t instances of the same class? True

Что, я утверждаю, не то, чего ожидали бы большинство людей.

__class__Подход наиболее близок к правильности, но он не будет работать в одном решающем случае: когда Переданный объект является старым стиль класс (! Не экземпляр), так как эти объекты не имеет такой атрибут.

Это наименьший фрагмент кода, о котором я мог подумать, который последовательно удовлетворяет такой законный вопрос:

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

будьте осторожны, используя isinstance

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

но типа

type(True) == bool
True
>>> type(True) == int
False
tnusraddinov
источник
3

В collections.abcдополнение к предыдущим ответам, стоит упомянуть о существовании, которое содержит несколько абстрактных базовых классов (ABC), которые дополняют типизацию утки.

Например, вместо явной проверки, является ли что-то списком с:

isinstance(my_obj, list)

если вы хотите узнать, позволяет ли ваш объект получать предметы, вы можете использовать collections.abc.Sequence:

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

если вы строго заинтересованы в объектах, которые позволяют получать, устанавливать и удалять элементы (например, изменяемые последовательности), вы бы выбрали collections.abc.MutableSequence.

Многие другие ABCs определяются там, Mappingдля объектов , которые могут быть использованы в качестве карты, Iterable, Callableи так далее. Полный список всего этого можно увидеть в документации по collections.abc.

Димитрис Фасаракис Хиллиард
источник
1

Как правило, вы можете извлечь строку из объекта с именем класса,

str_class = object.__class__.__name__

и использовать его для сравнения,

if str_class == 'dict':
    # blablabla..
elif str_class == 'customclass':
    # blebleble..
Хосе Креспо Барриос
источник
1

Во многих практических случаях вместо использования typeили isinstanceвы также можете использовать @functools.singledispatch, который используется для определения универсальных функций ( функция, состоящая из нескольких функций, реализующих одну и ту же операцию для разных типов ).

Другими словами, вы хотели бы использовать его, когда у вас есть код, подобный следующему:

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

Вот небольшой пример того, как это работает:

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

Кроме того, мы можем использовать абстрактные классы для одновременного покрытия нескольких типов:

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()является лучшим решением, чем isinstance(), в частности, для booleans:

Trueи Falseпросто ключевые слова, которые означают 1и 0в Python. Таким образом,

isinstance(True, int)

а также

isinstance(False, int)

оба возвращаются True. Оба логических значения являются экземпляром целого числа. type()Впрочем, умнее:

type(True) == int

возвращается False.

Алек Аламеддин
источник