Python: isinstance vs. type

Se você já programa em Python, é possível que já tenha se deparado com as funções isinstance e type. Ambas são funções built-in que estão relacionadas ao tipo de uma variável. Mas por quê há duas funções para desempenhar o que aparentemente é a mesma utilidade? Neste artigo, vamos examinar cada uma e determinar qual a diferença.

Sumário

  1. Sintaxe 1.1 type 1.2 isinstance
  2. A diferença 2.1 Exemplo
  3. Conclusão

Sintaxe

Antes de começar, vamos observar a sintaxe de cada função.

type

A função type pode receber até três argumentos. O primeiro é um objeto, e, caso apenas ele seja passado, a função retornará sua classe. Portanto, dado:

print(type(1))
print(type("tab news"))
print(type([1, 2, 3]))

temos:

<class 'int'>
<class 'str'>
<class 'list'>

e, se fosse para checarmos o tipo de uma variável, poderíamos utilizar a seguinte expressão:

print(type(True) == bool)
print(type(5) == bool) 

para obtermos o seguinte resultado:

True
False

Dito isso, type pode ser utilizado em estruturas condicionais que dependam do tipo de variável que foi passada, o que pode ser bastante útil em uma linguagem de tipagem dinâmica como Python.

Devo dizer também que, na realidade, type não é uma função, mas sim uma classe. Quando a invocamos e passamos um objeto como argumento, na realidade estamos inicializando um objeto novo. Por conta disso, quando verificamos o tipo de um tipo, temos o seguinte retorno:

print(type(str))
print(type(int))
print(type(type))
<class 'type'>
<class 'type'>
<class 'type'>

Curiosidade: os outros dois argumentos que podemos passar na verdade são parte da construção desse objeto, mas não vamos nos aprofundar nesse tópico pois está fora do escopo deste artigo.

isinstance

Agora, falando sobre a função isinstance, que realmente é uma função, nós podemos passar dois argumentos: um objeto de qualquer tipo e um objeto do tipo type.

print(isinstance(10, int))
print(isinstance("tabbie", str))
print(isinstance(False, float))
True
True
False

Alternativamente, podemos passar múltiplos tipos como uma tupla no lugar do segundo argumento. Assim, a função retornará True caso o objeto seja de algum dos tipos indicados:

isinstance(10, (str, int)) # é a mesma coisa que isinstance(10, str) or isinstance(10, int)
True

Logo, também podemos utilizar essa função em estruturas condicionais, da mesma forma como faríamos utilizando type.

A diferença

Na prática, vimos que na situação especificada (uso da função/classe em estruturas condicionais), ambas as técnicas parecem ter o mesmo resultado. Porém, isso não é sempre verdade, e se você não se atentar a um detalhe técnico, seu código pode acabar por gerar um comportamento inesperado.

A classe type retorna exatamente o tipo do objeto que está sendo analisado. Isso significa que ela não leva em consideração a classe da qual a classe do objeto herdou suas características. Por outro lado, isinstance considera.

Suponhamos que nós temos o seguinte código:


class A:
    ...
    
class B(A):
    ...

obj = B()

if type(obj) == B:
    print("de acordo com type, 'obj' é um objeto do tipo B")

if type(obj) == A:
    print("de acordo com type, 'obj' é um objeto do tipo A")

if isinstance(obj, B):
    print("de acordo com isinstance, 'obj' é um objeto do tipo B")
    
if isinstance(obj, A):
    print("de acordo com isinstance, 'obj' é um objeto do tipo A")

nós teríamos a seguinte saída:

de acordo com type, 'obj' é um objeto do tipo B
de acordo com isinstance, 'obj' é um objeto do tipo B
de acordo com isinstance, 'obj' é um objeto do tipo A

ou seja, ambos trazem resultados diferentes. isinstance deve ser utilizado quando há intenção de incluir a "classe pai" na hora da verificação do tipo. type, por outro lado, não nos permitiria ter essa noção polimórfica do nosso objeto.

Exemplo

Suponhamos que o nosso programa possua duas classes: uma classe Button e uma classe Switch, que herda de Button:

class Button:
    ...
    
class Switch(Button):
    ...

Em nosso programa, temos essa diferenciação entre as duas classes pois, apesar de o Switch ter todas as características de um botão, ela apresenta um comportamento diferente ao ser pressionada. Quando o botão é pressionado, ele liga e desliga, mas quando o Switch é pressionado ele fica ligado até ser pressionado de novo.

Se precisássemos aplicar algum efeito a todo tipo de botão do nosso programa, o correto seria utilizar o isinstance. Assim, alcançaríamos classes que herdam de Button, mas que não são Button.

Por outro lado, se precisássemos de algo específico para objetos Switch, ou para qualquer outra classe que herde de Button, o certo seria utilizar type para que não houvesse comportamento inesperado.

btn = Switch()

# código para desligar apenas objetos Switch que estejam ligados
if type(btn) == Switch and btn.is_on:
    btn.click() # se fosse outro botão, ele ligaria e desligaria em vez de só desligar
    
# código para mudar a cor de todos os botões
if isinstance(btn, Button):
    btn.color = "red" # nosso objeto Switch cai aqui também

Conclusão

Espero que tenha ficado claro a diferença entre isinstance e type e o porquê de ambas serem utilizadas em alguns códigos. Obrigado pela atenção! =)

Interessante csant nunca reparei esse promenor, sempre que poderes faça mais posts sobre Python teremos muito gosto em aprender e contribuir. Valeu

Valeu pelo apoio! =)