Quelle est la manière la plus pythonique de vérifier si un objet est un nombre?

114

Étant donné un objet Python arbitraire, quel est le meilleur moyen de déterminer s'il s'agit d'un nombre? Ici isest défini comme acts like a number in certain circumstances.

Par exemple, disons que vous écrivez une classe vectorielle. Si un autre vecteur est donné, vous souhaitez trouver le produit scalaire. Si un scalaire est donné, vous souhaitez mettre à l'échelle le vecteur entier.

Vérifier si quelque chose est int, float, long, boolest ennuyeux et ne couvre pas les objets définis par l' utilisateur qui peuvent agir comme des numéros. Mais la vérification __mul__, par exemple, n'est pas suffisante car la classe vectorielle que je viens de décrire définirait __mul__, mais ce ne serait pas le type de nombre que je veux.

Claudiu
la source

Réponses:

135

Utilisation Numberdepuis le numbersmodule à tester isinstance(n, Number)(disponible depuis 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

Ceci est, bien sûr, contraire au typage du canard. Si vous êtes plus préoccupé par la façon dont un objet agit plutôt que par ce qu'il est , effectuez vos opérations comme si vous aviez un nombre et utilisez des exceptions pour vous dire le contraire.

Steven Rumbalski
la source
3
Faire la chose intelligente, plutôt que la chose de canard, est préféré lorsque vous multiplier un vecteur par X. Dans ce cas , vous voulez faire des choses différentes en fonction de ce que X est . (Cela pourrait agir comme quelque chose qui se multiplie, mais le résultat pourrait être insensé.)
Evgeni Sergeev
3
cette réponse donne dirait que True est un nombre .. ce qui n'est probablement pas toujours ce que vous voulez. Pour exclure les booléens (pensez validation fe), je diraisisinstance(value, Number) and type(value) != bool
Yo Ludke
32

Vous voulez vérifier si un objet

agit comme un nombre dans certaines circonstances

Si vous utilisez Python 2.5 ou une version antérieure, le seul vrai moyen est de vérifier certaines de ces «circonstances» et de voir.

Dans la version 2.6 ou supérieure, vous pouvez utiliser isinstanceavec des nombres.Number - une classe de base abstraite (ABC) qui existe exactement à cette fin (beaucoup plus d'ABC existent dans lecollections module pour diverses formes de collections / conteneurs, commençant à nouveau par 2.6; et, également uniquement dans ces versions, vous pouvez facilement ajouter vos propres classes de base abstraites si vous en avez besoin).

Bach à 2.5 et plus tôt, "peut être ajouté 0et n'est pas itérable" pourrait être une bonne définition dans certains cas. Mais, vous devez vraiment vous demander ce que vous demandez que ce que vous voulez considérer comme "un nombre" doit certainement pouvoir faire , et ce qu'il ne doit absolument pas être de faire - et de vérifier.

Cela peut également être nécessaire dans la version 2.6 ou ultérieure, peut-être dans le but de faire vos propres enregistrements pour ajouter des types qui vous intéressent et qui ne sont pas déjà enregistrés numbers.Numbers- si vous souhaitez exclure certains types qui prétendent être des numéros, mais vous ne peut tout simplement pas gérer, cela prend encore plus de soin, car les ABC n'ont pas de unregisterméthode [[par exemple, vous pouvez créer votre propre ABC WeirdNumet y enregistrer tous ces types bizarres pour vous, puis vérifiez d'abord si cela vous permet isinstancede renflouer avant de continuer à la vérification isinstancede la normale numbers.Numberpour continuer avec succès.

BTW, si et quand vous avez besoin de vérifier si vous xpouvez ou ne pouvez pas faire quelque chose, vous devez généralement essayer quelque chose comme:

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

La présence de en __add__soi ne vous dit rien d'utile, puisque par exemple toutes les séquences l'ont dans le but de concaténer avec d'autres séquences. Cette vérification équivaut à la définition «un nombre est quelque chose de tel qu'une séquence de telles choses est un argument unique valide pour la fonction intégrée sum», par exemple. Les types totalement étranges (par exemple ceux qui lèvent la "mauvaise" exception lorsqu'ils sont sommés à 0, comme, par exemple, a ZeroDivisionErrorou ValueError& c) propageront l'exception, mais c'est OK, faites savoir à l'utilisateur dès que possible que ces types fous ne sont tout simplement pas acceptables en bon compagnie;-); mais, un "vecteur" qui peut être sommé en un scalaire (la bibliothèque standard de Python n'en a pas, mais bien sûr, ils sont populaires en tant qu'extensions tierces) donnerait également un résultat erroné ici, donc (par exemplecelui «non autorisé à être itérable» (par exemple, chèque qui iter(x)soulève TypeError, ou pour la présence d'une méthode spéciale __iter__- si vous êtes en version 2.5 ou antérieure et que vous avez donc besoin de vos propres vérifications).

Un bref aperçu de ces complications peut être suffisant pour vous motiver à vous fier à la place à des classes de base abstraites chaque fois que c'est possible ... ;-).

Alex Martelli
la source
Mais il y a un ABC pour Number dans le module des nombres. C'est ce que prétendent les documents: "Le module de nombres (PEP 3141) définit une hiérarchie de classes de base abstraites numériques qui définissent progressivement plus d'opérations."
Steven Rumbalski
17

C'est un bon exemple où les exceptions brillent vraiment. Faites simplement ce que vous feriez avec les types numériques et attrapez TypeErrortout le reste.

Mais évidemment, cela ne vérifie que si une opération fonctionne , pas si elle a du sens ! La seule vraie solution pour cela est de ne jamais mélanger les types et de toujours savoir exactement à quelle classe de types appartiennent vos valeurs.

Jochen Ritzel
la source
1
+1 pour Duck Typing: peu importe le type de mes données, que je puisse ou non en faire ce que je veux.
systempuntoout
12
C'était l'approche traditionnelle, mais les ABC ont été introduits en grande partie pour s'éloigner du typage purement canard et se déplacer vers un monde où il isinstancepeut être utile dans de nombreux cas (== "check it makes sense" ainsi que l'applicabilité formelle des opérations). Changement difficile pour les personnes de longue date uniquement Python, mais une tendance subtile très importante dans la philosophie de Python selon laquelle ce serait une grave erreur d'ignorer.
Alex Martelli
@Alex: C'est vrai et j'adore les typeclasses (surtout collections.Sequenceet les amis). Mais afaik, il n'y a pas de telles classes pour les nombres, les vecteurs ou tout autre objet mathématique.
Jochen Ritzel
1
Rien contre la frappe de canard. C'est ce que je ferais. Mais il existe une classe de base abstraite pour les nombres: les nombres.
Steven Rumbalski
4

Multipliez l'objet par zéro. Tout nombre multiplié par zéro vaut zéro. Tout autre résultat signifie que l'objet n'est pas un nombre (y compris les exceptions)

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

L'utilisation de isNumber donnera ainsi la sortie suivante:

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

Production:

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)

Il y a probablement des objets non numériques dans le monde qui définissent __mul__de renvoyer zéro lorsqu'ils sont multipliés par zéro, mais c'est une exception extrême. Cette solution devrait couvrir tout le code normal et sain que vous générez / encouter.

Exemple de 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()))

production:

False == isNumber([0 1])
musaraigne
la source
5
True * 0 == 0
endolith
4
Votre fonction dira à tort que les booléens sont des nombres
endolith
1
@endolith, les booléens agissent exactement comme des nombres. Vrai toujours == 1 et Faux toujours == 0. C'est exactement ce que l'interrogateur a demandé, "Ici," est "est défini comme" agit comme un nombre dans certaines circonstances "."
shrewmouse
1
@endolith, En fait, les booléens sont des nombres. Boolean dérive intdonc ma fonction dira correctement que les booléens sont des nombres.
shrewmouse
1
@NicolasAbril, convertissez 0 * x == 0 en booléen dans isNumber.
shrewmouse
3

Pour reformuler votre question, vous essayez de déterminer si quelque chose est une collection ou une valeur unique. Essayer de comparer si quelque chose est un vecteur ou un nombre, c'est comparer des pommes à des oranges - je peux avoir un vecteur de chaînes ou de nombres, et je peux avoir une seule chaîne ou un seul nombre. Vous êtes intéressé par le nombre que vous en avez (1 ou plus) , pas par le type que vous avez réellement.

ma solution à ce problème est de vérifier si l'entrée est une valeur unique ou une collection en vérifiant la présence de __len__. Par exemple:

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, pour l'approche de type canard, vous pouvez essayer d'itérer d' fooabord:

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]

En fin de compte, il est plus facile de tester si quelque chose est de type vectoriel que de tester si quelque chose est de type scalaire. Si vous avez des valeurs de type différent (c.-à-d. Chaîne, numérique, etc.), alors la logique de votre programme peut nécessiter un peu de travail - comment avez-vous fini par essayer de multiplier une chaîne par un vecteur numérique?

Gordon Bean
la source
3

Pour résumer / évaluer les méthodes existantes:

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

(Je suis venu ici par cette question )

Code

#!/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
la source
TODO pour moi:, float('nan'), 'nan', '123.45', '42', '42a', '0x8', '0xa'ajoutermath.isnan
Martin Thoma
2

Il vaut probablement mieux faire l'inverse: vous vérifiez si c'est un vecteur. Si c'est le cas, vous effectuez un produit scalaire et dans tous les autres cas, vous tentez une multiplication scalaire.

Vérifier le vecteur est facile, car il doit être de votre type de classe de vecteur (ou hérité de celui-ci). Vous pouvez également simplement essayer d'abord de faire un produit scalaire, et si cela échoue (= ce n'était pas vraiment un vecteur), alors retombez à la multiplication scalaire.

qc
la source
1

Juste pour ajouter. Peut-être pouvons-nous utiliser une combinaison de isinstance et isdigit comme suit pour déterminer si une valeur est un nombre (int, float, etc.)

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

shadab.tughlaq
la source
0

Pour la classe vectorielle hypothétique:

Supposons que ce vsoit un vecteur, et nous le multiplions par x. S'il est logique de multiplier chaque composant de vpar x, c'est probablement ce que nous voulions dire, alors essayez d'abord. Sinon, peut-être pouvons-nous dot? Sinon, c'est une erreur de type.

EDIT - le code ci-dessous ne fonctionne pas, car 2*[0]==[0,0]au lieu de lever un fichier TypeError. Je le laisse parce qu'il a été commenté.

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
la source
si xest un vecteur, alors [comp * x for comp in self]donnera le produit externe de xan v. Ceci est un tenseur de rang 2, pas un scalaire.
aaronasterling
remplacez "pas un scalaire" par "pas un vecteur". du moins pas dans l'espace vectoriel d'origine.
aaronasterling
Heh, en fait nous avons tous les deux tort. Vous supposez que comp*xsera l' échelle xpar comp, je suppose qu'il soulèverait un TypeError. Malheureusement, il concaténera xavec lui-même les comptemps. Oups.
Katriel
meh. si xest un vecteur, alors il devrait avoir une __rmul__méthode ( __rmul__ = __mul__) de sorte qu'il comp * xdevrait être mis à l'échelle xde la même manière qui x * compest apparemment prévue.
aaronasterling
0

J'ai eu un problème similaire lors de l'implémentation d'une sorte de classe vectorielle. Une façon de vérifier un nombre est de simplement le convertir en un, c'est-à-dire en utilisant

float(x)

Cela devrait rejeter les cas où x ne peut pas être converti en nombre; mais peut également rejeter d'autres types de structures de type nombre qui pourraient être valides, par exemple des nombres complexes.

Ant6n
la source
0

Si vous souhaitez appeler différentes méthodes en fonction du ou des types d'argument, regardez dans multipledispatch.

Par exemple, disons que vous écrivez une classe vectorielle. Si un autre vecteur est donné, vous souhaitez trouver le produit scalaire. Si un scalaire est donné, vous souhaitez mettre à l'échelle le vecteur entier.

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]

Malheureusement, (à ma connaissance) nous ne pouvons pas écrire @dispatch(Vector)car nous sommes encore en train de définir le type Vector, donc ce nom de type n'est pas encore défini. Au lieu de cela, j'utilise le type de base list, qui vous permet même de trouver le produit scalaire de a Vectoret a list.

AJNeufeld
la source
0

Voie courte et simple:

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

Production :

True

Si l'objet est une chaîne, 'False' sera renvoyé:

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

Production :

False
Shekhar
la source
0

Vous avez un élément de données, dites rec_dayque lorsqu'il est écrit dans un fichier sera un fichier float. Mais pendant le traitement du programme, il peut être soit float, intsoit de strtype (le strest utilisé lors de l'initialisation d'un nouvel enregistrement et contient une valeur d'indicateur factice).

Vous pouvez ensuite vérifier si vous avez un numéro avec ce

                type(rec_day) != str 

J'ai structuré un programme python de cette façon et je viens de mettre un `` correctif de maintenance '' en l'utilisant comme une vérification numérique. Est-ce la voie pythonique? Probablement pas depuis que je programmais en COBOL.

CopyPasteIt
la source
-1

Vous pouvez utiliser la fonction isdigit ().

>>> x = "01234"
>>> a.isdigit()
True
>>> y = "1234abcd"
>>> y.isdigit()
False
rsy
la source
"01234" n'est pas un nombre, c'est une chaîne de caractères.
Alexey le