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

Каковы различия между этими двумя фрагментами кода? С помощью 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()

8 ответов

Решение

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

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

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

basestring однако, это особый случай - встроенный тип, который существуеттолько для того, чтобы вы могли использовать isinstance (и то и другое str а также unicode подкласс basestring). Строки - это последовательности (вы можете зацикливать их, индексировать их, нарезать их, ...), но вы, как правило, хотите рассматривать их как "скалярные" типы - это несколько неудобно (но достаточно часто используется) для обработки всех видов строки (и, возможно, другие скалярные типы, т. е. те, которые вы не можете зациклить) одним способом, все контейнеры (списки, наборы, dict,...) другим способом, и 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 часто могут заменить типизацию утки, обычно нет большого давления для этого (см. Здесь). Азбука, реализованная в последних версиях Python, предлагает дополнительные возможности: isinstance (а также issubclass) теперь может означать больше, чем просто "[экземпляр] производного класса" (в частности, любой класс может быть "зарегистрирован" с помощью 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), заключается в том, что они делают isinstance (а также issubclass) гораздо более привлекательным и распространенным (в Python 2.6 и в дальнейшем), чем они были (в 2.5 и ранее), и поэтому, напротив, проверка равенства типов стала еще худшей практикой в ​​последних версиях Python, чем это было раньше,

Вот почему 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?

Различия между isinstance() а также type() в питоне?

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

isinstance(obj, Base)

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

isinstance(obj, (Base1, Base2))

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

type(obj) is Base

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


Как знак, 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() в питоне?

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

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 (как и должно быть, если мы ожидаем, что наш код будет следовать принципу подстановки Лискова, то подтипы могут быть заменены типами) наш код ломается!:

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 - если вам действительно не нужно знать точный класс экземпляра.

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

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

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

if isinstance(b, basestring):
    do_something_else()

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

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

isinstance(True, int)

а также

isinstance(False, int)

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

type(True) == int

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

В соответствии с документацией Python вот заявление:

8,15. типы - имена для встроенных типов

Начиная с Python 2.2, встроенные фабричные функции, такие как int() а также str() также имена для соответствующих типов.

Так isinstance() должно быть предпочтительнее, чем type(),

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

type()-> возвращает тип объекта

isinstance()-> возвращает логическое значение

Вообще говоря isinstanceявляется «более» элегантным способом проверки, относится ли объект к определенному «типу» (поскольку вы знаете о цепочке наследования).

С другой стороны, если вы не знаете о цепочке наследования и вам нужно, чтобы вас выбрали, перейдите к type(x) == ...

Еще один интересный случай для typeкогда вы проверяете bool

      ----Case bool----

print(type(True) == int) # False
print(type(False) == int) # False
print(type(True) == bool) # True
print(type(False) == bool) # True

print(isinstance(True, int)) # True
print(isinstance(True, int)) # True



----Case inheritance----
class A:
    x=1

class B(A):
    x=2

class C(B):
    x=3
    
var1 = A()
var2 = B()
var3 = C()

print(type(var1)) # <class '__main__.A'>
print(type(var1) == A) # True
print(type(var2) == A) # False
print(type(var3) == A) # False

print(isinstance(var1, A)) # True
print(isinstance(var2, A)) # True
print(isinstance(var3, A)) # True



print(type(var2)) # <class '__main__.B'>
print(type(var1) == B) # False
print(type(var2) == B) # True
print(type(var3) == B) # False

print(isinstance(var1, B)) # False
print(isinstance(var2, B)) # True
print(isinstance(var3, B)) # True



print(type(var3)) # <class '__main__.C'>
print(type(var1) == C) # False
print(type(var2) == C) # False
print(type(var3) == C) # True

print(isinstance(var1, C)) # False
print(isinstance(var2, C)) # False
print(isinstance(var3, C)) # True

Для реальных различий мы можем найти его в 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)

Другие вопросы по тегам