Які відмінності між типом () та речовиною ()?


1247

Які відмінності між цими двома фрагментами коду?

Використання 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

Відповіді:


1270

Для узагальнення вмісту інших (вже хороших!) Відповідей isinstanceзадовольняє успадкування (екземпляр похідного класу теж є екземпляром базового класу), при цьому перевіряючи рівність цього typeнемає (це вимагає ідентифікації типів та відхиляє екземпляри підтипів, підкласи AKA).

Зазвичай в Python, звичайно, ви хочете, щоб ваш код підтримував успадкування (оскільки успадкування настільки зручно, було б погано зупинити код, використовуючи ваш, щоб використовувати його!), Так що isinstanceце менш погано, ніж перевірка ідентичності types, оскільки вона безперебійно підтримує спадкування.

Це не означає, що isinstanceце добре , зауважте, це просто так погано , ніж перевірка рівності типів. Нормальне, пітонічне вподобане рішення майже незмінно "набирає качку": спробуйте використовувати аргумент так, ніби він був певного бажаного типу, зробіть це в try/ exceptзаяві, вловлюючи всі винятки, які можуть виникнути, якби аргумент насправді не був. введіть (або будь-який інший тип, який добре качкує, імітуючи його ;-), і в 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.

ПЕП дає зрозуміти, що, хоча АВС часто може замінити набір качок, в цілому немає великого тиску для цього (див. Тут ). ABC, що впроваджені в останніх версіях Python, однак пропонують додаткові переваги: isinstanceissubclass) тепер можуть означати більше, ніж просто "[екземпляр] похідного класу" (зокрема, будь-який клас можна "зареєструвати" в ABC, щоб він був показати як підклас, а його екземпляри як екземпляри ABC); і ABC також можуть запропонувати додаткову зручність фактичним підкласам дуже природним шляхом за допомогою шаблонного дизайну прикладних програм (див. тут і тут [[частина II]] для отримання додаткової інформації про TM DP, взагалі та конкретно в Python, незалежно від ABC). .

Про основні механізми підтримки ABC, запропоновані в Python 2.6, дивіться тут ; їх версію 3.1, дуже схожу, дивіться тут . В обох версіях стандартні колекції модулів бібліотеки (це версія 3.1 - для дуже подібної версії 2.6 див. Тут ) пропонує декілька корисних азбук.

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


9
"Це не те, що речовина добре, зауважте, це просто менше, ніж перевірка рівності типів. Нормальне, пітонічне вподобане рішення майже незмінно "набирає качку" "Це досить обмежений погляд: є дуже хороші випадки використання isin substance (), скажімо, інтерпретатора, де типи відображають граматику. Бути "пітонічним" - це не все!
Джин Каллахан

2
basestring недоступний у Python 3.
erobertc

@GeneCallahan, оскільки є дуже хороші випадки, не означає, що сказане, не є хорошим загальним правилом. Я погоджуюся, що перевірка типу достроково, безумовно, має місце, але дозволяти качкам шарлатати повинно охоплювати більшість випадків більш гнучко та ефективно.
Ерік Ед Ломар

@erobertc, згідно з новими в Python 3.0 , "Вбудований абстрактний тип базових рядків видалено. Замість цього використовуйте str."
неврит

344

Ось приклад, де 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, тому що "речовина краща за тип" - це оманливий коментар. з першого погляду це розуміється як " typeзастаріле, isinstanceзамість цього". Наприклад, те, що я хотів, було саме type()перевірити, але мене на короткий час ввели в оману (і довелося трохи відладжувати) з цієї причини.
ceremcem

8
Це гарний приклад того, як вони працюють по-різному, але я просто натрапив на випадок, коли мені спеціально потрібно, type()а ні isinstance(). Один не кращий; вони для різних речей.
EL_DON

103

Відмінності між Python isinstance()і type()в них?

Перевірка типу с

isinstance(obj, Base)

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

isinstance(obj, (Base1, Base2))

тоді як перевірка типу за допомогою

type(obj) is Base

підтримує лише тип, на який посилається.


Що стосується сиденота, isшвидше за все, більше, ніж

type(obj) == Base

тому що класи - одиночні.

Уникайте перевірки типу - використовуйте поліморфізм (тип качки)

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

і наш код все ще працює.

Однак є деякі випадки, коли бажано чітко перевірити тип. Можливо, у вас є розумні речі, що стосуються різних типів об'єктів. Наприклад, об'єкт фрейму даних Pandas може бути побудований з диктів або записів. У такому випадку ваш код повинен знати, який тип аргументу він отримує, щоб він міг правильно обробити його.

Отже, щоб відповісти на запитання:

Відмінності між Python 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- якщо вам справді не потрібно знати точний клас екземпляра.


Якщо у вас є your_module.py, куди ви перевіряєте isinstance(instance, y)та використовуєте from v.w.x import y, та імпортуєте цей чек, але коли ви примірник instanceвикористовуєте from x import yзамість того, як y було імпортовано у your_module.py, перевірка обставин не вдасться, навіть якщо це той самий клас.
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__ відповідно до __instancheck__ .

Зверху 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)})

BTW: краще не змішувати використання relative and absolutely import, використовувати absolutely importз project_dir (додав sys.path)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.