web-dev-qa-db-fra.com

Quelle est la façon la plus Pythonique de vérifier si un objet est un nombre?

Étant donné un objet arbitraire python, quelle est la meilleure façon de déterminer s'il s'agit d'un nombre? Ici is est défini comme acts like a number in certain circumstances.

Par exemple, supposons que vous écrivez une classe vectorielle. Si on vous donne un autre vecteur, vous voulez trouver le produit scalaire. Si on lui donne un scalaire, vous voulez mettre à l'échelle le vecteur entier.

Vérifier si quelque chose est int, float, long, bool est ennuyeux et ne couvre pas les objets définis par l'utilisateur qui pourraient agir comme des nombres. Mais, en vérifiant __mul__, par exemple, n'est pas suffisant car la classe de vecteurs que je viens de décrire définirait __mul__, mais ce ne serait pas le genre de numéro que je veux.

100
Claudiu

Utilisez Number du module numbers pour 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 '%15s %s' % (n.__repr__(), 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 à la frappe de 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 numéro et utilisez des exceptions pour vous dire le contraire.

120
Steven Rumbalski

Vous voulez vérifier si un objet

agit comme un numéro dans certaines circonstances

Si vous utilisez Python 2.5 ou plus ancien, le seul vrai moyen est de vérifier certaines de ces "certaines circonstances" et de voir.

Dans 2.6 ou mieux, vous pouvez utiliser isinstance avec nombres.Nombre - une classe de base abstraite (ABC) qui existe exactement à cette fin (beaucoup plus d'ABC existent dans le collections module pour diverses formes de collections/conteneurs, commençant à nouveau par 2.6; et, uniquement dans ces versions, vous pouvez facilement ajouter vos propres classes de base abstraites si vous en avez besoin).

Bach à 2.5 et versions antérieures, "peut être ajouté à 0 Et 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 doit absolument être incapable 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 qui n'ont pas déjà été enregistrés sur numbers.Numbers - si vous voulez exclure certains types qui prétendent être des nombres mais que vous ne pouvez tout simplement pas gérer, cela demande encore plus de soin, car les ABC n'ont pas de méthode unregister [[par exemple, vous pouvez créer votre propre ABC WeirdNum et enregistrez-y tous ces types bizarres pour vous, puis vérifiez d'abord isinstance pour renflouer avant de procéder à la vérification de isinstance du code normal numbers.Number pour continuer avec succès.

BTW, si et quand vous devez vérifier si x peut ou ne peut 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 __add__ En soi ne vous dit rien d'utile, car par exemple, toutes les séquences l'ont pour des fins de concaténation avec d'autres séquences. Cette vérification est équivalente à la définition "un nombre est quelque chose de tel qu'une séquence de telles choses est un seul argument valide pour la fonction intégrée sum", par exemple. Les types totalement bizarres (par exemple ceux qui déclenchent la "mauvaise" exception lorsqu'ils sont additionnés à 0, comme, par exemple, un ZeroDivisionError ou ValueError & c) propagent une exception, mais c'est OK, laissez l'utilisateur sachez dès que possible que de tels types fous ne sont tout simplement pas acceptables en bonne compagnie ;-); mais, un "vecteur" qui peut être sommé à 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 le mauvais résultat ici, donc (par exemple) cette vérification devrait venir = après le "non autorisé à être itérable" (par exemple, vérifiez que iter(x) lè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 avez donc besoin de vos propres chèques).

Un bref aperçu de ces complications peut être suffisant pour vous motiver à vous fier plutôt aux classes de base abstraites chaque fois que possible ... ;-).

30
Alex Martelli

C'est un bon exemple où les exceptions brillent vraiment. Faites simplement ce que vous feriez avec les types numériques et récupérez le TypeError de tout le reste.

Mais évidemment, cela vérifie seulement 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 vos valeurs appartiennent.

16
Jochen Ritzel

Multipliez l'objet par zéro. Tout nombre multiplié par zéro est égal à 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 donc 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()))

Sortie:

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 existe probablement des objets non numériques dans le monde qui définissent __mul__ pour retourner zéro lorsqu'il est multiplié 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 avec 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()))

sortie:

False == isNumber([0 1])
4
shrewmouse

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 compare 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 voulez savoir combien vous en avez (1 ou plus), pas quel type vous avez réellement.

ma solution pour 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 d'abord essayer d'itérer sur foo:

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.), 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 en premier lieu?

3
Gordon Bean

Il vaut probablement mieux le faire dans l'autre sens: vous vérifiez s'il s'agit d'un vecteur. Si c'est le cas, vous faites un produit scalaire et dans tous les autres cas, vous tentez une multiplication scalaire.

La vérification du vecteur est facile, car il doit être de votre type de classe de vecteur (ou hérité de celui-ci). Vous pouvez également essayer d'abord de faire un produit scalaire, et si cela échoue (= ce n'était pas vraiment un vecteur), puis retomber dans la multiplication scalaire.

2
sth

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))
2
Martin Thoma

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

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

1
shadab.tughlaq

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

Par exemple, supposons que vous écrivez une classe vectorielle. Si on vous donne un autre vecteur, vous voulez trouver le produit scalaire. Si on lui donne un scalaire, vous voulez 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 définissons toujours 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 d'un Vector et d'un list.

0
AJNeufeld

Pour la classe vectorielle hypothétique:

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

MODIFIER - le code ci-dessous ne fonctionne pas, car 2*[0]==[0,0] au lieu de lever un TypeError. Je le laisse car 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 )
0
Katriel

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 numérique qui pourraient être valides, par exemple des nombres complexes.

0
Ant6n