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