python hint - Quali sono le differenze tra type()e isinstance()?




checking mypy (6)

Quali sono le differenze tra questi due frammenti di codice? Usando type() :

import types

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

Utilizzando isinstance() :

if isinstance(a, dict):
    do_something()
if isinstance(b, str) or isinstance(b, unicode):
    do_something_else()

Answers

Secondo la documentazione di Python, ecco una dichiarazione:

8.15. types - Nomi per tipi integrati

A partire da Python 2.2, le funzioni di fabbrica integrate come int() e str() sono anche nomi per i tipi corrispondenti.

Quindi isinstance() dovrebbe essere preferito rispetto a type() .


Per le reali differenze, possiamo trovarlo nel code , ma non riesco a trovare l'implementazione del comportamento predefinito di isinstance() .

Tuttavia possiamo ottenere il simile abc.__instancecheck__ secondo __instancecheck__ .

Da sopra abc.__instancecheck__ , dopo aver usato il test di seguito:

# 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

Ho questa conclusione, per type :

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

Per 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: meglio non mescolare uso relative and absolutely import , usare absolutely import da project_dir (aggiunto da sys.path )


Differenze tra isinstance() e type() in Python?

Tipo di controllo con

isinstance(obj, Base)

consente le istanze di sottoclassi e più possibili basi:

isinstance(obj, (Base1, Base2))

mentre il controllo del tipo con

type(obj) is Base

supporta solo il tipo di riferimento.

Come sidenote, è probabilmente più appropriato di

type(obj) == Base

perché le classi sono singleton.

Evita il controllo dei caratteri - usa il polimorfismo (tipizzazione delle anatre)

In Python, in genere si desidera consentire qualsiasi tipo per gli argomenti, trattarlo come previsto e, se l'oggetto non si comporta come previsto, genererà un errore appropriato. Questo è noto come polimorfismo, noto anche come dattilografia.

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

Se il codice sopra funziona, possiamo presumere che la nostra argomentazione sia un'anatra. Così possiamo passare in altre cose sono sottotipi effettivi di anatra:

function_of_duck(mallard)

o che funziona come un'anatra:

function_of_duck(object_that_quacks_and_swims_like_a_duck)

e il nostro codice funziona ancora.

Tuttavia, ci sono alcuni casi in cui è consigliabile esplicitamente il controllo del tipo. Forse hai cose sensate da fare con diversi tipi di oggetti. Ad esempio, l'oggetto Pandora Dataframe può essere costruito da dicts o record. In tal caso, il tuo codice deve sapere quale tipo di argomento sta ottenendo in modo che possa gestirlo correttamente.

Quindi, per rispondere alla domanda:

Differenze tra isinstance() e type() in Python?

Permettimi di dimostrare la differenza:

type

Supponiamo che sia necessario garantire un determinato comportamento se la funzione ottiene un certo tipo di argomento (un caso d'uso comune per i costruttori). Se controlli il tipo in questo modo:

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

Se proviamo a passare in un ditt che è una sottoclasse di dict (come dovremmo essere in grado, se ci aspettiamo che il nostro codice segua il principio di Liskov Substitution , che i sottotipi possano essere sostituiti per i tipi) il nostro codice si rompe !:

from collections import OrderedDict

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

solleva un errore!

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

isinstance

Ma se usiamo isinstance , possiamo supportare la sostituzione di Liskov !:

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

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

Classi di base astratte

In effetti, possiamo fare ancora meglio. collections forniscono classi base astratte che applicano protocolli minimi per vari tipi. Nel nostro caso, se ci aspettiamo solo il protocollo di Mapping , possiamo fare quanto segue e il nostro codice diventa ancora più flessibile:

from collections import Mapping

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

Risposta al commento:

Va notato che il tipo può essere usato per verificare contro più classi usando il type(obj) in (A, B, C)

Sì, puoi testare l'uguaglianza dei tipi, ma al posto di quanto sopra, usa le basi multiple per il controllo del flusso, a meno che tu non stia specificatamente permettendo solo quei tipi:

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

La differenza, ancora una volta, è che l' isinstance supporta sottoclassi che possono essere sostituite al genitore senza interrompere il programma, una proprietà nota come sostituzione di Liskov.

Ancora meglio, però, invertire le dipendenze e non controllare affatto i tipi specifici.

Conclusione

Quindi, dal momento che vogliamo supportare la sostituzione delle sottoclassi, nella maggior parte dei casi, vogliamo evitare il controllo dei type con il type e preferiamo il controllo dei isinstance con isinstance , a meno che tu non abbia davvero bisogno di conoscere la classe precisa di un'istanza.


Ecco perché isinstance è migliore del type :

class Vehicle:
    pass

class Truck(Vehicle):
    pass

in questo caso, un oggetto camion è un veicolo, ma otterrai questo:

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.

In altre parole, isinstance è vero anche per le sottoclassi.

Vedi anche: come confrontare il tipo di un oggetto in Python?


Quest'ultimo è preferito, perché gestirà correttamente le sottoclassi. In effetti, il tuo esempio può essere scritto anche più facilmente perché il secondo parametro di isinstance() potrebbe essere una tupla:

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

oppure, usando la classe astratta basestring :

if isinstance(b, basestring):
    do_something_else()

Una funzione astratta è "giusta" una firma, senza un'implementazione. È usato in un'interfaccia per dichiarare come la classe può essere usata. Deve essere implementato in una delle classi derivate.

La funzione virtuale (metodo in realtà), è una funzione dichiarata e dovrebbe essere implementata in una delle classi di gerarchia di ereditarietà.

Le istanze ereditate di tale classe ereditano anche l'implementazione, a meno che non la si implementi, in una classe gerarchica inferiore.





python oop inheritance types