Каковы различия между type () и isinstance ()?

1250

Каковы различия между этими двумя фрагментами кода?

Использование type():

import types

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

Использование isinstance():

if isinstance(a, dict):
    do_something()
if isinstance(b, str) or isinstance(b, unicode):
    do_something_else()
аббат
источник
Примечание. Если это не так strи unicode(где вы можете просто проверить basestring), вы можете использовать кортеж для проверки нескольких типов. Чтобы проверить, если somethingесть intили strиспользуйте isinstance(something, (int, str)).
xuiqzy

Ответы:

1271

Подводя итог содержанию других (уже хороших!) Ответов, isinstanceобслуживает наследование (экземпляр производного класса также является экземпляром базового класса), а проверка на равенство - typeнет (требует идентификации типов и отклоняет экземпляры). подтипов, подклассы АКА).

Как правило, в Python вы хотите, чтобы ваш код поддерживал наследование (конечно, поскольку наследование очень удобно, было бы плохо не использовать код, использующий ваш!), Так что isinstanceэто не так плохо, как проверка идентичности types, потому что он полностью поддерживает наследование.

Это не значит, что isinstanceэто хорошо , заметьте, это просто так плохо , чем проверка равенства типов. Нормальное, предпочтительное для Pythonic решение - это почти всегда «типизированная утка»: попробуйте использовать аргумент, как если бы он был определенного желаемого типа, сделайте это в операторе try/, exceptперехватив все исключения, которые могут возникнуть, если аргумент не является фактическим. type (или любой другой тип, прекрасно имитирующий утку ;-), и в exceptпредложении попробуйте что-нибудь другое (используя аргумент «как будто», он был другого типа).

basestring это , однако, довольно особый случай - встроенный тип, который существует только для того, чтобы вы могли его использовать isinstance(как strи unicodeподкласс basestring). Строки - это последовательности (вы можете зациклить их, проиндексировать их, нарезать их, ...), но вы, как правило, хотите рассматривать их как «скалярные» типы - это несколько неудобно (но достаточно часто используется) для обработки всех видов строки (и, возможно, другие скалярные типы, т. е. те, которые вы не можете зациклить) одним способом, все контейнеры (списки, наборы, подсказки, ...) другим способом, и basestringплюс isinstanceпомогает вам сделать это - общая структура этого идиома это что-то вроде:

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

Можно сказать, что basestringэто абстрактный базовый класс («ABC») - он не предлагает никаких конкретных функций для подклассов, а скорее существует как «маркер», в основном для использования с isinstance. Эта концепция явно расширяется в Python, поскольку PEP 3119 , который вводит ее обобщение, был принят и реализован начиная с Python 2.6 и 3.0.

PEP ясно дает понять, что, хотя ABC часто могут заменить типизацию утки, как правило, нет большого давления, чтобы сделать это (см. Здесь ). Однако ABC, реализованные в последних версиях Python, предлагают дополнительные плюсы: isinstanceissubclass) теперь может означать больше, чем просто «[экземпляр] производного класса» (в частности, любой класс может быть «зарегистрирован» в ABC, так что он будет показать как подкласс, а его экземпляры как экземпляры ABC); и ABC могут также предложить дополнительное удобство для реальных подклассов очень естественным способом с помощью шаблонных приложений шаблонного метода (см. здесь и здесь [[часть II]] для получения дополнительной информации о TM DP, в целом и особенно в Python, независимо от ABC) ,

Основную механику поддержки ABC, предложенную в Python 2.6, смотрите здесь ; для их версии 3.1, очень похожей, смотрите здесь . В обеих версиях стандартные коллекции модулей библиотеки (это версия 3.1 - для очень похожей версии 2.6, см. Здесь ) предлагают несколько полезных ABC.

Для целей этого ответа главное, что следует сохранить в ABC (помимо, возможно, более естественного размещения функциональности TM DP, по сравнению с классической Python-альтернативой смешанных классов, таких как UserDict.DictMixin ), заключается в том, что они делают isinstanceissubclass) намного больше привлекательный и распространенный (в Python 2.6 и в дальнейшем), чем раньше (в 2.5 и ранее), и поэтому, напротив, проверка равенства типов является еще худшей практикой в ​​последних версиях Python, чем это было раньше.

Алекс Мартелли
источник
9
«Дело не в том, что экземпляр хорош, заметьте, он просто менее плох, чем проверка равенства типов. Нормальное, Pythonic, предпочтительное решение - это почти всегда «типизирование утки». Это довольно ограниченное представление: есть очень хорошие случаи использования isinstance (), скажем, в интерпретаторе, где типы отражают грамматику. Быть «питоном» - это еще не все!
Джин Каллахан
2
базовая строка не доступна в Python 3.
erobertc
@GeneCallahan, потому что есть очень хорошие случаи, не означает, что сказанное не является хорошим общим правилом. Я согласен, что проверка типа заранее определенно имеет свое место, но позволение уткам шарлатанам должно охватывать большинство случаев более гибко и эффективно.
Эрик Эд
@erobertc, согласно « Что нового в Python 3.0» , «встроенный абстрактный тип базовой строки был удален. Вместо этого используйте str».
неврит
345

Вот пример, где isinstanceдостигается то, что typeне может:

class Vehicle:
    pass

class Truck(Vehicle):
    pass

в этом случае объект грузовика - это Автомобиль, но вы получите следующее:

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.

Другими словами, isinstanceверно и для подклассов.

Также см .: Как сравнить тип объекта в Python?

Питер
источник
143
потому что есть случай, когда вам не нужно поведение isInstance, я бы сказал, что «лучшего» не существует. Они просто делают что-то другое.
philgo20
27
-1, потому что «isinstance лучше, чем type» - вводящий в заблуждение комментарий. это понимается как « typeустарело, используйте isinstanceвместо» на первый взгляд. например, то, что я хотел, было именно type()проверкой, но я был введен в заблуждение в течение короткого времени (и должен был немного отладить) по этой причине.
ceremcem
8
Это хороший пример того, как они работают по-разному, но я просто натолкнулся на случай, когда мне конкретно это нужно type()и не нужно isinstance(). Один не лучше; они для разных вещей.
EL_DON
103

Различия между isinstance()и type()в Python?

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

isinstance(obj, Base)

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

isinstance(obj, (Base1, Base2))

тогда как проверка типа с

type(obj) is Base

поддерживает только указанный тип.


В качестве sidenote, isвероятно, более подходящим, чем

type(obj) == Base

потому что классы синглтоны.

Избегайте проверки типов - используйте Polymorphism (duck-typing)

В Python, как правило, вы хотите разрешить любой тип для ваших аргументов, обрабатывать его как положено, и если объект не ведет себя должным образом, он вызовет соответствующую ошибку. Это известно как полиморфизм, также известный как типирование утки.

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

Если приведенный выше код работает, мы можем предположить, что наш аргумент - утка. Таким образом мы можем передать в другие вещи актуальные подтипы утки:

function_of_duck(mallard)

или это работает как утка

function_of_duck(object_that_quacks_and_swims_like_a_duck)

и наш код все еще работает.

Однако в некоторых случаях желательно явно проверять тип. Возможно, вы имеете дело с разными типами объектов. Например, объект Dataframe Pandas может быть создан из диктовок или записей. В таком случае ваш код должен знать, какой тип аргумента он получает, чтобы он мог правильно с ним справиться.

Итак, чтобы ответить на вопрос:

Различия между isinstance()и type()в Python?

Позвольте мне продемонстрировать разницу:

type

Скажем, вам нужно обеспечить определенное поведение, если ваша функция получает аргумент определенного типа (общий вариант использования для конструкторов). Если вы проверите для типа, как это:

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

Если мы попытаемся передать dict, который является подклассом dict(как мы должны быть в состоянии, если мы ожидаем, что наш код будет следовать принципу подстановки Лискова , эти подтипы могут быть заменены типами), наш код нарушится !:

from collections import OrderedDict

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

выдает ошибку!

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

isinstance

Но если мы воспользуемся isinstance, мы можем поддержать замену Лискова!

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

возвращается OrderedDict([('foo', 'bar'), ('fizz', 'buzz')])

Абстрактные базовые классы

На самом деле, мы можем сделать еще лучше. collectionsпредоставляет абстрактные базовые классы, которые обеспечивают минимальные протоколы для различных типов. В нашем случае, если мы ожидаем только Mappingпротокола, мы можем сделать следующее, и наш код станет еще более гибким:

from collections import Mapping

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

Ответ на комментарий:

Следует отметить, что тип может использоваться для проверки нескольких классов с использованием type(obj) in (A, B, C)

Да, вы можете проверить на равенство типов, но вместо вышеперечисленного используйте несколько баз для потока управления, если только вы специально не разрешаете только эти типы:

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

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

Еще лучше, однако, инвертировать ваши зависимости и вообще не проверять конкретные типы.

Вывод

Так как мы хотим поддерживать замену подклассов, в большинстве случаев мы хотим избегать проверки типов с помощью typeи предпочитаем проверку типов с помощью isinstance- если вам действительно не нужно знать точный класс экземпляра.

Аарон Холл
источник
Если у вас есть your_module.py, где вы проверяете isinstance(instance, y)и используете from v.w.x import y, и вы импортируете эту проверку, но при создании экземпляра instanceвы используете from x import yвместо того, как y был импортирован в your_module.py, проверка isinstance завершится неудачей, даже если это тот же класс.
toonarmycaptain
64

Последний является предпочтительным, потому что он будет правильно обрабатывать подклассы. Фактически, ваш пример может быть написан еще проще, поскольку isinstance()второй параметр может быть кортежем:

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

или, используя basestringабстрактный класс:

if isinstance(b, basestring):
    do_something_else()
Джон Милликин
источник
9

Разница в практическом использовании заключается в том, как они обрабатывают booleans:

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

isinstance(True, int)

а также

isinstance(False, int)

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

type(True) == int

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

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

Для реальных отличий мы можем найти это code, но я не могу найти реализацию поведения по умолчанию isinstance().

Однако мы можем получить аналогичный abc .__ instancecheck__ в соответствии с __instancecheck__ .

Сверху abc.__instancecheck__, после использования теста ниже:

# 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

Я получаю этот вывод, для type:

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

Для 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)})

Кстати: лучше не смешивать использование relative and absolutely import, используйте absolutely importиз project_dir (добавлено sys.path)

Чейни
источник