Який найбільш пітонічний спосіб перевірити, чи є об’єкт числом?


114

З огляду на довільний об’єкт пітона, який найкращий спосіб визначити, чи є це число? Тут isвизначено як acts like a number in certain circumstances.

Наприклад, скажіть, що ви пишете векторний клас. Якщо вам надано інший вектор, потрібно знайти крапковий добуток. Якщо вам дано скаляр, ви хочете масштабувати весь вектор.

Перевірка якщо що - то int, float, long, boolдратує і не поширюється на певні користувачем об'єкти , які можуть діяти як числа. __mul__Наприклад , перевірка , наприклад, недостатньо хороша, тому що векторний клас, який я тільки що описав, визначав би __mul__, але це не було б таким числом, яке я хочу.

Відповіді:


135

Використовуйте Numberз numbersмодуля тестування isinstance(n, Number)(доступно починаючи з 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

Це, звичайно, суперечить набору качок. Якщо вас більше турбує, як об’єкт діє, а не який він є , виконайте свої операції так, ніби у вас є номер, і використовуйте винятки, щоб сказати вам інакше.


3
Роблячи розумні речі, а не качка речей, є кращою , коли ви множення вектора на X. В цьому випадку , якщо ви хочете , щоб робити різні речі , грунтуючись на тому, що X є . (Це може діяти як щось, що розмножується, але результат може бути безглуздим.)
Євгеній Сергєєв,

3
ця відповідь дає би сказати, що «Істинне» - це число, яке, мабуть, не завжди є тим, що ви хочете. Для висловлення буленів (думаю, валідація fe) я б сказавisinstance(value, Number) and type(value) != bool
Йо Людке

32

Ви хочете перевірити, чи є якийсь об’єкт

діє як число у певних обставинах

Якщо ви використовуєте Python 2.5 або новіші, єдиний реальний спосіб - перевірити деякі з цих "певних обставин" і побачити.

В 2.6 або вище, ви можете використовувати isinstanceз numbers.Number - це абстрактний базовий клас (ABC) , яка існує саме для цієї мети (існує багато більше ABCs в collectionsмодулі для різних форм колекцій / контейнерів, знову ж, починаючи з 2.6, і, також лише в цих випусках ви можете легко додати свої власні абстрактні базові класи, якщо вам потрібно).

Бах до 0версії 2.5 і раніше "може бути доданий і не є ітерабельним", може бути хорошим визначенням у деяких випадках. Але вам справді потрібно запитати себе, що це таке, що ви запитуєте, що те, що ви хочете вважати "числом", безумовно, має бути в змозі зробити , а що воно абсолютно не може зробити - і перевірити.

Це також може знадобитися через 2.6 або пізнішої версії, можливо, для того, щоб зробити власні реєстрації, щоб додати важливі для вас типи, які ще не були зареєстровані numbers.Numbers- якщо ви хочете виключити деякі типи, які стверджують, що вони номери, але ви просто не вдається впоратися, що вимагає ще більшої обережності, оскільки ABC не має unregisterметоду [[наприклад, ви можете зробити свій власний ABC WeirdNumі зареєструвати там усі такі дивні для вас типи, а потім спочатку перевірте, чи isinstanceне виходить це, перш ніж продовжувати щоб перевірити isinstanceнормальність, numbers.Numberщоб продовжувати успішно.

До речі, якщо і коли вам потрібно перевірити, чи xможете чи не можете щось зробити, зазвичай вам потрібно спробувати щось на кшталт:

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

Присутність __add__сама по собі не дає нічого корисного, оскільки, наприклад, всі послідовності мають це для об'єднання з іншими послідовностями. Ця перевірка еквівалентна визначенню "число є чимось таким, що послідовність таких речей є дійсним єдиним аргументом вбудованої функції sum", наприклад. Цілком дивні типи (наприклад, ті, які підвищують "неправильний" виняток, коли їх підсумовують до 0, наприклад, a ZeroDivisionErrorабо ValueErrorc), поширюватимуть виключення, але це нормально, нехай користувач знає якомога швидше, що такі божевільні типи просто не прийнятні компанія ;-); але "вектор", який підсумовується скаляру (стандартна бібліотека Python не має такого, але, звичайно, вони популярні як розширення сторонніх розробників), тут також дасть неправильний результат, так (наприклад,"не дозволено бути ітерабельним" (наприклад, перевірити, що iter(x)підвищується TypeError, або на наявність спеціального методу __iter__- якщо ви перебуваєте у версії 2.5 чи раніше і тому потрібні ваші власні перевірки).

Короткий огляд таких ускладнень може бути достатнім, щоб спонукати вас покладатися на абстрактні базові класи, коли це можливо ... ;-).


Але в модулі чисел є ABC for Number. Ось що стверджують документи: "Модуль чисел (PEP 3141) визначає ієрархію числових абстрактних базових класів, які прогресивно визначають більше операцій."
Стівен Румбальський

17

Це хороший приклад, коли винятки справді світяться. Просто робіть те, що ви зробили б із числовими типами, і виловлюйте їх TypeErrorіз усього іншого.

Але очевидно, що це тільки перевіряє , є чи операція по роботам , які не є це має сенс ! Єдине реальне рішення для цього - ніколи не змішувати типи і завжди точно знати, до якого типу класів належать ваші значення.


1
+1 для Duck Typing: не важливо, для якого типу мої дані, а лише чи можу я з ними робити те, що хочу.
systempuntoout

12
Це був традиційний підхід, але ABC були введені в хорошій частині, щоб відійти від чистого набору качок і перенести деяку відстань у світ, де isinstanceнасправді може бути корисним у багатьох випадках (== "перевірити це має сенс", а також формальної застосовності операцій). Складний зсув для давніх людей, які працюють лише в Python, але дуже важлива тонка тенденція філософії Python, що ігнорувати було б серйозною помилкою.
Алекс Мартеллі

@Alex: Правда, і я люблю машинологічні класи (в основному collections.Sequenceі друзі). Але afaik, таких класів для чисел, векторів чи будь-яких інших математичних об'єктів немає.
Йохен Рітцель

1
Нічого проти набору качок. Це те, що я би робив. Але є абстрактний базовий клас для чисел: numbers.Number.
Стівен Румбальський

4

Помножте об’єкт на нуль. Будь-яке число, що дорівнює нулю, дорівнює нулю. Будь-який інший результат означає, що об'єкт не є числом (включаючи винятки)

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

Використання isNumber таким чином дасть такий вихід:

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

Вихід:

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)

Напевно, у світі є деякі об'єкти, що не мають чисел, які визначають __mul__повернути нуль при множенні на нуль, але це крайній виняток. Це рішення повинно охоплювати всі нормальні та здорові коди, які ви створюєте / сприяєте.

Приклад 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()))

вихід:

False == isNumber([0 1])

5
True * 0 == 0
ендоліт

4
Ваша функція буде неправильно сказати , що булеві є цифри
ендоліти

1
@endolith, булеви діють точно так само, як числа. Істинно завжди == 1 і Неправдиво завжди == 0. Це саме запитуючий запитував: "Тут" є "визначається як" діє як число за певних обставин ".
кривошик

1
@endolith, насправді булеви - це числа. Булевий походить від intтого, що моя функція правильно скаже, що булеві числа - це числа.
кривошик

1
@NicolasAbril, конвертуйте 0 * x == 0 у bool всередині isNumber.
кривошик

3

Перефразовуючи своє запитання, ви намагаєтеся визначити, чи є щось колекцією чи єдиним значенням. Намагаючись порівняти, чи є вектор чи число, порівнюємо яблука з апельсинами - у мене може бути векторний рядок чи цифри, і я можу мати один рядок або одне число. Вас цікавить, скільки у вас є (1 або більше) , а не який тип у вас є насправді.

моє рішення цієї проблеми полягає в тому, щоб перевірити, чи є вхід єдиним значенням або колекцією, перевіривши наявність __len__. Наприклад:

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]

Або для підходу набору качки ви можете спробувати ітерацію 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]

Зрештою, простіше перевірити, чи щось є векторне, ніж тестувати, чи щось схоже на скаляр. Якщо у вас надходять значення різного типу (тобто рядкові, числові тощо), то логіці вашої програми може знадобитися певна робота - як ви в першу чергу намагалися перемножити рядок на числовий вектор?


3

Для узагальнення / оцінки існуючих методів:

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

(Я прийшов сюди з цього питання )

Код

#!/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))

TODO для себе:, float('nan'), 'nan', '123.45', '42', '42a', '0x8', '0xa'додамоmath.isnan
Мартін Тома

2

Напевно, краще просто зробити це навпаки: ви перевіряєте, чи це вектор. Якщо це так, ви робите крапковий продукт і в усіх інших випадках ви намагаєтеся скалярне множення.

Перевірка на вектор легко, оскільки він повинен бути у вашому типі векторного класу (або успадкований від нього). Ви також можете спробувати спершу зробити точковий продукт, і якщо це не вдасться (= це був насправді не вектор), потім повернутися до скалярного множення.


1

Просто додати. Можливо, ми можемо використати комбінацію isin substance та isdigit так, щоб дізнатися, чи є значення числом (int, float тощо)

якщо isin substance (num1, int) або isin substance (num1, float) або num1.isdigit ():


0

Для гіпотетичного векторного класу:

Припустимо v, це вектор, і ми його множимо на x. Якщо є сенс перемножувати кожен компонент vна x, ми, мабуть, мали на увазі це, тому спробуйте це спочатку. Якщо ні, може, ми можемо розставити крапки? Інакше це помилка типу.

EDIT - наведений нижче код не працює, тому що 2*[0]==[0,0]замість підвищення а TypeError. Я залишаю це, оскільки це було прокоментовано.

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 )

якщо xвектор потім [comp * x for comp in self]дасть зовнішній продукт . Це тензор 2 рангу, а не скаляр. xv
aaronasterling

змінити "не скаляр" на "не вектор". принаймні, не у вихідному векторному просторі.
aaronasterling

Хе, насправді ми обидва помиляємось. Ви припускаєте , що comp*xбуде масштабироваться xшляхом comp, я припускав , що це підніме TypeError. На жаль, це фактично поєднується xіз самими compчасом. На жаль
Катріель

мех. якщо xце вектор, то він повинен мати __rmul__метод ( __rmul__ = __mul__), так що він comp * xповинен масштабуватись xтак само, x * compяк, очевидно, призначено.
aaronasterling

0

У мене була схожа проблема, коли реалізували свого роду векторний клас. Один із способів перевірити число - це просто перетворити на одне, тобто за допомогою

float(x)

Це повинно відхилити випадки, коли х не можна перетворити на число; але може також відхиляти інші типи числових структур, які могли б бути дійсними, наприклад, складні числа.


0

Якщо ви хочете викликати різні методи залежно від типу аргументів, перегляньте multipledispatch.

Наприклад, скажіть, що ви пишете векторний клас. Якщо вам надано інший вектор, потрібно знайти крапковий добуток. Якщо вам дано скаляр, ви хочете масштабувати весь вектор.

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]

На жаль, (наскільки мені відомо) ми не можемо писати, @dispatch(Vector)оскільки ми все ще визначаємо тип Vector, тому назва типу ще не визначена. Натомість я використовую базовий тип list, що дозволяє вам навіть знайти точковий добуток a Vectorі a list.


0

Короткий і простий спосіб:

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

Вихід:

True

Якщо об'єктом є рядок, буде повернуто значення "False":

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

Вихід:

False

0

У вас є елемент даних, скажіть, rec_dayщо при написанні у файл буде а float. Але під час обробки програми він може бути або вводити float, intабо strвводити (the strвикористовується при ініціалізації нового запису та містить фіктивне значення прапора).

Потім ви можете перевірити, чи є у вас номер із цим

                type(rec_day) != str 

Я структурував програму python таким чином і просто поставив "патч технічного обслуговування", використовуючи це в якості числової перевірки. Це пітонічний шлях? Швидше за все, не з тих пір, як я програвав у COBOL.


-1

Ви можете використовувати функцію isdigit ().

>>> x = "01234"
>>> a.isdigit()
True
>>> y = "1234abcd"
>>> y.isdigit()
False

"01234" - це не число, це символьний рядок.
Олексій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.