Jakie są różnice między typem () a isinstance ()?

1247

Jakie są różnice między tymi dwoma fragmentami kodu?

Używanie type():

import types

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

Używanie isinstance():

if isinstance(a, dict):
    do_something()
if isinstance(b, str) or isinstance(b, unicode):
    do_something_else()
opat
źródło
Uwaga: Jeśli nie jest ( stri unicodegdzie można to po prostu sprawdzić basestring), możesz użyć krotki do sprawdzania wielu typów. Aby sprawdzić, czy somethingjest intlub strużyj isinstance(something, (int, str)).
xuiqzy

Odpowiedzi:

1270

Podsumowując treść innych (już dobrych!) Odpowiedzi, isinstanceuwzględnia dziedziczenie (instancja klasy pochodnej jest również instancją klasy podstawowej), a sprawdzanie równości typenie (wymaga identyczności typów i odrzuca instancje podtypów, podklasy AKA).

Zwykle w Pythonie chcesz, aby Twój kod obsługiwał dziedziczenie, oczywiście (ponieważ dziedziczenie jest tak przydatne, byłoby źle, aby przestać używać kodu z twojego!), Więc isinstancejest mniej złe niż sprawdzanie tożsamości types, ponieważ płynnie obsługuje dziedzictwo.

Pamiętaj, że isinstanceto nie jest dobre - jest po prostu mniej złe niż sprawdzanie równości typów. Normalnym, Pythonicznym, preferowanym rozwiązaniem jest prawie zawsze „pisanie kaczki”: spróbuj użyć argumentu tak, jakby był z określonego pożądanego typu, zrób to w instrukcji try/ exceptwychwytując wszystkie wyjątki, które mogłyby powstać, gdyby argument nie był w rzeczywistości type (lub jakikolwiek inny typ ładnie naśladujący to kaczkę ;-), a w exceptklauzuli spróbuj czegoś innego (używając argumentu „jak gdyby” był innego typu).

basestring jest jednak dość szczególnym przypadkiem - wbudowanym typem, który istnieje tylko po to, abyś mógł używać isinstance(zarówno podklasy, jak stri unicodepodklasy basestring). Ciągi są sekwencjami (można je nad nimi zapętlać, indeksować, pokroić, ...), ale generalnie chcesz traktować je jako typy „skalarne” - jest to nieco niewygodne (ale dość częsty przypadek użycia) do traktowania wszystkich rodzajów ciągi (i być może inne typy skalarne, tj. takie, których nie można zapętlić) w jeden sposób, wszystkie kontenery (listy, zestawy, dykty, ...) w inny sposób, a basestringplus isinstancepomaga to zrobić - ogólna struktura tego idiom jest jak:

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

Można powiedzieć, że basestringjest to Abstrakcyjna Klasa Podstawowa („ABC”) - nie oferuje konkretnych funkcji dla podklas, ale istnieje raczej jako „marker”, głównie do użytku z isinstance. Koncepcja jest oczywiście coraz popularniejsza w Pythonie, ponieważ PEP 3119 , który wprowadza uogólnienie, został zaakceptowany i został wdrożony począwszy od Pythona 2.6 i 3.0.

PEP wyjaśnia, że ​​chociaż ABC często zastępuje pisanie kaczką, generalnie nie ma na to dużej presji (patrz tutaj ). ABC zaimplementowane w najnowszych wersjach Pythona oferują jednak dodatkowe korzyści: isinstance(i issubclass) mogą teraz oznaczać więcej niż tylko „[instancję] klasy pochodnej” (w szczególności każdą klasę można „zarejestrować” w ABC, aby była pokaż jako podklasę, a jej instancje jako instancje ABC); i ABC mogą również oferować dodatkową wygodę dla rzeczywistych podklas w bardzo naturalny sposób dzięki aplikacjom wzorcowym do projektowania szablonów (patrz tutaj i tutaj [[część II]], aby uzyskać więcej informacji na temat TM DP, ogólnie, a konkretnie w Pythonie, niezależnie od ABC) .

Podstawowe mechanizmy obsługi ABC oferowane w Pythonie 2.6 znajdują się tutaj ; ich bardzo podobna wersja 3.1, patrz tutaj . W obu wersjach standardowe kolekcje modułów bibliotecznych (to jest wersja 3.1 - bardzo podobna wersja 2.6, patrz tutaj ) oferuje kilka przydatnych ABC.

Dla celów tej odpowiedzi kluczową rzeczą do zapamiętania na temat ABC (poza prawdopodobnie bardziej naturalnym rozmieszczeniem dla funkcji TM DP, w porównaniu do klasycznej alternatywy Python dla klas mixin, takich jak UserDict.DictMixin ) jest to, że tworzą isinstance(i issubclass) znacznie więcej atrakcyjne i wszechobecne (w Pythonie 2.6 i późniejszych) niż kiedyś (w wersji 2.5 i wcześniejszych), a tym samym sprawiają, że sprawdzanie równości typów jest jeszcze gorszą praktyką w najnowszych wersjach Pythona niż kiedyś.

Alex Martelli
źródło
9
„Nie chodzi o to, że instancja jest dobra, pamiętajcie - jest po prostu mniej zła niż sprawdzanie równości typów. Normalnym, preferowanym Pythonem jest prawie zawsze „pisanie kaczką”. Jest to raczej ograniczony pogląd: istnieją bardzo dobre przypadki użycia isinstance () w, powiedzmy, interpretera, w którym typy odzwierciedlają gramatykę. Bycie „Pythonem” to nie wszystko!
Gene Callahan
2
basestring nie jest dostępny w Pythonie 3.
erobertc
@GeneCallahan, ponieważ istnieją bardzo dobre przypadki, nie oznacza, że ​​to, co zostało powiedziane, nie jest dobrą ogólną zasadą. Zgadzam się, że sprawdzanie typu z wyprzedzeniem zdecydowanie ma swoje miejsce, ale pozwalanie kaczkom na kwakowanie powinno obejmować większość przypadków bardziej elastycznie i wydajniej.
Eric Ed Lohmar,
@erobertc, zgodnie z Co nowego w Pythonie 3.0 , „Wbudowany typ abstrakcyjny łańcucha bazowego został usunięty. Zamiast tego użyj str.”
neuryt
344

Oto przykład, w którym isinstanceosiąga coś, typeczego nie można:

class Vehicle:
    pass

class Truck(Vehicle):
    pass

w tym przypadku obiektem ciężarówki jest pojazd, ale otrzymasz to:

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.

Innymi słowy, isinstancedotyczy to również podklas.

Zobacz także: Jak porównać typ obiektu w Pythonie?

Piotr
źródło
143
ponieważ istnieje przypadek, w którym nie chcesz zachowania isInstance, argumentowałbym, że nie ma „lepszego”. Po prostu robią coś innego.
philgo20
27
-1, ponieważ „isinstance is better than type” to mylący komentarz. na pierwszy rzut oka należy rozumieć, że „ typejest przestarzałe, użyj isinstancezamiast niego”. na przykład chciałem dokładnie type()sprawdzić, ale z tego powodu zostałem wprowadzony w błąd przez krótki czas (i musiałem trochę debugować).
ceremonie
8
To dobry przykład tego, jak działają inaczej, ale właśnie natknąłem się na przypadek, w którym szczególnie tego potrzebowałem, type()a nie isinstance(). Jedno nie jest lepsze; są do różnych rzeczy.
EL_DON
103

Różnice między Pythonem isinstance()i pomiędzy nimi type()?

Sprawdzanie typu za pomocą

isinstance(obj, Base)

pozwala na wystąpienia podklas i wielu możliwych baz:

isinstance(obj, (Base1, Base2))

podczas gdy kontrola typu za pomocą

type(obj) is Base

obsługuje tylko wskazany typ.


Jako sidenote, isjest prawdopodobnie bardziej odpowiednie niż

type(obj) == Base

ponieważ zajęcia są singletonami.

Unikaj sprawdzania typu - używaj polimorfizmu (pisanie kaczką)

W Pythonie zazwyczaj chcesz zezwolić na dowolny typ argumentów, traktować go zgodnie z oczekiwaniami, a jeśli obiekt nie zachowuje się zgodnie z oczekiwaniami, wygeneruje odpowiedni błąd. Jest to znane jako polimorfizm, znany również jako pisanie kaczek.

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

Jeśli powyższy kod działa, możemy założyć, że nasz argument jest kaczką. Możemy więc przekazać, że inne rzeczy to rzeczywiste podtypy kaczek:

function_of_duck(mallard)

lub które działają jak kaczka:

function_of_duck(object_that_quacks_and_swims_like_a_duck)

a nasz kod nadal działa.

Istnieją jednak przypadki, w których pożądane jest jawne sprawdzenie typu. Być może masz sensowne rzeczy związane z różnymi typami obiektów. Na przykład obiekt Pandas Dataframe można zbudować z nagrań lub nagrań. W takim przypadku Twój kod musi wiedzieć, jaki typ argumentu otrzymuje, aby mógł poprawnie go obsłużyć.

Tak więc, aby odpowiedzieć na pytanie:

Różnice między Pythonem isinstance()i pomiędzy nimi type()?

Pozwól mi zademonstrować różnicę:

type

Powiedz, że musisz zapewnić określone zachowanie, jeśli twoja funkcja otrzyma pewien rodzaj argumentu (częsty przypadek użycia dla konstruktorów). Jeśli zaznaczysz taki typ:

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

Jeśli spróbujemy przekazać dyktandę, która jest podklasą dict(tak jak powinniśmy, jeśli oczekujemy, że nasz kod będzie zgodny z zasadą podstawienia Liskowa , że podtypy można zastąpić typami), nasz kod się zepsuje !:

from collections import OrderedDict

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

wywołuje błąd!

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

isinstance

Ale jeśli skorzystamy isinstance, możemy wesprzeć zastąpienie Liskowa !:

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

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

Abstrakcyjne klasy podstawowe

W rzeczywistości możemy zrobić jeszcze lepiej. collectionszapewnia abstrakcyjne klasy podstawowe, które egzekwują minimalne protokoły dla różnych typów. W naszym przypadku, jeśli oczekujemy tylko Mappingprotokołu, możemy wykonać następujące czynności, a nasz kod staje się jeszcze bardziej elastyczny:

from collections import Mapping

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

Odpowiedź na komentarz:

Należy zauważyć, że typ może być używany do sprawdzania przy użyciu wielu klas type(obj) in (A, B, C)

Tak, możesz przetestować równość typów, ale zamiast powyższego użyj wielu zasad dla przepływu sterowania, chyba że zezwalasz tylko na te typy:

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

Znowu różnica polega na tym, że isinstanceobsługuje podklasy, które można podstawić dla elementu nadrzędnego, nie przerywając w inny sposób programu, właściwość zwaną podstawieniem Liskov.

Co więcej, odwróć swoje zależności i nie sprawdzaj w ogóle konkretnych typów.

Wniosek

Ponieważ chcemy wspierać zastępowanie podklas, w większości przypadków chcemy unikać sprawdzania typetypów i wolimy sprawdzanie typów za pomocą isinstance- chyba że naprawdę potrzebujesz znać dokładną klasę instancji.

Aaron Hall
źródło
Jeśli masz plik your_module.py, w którym sprawdzasz isinstance(instance, y)i używasz from v.w.x import y, i importujesz ten czek, ale gdy utworzysz instancję instance, użyjesz from x import yzamiast tego, w jaki sposób y zostało zaimportowane w pliku twoja_module.py, kontrola isinstance zakończy się niepowodzeniem, nawet jeśli jest to ta sama klasa.
toonarmycaptain
64

Ten ostatni jest preferowany, ponieważ będzie poprawnie obsługiwał podklasy. W rzeczywistości twój przykład można napisać jeszcze łatwiej, ponieważ isinstance()drugim parametrem może być krotka:

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

lub za pomocą basestringklasy abstrakcyjnej:

if isinstance(b, basestring):
    do_something_else()
John Millikin
źródło
9

Praktyczną różnicą w użyciu jest sposób, w jaki obsługują booleans:

Truei Falseto tylko słowa kluczowe, to znaczy, że 1i 0w Pythonie. A zatem,

isinstance(True, int)

i

isinstance(False, int)

oba wracają True. Oba booleany są wystąpieniem liczby całkowitej. type()jest jednak bardziej sprytny:

type(True) == int

zwraca False.

Alec Alameddine
źródło
0

Dla prawdziwych różnic możemy go znaleźć code, ale nie mogę znaleźć implementacji domyślnego zachowania isinstance().

Możemy jednak uzyskać podobny abc .__ instancecheck__ zgodnie z __instancecheck__ .

Z góry abc.__instancecheck__, po zastosowaniu testu poniżej:

# 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

Mam taki wniosek, ponieważ type:

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

Dla 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: lepiej nie mieszać użycia relative and absolutely import, użyj absolutely importz project_dir (dodane przez sys.path)

Cheney
źródło