1use pyo3::prelude::*;
2use pyo3::pybacked::PyBackedStr;
3use pyo3::types::{PyBool, PyCFunction, PyFloat, PyList, PyString, PyTuple};
4
5use super::*;
6use crate::py_modules::{pl_series, polars};
7
8fn infer_and_finish<'a, A: ApplyLambda<'a>>(
10 applyer: &'a A,
11 py: Python<'a>,
12 lambda: &'a Bound<'a, PyAny>,
13 out: &Bound<'a, PyAny>,
14 null_count: usize,
15) -> PyResult<PySeries> {
16 if out.is_instance_of::<PyBool>() {
17 let first_value = out.extract::<bool>().unwrap();
18 applyer
19 .apply_lambda_with_bool_out_type(py, lambda, null_count, Some(first_value))
20 .map(|ca| ca.into_series().into())
21 } else if out.is_instance_of::<PyFloat>() {
22 let first_value = out.extract::<f64>().unwrap();
23 applyer
24 .apply_lambda_with_primitive_out_type::<Float64Type>(
25 py,
26 lambda,
27 null_count,
28 Some(first_value),
29 )
30 .map(|ca| ca.into_series().into())
31 } else if out.is_instance_of::<PyString>() {
32 let first_value = out.extract::<PyBackedStr>().unwrap();
33 applyer
34 .apply_lambda_with_string_out_type(py, lambda, null_count, Some(first_value))
35 .map(|ca| ca.into_series().into())
36 } else if out.hasattr("_s")? {
37 let py_pyseries = out.getattr("_s").unwrap();
38 let series = py_pyseries.extract::<PySeries>().unwrap().series;
39 let dt = series.dtype();
40 applyer
41 .apply_lambda_with_list_out_type(
42 py,
43 lambda.to_owned().unbind(),
44 null_count,
45 Some(&series),
46 dt,
47 )
48 .map(|ca| ca.into_series().into())
49 } else if out.is_instance_of::<PyList>() || out.is_instance_of::<PyTuple>() {
50 let series = pl_series(py).call1(py, (out,))?;
51 let py_pyseries = series.getattr(py, "_s").unwrap();
52 let series = py_pyseries.extract::<PySeries>(py).unwrap().series;
53
54 let dt = series.dtype();
55
56 if dt.is_nested_null() {
58 let av = out.extract::<Wrap<AnyValue>>()?;
59 return applyer
60 .apply_extract_any_values(py, lambda, null_count, av.0)
61 .map(|s| s.into());
62 }
63
64 let lambda_owned = lambda.to_owned().unbind();
68 let new_lambda = PyCFunction::new_closure(py, None, None, move |args, _kwargs| {
69 Python::with_gil(|py| {
70 let out = lambda_owned.call1(py, args)?;
71 pl_series(py).call1(py, (out,))
73 })
74 })?
75 .into_any()
76 .unbind();
77
78 let result = applyer
79 .apply_lambda_with_list_out_type(py, new_lambda, null_count, Some(&series), dt)
80 .map(|ca| ca.into_series().into());
81 match result {
82 Ok(out) => Ok(out),
83 Err(_) => {
85 let av = out.extract::<Wrap<AnyValue>>()?;
86 applyer
87 .apply_extract_any_values(py, lambda, null_count, av.0)
88 .map(|s| s.into())
89 },
90 }
91 } else if out.is_instance_of::<PyDict>() {
92 let first = out.extract::<Wrap<AnyValue<'_>>>()?;
93 applyer.apply_into_struct(py, lambda, null_count, first.0)
94 }
95 else if out.extract::<i64>().is_ok() {
98 let first_value = out.extract::<i64>().unwrap();
99 applyer
100 .apply_lambda_with_primitive_out_type::<Int64Type>(
101 py,
102 lambda,
103 null_count,
104 Some(first_value),
105 )
106 .map(|ca| ca.into_series().into())
107 } else if let Ok(av) = out.extract::<Wrap<AnyValue>>() {
108 applyer
109 .apply_extract_any_values(py, lambda, null_count, av.0)
110 .map(|s| s.into())
111 } else {
112 #[cfg(feature = "object")]
113 {
114 applyer
115 .apply_lambda_with_object_out_type(
116 py,
117 lambda,
118 null_count,
119 Some(out.to_owned().unbind().into()),
120 )
121 .map(|ca| ca.into_series().into())
122 }
123 #[cfg(not(feature = "object"))]
124 {
125 todo!()
126 }
127 }
128}
129
130pub trait ApplyLambda<'a> {
131 fn apply_lambda_unknown(
132 &'a self,
133 _py: Python<'a>,
134 _lambda: &'a Bound<'a, PyAny>,
135 ) -> PyResult<PySeries>;
136
137 fn apply_into_struct(
139 &'a self,
140 py: Python<'a>,
141 lambda: &Bound<'a, PyAny>,
142 init_null_count: usize,
143 first_value: AnyValue<'a>,
144 ) -> PyResult<PySeries>;
145
146 fn apply_lambda_with_primitive_out_type<D>(
148 &'a self,
149 py: Python<'a>,
150 lambda: &Bound<'a, PyAny>,
151 init_null_count: usize,
152 first_value: Option<D::Native>,
153 ) -> PyResult<ChunkedArray<D>>
154 where
155 D: PyPolarsNumericType,
156 D::Native: IntoPyObject<'a> + FromPyObject<'a>;
157
158 fn apply_lambda_with_bool_out_type(
160 &'a self,
161 py: Python<'a>,
162 lambda: &Bound<'a, PyAny>,
163 init_null_count: usize,
164 first_value: Option<bool>,
165 ) -> PyResult<ChunkedArray<BooleanType>>;
166
167 fn apply_lambda_with_string_out_type(
169 &'a self,
170 py: Python<'a>,
171 lambda: &Bound<'a, PyAny>,
172 init_null_count: usize,
173 first_value: Option<PyBackedStr>,
174 ) -> PyResult<StringChunked>;
175
176 fn apply_lambda_with_list_out_type(
178 &'a self,
179 py: Python<'a>,
180 lambda: PyObject,
181 init_null_count: usize,
182 first_value: Option<&Series>,
183 dt: &DataType,
184 ) -> PyResult<ListChunked>;
185
186 fn apply_extract_any_values(
187 &'a self,
188 py: Python<'a>,
189 lambda: &'a Bound<'a, PyAny>,
190 init_null_count: usize,
191 first_value: AnyValue<'a>,
192 ) -> PyResult<Series>;
193
194 #[cfg(feature = "object")]
196 fn apply_lambda_with_object_out_type(
197 &'a self,
198 py: Python<'a>,
199 lambda: &Bound<'a, PyAny>,
200 init_null_count: usize,
201 first_value: Option<ObjectValue>,
202 ) -> PyResult<ObjectChunked<ObjectValue>>;
203}
204
205pub fn call_lambda<'a, T>(
206 py: Python<'a>,
207 lambda: &Bound<'a, PyAny>,
208 in_val: T,
209) -> PyResult<Bound<'a, PyAny>>
210where
211 T: IntoPyObject<'a>,
212{
213 let arg = PyTuple::new(py, [in_val])?;
214 lambda.call1(arg)
215}
216
217pub(crate) fn call_lambda_and_extract<'py, T, S>(
218 py: Python<'py>,
219 lambda: &Bound<'py, PyAny>,
220 in_val: T,
221) -> PyResult<Option<S>>
222where
223 T: IntoPyObject<'py>,
224 S: FromPyObject<'py>,
225{
226 let out = call_lambda(py, lambda, in_val)?;
227 if out.is_none() {
228 Ok(None)
229 } else {
230 out.extract::<S>().map(Some)
231 }
232}
233
234fn call_lambda_series_out<'py, T>(
235 py: Python<'py>,
236 lambda: &Bound<PyAny>,
237 in_val: T,
238) -> PyResult<Series>
239where
240 T: IntoPyObject<'py>,
241{
242 let arg = PyTuple::new(py, [in_val])?;
243 let out = lambda.call1(arg)?;
244 let py_series = out.getattr("_s")?;
245 py_series.extract::<PySeries>().map(|s| s.series)
246}
247
248fn extract_anyvalues<'a, T, I>(
249 py: Python<'a>,
250 lambda: &'a Bound<PyAny>,
251 len: usize,
252 init_null_count: usize,
253 iter: I,
254 first_value: AnyValue<'a>,
255) -> PyResult<Vec<AnyValue<'a>>>
256where
257 T: IntoPyObject<'a>,
258 I: Iterator<Item = Option<T>> + 'a,
259{
260 let mut avs = Vec::with_capacity(len);
261 avs.extend(std::iter::repeat(AnyValue::Null).take(init_null_count));
262 avs.push(first_value);
263
264 for opt_val in iter {
265 let av = match opt_val {
266 None => AnyValue::Null,
267 Some(val) => {
268 let val: Option<Wrap<AnyValue>> = call_lambda_and_extract(py, lambda, val)?;
269 match val {
270 None => AnyValue::Null,
271 Some(av) => av.0,
272 }
273 },
274 };
275 avs.push(av)
276 }
277 Ok(avs)
278}
279
280impl<'a> ApplyLambda<'a> for BooleanChunked {
281 fn apply_lambda_unknown(&'a self, py: Python, lambda: &Bound<'a, PyAny>) -> PyResult<PySeries> {
282 let mut null_count = 0;
283 for opt_v in self.into_iter() {
284 if let Some(v) = opt_v {
285 let arg = PyTuple::new(py, [v])?;
286 let out = lambda.call1(arg)?;
287 if out.is_none() {
288 null_count += 1;
289 continue;
290 }
291 return infer_and_finish(self, py, lambda, &out, null_count);
292 } else {
293 null_count += 1
294 }
295 }
296 Ok(Self::full_null(self.name().clone(), self.len())
297 .into_series()
298 .into())
299 }
300
301 fn apply_into_struct(
302 &'a self,
303 py: Python,
304 lambda: &Bound<'a, PyAny>,
305 init_null_count: usize,
306 first_value: AnyValue<'a>,
307 ) -> PyResult<PySeries> {
308 let skip = 1;
309 let it = self
310 .into_iter()
311 .skip(init_null_count + skip)
312 .map(|opt_val| opt_val.map(|val| call_lambda(py, lambda, val)).transpose());
313 iterator_to_struct(
314 py,
315 it,
316 init_null_count,
317 first_value,
318 self.name().clone(),
319 self.len(),
320 )
321 }
322
323 fn apply_lambda_with_primitive_out_type<D>(
324 &'a self,
325 py: Python<'a>,
326 lambda: &Bound<'a, PyAny>,
327 init_null_count: usize,
328 first_value: Option<D::Native>,
329 ) -> PyResult<ChunkedArray<D>>
330 where
331 D: PyPolarsNumericType,
332 D::Native: IntoPyObject<'a> + FromPyObject<'a>,
333 {
334 let skip = usize::from(first_value.is_some());
335 if init_null_count == self.len() {
336 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
337 } else {
338 let it = self
339 .into_iter()
340 .skip(init_null_count + skip)
341 .map(|opt_val| {
342 opt_val
343 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
344 .transpose()
345 });
346 iterator_to_primitive(
347 it,
348 init_null_count,
349 first_value,
350 self.name().clone(),
351 self.len(),
352 )
353 }
354 }
355
356 fn apply_lambda_with_bool_out_type(
357 &'a self,
358 py: Python,
359 lambda: &Bound<'a, PyAny>,
360 init_null_count: usize,
361 first_value: Option<bool>,
362 ) -> PyResult<BooleanChunked> {
363 let skip = usize::from(first_value.is_some());
364 if init_null_count == self.len() {
365 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
366 } else {
367 let it = self
368 .into_iter()
369 .skip(init_null_count + skip)
370 .map(|opt_val| {
371 opt_val
372 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
373 .transpose()
374 });
375 iterator_to_bool(
376 it,
377 init_null_count,
378 first_value,
379 self.name().clone(),
380 self.len(),
381 )
382 }
383 }
384
385 fn apply_lambda_with_string_out_type(
386 &'a self,
387 py: Python,
388 lambda: &Bound<'a, PyAny>,
389 init_null_count: usize,
390 first_value: Option<PyBackedStr>,
391 ) -> PyResult<StringChunked> {
392 let skip = usize::from(first_value.is_some());
393 if init_null_count == self.len() {
394 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
395 } else {
396 let it = self
397 .into_iter()
398 .skip(init_null_count + skip)
399 .map(|opt_val| {
400 opt_val
401 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
402 .transpose()
403 });
404 iterator_to_string(
405 it,
406 init_null_count,
407 first_value,
408 self.name().clone(),
409 self.len(),
410 )
411 }
412 }
413
414 fn apply_lambda_with_list_out_type(
415 &'a self,
416 py: Python,
417 lambda: PyObject,
418 init_null_count: usize,
419 first_value: Option<&Series>,
420 dt: &DataType,
421 ) -> PyResult<ListChunked> {
422 let skip = usize::from(first_value.is_some());
423 let lambda = lambda.bind(py);
424 if init_null_count == self.len() {
425 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
426 } else {
427 let it = self
428 .into_iter()
429 .skip(init_null_count + skip)
430 .map(|opt_val| {
431 opt_val
432 .map(|val| call_lambda_series_out(py, lambda, val))
433 .transpose()
434 });
435 iterator_to_list(
436 dt,
437 it,
438 init_null_count,
439 first_value,
440 self.name().clone(),
441 self.len(),
442 )
443 }
444 }
445
446 fn apply_extract_any_values(
447 &'a self,
448 py: Python,
449 lambda: &'a Bound<'a, PyAny>,
450 init_null_count: usize,
451 first_value: AnyValue<'a>,
452 ) -> PyResult<Series> {
453 let iter = self.into_iter().skip(init_null_count + 1);
454 let avs = extract_anyvalues(py, lambda, self.len(), init_null_count, iter, first_value)?;
455
456 Ok(Series::new(self.name().clone(), &avs))
457 }
458
459 #[cfg(feature = "object")]
460 fn apply_lambda_with_object_out_type(
461 &'a self,
462 py: Python,
463 lambda: &Bound<'a, PyAny>,
464 init_null_count: usize,
465 first_value: Option<ObjectValue>,
466 ) -> PyResult<ObjectChunked<ObjectValue>> {
467 let skip = usize::from(first_value.is_some());
468 if init_null_count == self.len() {
469 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
470 } else {
471 let it = self
472 .into_iter()
473 .skip(init_null_count + skip)
474 .map(|opt_val| {
475 opt_val
476 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
477 .transpose()
478 });
479 iterator_to_object(
480 it,
481 init_null_count,
482 first_value,
483 self.name().clone(),
484 self.len(),
485 )
486 }
487 }
488}
489
490impl<'a, T> ApplyLambda<'a> for ChunkedArray<T>
491where
492 T: PyPolarsNumericType,
493 T::Native: IntoPyObject<'a> + FromPyObject<'a>,
494 ChunkedArray<T>: IntoSeries,
495{
496 fn apply_lambda_unknown(
497 &'a self,
498 py: Python<'a>,
499 lambda: &'a Bound<'a, PyAny>,
500 ) -> PyResult<PySeries> {
501 let mut null_count = 0;
502 for opt_v in self.into_iter() {
503 if let Some(v) = opt_v {
504 let arg = PyTuple::new(py, [v])?;
505 let out = lambda.call1(arg)?;
506 if out.is_none() {
507 null_count += 1;
508 continue;
509 }
510 return infer_and_finish(self, py, lambda, &out, null_count);
511 } else {
512 null_count += 1
513 }
514 }
515 Ok(Self::full_null(self.name().clone(), self.len())
516 .into_series()
517 .into())
518 }
519
520 fn apply_into_struct(
521 &'a self,
522 py: Python<'a>,
523 lambda: &Bound<'a, PyAny>,
524 init_null_count: usize,
525 first_value: AnyValue<'a>,
526 ) -> PyResult<PySeries> {
527 let skip = 1;
528
529 let it = self
530 .into_iter()
531 .skip(init_null_count + skip)
532 .map(|opt_val| opt_val.map(|val| call_lambda(py, lambda, val)).transpose());
533 iterator_to_struct(
534 py,
535 it,
536 init_null_count,
537 first_value,
538 self.name().clone(),
539 self.len(),
540 )
541 }
542
543 fn apply_lambda_with_primitive_out_type<D>(
544 &'a self,
545 py: Python<'a>,
546 lambda: &Bound<'a, PyAny>,
547 init_null_count: usize,
548 first_value: Option<D::Native>,
549 ) -> PyResult<ChunkedArray<D>>
550 where
551 D: PyPolarsNumericType,
552 D::Native: IntoPyObject<'a> + FromPyObject<'a>,
553 {
554 let skip = usize::from(first_value.is_some());
555 if init_null_count == self.len() {
556 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
557 } else {
558 let it = self
559 .into_iter()
560 .skip(init_null_count + skip)
561 .map(|opt_val| {
562 opt_val
563 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
564 .transpose()
565 });
566 iterator_to_primitive(
567 it,
568 init_null_count,
569 first_value,
570 self.name().clone(),
571 self.len(),
572 )
573 }
574 }
575
576 fn apply_lambda_with_bool_out_type(
577 &'a self,
578 py: Python<'a>,
579 lambda: &Bound<'a, PyAny>,
580 init_null_count: usize,
581 first_value: Option<bool>,
582 ) -> PyResult<BooleanChunked> {
583 let skip = usize::from(first_value.is_some());
584 if init_null_count == self.len() {
585 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
586 } else {
587 let it = self
588 .into_iter()
589 .skip(init_null_count + skip)
590 .map(|opt_val| {
591 opt_val
592 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
593 .transpose()
594 });
595 iterator_to_bool(
596 it,
597 init_null_count,
598 first_value,
599 self.name().clone(),
600 self.len(),
601 )
602 }
603 }
604
605 fn apply_lambda_with_string_out_type(
606 &'a self,
607 py: Python<'a>,
608 lambda: &Bound<'a, PyAny>,
609 init_null_count: usize,
610 first_value: Option<PyBackedStr>,
611 ) -> PyResult<StringChunked> {
612 let skip = usize::from(first_value.is_some());
613 if init_null_count == self.len() {
614 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
615 } else {
616 let it = self
617 .into_iter()
618 .skip(init_null_count + skip)
619 .map(|opt_val| {
620 opt_val
621 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
622 .transpose()
623 });
624 iterator_to_string(
625 it,
626 init_null_count,
627 first_value,
628 self.name().clone(),
629 self.len(),
630 )
631 }
632 }
633
634 fn apply_lambda_with_list_out_type(
635 &'a self,
636 py: Python<'a>,
637 lambda: PyObject,
638 init_null_count: usize,
639 first_value: Option<&Series>,
640 dt: &DataType,
641 ) -> PyResult<ListChunked> {
642 let skip = usize::from(first_value.is_some());
643 let lambda = lambda.bind(py);
644 if init_null_count == self.len() {
645 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
646 } else {
647 let it = self
648 .into_iter()
649 .skip(init_null_count + skip)
650 .map(|opt_val| {
651 opt_val
652 .map(|val| call_lambda_series_out(py, lambda, val))
653 .transpose()
654 });
655 iterator_to_list(
656 dt,
657 it,
658 init_null_count,
659 first_value,
660 self.name().clone(),
661 self.len(),
662 )
663 }
664 }
665
666 fn apply_extract_any_values(
667 &'a self,
668 py: Python<'a>,
669 lambda: &'a Bound<'a, PyAny>,
670 init_null_count: usize,
671 first_value: AnyValue<'a>,
672 ) -> PyResult<Series> {
673 let iter = self.into_iter().skip(init_null_count + 1);
674 let avs = extract_anyvalues(py, lambda, self.len(), init_null_count, iter, first_value)?;
675
676 Ok(Series::new(self.name().clone(), &avs))
677 }
678
679 #[cfg(feature = "object")]
680 fn apply_lambda_with_object_out_type(
681 &'a self,
682 py: Python<'a>,
683 lambda: &Bound<'a, PyAny>,
684 init_null_count: usize,
685 first_value: Option<ObjectValue>,
686 ) -> PyResult<ObjectChunked<ObjectValue>> {
687 let skip = usize::from(first_value.is_some());
688 if init_null_count == self.len() {
689 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
690 } else {
691 let it = self
692 .into_iter()
693 .skip(init_null_count + skip)
694 .map(|opt_val| {
695 opt_val
696 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
697 .transpose()
698 });
699 iterator_to_object(
700 it,
701 init_null_count,
702 first_value,
703 self.name().clone(),
704 self.len(),
705 )
706 }
707 }
708}
709
710impl<'a> ApplyLambda<'a> for StringChunked {
711 fn apply_lambda_unknown(
712 &'a self,
713 py: Python,
714 lambda: &'a Bound<'a, PyAny>,
715 ) -> PyResult<PySeries> {
716 let mut null_count = 0;
717 for opt_v in self.into_iter() {
718 if let Some(v) = opt_v {
719 let arg = PyTuple::new(py, [v])?;
720 let out = lambda.call1(arg)?;
721 if out.is_none() {
722 null_count += 1;
723 continue;
724 }
725 return infer_and_finish(self, py, lambda, &out, null_count);
726 } else {
727 null_count += 1
728 }
729 }
730 Ok(Self::full_null(self.name().clone(), self.len())
731 .into_series()
732 .into())
733 }
734
735 fn apply_into_struct(
736 &'a self,
737 py: Python,
738 lambda: &Bound<'a, PyAny>,
739 init_null_count: usize,
740 first_value: AnyValue<'a>,
741 ) -> PyResult<PySeries> {
742 let skip = 1;
743
744 let it = self
745 .into_iter()
746 .skip(init_null_count + skip)
747 .map(|opt_val| opt_val.map(|val| call_lambda(py, lambda, val)).transpose());
748 iterator_to_struct(
749 py,
750 it,
751 init_null_count,
752 first_value,
753 self.name().clone(),
754 self.len(),
755 )
756 }
757
758 fn apply_lambda_with_primitive_out_type<D>(
759 &'a self,
760 py: Python<'a>,
761 lambda: &Bound<'a, PyAny>,
762 init_null_count: usize,
763 first_value: Option<D::Native>,
764 ) -> PyResult<ChunkedArray<D>>
765 where
766 D: PyPolarsNumericType,
767 D::Native: IntoPyObject<'a> + FromPyObject<'a>,
768 {
769 let skip = usize::from(first_value.is_some());
770 if init_null_count == self.len() {
771 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
772 } else {
773 let it = self
774 .into_iter()
775 .skip(init_null_count + skip)
776 .map(|opt_val| {
777 opt_val
778 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
779 .transpose()
780 });
781 iterator_to_primitive(
782 it,
783 init_null_count,
784 first_value,
785 self.name().clone(),
786 self.len(),
787 )
788 }
789 }
790
791 fn apply_lambda_with_bool_out_type(
792 &'a self,
793 py: Python,
794 lambda: &Bound<'a, PyAny>,
795 init_null_count: usize,
796 first_value: Option<bool>,
797 ) -> PyResult<BooleanChunked> {
798 let skip = usize::from(first_value.is_some());
799 if init_null_count == self.len() {
800 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
801 } else {
802 let it = self
803 .into_iter()
804 .skip(init_null_count + skip)
805 .map(|opt_val| {
806 opt_val
807 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
808 .transpose()
809 });
810 iterator_to_bool(
811 it,
812 init_null_count,
813 first_value,
814 self.name().clone(),
815 self.len(),
816 )
817 }
818 }
819
820 fn apply_lambda_with_string_out_type(
821 &self,
822 py: Python,
823 lambda: &Bound<'a, PyAny>,
824 init_null_count: usize,
825 first_value: Option<PyBackedStr>,
826 ) -> PyResult<StringChunked> {
827 let skip = usize::from(first_value.is_some());
828 if init_null_count == self.len() {
829 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
830 } else {
831 let it = self
832 .into_iter()
833 .skip(init_null_count + skip)
834 .map(|opt_val| {
835 opt_val
836 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
837 .transpose()
838 });
839 iterator_to_string(
840 it,
841 init_null_count,
842 first_value,
843 self.name().clone(),
844 self.len(),
845 )
846 }
847 }
848 fn apply_lambda_with_list_out_type(
849 &'a self,
850 py: Python,
851 lambda: PyObject,
852 init_null_count: usize,
853 first_value: Option<&Series>,
854 dt: &DataType,
855 ) -> PyResult<ListChunked> {
856 let skip = usize::from(first_value.is_some());
857 let lambda = lambda.bind(py);
858 if init_null_count == self.len() {
859 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
860 } else {
861 let it = self
862 .into_iter()
863 .skip(init_null_count + skip)
864 .map(|opt_val| {
865 opt_val
866 .map(|val| call_lambda_series_out(py, lambda, val))
867 .transpose()
868 });
869 iterator_to_list(
870 dt,
871 it,
872 init_null_count,
873 first_value,
874 self.name().clone(),
875 self.len(),
876 )
877 }
878 }
879
880 fn apply_extract_any_values(
881 &'a self,
882 py: Python<'a>,
883 lambda: &'a Bound<'a, PyAny>,
884 init_null_count: usize,
885 first_value: AnyValue<'a>,
886 ) -> PyResult<Series> {
887 let iter = self.into_iter().skip(init_null_count + 1);
888 let avs = extract_anyvalues(py, lambda, self.len(), init_null_count, iter, first_value)?;
889 Ok(Series::new(self.name().clone(), &avs))
890 }
891
892 #[cfg(feature = "object")]
893 fn apply_lambda_with_object_out_type(
894 &'a self,
895 py: Python,
896 lambda: &Bound<'a, PyAny>,
897 init_null_count: usize,
898 first_value: Option<ObjectValue>,
899 ) -> PyResult<ObjectChunked<ObjectValue>> {
900 let skip = usize::from(first_value.is_some());
901 if init_null_count == self.len() {
902 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
903 } else {
904 let it = self
905 .into_iter()
906 .skip(init_null_count + skip)
907 .map(|opt_val| {
908 opt_val
909 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
910 .transpose()
911 });
912 iterator_to_object(
913 it,
914 init_null_count,
915 first_value,
916 self.name().clone(),
917 self.len(),
918 )
919 }
920 }
921}
922
923fn call_series_lambda(
924 pypolars: &Bound<PyModule>,
925 lambda: &Bound<PyAny>,
926 series: Series,
927) -> PyResult<Option<Series>> {
928 let pyseries = PySeries::new(series);
930 let python_series_wrapper = pypolars
932 .getattr("wrap_s")
933 .unwrap()
934 .call1((pyseries,))
935 .unwrap();
936
937 let out = lambda.call1((python_series_wrapper,))?;
939 let py_pyseries = out
941 .getattr("_s")
942 .expect("could not get Series attribute '_s'");
943 Ok(py_pyseries.extract::<PySeries>().ok().map(|s| s.series))
944}
945
946impl<'a> ApplyLambda<'a> for ListChunked {
947 fn apply_lambda_unknown(
948 &'a self,
949 py: Python,
950 lambda: &'a Bound<'a, PyAny>,
951 ) -> PyResult<PySeries> {
952 let pypolars = polars(py).bind(py);
953 let mut null_count = 0;
954 for opt_v in self.into_iter() {
955 if let Some(v) = opt_v {
956 let pyseries = PySeries::new(v);
958 let python_series_wrapper = pypolars
960 .getattr("wrap_s")
961 .unwrap()
962 .call1((pyseries,))
963 .unwrap();
964
965 let out = lambda.call1((python_series_wrapper,))?;
966 if out.is_none() {
967 null_count += 1;
968 continue;
969 }
970 return infer_and_finish(self, py, lambda, &out, null_count);
971 } else {
972 null_count += 1
973 }
974 }
975 Ok(Self::full_null(self.name().clone(), self.len())
976 .into_series()
977 .into())
978 }
979
980 fn apply_into_struct(
981 &'a self,
982 py: Python,
983 lambda: &Bound<'a, PyAny>,
984 init_null_count: usize,
985 first_value: AnyValue<'a>,
986 ) -> PyResult<PySeries> {
987 let skip = 1;
988 let pypolars = polars(py).bind(py);
990
991 let it = self
992 .into_iter()
993 .skip(init_null_count + skip)
994 .map(|opt_val| {
995 opt_val
996 .map(|val| {
997 let pyseries = PySeries::new(val);
999 let python_series_wrapper = pypolars
1001 .getattr("wrap_s")
1002 .unwrap()
1003 .call1((pyseries,))
1004 .unwrap();
1005 call_lambda(py, lambda, python_series_wrapper)
1006 })
1007 .transpose()
1008 });
1009 iterator_to_struct(
1010 py,
1011 it,
1012 init_null_count,
1013 first_value,
1014 self.name().clone(),
1015 self.len(),
1016 )
1017 }
1018
1019 fn apply_lambda_with_primitive_out_type<D>(
1020 &'a self,
1021 py: Python<'a>,
1022 lambda: &Bound<'a, PyAny>,
1023 init_null_count: usize,
1024 first_value: Option<D::Native>,
1025 ) -> PyResult<ChunkedArray<D>>
1026 where
1027 D: PyPolarsNumericType,
1028 D::Native: IntoPyObject<'a> + FromPyObject<'a>,
1029 {
1030 let skip = usize::from(first_value.is_some());
1031 let pypolars = polars(py).bind(py);
1032 if init_null_count == self.len() {
1033 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1034 } else {
1035 let it = self
1036 .into_iter()
1037 .skip(init_null_count + skip)
1038 .map(|opt_val| {
1039 opt_val
1040 .and_then(|val| {
1041 let pyseries = PySeries::new(val);
1043 let python_series_wrapper = pypolars
1045 .getattr("wrap_s")
1046 .unwrap()
1047 .call1((pyseries,))
1048 .unwrap();
1049 call_lambda_and_extract(py, lambda, python_series_wrapper).transpose()
1050 })
1051 .transpose()
1052 });
1053 iterator_to_primitive(
1054 it,
1055 init_null_count,
1056 first_value,
1057 self.name().clone(),
1058 self.len(),
1059 )
1060 }
1061 }
1062
1063 fn apply_lambda_with_bool_out_type(
1064 &'a self,
1065 py: Python,
1066 lambda: &Bound<'a, PyAny>,
1067 init_null_count: usize,
1068 first_value: Option<bool>,
1069 ) -> PyResult<BooleanChunked> {
1070 let skip = usize::from(first_value.is_some());
1071 let pypolars = polars(py).bind(py);
1072 if init_null_count == self.len() {
1073 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1074 } else {
1075 let it = self
1076 .into_iter()
1077 .skip(init_null_count + skip)
1078 .map(|opt_val| {
1079 opt_val
1080 .and_then(|val| {
1081 let pyseries = PySeries::new(val);
1083 let python_series_wrapper = pypolars
1085 .getattr("wrap_s")
1086 .unwrap()
1087 .call1((pyseries,))
1088 .unwrap();
1089 call_lambda_and_extract(py, lambda, python_series_wrapper).transpose()
1090 })
1091 .transpose()
1092 });
1093 iterator_to_bool(
1094 it,
1095 init_null_count,
1096 first_value,
1097 self.name().clone(),
1098 self.len(),
1099 )
1100 }
1101 }
1102
1103 fn apply_lambda_with_string_out_type(
1104 &'a self,
1105 py: Python,
1106 lambda: &Bound<'a, PyAny>,
1107 init_null_count: usize,
1108 first_value: Option<PyBackedStr>,
1109 ) -> PyResult<StringChunked> {
1110 let skip = usize::from(first_value.is_some());
1111 let pypolars = polars(py).bind(py);
1113
1114 if init_null_count == self.len() {
1115 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1116 } else {
1117 let it = self
1118 .into_iter()
1119 .skip(init_null_count + skip)
1120 .map(|opt_val| {
1121 opt_val
1122 .and_then(|val| {
1123 let pyseries = PySeries::new(val);
1125 let python_series_wrapper = pypolars
1127 .getattr("wrap_s")
1128 .unwrap()
1129 .call1((pyseries,))
1130 .unwrap();
1131 call_lambda_and_extract(py, lambda, python_series_wrapper).transpose()
1132 })
1133 .transpose()
1134 });
1135 iterator_to_string(
1136 it,
1137 init_null_count,
1138 first_value,
1139 self.name().clone(),
1140 self.len(),
1141 )
1142 }
1143 }
1144 fn apply_lambda_with_list_out_type(
1145 &'a self,
1146 py: Python,
1147 lambda: PyObject,
1148 init_null_count: usize,
1149 first_value: Option<&Series>,
1150 dt: &DataType,
1151 ) -> PyResult<ListChunked> {
1152 let skip = usize::from(first_value.is_some());
1153 let pypolars = polars(py).bind(py);
1154 let lambda = lambda.bind(py);
1155 if init_null_count == self.len() {
1156 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1157 } else {
1158 let it = self
1159 .into_iter()
1160 .skip(init_null_count + skip)
1161 .map(|opt_val| {
1162 opt_val
1163 .map(|val| call_series_lambda(pypolars, lambda, val))
1164 .transpose()
1165 .map(|v| v.flatten())
1166 });
1167 iterator_to_list(
1168 dt,
1169 it,
1170 init_null_count,
1171 first_value,
1172 self.name().clone(),
1173 self.len(),
1174 )
1175 }
1176 }
1177
1178 fn apply_extract_any_values(
1179 &'a self,
1180 py: Python<'a>,
1181 lambda: &'a Bound<'a, PyAny>,
1182 init_null_count: usize,
1183 first_value: AnyValue<'a>,
1184 ) -> PyResult<Series> {
1185 let pypolars = polars(py).bind(py);
1186 let mut avs = Vec::with_capacity(self.len());
1187 avs.extend(std::iter::repeat(AnyValue::Null).take(init_null_count));
1188 avs.push(first_value);
1189
1190 let call_with_value = |val: Series| {
1191 let pyseries = PySeries::new(val);
1193 let python_series_wrapper = pypolars
1195 .getattr("wrap_s")
1196 .unwrap()
1197 .call1((pyseries,))
1198 .unwrap();
1199 call_lambda_and_extract::<_, Wrap<AnyValue>>(py, lambda, python_series_wrapper).map(
1200 |opt_wrap| match opt_wrap {
1201 None => AnyValue::Null,
1202 Some(w) => w.0,
1203 },
1204 )
1205 };
1206
1207 for opt_val in self.into_iter().skip(init_null_count + 1) {
1208 if let Some(s) = opt_val {
1209 let av = call_with_value(s)?;
1210 avs.push(av);
1211 } else {
1212 avs.push(AnyValue::Null);
1213 }
1214 }
1215 Ok(Series::new(self.name().clone(), &avs))
1216 }
1217
1218 #[cfg(feature = "object")]
1219 fn apply_lambda_with_object_out_type(
1220 &'a self,
1221 py: Python,
1222 lambda: &Bound<'a, PyAny>,
1223 init_null_count: usize,
1224 first_value: Option<ObjectValue>,
1225 ) -> PyResult<ObjectChunked<ObjectValue>> {
1226 let skip = usize::from(first_value.is_some());
1227 let pypolars = polars(py).bind(py);
1228 if init_null_count == self.len() {
1229 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1230 } else {
1231 let it = self
1232 .into_iter()
1233 .skip(init_null_count + skip)
1234 .map(|opt_val| {
1235 opt_val
1236 .and_then(|val| {
1237 let pyseries = PySeries::new(val);
1239 let python_series_wrapper = pypolars
1241 .getattr("wrap_s")
1242 .unwrap()
1243 .call1((pyseries,))
1244 .unwrap();
1245 call_lambda_and_extract(py, lambda, python_series_wrapper).transpose()
1246 })
1247 .transpose()
1248 });
1249 iterator_to_object(
1250 it,
1251 init_null_count,
1252 first_value,
1253 self.name().clone(),
1254 self.len(),
1255 )
1256 }
1257 }
1258}
1259
1260#[cfg(feature = "dtype-array")]
1261impl<'a> ApplyLambda<'a> for ArrayChunked {
1262 fn apply_lambda_unknown(
1263 &'a self,
1264 py: Python,
1265 lambda: &'a Bound<'a, PyAny>,
1266 ) -> PyResult<PySeries> {
1267 let pypolars = polars(py).bind(py);
1268 let mut null_count = 0;
1269 for opt_v in self.into_iter() {
1270 if let Some(v) = opt_v {
1271 let pyseries = PySeries::new(v);
1273 let python_series_wrapper = pypolars
1275 .getattr("wrap_s")
1276 .unwrap()
1277 .call1((pyseries,))
1278 .unwrap();
1279
1280 let out = lambda.call1((python_series_wrapper,))?;
1281 if out.is_none() {
1282 null_count += 1;
1283 continue;
1284 }
1285 return infer_and_finish(self, py, lambda, &out, null_count);
1286 } else {
1287 null_count += 1
1288 }
1289 }
1290 Ok(Self::full_null(self.name().clone(), self.len())
1291 .into_series()
1292 .into())
1293 }
1294
1295 fn apply_into_struct(
1296 &'a self,
1297 py: Python,
1298 lambda: &Bound<'a, PyAny>,
1299 init_null_count: usize,
1300 first_value: AnyValue<'a>,
1301 ) -> PyResult<PySeries> {
1302 let skip = 1;
1303 let pypolars = polars(py).bind(py);
1305
1306 let it = self
1307 .into_iter()
1308 .skip(init_null_count + skip)
1309 .map(|opt_val| {
1310 opt_val
1311 .map(|val| {
1312 let pyseries = PySeries::new(val);
1314 let python_series_wrapper = pypolars
1316 .getattr("wrap_s")
1317 .unwrap()
1318 .call1((pyseries,))
1319 .unwrap();
1320 call_lambda(py, lambda, python_series_wrapper)
1321 })
1322 .transpose()
1323 });
1324 iterator_to_struct(
1325 py,
1326 it,
1327 init_null_count,
1328 first_value,
1329 self.name().clone(),
1330 self.len(),
1331 )
1332 }
1333
1334 fn apply_lambda_with_primitive_out_type<D>(
1335 &'a self,
1336 py: Python<'a>,
1337 lambda: &Bound<'a, PyAny>,
1338 init_null_count: usize,
1339 first_value: Option<D::Native>,
1340 ) -> PyResult<ChunkedArray<D>>
1341 where
1342 D: PyPolarsNumericType,
1343 D::Native: IntoPyObject<'a> + FromPyObject<'a>,
1344 {
1345 let skip = usize::from(first_value.is_some());
1346 let pypolars = polars(py).bind(py);
1347 if init_null_count == self.len() {
1348 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1349 } else {
1350 let it = self
1351 .into_iter()
1352 .skip(init_null_count + skip)
1353 .map(|opt_val| {
1354 opt_val
1355 .and_then(|val| {
1356 let pyseries = PySeries::new(val);
1358 let python_series_wrapper = pypolars
1360 .getattr("wrap_s")
1361 .unwrap()
1362 .call1((pyseries,))
1363 .unwrap();
1364 call_lambda_and_extract(py, lambda, python_series_wrapper).transpose()
1365 })
1366 .transpose()
1367 });
1368 iterator_to_primitive(
1369 it,
1370 init_null_count,
1371 first_value,
1372 self.name().clone(),
1373 self.len(),
1374 )
1375 }
1376 }
1377
1378 fn apply_lambda_with_bool_out_type(
1379 &'a self,
1380 py: Python,
1381 lambda: &Bound<'a, PyAny>,
1382 init_null_count: usize,
1383 first_value: Option<bool>,
1384 ) -> PyResult<BooleanChunked> {
1385 let skip = usize::from(first_value.is_some());
1386 let pypolars = polars(py).bind(py);
1387 if init_null_count == self.len() {
1388 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1389 } else {
1390 let it = self
1391 .into_iter()
1392 .skip(init_null_count + skip)
1393 .map(|opt_val| {
1394 opt_val
1395 .and_then(|val| {
1396 let pyseries = PySeries::new(val);
1398 let python_series_wrapper = pypolars
1400 .getattr("wrap_s")
1401 .unwrap()
1402 .call1((pyseries,))
1403 .unwrap();
1404 call_lambda_and_extract(py, lambda, python_series_wrapper).transpose()
1405 })
1406 .transpose()
1407 });
1408 iterator_to_bool(
1409 it,
1410 init_null_count,
1411 first_value,
1412 self.name().clone(),
1413 self.len(),
1414 )
1415 }
1416 }
1417
1418 fn apply_lambda_with_string_out_type(
1419 &'a self,
1420 py: Python,
1421 lambda: &Bound<'a, PyAny>,
1422 init_null_count: usize,
1423 first_value: Option<PyBackedStr>,
1424 ) -> PyResult<StringChunked> {
1425 let skip = usize::from(first_value.is_some());
1426 let pypolars = polars(py).bind(py);
1428
1429 if init_null_count == self.len() {
1430 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1431 } else {
1432 let it = self
1433 .into_iter()
1434 .skip(init_null_count + skip)
1435 .map(|opt_val| {
1436 opt_val
1437 .and_then(|val| {
1438 let pyseries = PySeries::new(val);
1440 let python_series_wrapper = pypolars
1442 .getattr("wrap_s")
1443 .unwrap()
1444 .call1((pyseries,))
1445 .unwrap();
1446 call_lambda_and_extract(py, lambda, python_series_wrapper).transpose()
1447 })
1448 .transpose()
1449 });
1450 iterator_to_string(
1451 it,
1452 init_null_count,
1453 first_value,
1454 self.name().clone(),
1455 self.len(),
1456 )
1457 }
1458 }
1459 fn apply_lambda_with_list_out_type(
1460 &'a self,
1461 py: Python,
1462 lambda: PyObject,
1463 init_null_count: usize,
1464 first_value: Option<&Series>,
1465 dt: &DataType,
1466 ) -> PyResult<ListChunked> {
1467 let skip = usize::from(first_value.is_some());
1468 let pypolars = polars(py).bind(py);
1469 let lambda = lambda.bind(py);
1470 if init_null_count == self.len() {
1471 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1472 } else {
1473 let it = self
1474 .into_iter()
1475 .skip(init_null_count + skip)
1476 .map(|opt_val| {
1477 opt_val
1478 .map(|val| call_series_lambda(pypolars, lambda, val))
1479 .transpose()
1480 .map(|v| v.flatten())
1481 });
1482 iterator_to_list(
1483 dt,
1484 it,
1485 init_null_count,
1486 first_value,
1487 self.name().clone(),
1488 self.len(),
1489 )
1490 }
1491 }
1492
1493 fn apply_extract_any_values(
1494 &'a self,
1495 py: Python<'a>,
1496 lambda: &'a Bound<'a, PyAny>,
1497 init_null_count: usize,
1498 first_value: AnyValue<'a>,
1499 ) -> PyResult<Series> {
1500 let pypolars = polars(py).bind(py);
1501 let mut avs = Vec::with_capacity(self.len());
1502 avs.extend(std::iter::repeat(AnyValue::Null).take(init_null_count));
1503 avs.push(first_value);
1504
1505 let call_with_value = |val: Series| {
1506 let pyseries = PySeries::new(val);
1508 let python_series_wrapper = pypolars
1510 .getattr("wrap_s")
1511 .unwrap()
1512 .call1((pyseries,))
1513 .unwrap();
1514 call_lambda_and_extract::<_, Wrap<AnyValue>>(py, lambda, python_series_wrapper).map(
1515 |opt_wrap| match opt_wrap {
1516 None => AnyValue::Null,
1517 Some(w) => w.0,
1518 },
1519 )
1520 };
1521
1522 for opt_val in self.into_iter().skip(init_null_count + 1) {
1523 if let Some(s) = opt_val {
1524 let av = call_with_value(s)?;
1525 avs.push(av);
1526 } else {
1527 avs.push(AnyValue::Null);
1528 }
1529 }
1530
1531 Ok(Series::new(self.name().clone(), &avs))
1532 }
1533
1534 #[cfg(feature = "object")]
1535 fn apply_lambda_with_object_out_type(
1536 &'a self,
1537 py: Python,
1538 lambda: &Bound<'a, PyAny>,
1539 init_null_count: usize,
1540 first_value: Option<ObjectValue>,
1541 ) -> PyResult<ObjectChunked<ObjectValue>> {
1542 let skip = usize::from(first_value.is_some());
1543 let pypolars = polars(py).bind(py);
1544 if init_null_count == self.len() {
1545 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1546 } else {
1547 let it = self
1548 .into_iter()
1549 .skip(init_null_count + skip)
1550 .map(|opt_val| {
1551 opt_val
1552 .and_then(|val| {
1553 let pyseries = PySeries::new(val);
1555 let python_series_wrapper = pypolars
1557 .getattr("wrap_s")
1558 .unwrap()
1559 .call1((pyseries,))
1560 .unwrap();
1561 call_lambda_and_extract(py, lambda, python_series_wrapper).transpose()
1562 })
1563 .transpose()
1564 });
1565 iterator_to_object(
1566 it,
1567 init_null_count,
1568 first_value,
1569 self.name().clone(),
1570 self.len(),
1571 )
1572 }
1573 }
1574}
1575
1576#[cfg(feature = "object")]
1577impl<'a> ApplyLambda<'a> for ObjectChunked<ObjectValue> {
1578 fn apply_lambda_unknown(
1579 &'a self,
1580 py: Python,
1581 lambda: &'a Bound<'a, PyAny>,
1582 ) -> PyResult<PySeries> {
1583 let mut null_count = 0;
1584 for opt_v in self.into_iter() {
1585 if let Some(v) = opt_v {
1586 let arg = PyTuple::new(py, [v])?;
1587 let out = lambda.call1(arg)?;
1588 if out.is_none() {
1589 null_count += 1;
1590 continue;
1591 }
1592 return infer_and_finish(self, py, lambda, &out, null_count);
1593 } else {
1594 null_count += 1
1595 }
1596 }
1597 Ok(Self::full_null(self.name().clone(), self.len())
1598 .into_series()
1599 .into())
1600 }
1601
1602 fn apply_into_struct(
1603 &'a self,
1604 py: Python<'a>,
1605 lambda: &Bound<'a, PyAny>,
1606 init_null_count: usize,
1607 first_value: AnyValue<'a>,
1608 ) -> PyResult<PySeries> {
1609 let skip = 1;
1610 let it = self
1611 .into_iter()
1612 .skip(init_null_count + skip)
1613 .map(|object_value| lambda.call1((object_value.map(|v| &v.inner),)).map(Some));
1614 iterator_to_struct(
1615 py,
1616 it,
1617 init_null_count,
1618 first_value,
1619 self.name().clone(),
1620 self.len(),
1621 )
1622 }
1623
1624 fn apply_lambda_with_primitive_out_type<D>(
1625 &'a self,
1626 py: Python<'a>,
1627 lambda: &Bound<'a, PyAny>,
1628 init_null_count: usize,
1629 first_value: Option<D::Native>,
1630 ) -> PyResult<ChunkedArray<D>>
1631 where
1632 D: PyPolarsNumericType,
1633 D::Native: IntoPyObject<'a> + FromPyObject<'a>,
1634 {
1635 let skip = usize::from(first_value.is_some());
1636 if init_null_count == self.len() {
1637 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1638 } else {
1639 let it = self
1640 .into_iter()
1641 .skip(init_null_count + skip)
1642 .map(|opt_val| {
1643 opt_val
1644 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
1645 .transpose()
1646 });
1647 iterator_to_primitive(
1648 it,
1649 init_null_count,
1650 first_value,
1651 self.name().clone(),
1652 self.len(),
1653 )
1654 }
1655 }
1656
1657 fn apply_lambda_with_bool_out_type(
1658 &'a self,
1659 py: Python,
1660 lambda: &Bound<'a, PyAny>,
1661 init_null_count: usize,
1662 first_value: Option<bool>,
1663 ) -> PyResult<BooleanChunked> {
1664 let skip = usize::from(first_value.is_some());
1665 if init_null_count == self.len() {
1666 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1667 } else {
1668 let it = self
1669 .into_iter()
1670 .skip(init_null_count + skip)
1671 .map(|opt_val| {
1672 opt_val
1673 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
1674 .transpose()
1675 });
1676 iterator_to_bool(
1677 it,
1678 init_null_count,
1679 first_value,
1680 self.name().clone(),
1681 self.len(),
1682 )
1683 }
1684 }
1685
1686 fn apply_lambda_with_string_out_type(
1687 &'a self,
1688 py: Python,
1689 lambda: &Bound<'a, PyAny>,
1690 init_null_count: usize,
1691 first_value: Option<PyBackedStr>,
1692 ) -> PyResult<StringChunked> {
1693 let skip = usize::from(first_value.is_some());
1694 if init_null_count == self.len() {
1695 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1696 } else {
1697 let it = self
1698 .into_iter()
1699 .skip(init_null_count + skip)
1700 .map(|opt_val| {
1701 opt_val
1702 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
1703 .transpose()
1704 });
1705 iterator_to_string(
1706 it,
1707 init_null_count,
1708 first_value,
1709 self.name().clone(),
1710 self.len(),
1711 )
1712 }
1713 }
1714
1715 fn apply_lambda_with_list_out_type(
1716 &'a self,
1717 py: Python,
1718 lambda: PyObject,
1719 init_null_count: usize,
1720 first_value: Option<&Series>,
1721 dt: &DataType,
1722 ) -> PyResult<ListChunked> {
1723 let skip = usize::from(first_value.is_some());
1724 let lambda = lambda.bind(py);
1725 if init_null_count == self.len() {
1726 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1727 } else {
1728 let it = self
1729 .into_iter()
1730 .skip(init_null_count + skip)
1731 .map(|opt_val| {
1732 opt_val
1733 .map(|val| call_lambda_series_out(py, lambda, val))
1734 .transpose()
1735 });
1736 iterator_to_list(
1737 dt,
1738 it,
1739 init_null_count,
1740 first_value,
1741 self.name().clone(),
1742 self.len(),
1743 )
1744 }
1745 }
1746
1747 fn apply_extract_any_values(
1748 &'a self,
1749 py: Python<'a>,
1750 lambda: &'a Bound<'a, PyAny>,
1751 init_null_count: usize,
1752 first_value: AnyValue<'a>,
1753 ) -> PyResult<Series> {
1754 let iter = self.into_iter().skip(init_null_count + 1);
1755 let avs = extract_anyvalues(py, lambda, self.len(), init_null_count, iter, first_value)?;
1756 Ok(Series::new(self.name().clone(), &avs))
1757 }
1758
1759 #[cfg(feature = "object")]
1760 fn apply_lambda_with_object_out_type(
1761 &'a self,
1762 py: Python,
1763 lambda: &Bound<'a, PyAny>,
1764 init_null_count: usize,
1765 first_value: Option<ObjectValue>,
1766 ) -> PyResult<ObjectChunked<ObjectValue>> {
1767 let skip = usize::from(first_value.is_some());
1768 if init_null_count == self.len() {
1769 Ok(ChunkedArray::full_null(self.name().clone(), self.len()))
1770 } else {
1771 let it = self
1772 .into_iter()
1773 .skip(init_null_count + skip)
1774 .map(|opt_val| {
1775 opt_val
1776 .and_then(|val| call_lambda_and_extract(py, lambda, val).transpose())
1777 .transpose()
1778 });
1779 iterator_to_object(
1780 it,
1781 init_null_count,
1782 first_value,
1783 self.name().clone(),
1784 self.len(),
1785 )
1786 }
1787 }
1788}
1789
1790fn iter_struct(ca: &StructChunked) -> impl Iterator<Item = AnyValue> {
1791 (0..ca.len()).map(|i| unsafe { ca.get_any_value_unchecked(i) })
1792}
1793
1794impl<'a> ApplyLambda<'a> for StructChunked {
1795 fn apply_lambda_unknown(
1796 &'a self,
1797 py: Python,
1798 lambda: &'a Bound<'a, PyAny>,
1799 ) -> PyResult<PySeries> {
1800 let mut null_count = 0;
1801
1802 for val in iter_struct(self) {
1803 match val {
1804 AnyValue::Null => null_count += 1,
1805 _ => {
1806 let out = lambda.call1((Wrap(val),))?;
1807 if out.is_none() {
1808 null_count += 1;
1809 continue;
1810 }
1811 return infer_and_finish(self, py, lambda, &out, null_count);
1812 },
1813 }
1814 }
1815
1816 Ok(Self::full_null(self.name().clone(), self.len())
1817 .into_series()
1818 .into())
1819 }
1820
1821 fn apply_into_struct(
1822 &'a self,
1823 py: Python,
1824 lambda: &Bound<'a, PyAny>,
1825 init_null_count: usize,
1826 first_value: AnyValue<'a>,
1827 ) -> PyResult<PySeries> {
1828 let skip = 1;
1829 let it = iter_struct(self)
1830 .skip(init_null_count + skip)
1831 .map(|val| match val {
1832 AnyValue::Null => Ok(None),
1833 _ => lambda.call1((Wrap(val),)).map(Some),
1834 });
1835 iterator_to_struct(
1836 py,
1837 it,
1838 init_null_count,
1839 first_value,
1840 self.name().clone(),
1841 self.len(),
1842 )
1843 }
1844
1845 fn apply_lambda_with_primitive_out_type<D>(
1846 &'a self,
1847 py: Python<'a>,
1848 lambda: &Bound<'a, PyAny>,
1849 init_null_count: usize,
1850 first_value: Option<D::Native>,
1851 ) -> PyResult<ChunkedArray<D>>
1852 where
1853 D: PyPolarsNumericType,
1854 D::Native: IntoPyObject<'a> + FromPyObject<'a>,
1855 {
1856 let skip = usize::from(first_value.is_some());
1857 let it = iter_struct(self)
1858 .skip(init_null_count + skip)
1859 .map(|val| match val {
1860 AnyValue::Null => Ok(None),
1861 _ => call_lambda_and_extract(py, lambda, Wrap(val)),
1862 });
1863
1864 iterator_to_primitive(
1865 it,
1866 init_null_count,
1867 first_value,
1868 self.name().clone(),
1869 self.len(),
1870 )
1871 }
1872
1873 fn apply_lambda_with_bool_out_type(
1874 &'a self,
1875 py: Python,
1876 lambda: &Bound<'a, PyAny>,
1877 init_null_count: usize,
1878 first_value: Option<bool>,
1879 ) -> PyResult<BooleanChunked> {
1880 let skip = usize::from(first_value.is_some());
1881 let it = iter_struct(self)
1882 .skip(init_null_count + skip)
1883 .map(|val| match val {
1884 AnyValue::Null => Ok(None),
1885 _ => call_lambda_and_extract(py, lambda, Wrap(val)),
1886 });
1887
1888 iterator_to_bool(
1889 it,
1890 init_null_count,
1891 first_value,
1892 self.name().clone(),
1893 self.len(),
1894 )
1895 }
1896
1897 fn apply_lambda_with_string_out_type(
1898 &'a self,
1899 py: Python,
1900 lambda: &Bound<'a, PyAny>,
1901 init_null_count: usize,
1902 first_value: Option<PyBackedStr>,
1903 ) -> PyResult<StringChunked> {
1904 let skip = usize::from(first_value.is_some());
1905 let it = iter_struct(self)
1906 .skip(init_null_count + skip)
1907 .map(|val| match val {
1908 AnyValue::Null => Ok(None),
1909 _ => call_lambda_and_extract(py, lambda, Wrap(val)),
1910 });
1911
1912 iterator_to_string(
1913 it,
1914 init_null_count,
1915 first_value,
1916 self.name().clone(),
1917 self.len(),
1918 )
1919 }
1920 fn apply_lambda_with_list_out_type(
1921 &'a self,
1922 py: Python,
1923 lambda: PyObject,
1924 init_null_count: usize,
1925 first_value: Option<&Series>,
1926 dt: &DataType,
1927 ) -> PyResult<ListChunked> {
1928 let skip = usize::from(first_value.is_some());
1929 let lambda = lambda.bind(py);
1930 let it = iter_struct(self)
1931 .skip(init_null_count + skip)
1932 .map(|val| match val {
1933 AnyValue::Null => Ok(None),
1934 _ => call_lambda_series_out(py, lambda, Wrap(val)).map(Some),
1935 });
1936 iterator_to_list(
1937 dt,
1938 it,
1939 init_null_count,
1940 first_value,
1941 self.name().clone(),
1942 self.len(),
1943 )
1944 }
1945
1946 fn apply_extract_any_values(
1947 &'a self,
1948 py: Python<'a>,
1949 lambda: &'a Bound<'a, PyAny>,
1950 init_null_count: usize,
1951 first_value: AnyValue<'a>,
1952 ) -> PyResult<Series> {
1953 let mut avs = Vec::with_capacity(self.len());
1954 avs.extend(std::iter::repeat(AnyValue::Null).take(init_null_count));
1955 avs.push(first_value);
1956
1957 for val in iter_struct(self).skip(init_null_count + 1) {
1958 let av: Option<Wrap<AnyValue>> = call_lambda_and_extract(py, lambda, Wrap(val))?;
1959 let out = match av {
1960 None => AnyValue::Null,
1961 Some(av) => av.0,
1962 };
1963 avs.push(out)
1964 }
1965
1966 Ok(Series::new(self.name().clone(), &avs))
1967 }
1968
1969 #[cfg(feature = "object")]
1970 fn apply_lambda_with_object_out_type(
1971 &'a self,
1972 py: Python,
1973 lambda: &Bound<'a, PyAny>,
1974 init_null_count: usize,
1975 first_value: Option<ObjectValue>,
1976 ) -> PyResult<ObjectChunked<ObjectValue>> {
1977 let skip = usize::from(first_value.is_some());
1978 let it = iter_struct(self)
1979 .skip(init_null_count + skip)
1980 .map(|val| match val {
1981 AnyValue::Null => Ok(None),
1982 _ => call_lambda_and_extract(py, lambda, Wrap(val)),
1983 });
1984
1985 iterator_to_object(
1986 it,
1987 init_null_count,
1988 first_value,
1989 self.name().clone(),
1990 self.len(),
1991 )
1992 }
1993}