1"""basic inference routines"""
2
3from __future__ import annotations
4
5from collections import abc
6from numbers import Number
7import re
8from re import Pattern
9from typing import (
10 TYPE_CHECKING,
11 TypeGuard,
12)
13
14import numpy as np
15
16from pandas._libs import lib
17from pandas.util._decorators import set_module
18
19if TYPE_CHECKING:
20 from collections.abc import Hashable
21
22is_bool = lib.is_bool
23
24is_integer = lib.is_integer
25
26is_float = lib.is_float
27
28is_complex = lib.is_complex
29
30is_scalar = lib.is_scalar
31
32is_decimal = lib.is_decimal
33
34is_list_like = lib.is_list_like
35
36is_iterator = lib.is_iterator
37
38
39@set_module("pandas.api.types")
40def is_number(obj: object) -> TypeGuard[Number | np.number]:
41 """
42 Check if the object is a number.
43
44 Returns True when the object is a number, and False if is not.
45
46 Parameters
47 ----------
48 obj : any type
49 The object to check if is a number.
50
51 Returns
52 -------
53 bool
54 Whether `obj` is a number or not.
55
56 See Also
57 --------
58 api.types.is_integer: Checks a subgroup of numbers.
59
60 Examples
61 --------
62 >>> from pandas.api.types import is_number
63 >>> is_number(1)
64 True
65 >>> is_number(7.15)
66 True
67
68 Booleans are valid because they are int subclass.
69
70 >>> is_number(False)
71 True
72
73 >>> is_number("foo")
74 False
75 >>> is_number("5")
76 False
77 """
78 return isinstance(obj, (Number, np.number))
79
80
81def iterable_not_string(obj: object) -> bool:
82 """
83 Check if the object is an iterable but not a string.
84
85 Parameters
86 ----------
87 obj : The object to check.
88
89 Returns
90 -------
91 is_iter_not_string : bool
92 Whether `obj` is a non-string iterable.
93
94 Examples
95 --------
96 >>> iterable_not_string([1, 2, 3])
97 True
98 >>> iterable_not_string("foo")
99 False
100 >>> iterable_not_string(1)
101 False
102 """
103 return isinstance(obj, abc.Iterable) and not isinstance(obj, str)
104
105
106@set_module("pandas.api.types")
107def is_file_like(obj: object) -> bool:
108 """
109 Check if the object is a file-like object.
110
111 For objects to be considered file-like, they must
112 be an iterator AND have either a `read` and/or `write`
113 method as an attribute.
114
115 Note: file-like objects must be iterable, but
116 iterable objects need not be file-like.
117
118 Parameters
119 ----------
120 obj : object
121 The object to check for file-like properties.
122 This can be any Python object, and the function will
123 check if it has attributes typically associated with
124 file-like objects (e.g., `read`, `write`, `__iter__`).
125
126 Returns
127 -------
128 bool
129 Whether `obj` has file-like properties.
130
131 See Also
132 --------
133 api.types.is_dict_like : Check if the object is dict-like.
134 api.types.is_hashable : Return True if hash(obj) will succeed, False otherwise.
135 api.types.is_named_tuple : Check if the object is a named tuple.
136 api.types.is_iterator : Check if the object is an iterator.
137
138 Examples
139 --------
140 >>> import io
141 >>> from pandas.api.types import is_file_like
142 >>> buffer = io.StringIO("data")
143 >>> is_file_like(buffer)
144 True
145 >>> is_file_like([1, 2, 3])
146 False
147 """
148 if not (hasattr(obj, "read") or hasattr(obj, "write")):
149 return False
150
151 return bool(hasattr(obj, "__iter__"))
152
153
154@set_module("pandas.api.types")
155def is_re(obj: object) -> TypeGuard[Pattern]:
156 """
157 Check if the object is a regex pattern instance.
158
159 Parameters
160 ----------
161 obj : object
162 The object to check for being a regex pattern. Typically,
163 this would be an object that you expect to be a compiled
164 pattern from the `re` module.
165
166 Returns
167 -------
168 bool
169 Whether `obj` is a regex pattern.
170
171 See Also
172 --------
173 api.types.is_float : Return True if given object is float.
174 api.types.is_iterator : Check if the object is an iterator.
175 api.types.is_integer : Return True if given object is integer.
176 api.types.is_re_compilable : Check if the object can be compiled
177 into a regex pattern instance.
178
179 Examples
180 --------
181 >>> from pandas.api.types import is_re
182 >>> import re
183 >>> is_re(re.compile(".*"))
184 True
185 >>> is_re("foo")
186 False
187 """
188 return isinstance(obj, Pattern)
189
190
191@set_module("pandas.api.types")
192def is_re_compilable(obj: object) -> bool:
193 """
194 Check if the object can be compiled into a regex pattern instance.
195
196 Parameters
197 ----------
198 obj : The object to check
199 The object to check if the object can be compiled into a regex pattern instance.
200
201 Returns
202 -------
203 bool
204 Whether `obj` can be compiled as a regex pattern.
205
206 See Also
207 --------
208 api.types.is_re : Check if the object is a regex pattern instance.
209
210 Examples
211 --------
212 >>> from pandas.api.types import is_re_compilable
213 >>> is_re_compilable(".*")
214 True
215 >>> is_re_compilable(1)
216 False
217 """
218 try:
219 re.compile(obj) # type: ignore[call-overload]
220 except TypeError:
221 return False
222 else:
223 return True
224
225
226@set_module("pandas.api.types")
227def is_array_like(obj: object) -> bool:
228 """
229 Check if the object is array-like.
230
231 For an object to be considered array-like, it must be list-like and
232 have a `dtype` attribute.
233
234 Parameters
235 ----------
236 obj : The object to check
237
238 Returns
239 -------
240 is_array_like : bool
241 Whether `obj` has array-like properties.
242
243 Examples
244 --------
245 >>> is_array_like(np.array([1, 2, 3]))
246 True
247 >>> is_array_like(pd.Series(["a", "b"]))
248 True
249 >>> is_array_like(pd.Index(["2016-01-01"]))
250 True
251 >>> is_array_like([1, 2, 3])
252 False
253 >>> is_array_like(("a", "b"))
254 False
255 """
256 return is_list_like(obj) and hasattr(obj, "dtype")
257
258
259def is_nested_list_like(obj: object) -> bool:
260 """
261 Check if the object is list-like, and that all of its elements
262 are also list-like.
263
264 Parameters
265 ----------
266 obj : The object to check
267
268 Returns
269 -------
270 is_list_like : bool
271 Whether `obj` has list-like properties.
272
273 Examples
274 --------
275 >>> is_nested_list_like([[1, 2, 3]])
276 True
277 >>> is_nested_list_like([{1, 2, 3}, {1, 2, 3}])
278 True
279 >>> is_nested_list_like(["foo"])
280 False
281 >>> is_nested_list_like([])
282 False
283 >>> is_nested_list_like([[1, 2, 3], 1])
284 False
285
286 Notes
287 -----
288 This won't reliably detect whether a consumable iterator (e. g.
289 a generator) is a nested-list-like without consuming the iterator.
290 To avoid consuming it, we always return False if the outer container
291 doesn't define `__len__`.
292
293 See Also
294 --------
295 is_list_like
296 """
297 return (
298 is_list_like(obj)
299 and hasattr(obj, "__len__")
300 # need PEP 724 to handle these typing errors
301 and len(obj) > 0 # pyright: ignore[reportArgumentType]
302 and all(is_list_like(item) for item in obj) # type: ignore[attr-defined]
303 )
304
305
306@set_module("pandas.api.types")
307def is_dict_like(obj: object) -> bool:
308 """
309 Check if the object is dict-like.
310
311 Parameters
312 ----------
313 obj : object
314 The object to check. This can be any Python object,
315 and the function will determine whether it
316 behaves like a dictionary.
317
318 Returns
319 -------
320 bool
321 Whether `obj` has dict-like properties.
322
323 See Also
324 --------
325 api.types.is_list_like : Check if the object is list-like.
326 api.types.is_file_like : Check if the object is a file-like.
327 api.types.is_named_tuple : Check if the object is a named tuple.
328
329 Examples
330 --------
331 >>> from pandas.api.types import is_dict_like
332 >>> is_dict_like({1: 2})
333 True
334 >>> is_dict_like([1, 2, 3])
335 False
336 >>> is_dict_like(dict)
337 False
338 >>> is_dict_like(dict())
339 True
340 """
341 dict_like_attrs = ("__getitem__", "keys", "__contains__")
342 return (
343 all(hasattr(obj, attr) for attr in dict_like_attrs)
344 # [GH 25196] exclude classes
345 and not isinstance(obj, type)
346 )
347
348
349@set_module("pandas.api.types")
350def is_named_tuple(obj: object) -> bool:
351 """
352 Check if the object is a named tuple.
353
354 Parameters
355 ----------
356 obj : object
357 The object that will be checked to determine
358 whether it is a named tuple.
359
360 Returns
361 -------
362 bool
363 Whether `obj` is a named tuple.
364
365 See Also
366 --------
367 api.types.is_dict_like: Check if the object is dict-like.
368 api.types.is_hashable: Return True if hash(obj)
369 will succeed, False otherwise.
370 api.types.is_categorical_dtype : Check if the dtype is categorical.
371
372 Examples
373 --------
374 >>> from collections import namedtuple
375 >>> from pandas.api.types import is_named_tuple
376 >>> Point = namedtuple("Point", ["x", "y"])
377 >>> p = Point(1, 2)
378 >>>
379 >>> is_named_tuple(p)
380 True
381 >>> is_named_tuple((1, 2))
382 False
383 """
384 return isinstance(obj, abc.Sequence) and hasattr(obj, "_fields")
385
386
387@set_module("pandas.api.types")
388def is_hashable(obj: object, allow_slice: bool = True) -> TypeGuard[Hashable]:
389 """
390 Return True if hash(obj) will succeed, False otherwise.
391
392 Some types will pass a test against collections.abc.Hashable but fail when
393 they are actually hashed with hash().
394
395 Distinguish between these and other types by trying the call to hash() and
396 seeing if they raise TypeError.
397
398 Parameters
399 ----------
400 obj : object
401 The object to check for hashability. Any Python object can be passed here.
402 allow_slice : bool
403 If True, return True if the object is hashable (including slices).
404 If False, return True if the object is hashable and not a slice.
405
406 Returns
407 -------
408 bool
409 True if object can be hashed (i.e., does not raise TypeError when
410 passed to hash()) and passes the slice check according to 'allow_slice'.
411 False otherwise (e.g., if object is mutable like a list or dictionary
412 or if allow_slice is False and object is a slice or contains a slice).
413
414 See Also
415 --------
416 api.types.is_float : Return True if given object is float.
417 api.types.is_iterator : Check if the object is an iterator.
418 api.types.is_list_like : Check if the object is list-like.
419 api.types.is_dict_like : Check if the object is dict-like.
420
421 Examples
422 --------
423 >>> import collections
424 >>> from pandas.api.types import is_hashable
425 >>> a = ([],)
426 >>> isinstance(a, collections.abc.Hashable)
427 True
428 >>> is_hashable(a)
429 False
430 """
431 # Unfortunately, we can't use isinstance(obj, collections.abc.Hashable),
432 # which can be faster than calling hash. That is because numpy scalars
433 # fail this test.
434
435 # Reconsider this decision once this numpy bug is fixed:
436 # https://github.com/numpy/numpy/issues/5562
437
438 if allow_slice is False:
439 if isinstance(obj, tuple) and any(isinstance(v, slice) for v in obj):
440 return False
441 elif isinstance(obj, slice):
442 return False
443
444 try:
445 hash(obj)
446 except TypeError:
447 return False
448 else:
449 return True
450
451
452def is_sequence(obj: object) -> bool:
453 """
454 Check if the object is a sequence of objects.
455 String types are not included as sequences here.
456
457 Parameters
458 ----------
459 obj : The object to check
460
461 Returns
462 -------
463 is_sequence : bool
464 Whether `obj` is a sequence of objects.
465
466 Examples
467 --------
468 >>> l = [1, 2, 3]
469 >>>
470 >>> is_sequence(l)
471 True
472 >>> is_sequence(iter(l))
473 False
474 """
475 try:
476 # Can iterate over it.
477 iter(obj) # type: ignore[call-overload]
478 # Has a length associated with it.
479 len(obj) # type: ignore[arg-type]
480 return not isinstance(obj, (str, bytes))
481 except (TypeError, AttributeError):
482 return False
483
484
485def is_dataclass(item: object) -> bool:
486 """
487 Checks if the object is a data-class instance
488
489 Parameters
490 ----------
491 item : object
492
493 Returns
494 --------
495 is_dataclass : bool
496 True if the item is an instance of a data-class,
497 will return false if you pass the data class itself
498
499 Examples
500 --------
501 >>> from dataclasses import dataclass
502 >>> @dataclass
503 ... class Point:
504 ... x: int
505 ... y: int
506
507 >>> is_dataclass(Point)
508 False
509 >>> is_dataclass(Point(0, 2))
510 True
511
512 """
513 try:
514 import dataclasses
515
516 return dataclasses.is_dataclass(item) and not isinstance(item, type)
517 except ImportError:
518 return False