@@ -1865,18 +1865,14 @@ cdef class Array(_PandasConvertible):
18651865 """
18661866 self ._assert_cpu()
18671867 cdef int64_t i, n = self .length()
1868- if maps_as_pydicts is not None :
1869- # Converting maps to dicts has per-entry semantics (duplicate-key
1870- # detection); use the Scalar-based conversion for exact behavior.
1871- return [x.as_py(maps_as_pydicts = maps_as_pydicts) for x in self ]
1872- return [self ._getitem_py(i) for i in range (n)]
1868+ return [self ._getitem_py(i, maps_as_pydicts) for i in range (n)]
18731869
1874- cdef object _getitem_py(self , int64_t i):
1870+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
18751871 # Return self[i] as a Python object, without creating a Python Scalar
18761872 # (nor, for nested types, per-row Array wrappers) where a subclass
18771873 # provides a specialization; this base implementation goes through
18781874 # Scalar.as_py and thus preserves its semantics exactly (see GH-50326).
1879- return self .getitem(i).as_py()
1875+ return self .getitem(i).as_py(maps_as_pydicts = maps_as_pydicts )
18801876
18811877 def tolist (self ):
18821878 """
@@ -2457,7 +2453,7 @@ cdef class BooleanArray(Array):
24572453 Concrete class for Arrow arrays of boolean data type.
24582454 """
24592455
2460- cdef object _getitem_py(self , int64_t i):
2456+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
24612457 if self .ap.IsNull(i):
24622458 return None
24632459 return (< CBooleanArray* > self .ap).Value(i)
@@ -2476,7 +2472,7 @@ cdef class NumericArray(Array):
24762472 A base class for Arrow numeric arrays.
24772473 """
24782474
2479- cdef object _getitem_py(self , int64_t i):
2475+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
24802476 cdef Type tid = self .ap.type_id()
24812477 if self .ap.IsNull(i):
24822478 return None
@@ -2502,7 +2498,7 @@ cdef class NumericArray(Array):
25022498 return (< CUInt8Array* > self .ap).Value(i)
25032499 # Subclasses whose as_py returns non-primitive objects (dates, times,
25042500 # timestamps, durations, half floats, ...) use the exact Scalar path.
2505- return Array._getitem_py(self , i)
2501+ return Array._getitem_py(self , i, maps_as_pydicts )
25062502
25072503
25082504cdef class IntegerArray(NumericArray):
@@ -2822,15 +2818,15 @@ cdef class ListArray(BaseListArray):
28222818 Concrete class for Arrow arrays of a list data type.
28232819 """
28242820
2825- cdef object _getitem_py(self , int64_t i):
2821+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
28262822 cdef CListArray* arr = < CListArray* > self .ap
28272823 if arr.IsNull(i):
28282824 return None
28292825 if self ._children_cache is None :
28302826 self ._children_cache = pyarrow_wrap_array(arr.values())
28312827 cdef Array values = < Array> self ._children_cache
28322828 cdef int64_t j, start = arr.value_offset(i), end = arr.value_offset(i + 1 )
2833- return [values._getitem_py(j) for j in range (start, end)]
2829+ return [values._getitem_py(j, maps_as_pydicts ) for j in range (start, end)]
28342830
28352831 @staticmethod
28362832 def from_arrays (offsets , values , DataType type = None , MemoryPool pool = None , mask = None ):
@@ -3017,15 +3013,15 @@ cdef class LargeListArray(BaseListArray):
30173013 Identical to ListArray, but 64-bit offsets.
30183014 """
30193015
3020- cdef object _getitem_py(self , int64_t i):
3016+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
30213017 cdef CLargeListArray* arr = < CLargeListArray* > self .ap
30223018 if arr.IsNull(i):
30233019 return None
30243020 if self ._children_cache is None :
30253021 self ._children_cache = pyarrow_wrap_array(arr.values())
30263022 cdef Array values = < Array> self ._children_cache
30273023 cdef int64_t j, start = arr.value_offset(i), end = arr.value_offset(i + 1 )
3028- return [values._getitem_py(j) for j in range (start, end)]
3024+ return [values._getitem_py(j, maps_as_pydicts ) for j in range (start, end)]
30293025
30303026 @staticmethod
30313027 def from_arrays (offsets , values , DataType type = None , MemoryPool pool = None , mask = None ):
@@ -3617,18 +3613,51 @@ cdef class MapArray(ListArray):
36173613 Concrete class for Arrow arrays of a map data type.
36183614 """
36193615
3620- cdef object _getitem_py(self , int64_t i):
3616+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
36213617 cdef CListArray* arr = < CListArray* > self .ap
3618+ cdef bint as_dicts = maps_as_pydicts is not None
3619+ if as_dicts and maps_as_pydicts != " lossy" and maps_as_pydicts != " strict" :
3620+ # Matches MapScalar.as_py, which validates before the null check.
3621+ raise ValueError (
3622+ " Invalid value for 'maps_as_pydicts': "
3623+ + " valid values are 'lossy', 'strict' or `None` (default). "
3624+ + f" Received {maps_as_pydicts!r}."
3625+ )
36223626 if arr.IsNull(i):
36233627 return None
36243628 if self ._children_cache is None :
36253629 self ._children_cache = (self .keys, self .items)
36263630 cdef Array keys = < Array> (< tuple > self ._children_cache)[0 ]
36273631 cdef Array items = < Array> (< tuple > self ._children_cache)[1 ]
36283632 cdef int64_t j, start = arr.value_offset(i), end = arr.value_offset(i + 1 )
3629- # Matches MapScalar.as_py with the default maps_as_pydicts=None:
3630- # an association list of (key, value) tuples.
3631- return [(keys._getitem_py(j), items._getitem_py(j)) for j in range (start, end)]
3633+ if not as_dicts:
3634+ # Matches MapScalar.as_py with the default maps_as_pydicts=None:
3635+ # an association list of (key, value) tuples.
3636+ return [
3637+ (keys._getitem_py(j, None ), items._getitem_py(j, maps_as_pydicts))
3638+ for j in range (start, end)
3639+ ]
3640+ cdef dict result = {}
3641+ for j in range (start, end):
3642+ result[keys._getitem_py(j, None )] = items._getitem_py(j, maps_as_pydicts)
3643+ if len (result) == end - start:
3644+ return result
3645+ # Duplicate keys: redo the row with the per-key loop so the 'lossy'
3646+ # warnings and 'strict' KeyError match MapScalar.as_py exactly.
3647+ result = {}
3648+ for j in range (start, end):
3649+ key = keys._getitem_py(j, None )
3650+ if key in result:
3651+ if maps_as_pydicts == " strict" :
3652+ raise KeyError (
3653+ " Converting to Python dictionary is not supported in strict mode "
3654+ f" when duplicate keys are present (duplicate key was '{key}')."
3655+ )
3656+ else :
3657+ warnings.warn(
3658+ f" Encountered key '{key}' which was already encountered." )
3659+ result[key] = items._getitem_py(j, maps_as_pydicts)
3660+ return result
36323661
36333662 @staticmethod
36343663 def from_arrays (offsets , keys , items , DataType type = None , MemoryPool pool = None , mask = None ):
@@ -3767,15 +3796,15 @@ cdef class FixedSizeListArray(BaseListArray):
37673796 Concrete class for Arrow arrays of a fixed size list data type.
37683797 """
37693798
3770- cdef object _getitem_py(self , int64_t i):
3799+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
37713800 cdef CFixedSizeListArray* arr = < CFixedSizeListArray* > self .ap
37723801 if arr.IsNull(i):
37733802 return None
37743803 if self ._children_cache is None :
37753804 self ._children_cache = pyarrow_wrap_array(arr.values())
37763805 cdef Array values = < Array> self ._children_cache
37773806 cdef int64_t j, start = arr.value_offset(i), end = arr.value_offset(i + 1 )
3778- return [values._getitem_py(j) for j in range (start, end)]
3807+ return [values._getitem_py(j, maps_as_pydicts ) for j in range (start, end)]
37793808
37803809 @staticmethod
37813810 def from_arrays (values , list_size = None , DataType type = None , mask = None ):
@@ -4063,7 +4092,7 @@ cdef class StringArray(Array):
40634092 Concrete class for Arrow arrays of string (or utf8) data type.
40644093 """
40654094
4066- cdef object _getitem_py(self , int64_t i):
4095+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
40674096 cdef:
40684097 int32_t length
40694098 const uint8_t* data
@@ -4105,7 +4134,7 @@ cdef class LargeStringArray(Array):
41054134 Concrete class for Arrow arrays of large string (or utf8) data type.
41064135 """
41074136
4108- cdef object _getitem_py(self , int64_t i):
4137+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
41094138 cdef:
41104139 int64_t length
41114140 const uint8_t* data
@@ -4152,7 +4181,7 @@ cdef class BinaryArray(Array):
41524181 Concrete class for Arrow arrays of variable-sized binary data type.
41534182 """
41544183
4155- cdef object _getitem_py(self , int64_t i):
4184+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
41564185 cdef:
41574186 int32_t length
41584187 const uint8_t* data
@@ -4175,7 +4204,7 @@ cdef class LargeBinaryArray(Array):
41754204 Concrete class for Arrow arrays of large variable-sized binary data type.
41764205 """
41774206
4178- cdef object _getitem_py(self , int64_t i):
4207+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
41794208 cdef:
41804209 int64_t length
41814210 const uint8_t* data
@@ -4357,7 +4386,7 @@ cdef class StructArray(Array):
43574386 Concrete class for Arrow arrays of a struct data type.
43584387 """
43594388
4360- cdef object _getitem_py(self , int64_t i):
4389+ cdef object _getitem_py(self , int64_t i, object maps_as_pydicts ):
43614390 if self .ap.IsNull(i):
43624391 return None
43634392 cdef int64_t k, num_fields = self .type.num_fields
@@ -4372,13 +4401,13 @@ cdef class StructArray(Array):
43724401 names, [self .field(k) for k in range (num_fields)])
43734402 names = (< tuple > self ._children_cache)[0 ]
43744403 if names is None :
4375- return Array._getitem_py(self , i)
4404+ return Array._getitem_py(self , i, maps_as_pydicts )
43764405 fields = (< tuple > self ._children_cache)[1 ]
43774406 cdef Array field_arr
43784407 result = {}
43794408 for k in range (num_fields):
43804409 field_arr = < Array> fields[k]
4381- result[names[k]] = field_arr._getitem_py(i)
4410+ result[names[k]] = field_arr._getitem_py(i, maps_as_pydicts )
43824411 return result
43834412
43844413 def field (self , index ):
0 commit comments