diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst index f94aa3d320b75..22f2add40c451 100644 --- a/doc/source/whatsnew/v0.24.0.rst +++ b/doc/source/whatsnew/v0.24.0.rst @@ -465,6 +465,7 @@ For situations where you need an ``ndarray`` of ``Interval`` objects, use np.asarray(idx) idx.values.astype(object) + .. _whatsnew_0240.api.timezone_offset_parsing: Parsing Datetime Strings with Timezone Offsets @@ -1471,6 +1472,7 @@ Other - :meth:`DataFrame.nlargest` and :meth:`DataFrame.nsmallest` now returns the correct n values when keep != 'all' also when tied on the first columns (:issue:`22752`) - :meth:`~pandas.io.formats.style.Styler.bar` now also supports tablewise application (in addition to rowwise and columnwise) with ``axis=None`` and setting clipping range with ``vmin`` and ``vmax`` (:issue:`21548` and :issue:`21526`). ``NaN`` values are also handled properly. - Logical operations ``&, |, ^`` between :class:`Series` and :class:`Index` will no longer raise ``ValueError`` (:issue:`22092`) +- Checking PEP 3141 numbers in :func:`~pandas.api.types.is_scalar` function returns ``True`` (:issue:`22903`) - Bug in :meth:`DataFrame.combine_first` in which column types were unexpectedly converted to float (:issue:`20699`) .. _whatsnew_0.24.0.contributors: @@ -1479,3 +1481,4 @@ Contributors ~~~~~~~~~~~~ .. contributors:: v0.23.4..HEAD + diff --git a/pandas/_libs/lib.pyx b/pandas/_libs/lib.pyx index e89c8fa579687..ad538ff103c2f 100644 --- a/pandas/_libs/lib.pyx +++ b/pandas/_libs/lib.pyx @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- from decimal import Decimal +from fractions import Fraction +from numbers import Number + import sys import cython @@ -15,7 +18,6 @@ from cpython.datetime cimport (PyDateTime_Check, PyDate_Check, PyDateTime_IMPORT) PyDateTime_IMPORT - import numpy as np cimport numpy as cnp from numpy cimport (ndarray, PyArray_GETITEM, @@ -105,23 +107,54 @@ def is_scalar(val: object) -> bool: """ Return True if given value is scalar. - This includes: - - numpy array scalar (e.g. np.int64) - - Python builtin numerics - - Python builtin byte arrays and strings - - None - - instances of datetime.datetime - - instances of datetime.timedelta - - Period - - instances of decimal.Decimal - - Interval - - DateOffset + Parameters + ---------- + val : object + This includes: + + - numpy array scalar (e.g. np.int64) + - Python builtin numerics + - Python builtin byte arrays and strings + - None + - datetime.datetime + - datetime.timedelta + - Period + - decimal.Decimal + - Interval + - DateOffset + - Fraction + - Number + + Returns + ------- + bool + Return True if given object is scalar, False otherwise + + Examples + -------- + >>> dt = pd.datetime.datetime(2018, 10, 3) + >>> pd.is_scalar(dt) + True + + >>> pd.api.types.is_scalar([2, 3]) + False + + >>> pd.api.types.is_scalar({0: 1, 2: 3}) + False + + >>> pd.api.types.is_scalar((0, 2)) + False + + pandas supports PEP 3141 numbers: + >>> from fractions import Fraction + >>> pd.api.types.is_scalar(Fraction(3, 5)) + True """ return (cnp.PyArray_IsAnyScalar(val) # As of numpy-1.9, PyArray_IsAnyScalar misses bytearrays on Py3. - or isinstance(val, bytes) + or isinstance(val, (bytes, Fraction, Number)) # We differ from numpy (as of 1.10), which claims that None is # not scalar in np.isscalar(). or val is None diff --git a/pandas/tests/dtypes/test_inference.py b/pandas/tests/dtypes/test_inference.py index f2552cffc6651..fd3222cd1119b 100644 --- a/pandas/tests/dtypes/test_inference.py +++ b/pandas/tests/dtypes/test_inference.py @@ -10,10 +10,11 @@ import re from datetime import datetime, date, timedelta, time from decimal import Decimal +from numbers import Number +from fractions import Fraction import numpy as np import pytz import pytest - import pandas as pd from pandas._libs import lib, iNaT, missing as libmissing from pandas import (Series, Index, DataFrame, Timedelta, @@ -1183,6 +1184,8 @@ def test_is_scalar_builtin_scalars(self): assert is_scalar(None) assert is_scalar(True) assert is_scalar(False) + assert is_scalar(Number()) + assert is_scalar(Fraction()) assert is_scalar(0.) assert is_scalar(np.nan) assert is_scalar('foobar')