Cách khó hiểu nhất để kiểm tra xem một đối tượng có phải là một con số không?


114

Với một đối tượng python tùy ý, cách tốt nhất để xác định xem nó có phải là một số hay không? Ở đây isđược định nghĩa là acts like a number in certain circumstances.

Ví dụ: giả sử bạn đang viết một lớp vectơ. Nếu cho một vectơ khác, bạn muốn tìm tích số chấm. Nếu cho một đại lượng vô hướng, bạn muốn mở rộng toàn bộ vectơ.

Kiểm tra nếu có điều gì là int, float, long, boollà gây phiền nhiễu và không bao gồm đối tượng người dùng định nghĩa có thể hành động như con số. Nhưng, __mul__ví dụ , kiểm tra không đủ tốt vì lớp vectơ mà tôi vừa mô tả sẽ xác định __mul__, nhưng nó sẽ không phải là loại số tôi muốn.

Câu trả lời:


135

Sử dụng Numbertừ numbersmô-đun để kiểm tra isinstance(n, Number)(khả dụng từ 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

Tất nhiên, điều này trái ngược với kiểu gõ vịt. Nếu bạn lo lắng về cách một đối tượng hoạt động hơn là nó gì , hãy thực hiện các thao tác của bạn như thể bạn có một số và sử dụng các ngoại lệ để cho bạn biết cách khác.


3
Làm điều thông minh, chứ không phải là điều con vịt, được ưa chuộng khi bạn nhân một vector của X. Trong trường hợp này bạn muốn làm những điều khác nhau dựa trên những gì X . (Nó có thể hoạt động như một cái gì đó nhân lên, nhưng kết quả có thể là vô nghĩa.)
Evgeni Sergeev

3
câu trả lời này cho biết True là một con số .. mà có lẽ không phải lúc nào bạn cũng muốn. Để loại bỏ các phép toán luận (suy nghĩ xác nhận fe), tôi sẽ nóiisinstance(value, Number) and type(value) != bool
Yo Ludke

32

Bạn muốn kiểm tra xem một số đối tượng

hoạt động giống như một số trong những trường hợp nhất định

Nếu bạn đang sử dụng Python 2.5 trở lên, cách thực sự duy nhất là kiểm tra một số "trường hợp nhất định" và xem.

Trong phiên bản 2.6 hoặc cao hơn, bạn có thể sử dụng isinstancevới các số. Số - một lớp cơ sở trừu tượng (ABC) tồn tại chính xác cho mục đích này (có nhiều ABC hơn tồn tại trong collectionsmô-đun cho các dạng bộ sưu tập / vùng chứa khác nhau, bắt đầu lại bằng 2,6; và, cũng chỉ trong các bản phát hành đó, bạn có thể dễ dàng thêm các lớp cơ sở trừu tượng của riêng mình nếu cần).

Bach đến 2.5 trở về trước, "có thể thêm vào 0và không thể lặp lại" có thể là một định nghĩa tốt trong một số trường hợp. Tuy nhiên, bạn thực sự cần phải tự hỏi bản thân rằng bạn đang hỏi điều gì mà bạn muốn coi là "một con số" chắc chắn phải làm được và những gì nó hoàn toàn không thể làm được - và kiểm tra.

Điều này cũng có thể cần thiết trong phiên bản 2.6 trở lên, có thể nhằm mục đích tạo đăng ký của riêng bạn để thêm các loại bạn quan tâm mà chưa được đăng ký numbers.Numbers- nếu bạn muốn loại trừ một số loại cho rằng chúng là số nhưng bạn chỉ là không thể xử lý, điều đó thậm chí còn cần phải cẩn thận hơn, vì ABC không có unregisterphương pháp nào [[ví dụ: bạn có thể tạo ABC của riêng mình WeirdNumvà đăng ký ở đó tất cả các loại kỳ lạ dành cho bạn, sau đó trước tiên hãy kiểm tra isinstancechúng để bảo lãnh trước khi bạn tiếp tục để kiểm tra isinstancebình thường numbers.Numberđể tiếp tục thành công.

BTW, nếu và khi bạn cần kiểm tra xem xcó thể hoặc không thể làm điều gì đó, bạn thường phải thử một cái gì đó như:

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

Sự hiện diện của "không được phép lặp lại" (ví dụ: kiểm tra tăng__add__ per se cho bạn biết không có gì hữu ích, vì ví dụ: tất cả các chuỗi đều có nó cho mục đích nối với các chuỗi khác. Việc kiểm tra này tương đương với định nghĩa "một số là một cái gì đó sao cho một chuỗi của những thứ như vậy là một đối số đơn hợp lệ cho hàm nội trang sum", chẳng hạn. Các kiểu hoàn toàn kỳ lạ (ví dụ: những kiểu tăng ngoại lệ "sai" khi được tổng hợp thành 0, chẳng hạn như, giả sử, a ZeroDivisionErrorhoặc ValueError& c) sẽ phổ biến ngoại lệ, nhưng không sao, hãy cho người dùng biết càng sớm càng tốt rằng những kiểu điên rồ như vậy không được chấp nhận một cách tốt Công ty;-); nhưng, một "vectơ" có thể tổng hợp thành một đại lượng vô hướng (thư viện tiêu chuẩn của Python không có, nhưng tất nhiên chúng phổ biến như là phần mở rộng của bên thứ ba) cũng sẽ đưa ra kết quả sai ở đây, vì vậy (ví dụ:iter(x)TypeError hoặc cho sự hiện diện của phương thức đặc biệt __iter__- nếu bạn đang ở 2.5 hoặc sớm hơn và do đó cần kiểm tra của riêng bạn).

Một cái nhìn thoáng qua về các biến chứng như vậy có thể đủ để thúc đẩy bạn thay vào đó dựa vào các lớp cơ sở trừu tượng bất cứ khi nào khả thi ... ;-).


Nhưng có một ABC cho Số trong mô-đun số. Đó là những gì các tài liệu tuyên bố: "Mô-đun số (PEP 3141) xác định một hệ thống phân cấp của các lớp cơ sở trừu tượng số, dần dần xác định nhiều hoạt động hơn."
Steven Rumbalski

17

Đây là một ví dụ điển hình nơi các ngoại lệ thực sự tỏa sáng. Chỉ cần làm những gì bạn sẽ làm với các loại số và bắtTypeError từ mọi thứ khác.

Nhưng rõ ràng, điều này chỉ kiểm tra xem một hoạt động có hoạt động hay không chứ không phải liệu nó có hợp lý hay không ! Giải pháp thực sự duy nhất cho điều đó là không bao giờ kết hợp các loại và luôn biết chính xác giá trị của bạn thuộc về loại phân loại nào.


1
+1 cho Duck Typing: không quan trọng loại dữ liệu của tôi là gì, chỉ là tôi có thể làm những gì tôi muốn với nó hay không.
systempuntoout

12
Đây là cách tiếp cận truyền thống, nhưng ABC đã được giới thiệu một phần tốt để thoát khỏi kiểu gõ vịt thuần túy và chuyển một số khoảng cách đến một thế giới nơi isinstancethực sự có thể hữu ích trong nhiều trường hợp (== "kiểm tra xem nó có ý nghĩa" cũng như khả năng áp dụng chính thức của hoạt động). Khó thay đổi đối với những người chỉ sử dụng Python lâu năm, nhưng một xu hướng tinh tế rất quan trọng trong triết lý của Python mà sẽ là một sai lầm nghiêm trọng nếu bỏ qua.
Alex Martelli

@Alex: Đúng và tôi yêu kính chữ (hầu hết là collections.Sequencevà bạn bè). Nhưng afaik, không có các lớp như vậy cho số, vectơ hoặc bất kỳ đối tượng toán học nào khác.
Jochen Ritzel

1
Không có gì chống lại việc gõ vịt. Đó là những gì tôi sẽ làm. Nhưng có một lớp cơ sở trừu tượng cho các số: number.Number.
Steven Rumbalski

4

Nhân đối tượng với không. Bất kỳ số nào nhân với số không là số không. Bất kỳ kết quả nào khác có nghĩa là đối tượng không phải là số (bao gồm cả các trường hợp ngoại lệ)

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

Do đó, sử dụng isNumber sẽ cho kết quả sau:

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

Đầu ra:

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)

Có thể có một số đối tượng không phải là số trên thế giới xác định __mul__trả về 0 khi nhân với 0 nhưng đó là một ngoại lệ cực đoan. Giải pháp này sẽ bao gồm tất cả mã bình thườnglành mạnh mà bạn tạo / encouter.

Ví dụ về 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()))

đầu ra:

False == isNumber([0 1])

5
True * 0 == 0
endolith

4
Chức năng của bạn sẽ sai nói rằng các phép toán luận là những con số
endolith

1
@endolith, Boolean hoạt động chính xác như các con số. True luôn luôn == 1 và False luôn == 0. Đây chính xác là câu hỏi mà người hỏi yêu cầu, "Here 'is' được định nghĩa là 'hoạt động giống như một số trong những trường hợp nhất định'."
shrewmouse, 27/07/17

1
@endolith, Trên thực tế, Boolean là những con số. Boolean bắt nguồn từ intvì vậy hàm của tôi sẽ nói chính xác rằng boolean là số.
shrewmouse, 27/07/17

1
@NicolasAbril, chuyển đổi 0 * x == 0 thành bool bên trong isNumber.
shrewmouse,

3

Để diễn đạt lại câu hỏi của mình, bạn đang cố gắng xác định xem một cái gì đó là một tập hợp hay một giá trị đơn lẻ. Thử so sánh xem thứ gì đó là vectơ hay số đang so sánh quả táo với quả cam - tôi có thể có một vectơ là chuỗi hoặc số và tôi có thể có một chuỗi hoặc một số đơn. Bạn quan tâm đến số lượng bạn có (1 hoặc nhiều) , chứ không phải loại bạn thực sự có.

giải pháp của tôi cho vấn đề này là kiểm tra xem đầu vào là một giá trị đơn lẻ hay một tập hợp bằng cách kiểm tra sự hiện diện của __len__. Ví dụ:

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]

Hoặc, đối với phương pháp gõ vịt, footrước tiên bạn có thể thử lặp lại :

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]

Cuối cùng, việc kiểm tra một thứ gì đó có dạng vectơ sẽ dễ dàng hơn là kiểm tra xem một thứ gì đó có dạng vô hướng hay không. Nếu bạn có các giá trị thuộc loại khác nhau (tức là chuỗi, số, v.v.), thì logic của chương trình của bạn có thể cần một số công việc - làm thế nào mà bạn đã cố gắng nhân một chuỗi với một vectơ số ngay từ đầu?


3

Để tóm tắt / đánh giá các phương pháp hiện có:

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

(Tôi đến đây bởi câu hỏi này )

#!/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 cho bản thân mình: float('nan'), 'nan', '123.45', '42', '42a', '0x8', '0xa', thêmmath.isnan
Martin Thoma

2

Có lẽ tốt hơn là chỉ nên làm theo cách khác: Bạn kiểm tra xem đó có phải là vectơ hay không. Nếu đúng, bạn thực hiện một tích số chấm và trong tất cả các trường hợp khác, bạn cố gắng nhân vô hướng.

Kiểm tra vectơ rất dễ dàng, vì nó phải thuộc loại lớp vectơ của bạn (hoặc kế thừa từ nó). Trước tiên, bạn cũng có thể thử làm một tích số chấm, và nếu điều đó không thành công (= nó không thực sự là một vectơ), thì hãy quay lại phép nhân vô hướng.


1

Chỉ để thêm vào. Có lẽ chúng ta có thể sử dụng kết hợp isinstance và isdigit như sau để tìm xem một giá trị có phải là một số hay không (int, float, v.v.)

if isinstance (num1, int) hoặc isinstance (num1, float) hoặc num1.isdigit ():


0

Đối với lớp vectơ giả thuyết:

Giả sử vlà một vectơ, và chúng ta đang nhân nó với x. Nếu nó làm cho tinh thần để nhân mỗi thành phần của vbằng x, chúng ta có thể đồng nghĩa với việc, vì vậy hãy thử mà lần đầu tiên. Nếu không, có lẽ chúng ta có thể chấm? Nếu không thì đó là lỗi loại.

CHỈNH SỬA - mã dưới đây không hoạt động, vì 2*[0]==[0,0]thay vì nâng cao a TypeError. Tôi để lại nó bởi vì nó đã được nhận xét.

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 )

nếu xlà một vectơ thì [comp * x for comp in self]sẽ mang lại tích ngoài của xmột v. Đây là một tensor hạng 2, không phải là một vô hướng.
aaronasterling

thay đổi "không phải là vô hướng" thành "không phải là vectơ". ít nhất là không trong không gian vectơ ban đầu.
aaronasterling

Heh, thực ra cả hai chúng ta đều sai. Bạn đang giả định rằng điều đó comp*xsẽ tăng xtheo tỷ lệ comp, tôi đã giả định rằng nó sẽ tăng TypeError. Thật không may, nó thực sự sẽ nối xvới compthời gian của chính nó . Giáo sư.
Katriel

meh. nếu xlà một vectơ thì nó phải có một __rmul__phương thức ( __rmul__ = __mul__) để comp * xmở rộng quy mô xtheo cùng một cách mà x * compdường như dự kiến.
aaronasterling

0

Tôi đã gặp vấn đề tương tự, khi triển khai một loại lớp vectơ. Một cách để kiểm tra một số là chỉ cần chuyển đổi thành một, tức là bằng cách sử dụng

float(x)

Điều này sẽ loại bỏ các trường hợp không thể chuyển x thành số; nhưng cũng có thể từ chối các loại cấu trúc giống số khác có thể hợp lệ, ví dụ số phức.


0

Nếu bạn muốn gọi các phương thức khác nhau tùy thuộc vào (các) loại đối số, hãy xem xét multipledispatch.

Ví dụ: giả sử bạn đang viết một lớp vectơ. Nếu cho một vectơ khác, bạn muốn tìm tích số chấm. Nếu cho một đại lượng vô hướng, bạn muốn mở rộng toàn bộ vectơ.

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]

Thật không may, (theo hiểu biết của tôi), chúng tôi không thể viết @dispatch(Vector)vì chúng tôi vẫn đang xác định kiểu Vector, vì vậy tên kiểu đó vẫn chưa được xác định. Thay vào đó, tôi đang sử dụng loại cơ sở list, cho phép bạn thậm chí tìm thấy sản phẩm dấu chấm của a Vectorvà a list.


0

Cách ngắn gọn và đơn giản:

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

Đầu ra:

True

Nếu đối tượng là một chuỗi, 'False' sẽ được trả về:

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

Đầu ra:

False

0

Bạn có một mục dữ liệu, giả sử rec_dayrằng khi ghi vào một tệp sẽ là một float. Nhưng trong quá trình chương trình nó có thể là một trong hai float, inthoặc strloại (cácstr được sử dụng khi khởi tạo một kỷ lục mới và chứa một giá trị cờ dummy).

Sau đó, bạn có thể kiểm tra xem bạn có số này không

                type(rec_day) != str 

Tôi đã cấu trúc một chương trình python theo cách này và chỉ cần đưa vào 'bản vá bảo trì' bằng cách sử dụng nó như một kiểm tra số. Nó có phải là cách Pythonic? Nhiều khả năng là không vì tôi đã từng lập trình trong COBOL.


-1

Bạn có thể sử dụng hàm isdigit ().

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

"01234" không phải là một số, nó là một chuỗi ký tự.
Alexey
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.