Qual é a maneira mais pítônica de verificar se um objeto é um número?

114

Dado um objeto Python arbitrário, qual é a melhor maneira de determinar se é um número? Aqui isé definido como acts like a number in certain circumstances.

Por exemplo, digamos que você esteja escrevendo uma classe de vetor. Se for dado outro vetor, você deseja encontrar o produto escalar. Se for fornecido um escalar, você deseja dimensionar todo o vetor.

Verificar se algo é int, float, long, boolé irritante e não cobre os objetos definidos pelo usuário que pode agir como números. Mas, verificar __mul__, por exemplo, não é bom o suficiente porque a classe de vetor que acabei de descrever definiria __mul__, mas não seria o tipo de número que desejo.

Claudiu
fonte

Respostas:

135

Use a Numberpartir do numbersmódulo para testar isinstance(n, Number)(disponível a partir de 2.6).

>>> from numbers import Number
... from decimal import Decimal
... from fractions import Fraction
... for n in [2, 2.0, Decimal('2.0'), complex(2, 0), Fraction(2, 1), '2']:
...     print(f'{n!r:>14} {isinstance(n, Number)}')
              2 True
            2.0 True
 Decimal('2.0') True
         (2+0j) True
 Fraction(2, 1) True
            '2' False

Obviamente, isso é contrário à digitação em pato. Se você está mais preocupado com a forma como um objeto age, e não com o que ele é , execute suas operações como se tivesse um número e use as exceções para dizer o contrário.

Steven Rumbalski
fonte
3
Fazer a coisa certa, ao invés de pato, é preferível quando você está multiplicando um vetor por X. Nesse caso, você deseja fazer coisas diferentes com base no que X é . (Pode agir como algo que se multiplica, mas o resultado pode ser absurdo.)
Evgeni Sergeev
3
esta resposta dá diria que Verdadeiro é um número ... que provavelmente nem sempre é o que você deseja. Excluindo booleanos (pense em validação fe), eu diriaisinstance(value, Number) and type(value) != bool
Yo Ludke
32

Você quer verificar se algum objeto

age como um número em certas circunstâncias

Se você estiver usando Python 2.5 ou anterior, a única maneira real é verificar algumas dessas "circunstâncias específicas" e ver.

Em 2.6 ou melhor, você pode usar isinstancecom números.Number - uma classe base abstrata (ABC) que existe exatamente para este propósito (muitos mais ABCs existem no collectionsmódulo para várias formas de coleções / contêineres, novamente começando com 2.6; e, também apenas nessas versões, você pode adicionar facilmente suas próprias classes base abstratas, se necessário).

Bach para 2.5 e anteriores, "pode ​​ser adicionado 0e não é iterável" pode ser uma boa definição em alguns casos. Mas, você realmente precisa se perguntar o que é que você está pedindo que o que você deseja considerar "um número" deve definitivamente ser capaz de fazer , e o que deve ser absolutamente incapaz de fazer - e verificar.

Isso também pode ser necessário no 2.6 ou posterior, talvez com o propósito de fazer seus próprios registros para adicionar tipos de seu interesse que ainda não tenham sido registrados numbers.Numbers- se você quiser excluir alguns tipos que afirmam serem números, mas você simplesmente não consigo lidar, isso leva ainda mais cuidado, já que os ABCs não têm nenhum unregistermétodo [[por exemplo, você pode fazer seu próprio ABC WeirdNume registrar lá todos esses tipos estranhos para você, então primeiro verifique se há algum método isinstanceantes de prosseguir para verificar isinstanceo normal numbers.Numberpara continuar com êxito.

BTW, se e quando você precisar verificar se xpode ou não fazer algo, geralmente você tem que tentar algo como:

try: 0 + x
except TypeError: canadd=False
else: canadd=True

A presença de __add__per se não diz nada de útil, uma vez que, por exemplo, todas as sequências o possuem para fins de concatenação com outras sequências. Essa verificação é equivalente à definição "um número é algo tal que uma sequência dessas coisas é um único argumento válido para a função embutida sum", por exemplo. Tipos totalmente estranhos (por exemplo, aqueles que levantam a exceção "errada" quando somados a 0, como, digamos, a ZeroDivisionErrorou ValueError& c) irão propagar a exceção, mas tudo bem, avise o usuário o mais rápido possível que esses tipos malucos não são aceitáveis ​​no bom companhia;-); mas, um "vetor" que pode ser somado a um escalar (a biblioteca padrão do Python não tem um, mas é claro que eles são populares como extensões de terceiros) também daria o resultado errado aqui, portanto (por exemploo "não permitido para ser iterável" (por exemplo, cheque que iter(x)aumenta TypeError, ou para a presença de método especial __iter__- se você estiver no 2.5 ou anterior e, portanto, precisa de seus próprios cheques).

Um breve vislumbre de tais complicações pode ser suficiente para motivá-lo a confiar em classes de base abstratas sempre que possível ... ;-).

Alex Martelli
fonte
Mas há um ABC para Número no módulo de números. É isso que os documentos afirmam: "O módulo de números (PEP 3141) define uma hierarquia de classes básicas abstratas numéricas que definem progressivamente mais operações."
Steven Rumbalski
17

Este é um bom exemplo onde as exceções realmente brilham. Basta fazer o que você faria com os tipos numéricos e pegar o TypeErrorde todo o resto.

Mas, obviamente, isso só verifica se uma operação funciona , não se faz sentido ! A única solução real para isso é nunca misturar tipos e sempre saber exatamente a qual typeclass seus valores pertencem.

Jochen Ritzel
fonte
1
+1 para Duck Typing: não importa o tipo de meus dados, apenas se posso ou não fazer o que quero com eles.
systempuntoout de
12
Essa era a abordagem tradicional, mas ABCs foram introduzidos em boa parte para fugir da digitação pura de pato e mudar alguma distância em direção a um mundo onde isinstancepode realmente ser útil em muitos casos (== "verifique se faz sentido", bem como aplicabilidade formal das operações). Mudança difícil para pessoas de longa data apenas com Python, mas uma tendência sutil muito importante na filosofia do Python que seria um erro sério ignorar.
Alex Martelli
@Alex: É verdade e eu adoro typeclasses (principalmente collections.Sequencee amigos). Mas afaik, não existem tais classes para números, vetores ou quaisquer outros objetos matemáticos.
Jochen Ritzel
1
Nada contra a digitação de pato. É o que eu faria. Mas existe uma classe base abstrata para números: números.Número.
Steven Rumbalski
4

Multiplique o objeto por zero. Qualquer número vezes zero é zero. Qualquer outro resultado significa que o objeto não é um número (incluindo exceções)

def isNumber(x):
    try:
        return bool(0 == x*0)
    except:
        return False

Usar isNumber, portanto, fornecerá a seguinte saída:

class A: pass 

def foo(): return 1

for x in [1,1.4, A(), range(10), foo, foo()]:
    answer = isNumber(x)
    print('{answer} == isNumber({x})'.format(**locals()))

Resultado:

True == isNumber(1)
True == isNumber(1.4)
False == isNumber(<__main__.A instance at 0x7ff52c15d878>)
False == isNumber([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
False == isNumber(<function foo at 0x7ff52c121488>)
True == isNumber(1)

Provavelmente existem alguns objetos não numéricos no mundo que definem __mul__retornar zero quando multiplicado por zero, mas essa é uma exceção extrema. Esta solução deve abranger todo o código normal e lógico que você gerar / encontrar.

exemplo numpy.array:

import numpy as np

def isNumber(x):
    try:
        return bool(x*0 == 0)
    except:
        return False

x = np.array([0,1])

answer = isNumber(x)
print('{answer} == isNumber({x})'.format(**locals()))

resultado:

False == isNumber([0 1])
megera
fonte
5
True * 0 == 0
endolith
4
Sua função dirá incorretamente que booleanos são números
endólito
1
@endolith, os booleanos agem exatamente como números. Verdadeiro sempre == 1 e Falso sempre == 0. Isso é exatamente o que o questionador pediu, "Aqui 'é' é definido como 'age como um número em certas circunstâncias'."
musaranho
1
@endolith, na verdade, booleanos são números. Boolean deriva de intentão minha função dirá corretamente que booleanos são números.
musaranho
1
@NicolasAbril, converta 0 * x == 0 em um bool dentro de isNumber.
musaranho
3

Para reformular sua pergunta, você está tentando determinar se algo é uma coleção ou um valor único. Tentar comparar se algo é um vetor ou um número está comparando maçãs com laranjas - posso ter um vetor de strings ou números, e posso ter uma única string ou um único número. Você está interessado em quantos você tem (1 ou mais) , não que tipo você realmente tem.

minha solução para esse problema é verificar se a entrada é um valor único ou uma coleção, verificando a presença de __len__. Por exemplo:

def do_mult(foo, a_vector):
    if hasattr(foo, '__len__'):
        return sum([a*b for a,b in zip(foo, a_vector)])
    else:
        return [foo*b for b in a_vector]

Ou, para a abordagem de digitação de pato, você pode tentar iterar fooprimeiro:

def do_mult(foo, a_vector):
    try:
        return sum([a*b for a,b in zip(foo, a_vector)])
    except TypeError:
        return [foo*b for b in a_vector]

Em última análise, é mais fácil testar se algo é semelhante a um vetor do que testar se algo é semelhante a escalar. Se você tiver valores de tipo diferente (ou seja, string, numérico, etc.), então a lógica do seu programa pode precisar de algum trabalho - como você acabou tentando multiplicar uma string por um vetor numérico em primeiro lugar?

Gordon Bean
fonte
3

Para resumir / avaliar os métodos existentes:

Candidate    | type                      | delnan | mat | shrewmouse | ant6n
-------------------------------------------------------------------------
0            | <type 'int'>              |      1 |   1 |          1 |     1
0.0          | <type 'float'>            |      1 |   1 |          1 |     1
0j           | <type 'complex'>          |      1 |   1 |          1 |     0
Decimal('0') | <class 'decimal.Decimal'> |      1 |   0 |          1 |     1
True         | <type 'bool'>             |      1 |   1 |          1 |     1
False        | <type 'bool'>             |      1 |   1 |          1 |     1
''           | <type 'str'>              |      0 |   0 |          0 |     0
None         | <type 'NoneType'>         |      0 |   0 |          0 |     0
'0'          | <type 'str'>              |      0 |   0 |          0 |     1
'1'          | <type 'str'>              |      0 |   0 |          0 |     1
[]           | <type 'list'>             |      0 |   0 |          0 |     0
[1]          | <type 'list'>             |      0 |   0 |          0 |     0
[1, 2]       | <type 'list'>             |      0 |   0 |          0 |     0
(1,)         | <type 'tuple'>            |      0 |   0 |          0 |     0
(1, 2)       | <type 'tuple'>            |      0 |   0 |          0 |     0

(Eu vim aqui por esta questão )

Código

#!/usr/bin/env python

"""Check if a variable is a number."""

import decimal


def delnan_is_number(candidate):
    import numbers
    return isinstance(candidate, numbers.Number)


def mat_is_number(candidate):
    return isinstance(candidate, (int, long, float, complex))


def shrewmouse_is_number(candidate):
    try:
        return 0 == candidate * 0
    except:
        return False


def ant6n_is_number(candidate):
    try:
        float(candidate)
        return True
    except:
        return False

# Test
candidates = (0, 0.0, 0j, decimal.Decimal(0),
              True, False, '', None, '0', '1', [], [1], [1, 2], (1, ), (1, 2))

methods = [delnan_is_number, mat_is_number, shrewmouse_is_number, ant6n_is_number]

print("Candidate    | type                      | delnan | mat | shrewmouse | ant6n")
print("-------------------------------------------------------------------------")
for candidate in candidates:
    results = [m(candidate) for m in methods]
    print("{:<12} | {:<25} | {:>6} | {:>3} | {:>10} | {:>5}"
          .format(repr(candidate), type(candidate), *results))
Martin Thoma
fonte
TODO para mim float('nan'), 'nan', '123.45', '42', '42a', '0x8', '0xa'math.isnan
:,
2

Provavelmente é melhor fazer o contrário: você verifica se é um vetor. Se for, você faz um produto escalar e, em todos os outros casos, tenta a multiplicação escalar.

Verificar o vetor é fácil, pois ele deve ser do seu tipo de classe de vetor (ou herdado dele). Você também pode tentar primeiro fazer um produto escalar e, se isso falhar (= não era realmente um vetor), volte para a multiplicação escalar.

sth
fonte
1

Apenas para acrescentar. Talvez possamos usar uma combinação de isinstance e isdigit da seguinte forma para descobrir se um valor é um número (int, float, etc)

if isinstance (num1, int) ou isinstance (num1, float) ou num1.isdigit ():

shadab.tughlaq
fonte
0

Para a aula de vetor hipotético:

Suponha que vseja um vetor e estejamos multiplicando por x. Se faz sentido multiplicar cada componente de vpor x, provavelmente isso é o que queríamos dizer, então tente primeiro. Se não, talvez possamos pontuar? Caso contrário, é um erro de tipo.

EDIT - o código abaixo não funciona, porque em 2*[0]==[0,0]vez de gerar um TypeError. Deixo porque foi comentado.

def __mul__( self, x ):
    try:
        return [ comp * x for comp in self ]
    except TypeError:
        return [ x * y for x, y in itertools.zip_longest( self, x, fillvalue = 0 )
Katriel
fonte
if xé um vetor, então [comp * x for comp in self], produzirá o produto externo de xum v. Este é um tensor de classificação 2, não um escalar.
aaronasterling
altere "não é um escalar" para "não é um vetor". pelo menos não no espaço vetorial original.
aaronasterling
Heh, na verdade nós dois estamos errados. Você está assumindo que comp*xserá ampliado xpor comp, eu estava assumindo que iria levantar um TypeError. Infelizmente, ele realmente irá concatenar xcom o próprio compvezes. Opa.
Katriel
meh. se xfor um vetor, então ele deve ter um __rmul__método ( __rmul__ = __mul__) para que comp * xseja escalonado xda mesma forma que x * compaparentemente se pretende.
aaronasterling
0

Eu tive um problema semelhante, ao implementar uma espécie de classe de vetor. Uma maneira de verificar um número é apenas convertê-lo em um, ou seja, usando

float(x)

Isso deve rejeitar os casos em que x não pode ser convertido em um número; mas também pode rejeitar outros tipos de estruturas semelhantes a números que podem ser válidas, por exemplo, números complexos.

Ant6n
fonte
0

Se você quiser chamar métodos diferentes dependendo do (s) tipo (s) de argumento (s), examine multipledispatch.

Por exemplo, digamos que você esteja escrevendo uma classe de vetor. Se for dado outro vetor, você deseja encontrar o produto escalar. Se for fornecido um escalar, você deseja dimensionar todo o vetor.

from multipledispatch import dispatch

class Vector(list):

    @dispatch(object)
    def __mul__(self, scalar):
        return Vector( x*scalar for x in self)

    @dispatch(list)
    def __mul__(self, other):
        return sum(x*y for x,y in zip(self, other))


>>> Vector([1,2,3]) * Vector([2,4,5])   # Vector time Vector is dot product
25
>>> Vector([1,2,3]) * 2                 # Vector times scalar is scaling
[2, 4, 6]

Infelizmente, (até onde sei) não podemos escrever, @dispatch(Vector)pois ainda estamos definindo o tipo Vector, de modo que o nome do tipo ainda não foi definido. Em vez disso, estou usando o tipo base list, que permite até mesmo encontrar o produto escalar de a Vectore a list.

AJNeufeld
fonte
0

Forma curta e simples:

obj = 12345
print(isinstance(obj,int))

Resultado :

True

Se o objeto for uma string, 'False' será retornado:

obj = 'some string'
print(isinstance(obj,int))

Resultado :

False
Shekhar
fonte
0

Você tem um item de dados, digamos rec_dayque, quando gravado em um arquivo, haverá um float. Mas durante o programa de processamento pode ser float, intou strdigite (o stré usado ao inicializar um novo recorde e contém um valor bandeira simulado).

Você pode então verificar se você tem um número com este

                type(rec_day) != str 

Estruturei um programa Python dessa maneira e apenas coloquei um 'patch de manutenção' usando isso como uma verificação numérica. É a maneira pitônica? Provavelmente não, já que eu costumava programar em COBOL.

CopyPasteIt
fonte
-1

Você pode usar a função isdigit ().

>>> x = "01234"
>>> a.isdigit()
True
>>> y = "1234abcd"
>>> y.isdigit()
False
rsy
fonte
"01234" não é um número, é uma sequência de caracteres.
Alexey de