Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pola-rs
GitHub Repository: pola-rs/polars
Path: blob/main/crates/polars-python/src/conversion/any_value.rs
7889 views
1
use std::borrow::{Borrow, Cow};
2
use std::sync::{Arc, Mutex};
3
4
use chrono::{
5
DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta, Timelike,
6
};
7
use chrono_tz::Tz;
8
use hashbrown::HashMap;
9
use num_traits::ToPrimitive;
10
#[cfg(feature = "object")]
11
use polars::chunked_array::object::PolarsObjectSafe;
12
#[cfg(feature = "object")]
13
use polars::datatypes::OwnedObject;
14
use polars::datatypes::{DataType, Field, TimeUnit};
15
use polars::prelude::{AnyValue, PlSmallStr, Series, TimeZone};
16
use polars_compute::decimal::{DEC128_MAX_PREC, DecimalFmtBuffer, dec128_fits};
17
use polars_core::utils::any_values_to_supertype_and_n_dtypes;
18
use polars_core::utils::arrow::temporal_conversions::date32_to_date;
19
use polars_utils::aliases::PlFixedStateQuality;
20
use pyo3::exceptions::{PyOverflowError, PyTypeError, PyValueError};
21
use pyo3::prelude::*;
22
use pyo3::sync::PyOnceLock;
23
use pyo3::types::{
24
PyBool, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyFloat, PyInt, PyList, PyMapping,
25
PyRange, PySequence, PyString, PyTime, PyTuple, PyType, PyTzInfo,
26
};
27
use pyo3::{IntoPyObjectExt, PyTypeCheck, intern};
28
29
use super::datetime::{
30
datetime_to_py_object, elapsed_offset_to_timedelta, nanos_since_midnight_to_naivetime,
31
};
32
use super::{ObjectValue, Wrap, struct_dict};
33
use crate::error::PyPolarsErr;
34
use crate::py_modules::{pl_series, pl_utils};
35
use crate::series::PySeries;
36
37
impl<'py> IntoPyObject<'py> for Wrap<AnyValue<'_>> {
38
type Target = PyAny;
39
type Output = Bound<'py, Self::Target>;
40
type Error = PyErr;
41
42
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
43
any_value_into_py_object(self.0, py)
44
}
45
}
46
47
impl<'py> IntoPyObject<'py> for &Wrap<AnyValue<'_>> {
48
type Target = PyAny;
49
type Output = Bound<'py, Self::Target>;
50
type Error = PyErr;
51
52
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
53
self.clone().into_pyobject(py)
54
}
55
}
56
57
impl<'py> FromPyObject<'py> for Wrap<AnyValue<'static>> {
58
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
59
py_object_to_any_value(ob, true, true).map(Wrap)
60
}
61
}
62
63
pub(crate) fn any_value_into_py_object<'py>(
64
av: AnyValue<'_>,
65
py: Python<'py>,
66
) -> PyResult<Bound<'py, PyAny>> {
67
let utils = pl_utils(py).bind(py);
68
match av {
69
AnyValue::UInt8(v) => v.into_bound_py_any(py),
70
AnyValue::UInt16(v) => v.into_bound_py_any(py),
71
AnyValue::UInt32(v) => v.into_bound_py_any(py),
72
AnyValue::UInt64(v) => v.into_bound_py_any(py),
73
AnyValue::UInt128(v) => v.into_bound_py_any(py),
74
AnyValue::Int8(v) => v.into_bound_py_any(py),
75
AnyValue::Int16(v) => v.into_bound_py_any(py),
76
AnyValue::Int32(v) => v.into_bound_py_any(py),
77
AnyValue::Int64(v) => v.into_bound_py_any(py),
78
AnyValue::Int128(v) => v.into_bound_py_any(py),
79
AnyValue::Float16(v) => v.to_f32().into_bound_py_any(py),
80
AnyValue::Float32(v) => v.into_bound_py_any(py),
81
AnyValue::Float64(v) => v.into_bound_py_any(py),
82
AnyValue::Null => py.None().into_bound_py_any(py),
83
AnyValue::Boolean(v) => v.into_bound_py_any(py),
84
AnyValue::String(v) => v.into_bound_py_any(py),
85
AnyValue::StringOwned(v) => v.into_bound_py_any(py),
86
AnyValue::Categorical(cat, map) | AnyValue::Enum(cat, map) => unsafe {
87
map.cat_to_str_unchecked(cat).into_bound_py_any(py)
88
},
89
AnyValue::CategoricalOwned(cat, map) | AnyValue::EnumOwned(cat, map) => unsafe {
90
map.cat_to_str_unchecked(cat).into_bound_py_any(py)
91
},
92
AnyValue::Date(v) => {
93
let date = date32_to_date(v);
94
date.into_bound_py_any(py)
95
},
96
AnyValue::Datetime(v, time_unit, time_zone) => {
97
datetime_to_py_object(py, v, time_unit, time_zone)
98
},
99
AnyValue::DatetimeOwned(v, time_unit, time_zone) => {
100
datetime_to_py_object(py, v, time_unit, time_zone.as_ref().map(AsRef::as_ref))
101
},
102
AnyValue::Duration(v, time_unit) => {
103
let time_delta = elapsed_offset_to_timedelta(v, time_unit);
104
time_delta.into_bound_py_any(py)
105
},
106
AnyValue::Time(v) => nanos_since_midnight_to_naivetime(v).into_bound_py_any(py),
107
AnyValue::Array(v, _) | AnyValue::List(v) => PySeries::new(v).to_list(py),
108
ref av @ AnyValue::Struct(_, _, flds) => {
109
Ok(struct_dict(py, av._iter_struct_av(), flds)?.into_any())
110
},
111
AnyValue::StructOwned(payload) => {
112
Ok(struct_dict(py, payload.0.into_iter(), &payload.1)?.into_any())
113
},
114
#[cfg(feature = "object")]
115
AnyValue::Object(v) => {
116
let object = v.as_any().downcast_ref::<ObjectValue>().unwrap();
117
Ok(object.inner.clone_ref(py).into_bound(py))
118
},
119
#[cfg(feature = "object")]
120
AnyValue::ObjectOwned(v) => {
121
let object = v.0.as_any().downcast_ref::<ObjectValue>().unwrap();
122
Ok(object.inner.clone_ref(py).into_bound(py))
123
},
124
AnyValue::Binary(v) => PyBytes::new(py, v).into_bound_py_any(py),
125
AnyValue::BinaryOwned(v) => PyBytes::new(py, &v).into_bound_py_any(py),
126
AnyValue::Decimal(v, prec, scale) => {
127
let convert = utils.getattr(intern!(py, "to_py_decimal"))?;
128
let mut buf = DecimalFmtBuffer::new();
129
let s = buf.format_dec128(v, scale, false, false);
130
convert.call1((prec, s))
131
},
132
}
133
}
134
135
/// Holds a Python type object and implements hashing / equality based on the pointer address of the
136
/// type object. This is used as a hashtable key instead of only the `usize` pointer value, as we
137
/// need to hold a ref to the Python type object to keep it alive.
138
#[derive(Debug)]
139
pub struct TypeObjectKey {
140
#[allow(unused)]
141
type_object: Py<PyType>,
142
/// We need to store this in a field for `Borrow<usize>`
143
address: usize,
144
}
145
146
impl TypeObjectKey {
147
fn new(type_object: Py<PyType>) -> Self {
148
let address = type_object.as_ptr() as usize;
149
Self {
150
type_object,
151
address,
152
}
153
}
154
}
155
156
impl PartialEq for TypeObjectKey {
157
fn eq(&self, other: &Self) -> bool {
158
self.address == other.address
159
}
160
}
161
162
impl Eq for TypeObjectKey {}
163
164
impl std::borrow::Borrow<usize> for TypeObjectKey {
165
fn borrow(&self) -> &usize {
166
&self.address
167
}
168
}
169
170
impl std::hash::Hash for TypeObjectKey {
171
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
172
let v: &usize = self.borrow();
173
v.hash(state)
174
}
175
}
176
177
type InitFn = fn(&Bound<'_, PyAny>, bool) -> PyResult<AnyValue<'static>>;
178
pub(crate) static LUT: Mutex<HashMap<TypeObjectKey, InitFn, PlFixedStateQuality>> =
179
Mutex::new(HashMap::with_hasher(PlFixedStateQuality::with_seed(0)));
180
181
/// Convert a Python object to an [`AnyValue`].
182
pub(crate) fn py_object_to_any_value(
183
ob: &Bound<'_, PyAny>,
184
strict: bool,
185
allow_object: bool,
186
) -> PyResult<AnyValue<'static>> {
187
// Conversion functions.
188
fn get_null(_ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
189
Ok(AnyValue::Null)
190
}
191
192
fn get_bool(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
193
let b = ob.extract::<bool>()?;
194
Ok(AnyValue::Boolean(b))
195
}
196
197
fn get_int(ob: &Bound<'_, PyAny>, strict: bool) -> PyResult<AnyValue<'static>> {
198
if let Ok(v) = ob.extract::<i64>() {
199
Ok(AnyValue::Int64(v))
200
} else if let Ok(v) = ob.extract::<i128>() {
201
Ok(AnyValue::Int128(v))
202
} else if let Ok(v) = ob.extract::<u64>() {
203
Ok(AnyValue::UInt64(v))
204
} else if let Ok(v) = ob.extract::<u128>() {
205
Ok(AnyValue::UInt128(v))
206
} else if !strict {
207
let f = ob.extract::<f64>()?;
208
Ok(AnyValue::Float64(f))
209
} else {
210
Err(PyOverflowError::new_err(format!(
211
"int value too large for Polars integer types: {ob}"
212
)))
213
}
214
}
215
216
fn get_float(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
217
Ok(AnyValue::Float64(ob.extract::<f64>()?))
218
}
219
220
fn get_str(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
221
// Ideally we'd be returning an AnyValue::String(&str) instead, as was
222
// the case in previous versions of this function. However, if compiling
223
// with abi3 for versions older than Python 3.10, the APIs that purport
224
// to return &str actually just encode to UTF-8 as a newly allocated
225
// PyBytes object, and then return reference to that. So what we're
226
// doing here isn't any different fundamentally, and the APIs to for
227
// converting to &str are deprecated in PyO3 0.21.
228
//
229
// Once Python 3.10 is the minimum supported version, converting to &str
230
// will be cheaper, and we should do that. Python 3.9 security updates
231
// end-of-life is Oct 31, 2025.
232
Ok(AnyValue::StringOwned(ob.extract::<String>()?.into()))
233
}
234
235
fn get_bytes(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
236
let value = ob.extract::<Vec<u8>>()?;
237
Ok(AnyValue::BinaryOwned(value))
238
}
239
240
fn get_date(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
241
const UNIX_EPOCH: NaiveDate = DateTime::UNIX_EPOCH.naive_utc().date();
242
let date = ob.extract::<NaiveDate>()?;
243
let elapsed = date.signed_duration_since(UNIX_EPOCH);
244
Ok(AnyValue::Date(elapsed.num_days() as i32))
245
}
246
247
fn get_datetime(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
248
let py = ob.py();
249
let tzinfo = ob.getattr(intern!(py, "tzinfo"))?;
250
251
if tzinfo.is_none() {
252
let datetime = ob.extract::<NaiveDateTime>()?;
253
let delta = datetime - DateTime::UNIX_EPOCH.naive_utc();
254
let timestamp = delta.num_microseconds().unwrap();
255
return Ok(AnyValue::Datetime(timestamp, TimeUnit::Microseconds, None));
256
}
257
258
// Try converting `pytz` timezone to `zoneinfo` timezone
259
let (ob, tzinfo) = if let Some(tz) = tzinfo
260
.getattr(intern!(py, "zone"))
261
.ok()
262
.and_then(|tz| (!tz.is_none()).then_some(tz))
263
{
264
let tzinfo = PyTzInfo::timezone(py, tz.downcast_into::<PyString>()?)?;
265
(
266
&ob.call_method(intern!(py, "astimezone"), (&tzinfo,), None)?,
267
tzinfo,
268
)
269
} else {
270
(ob, tzinfo.downcast_into()?)
271
};
272
273
let (timestamp, tz) = if tzinfo.hasattr(intern!(py, "key"))? {
274
let datetime = ob.extract::<DateTime<Tz>>()?;
275
let tz = unsafe { TimeZone::from_static(datetime.timezone().name()) };
276
if datetime.year() >= 2100 {
277
// chrono-tz does not support dates after 2100
278
// https://github.com/chronotope/chrono-tz/issues/135
279
(
280
pl_utils(py)
281
.bind(py)
282
.getattr(intern!(py, "datetime_to_int"))?
283
.call1((ob, intern!(py, "us")))?
284
.extract::<i64>()?,
285
tz,
286
)
287
} else {
288
let delta = datetime.to_utc() - DateTime::UNIX_EPOCH;
289
(delta.num_microseconds().unwrap(), tz)
290
}
291
} else {
292
let datetime = ob.extract::<DateTime<FixedOffset>>()?;
293
let delta = datetime.to_utc() - DateTime::UNIX_EPOCH;
294
(delta.num_microseconds().unwrap(), TimeZone::UTC)
295
};
296
297
Ok(AnyValue::DatetimeOwned(
298
timestamp,
299
TimeUnit::Microseconds,
300
Some(Arc::new(tz)),
301
))
302
}
303
304
fn get_timedelta(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
305
let timedelta = ob.extract::<TimeDelta>()?;
306
if let Some(micros) = timedelta.num_microseconds() {
307
Ok(AnyValue::Duration(micros, TimeUnit::Microseconds))
308
} else {
309
Ok(AnyValue::Duration(
310
timedelta.num_milliseconds(),
311
TimeUnit::Milliseconds,
312
))
313
}
314
}
315
316
fn get_time(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
317
let time = ob.extract::<NaiveTime>()?;
318
319
Ok(AnyValue::Time(
320
(time.num_seconds_from_midnight() as i64) * 1_000_000_000 + time.nanosecond() as i64,
321
))
322
}
323
324
fn get_decimal(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
325
fn abs_decimal_from_digits(
326
digits: impl IntoIterator<Item = u8>,
327
exp: i32,
328
) -> Option<(i128, usize)> {
329
let mut v = 0_i128;
330
for d in digits {
331
v = v.checked_mul(10)?.checked_add(d as i128)?;
332
}
333
let scale = if exp > 0 {
334
v = 10_i128.checked_pow(exp as u32)?.checked_mul(v)?;
335
0
336
} else {
337
(-exp) as usize
338
};
339
dec128_fits(v, DEC128_MAX_PREC).then_some((v, scale))
340
}
341
342
// Note: Using Vec<u8> is not the most efficient thing here (input is a tuple)
343
let (sign, digits, exp): (i8, Vec<u8>, i32) = ob
344
.call_method0(intern!(ob.py(), "as_tuple"))
345
.unwrap()
346
.extract()
347
.unwrap();
348
let (mut v, scale) = abs_decimal_from_digits(digits, exp).ok_or_else(|| {
349
PyErr::from(PyPolarsErr::Other(
350
"Decimal is too large to fit in Decimal128".into(),
351
))
352
})?;
353
if sign > 0 {
354
v = -v; // Won't overflow since -i128::MAX > i128::MIN
355
}
356
Ok(AnyValue::Decimal(v, DEC128_MAX_PREC, scale))
357
}
358
359
fn get_list(ob: &Bound<'_, PyAny>, strict: bool) -> PyResult<AnyValue<'static>> {
360
fn get_list_with_constructor(
361
ob: &Bound<'_, PyAny>,
362
strict: bool,
363
) -> PyResult<AnyValue<'static>> {
364
// Use the dedicated constructor.
365
// This constructor is able to go via dedicated type constructors
366
// so it can be much faster.
367
let py = ob.py();
368
let kwargs = PyDict::new(py);
369
kwargs.set_item("strict", strict)?;
370
let s = pl_series(py).call(py, (ob,), Some(&kwargs))?;
371
get_list_from_series(s.bind(py), strict)
372
}
373
374
if ob.is_empty()? {
375
Ok(AnyValue::List(Series::new_empty(
376
PlSmallStr::EMPTY,
377
&DataType::Null,
378
)))
379
} else if ob.is_instance_of::<PyList>() | ob.is_instance_of::<PyTuple>() {
380
let list = ob.downcast::<PySequence>()?;
381
382
// Try to find first non-null.
383
let length = list.len()?;
384
let mut iter = list.try_iter()?;
385
let mut avs = Vec::new();
386
for item in &mut iter {
387
let av = py_object_to_any_value(&item?, strict, true)?;
388
let is_null = av.is_null();
389
avs.push(av);
390
if is_null {
391
break;
392
}
393
}
394
395
// Try to use a faster converter.
396
if let Some(av) = avs.last()
397
&& !av.is_null()
398
&& av.dtype().is_primitive()
399
{
400
// Always use strict, we will filter the error if we're not
401
// strict and try again using a slower converter with supertype.
402
match get_list_with_constructor(ob, true) {
403
Ok(ret) => return Ok(ret),
404
Err(e) => {
405
if strict {
406
return Err(e);
407
}
408
},
409
}
410
}
411
412
// Push the rest of the anyvalues and use slower converter.
413
avs.reserve(length);
414
for item in &mut iter {
415
avs.push(py_object_to_any_value(&item?, strict, true)?);
416
}
417
418
let (dtype, _n_dtypes) = any_values_to_supertype_and_n_dtypes(&avs)
419
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
420
let s = Series::from_any_values_and_dtype(PlSmallStr::EMPTY, &avs, &dtype, strict)
421
.map_err(|e| {
422
PyTypeError::new_err(format!(
423
"{e}\n\nHint: Try setting `strict=False` to allow passing data with mixed types."
424
))
425
})?;
426
Ok(AnyValue::List(s))
427
} else {
428
// range will take this branch
429
get_list_with_constructor(ob, strict)
430
}
431
}
432
433
fn get_list_from_series(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
434
let s = super::get_series(ob)?;
435
Ok(AnyValue::List(s))
436
}
437
438
fn get_mapping(ob: &Bound<'_, PyAny>, strict: bool) -> PyResult<AnyValue<'static>> {
439
let mapping = ob.downcast::<PyMapping>()?;
440
let len = mapping.len()?;
441
let mut keys = Vec::with_capacity(len);
442
let mut vals = Vec::with_capacity(len);
443
444
for item in mapping.items()?.try_iter()? {
445
let item = item?.downcast_into::<PyTuple>()?;
446
let (key_py, val_py) = (item.get_item(0)?, item.get_item(1)?);
447
448
let key: Cow<str> = key_py.extract()?;
449
let val = py_object_to_any_value(&val_py, strict, true)?;
450
451
keys.push(Field::new(key.as_ref().into(), val.dtype()));
452
vals.push(val);
453
}
454
Ok(AnyValue::StructOwned(Box::new((vals, keys))))
455
}
456
457
fn get_struct(ob: &Bound<'_, PyAny>, strict: bool) -> PyResult<AnyValue<'static>> {
458
let dict = ob.downcast::<PyDict>().unwrap();
459
let len = dict.len();
460
let mut keys = Vec::with_capacity(len);
461
let mut vals = Vec::with_capacity(len);
462
for (k, v) in dict.into_iter() {
463
let key = k.extract::<Cow<str>>()?;
464
let val = py_object_to_any_value(&v, strict, true)?;
465
let dtype = val.dtype();
466
keys.push(Field::new(key.as_ref().into(), dtype));
467
vals.push(val)
468
}
469
Ok(AnyValue::StructOwned(Box::new((vals, keys))))
470
}
471
472
fn get_namedtuple(ob: &Bound<'_, PyAny>, strict: bool) -> PyResult<AnyValue<'static>> {
473
let tuple = ob.downcast::<PyTuple>().unwrap();
474
let len = tuple.len();
475
let fields = ob
476
.getattr(intern!(ob.py(), "_fields"))?
477
.downcast_into::<PyTuple>()?;
478
let mut keys = Vec::with_capacity(len);
479
let mut vals = Vec::with_capacity(len);
480
for (k, v) in fields.into_iter().zip(tuple.into_iter()) {
481
let key = k.extract::<Cow<str>>()?;
482
let val = py_object_to_any_value(&v, strict, true)?;
483
let dtype = val.dtype();
484
keys.push(Field::new(key.as_ref().into(), dtype));
485
vals.push(val)
486
}
487
Ok(AnyValue::StructOwned(Box::new((vals, keys))))
488
}
489
490
fn get_object(ob: &Bound<'_, PyAny>, _strict: bool) -> PyResult<AnyValue<'static>> {
491
#[cfg(feature = "object")]
492
{
493
// This is slow, but hey don't use objects.
494
let v = &ObjectValue {
495
inner: ob.clone().unbind(),
496
};
497
Ok(AnyValue::ObjectOwned(OwnedObject(v.to_boxed())))
498
}
499
#[cfg(not(feature = "object"))]
500
panic!("activate object")
501
}
502
503
/// Determine which conversion function to use for the given object.
504
///
505
/// Note: This function is only ran if the object's type is not already in the
506
/// lookup table.
507
fn get_conversion_function(ob: &Bound<'_, PyAny>, allow_object: bool) -> PyResult<InitFn> {
508
let py = ob.py();
509
if ob.is_none() {
510
Ok(get_null)
511
}
512
// bool must be checked before int because Python bool is an instance of int.
513
else if ob.is_instance_of::<PyBool>() {
514
Ok(get_bool)
515
} else if ob.is_instance_of::<PyInt>() {
516
Ok(get_int)
517
} else if ob.is_instance_of::<PyFloat>() {
518
Ok(get_float)
519
} else if ob.is_instance_of::<PyString>() {
520
Ok(get_str)
521
} else if ob.is_instance_of::<PyBytes>() {
522
Ok(get_bytes)
523
} else if ob.is_instance_of::<PyTuple>() {
524
// NamedTuple-like object?
525
if ob.hasattr(intern!(py, "_fields"))? {
526
Ok(get_namedtuple)
527
} else {
528
Ok(get_list)
529
}
530
} else if ob.is_instance_of::<PyList>() {
531
Ok(get_list)
532
} else if ob.is_instance_of::<PyDict>() {
533
Ok(get_struct)
534
} else if PyMapping::type_check(ob) {
535
Ok(get_mapping)
536
}
537
// note: datetime must be checked *before* date
538
// (as python datetime is an instance of date)
539
else if PyDateTime::type_check(ob) {
540
Ok(get_datetime as InitFn)
541
} else if PyDate::type_check(ob) {
542
Ok(get_date as InitFn)
543
} else if PyTime::type_check(ob) {
544
Ok(get_time as InitFn)
545
} else if PyDelta::type_check(ob) {
546
Ok(get_timedelta as InitFn)
547
} else if ob.is_instance_of::<PyRange>() {
548
Ok(get_list as InitFn)
549
} else if ob.is_instance(pl_series(py).bind(py))? {
550
Ok(get_list_from_series as InitFn)
551
} else {
552
static NDARRAY_TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
553
if let Ok(ndarray_type) = NDARRAY_TYPE.import(py, "numpy", "ndarray") {
554
if ob.is_instance(ndarray_type)? {
555
// will convert via Series -> mmap_numpy_array
556
return Ok(get_list as InitFn);
557
}
558
}
559
static DECIMAL_TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
560
if ob.is_instance(DECIMAL_TYPE.import(py, "decimal", "Decimal")?)? {
561
return Ok(get_decimal as InitFn);
562
}
563
564
// support NumPy scalars
565
if ob.extract::<i64>().is_ok() || ob.extract::<u64>().is_ok() {
566
return Ok(get_int as InitFn);
567
} else if ob.extract::<f64>().is_ok() {
568
return Ok(get_float as InitFn);
569
}
570
571
if allow_object {
572
Ok(get_object as InitFn)
573
} else {
574
Err(PyValueError::new_err(format!("Cannot convert {ob}")))
575
}
576
}
577
}
578
579
let py_type = ob.get_type();
580
let py_type_address = py_type.as_ptr() as usize;
581
582
let conversion_func = {
583
if let Some(cached_func) = LUT.lock().unwrap().get(&py_type_address) {
584
*cached_func
585
} else {
586
let k = TypeObjectKey::new(py_type.clone().unbind());
587
assert_eq!(k.address, py_type_address);
588
589
let func = get_conversion_function(ob, allow_object)?;
590
LUT.lock().unwrap().insert(k, func);
591
func
592
}
593
};
594
595
conversion_func(ob, strict)
596
}
597
598