1"""
2A place for internal code
3
4Some things are more easily handled Python.
5
6"""
7import ast
8import math
9import re
10import sys
11import warnings
12
13from numpy import _NoValue
14from numpy.exceptions import DTypePromotionError
15
16from .multiarray import StringDType, array, dtype, promote_types
17
18try:
19 import ctypes
20except ImportError:
21 ctypes = None
22
23IS_PYPY = sys.implementation.name == 'pypy'
24
25if sys.byteorder == 'little':
26 _nbo = '<'
27else:
28 _nbo = '>'
29
30def _makenames_list(adict, align):
31 allfields = []
32
33 for fname, obj in adict.items():
34 n = len(obj)
35 if not isinstance(obj, tuple) or n not in (2, 3):
36 raise ValueError("entry not a 2- or 3- tuple")
37 if n > 2 and obj[2] == fname:
38 continue
39 num = int(obj[1])
40 if num < 0:
41 raise ValueError("invalid offset.")
42 format = dtype(obj[0], align=align)
43 if n > 2:
44 title = obj[2]
45 else:
46 title = None
47 allfields.append((fname, format, num, title))
48 # sort by offsets
49 allfields.sort(key=lambda x: x[2])
50 names = [x[0] for x in allfields]
51 formats = [x[1] for x in allfields]
52 offsets = [x[2] for x in allfields]
53 titles = [x[3] for x in allfields]
54
55 return names, formats, offsets, titles
56
57# Called in PyArray_DescrConverter function when
58# a dictionary without "names" and "formats"
59# fields is used as a data-type descriptor.
60def _usefields(adict, align):
61 try:
62 names = adict[-1]
63 except KeyError:
64 names = None
65 if names is None:
66 names, formats, offsets, titles = _makenames_list(adict, align)
67 else:
68 formats = []
69 offsets = []
70 titles = []
71 for name in names:
72 res = adict[name]
73 formats.append(res[0])
74 offsets.append(res[1])
75 if len(res) > 2:
76 titles.append(res[2])
77 else:
78 titles.append(None)
79
80 return dtype({"names": names,
81 "formats": formats,
82 "offsets": offsets,
83 "titles": titles}, align)
84
85
86# construct an array_protocol descriptor list
87# from the fields attribute of a descriptor
88# This calls itself recursively but should eventually hit
89# a descriptor that has no fields and then return
90# a simple typestring
91
92def _array_descr(descriptor):
93 fields = descriptor.fields
94 if fields is None:
95 subdtype = descriptor.subdtype
96 if subdtype is None:
97 if descriptor.metadata is None:
98 return descriptor.str
99 else:
100 new = descriptor.metadata.copy()
101 if new:
102 return (descriptor.str, new)
103 else:
104 return descriptor.str
105 else:
106 return (_array_descr(subdtype[0]), subdtype[1])
107
108 names = descriptor.names
109 ordered_fields = [fields[x] + (x,) for x in names]
110 result = []
111 offset = 0
112 for field in ordered_fields:
113 if field[1] > offset:
114 num = field[1] - offset
115 result.append(('', f'|V{num}'))
116 offset += num
117 elif field[1] < offset:
118 raise ValueError(
119 "dtype.descr is not defined for types with overlapping or "
120 "out-of-order fields")
121 if len(field) > 3:
122 name = (field[2], field[3])
123 else:
124 name = field[2]
125 if field[0].subdtype:
126 tup = (name, _array_descr(field[0].subdtype[0]),
127 field[0].subdtype[1])
128 else:
129 tup = (name, _array_descr(field[0]))
130 offset += field[0].itemsize
131 result.append(tup)
132
133 if descriptor.itemsize > offset:
134 num = descriptor.itemsize - offset
135 result.append(('', f'|V{num}'))
136
137 return result
138
139
140# format_re was originally from numarray by J. Todd Miller
141
142format_re = re.compile(r'(?P<order1>[<>|=]?)'
143 r'(?P<repeats> *[(]?[ ,0-9]*[)]? *)'
144 r'(?P<order2>[<>|=]?)'
145 r'(?P<dtype>[A-Za-z0-9.?]*(?:\[[a-zA-Z0-9,.]+\])?)')
146sep_re = re.compile(r'\s*,\s*')
147space_re = re.compile(r'\s+$')
148
149# astr is a string (perhaps comma separated)
150
151_convorder = {'=': _nbo}
152
153def _commastring(astr):
154 startindex = 0
155 result = []
156 islist = False
157 while startindex < len(astr):
158 mo = format_re.match(astr, pos=startindex)
159 try:
160 (order1, repeats, order2, dtype) = mo.groups()
161 except (TypeError, AttributeError):
162 raise ValueError(
163 f'format number {len(result) + 1} of "{astr}" is not recognized'
164 ) from None
165 startindex = mo.end()
166 # Separator or ending padding
167 if startindex < len(astr):
168 if space_re.match(astr, pos=startindex):
169 startindex = len(astr)
170 else:
171 mo = sep_re.match(astr, pos=startindex)
172 if not mo:
173 raise ValueError(
174 'format number %d of "%s" is not recognized' %
175 (len(result) + 1, astr))
176 startindex = mo.end()
177 islist = True
178
179 if order2 == '':
180 order = order1
181 elif order1 == '':
182 order = order2
183 else:
184 order1 = _convorder.get(order1, order1)
185 order2 = _convorder.get(order2, order2)
186 if (order1 != order2):
187 raise ValueError(
188 f'inconsistent byte-order specification {order1} and {order2}')
189 order = order1
190
191 if order in ('|', '=', _nbo):
192 order = ''
193 dtype = order + dtype
194 if repeats == '':
195 newitem = dtype
196 else:
197 if (repeats[0] == "(" and repeats[-1] == ")"
198 and repeats[1:-1].strip() != ""
199 and "," not in repeats):
200 warnings.warn(
201 'Passing in a parenthesized single number for repeats '
202 'is deprecated; pass either a single number or indicate '
203 'a tuple with a comma, like "(2,)".', DeprecationWarning,
204 stacklevel=2)
205 newitem = (dtype, ast.literal_eval(repeats))
206
207 result.append(newitem)
208
209 return result if islist else result[0]
210
211class dummy_ctype:
212
213 def __init__(self, cls):
214 self._cls = cls
215
216 def __mul__(self, other):
217 return self
218
219 def __call__(self, *other):
220 return self._cls(other)
221
222 def __eq__(self, other):
223 return self._cls == other._cls
224
225 def __ne__(self, other):
226 return self._cls != other._cls
227
228def _getintp_ctype():
229 val = _getintp_ctype.cache
230 if val is not None:
231 return val
232 if ctypes is None:
233 import numpy as np
234 val = dummy_ctype(np.intp)
235 else:
236 char = dtype('n').char
237 if char == 'i':
238 val = ctypes.c_int
239 elif char == 'l':
240 val = ctypes.c_long
241 elif char == 'q':
242 val = ctypes.c_longlong
243 else:
244 val = ctypes.c_long
245 _getintp_ctype.cache = val
246 return val
247
248
249_getintp_ctype.cache = None
250
251# Used for .ctypes attribute of ndarray
252
253class _missing_ctypes:
254 def cast(self, num, obj):
255 return num.value
256
257 class c_void_p:
258 def __init__(self, ptr):
259 self.value = ptr
260
261
262class _ctypes:
263 def __init__(self, array, ptr=None):
264 self._arr = array
265
266 if ctypes:
267 self._ctypes = ctypes
268 self._data = self._ctypes.c_void_p(ptr)
269 else:
270 # fake a pointer-like object that holds onto the reference
271 self._ctypes = _missing_ctypes()
272 self._data = self._ctypes.c_void_p(ptr)
273 self._data._objects = array
274
275 if self._arr.ndim == 0:
276 self._zerod = True
277 else:
278 self._zerod = False
279
280 def data_as(self, obj):
281 """
282 Return the data pointer cast to a particular c-types object.
283 For example, calling ``self._as_parameter_`` is equivalent to
284 ``self.data_as(ctypes.c_void_p)``. Perhaps you want to use
285 the data as a pointer to a ctypes array of floating-point data:
286 ``self.data_as(ctypes.POINTER(ctypes.c_double))``.
287
288 The returned pointer will keep a reference to the array.
289 """
290 # _ctypes.cast function causes a circular reference of self._data in
291 # self._data._objects. Attributes of self._data cannot be released
292 # until gc.collect is called. Make a copy of the pointer first then
293 # let it hold the array reference. This is a workaround to circumvent
294 # the CPython bug https://bugs.python.org/issue12836.
295 ptr = self._ctypes.cast(self._data, obj)
296 ptr._arr = self._arr
297 return ptr
298
299 def shape_as(self, obj):
300 """
301 Return the shape tuple as an array of some other c-types
302 type. For example: ``self.shape_as(ctypes.c_short)``.
303 """
304 if self._zerod:
305 return None
306 return (obj * self._arr.ndim)(*self._arr.shape)
307
308 def strides_as(self, obj):
309 """
310 Return the strides tuple as an array of some other
311 c-types type. For example: ``self.strides_as(ctypes.c_longlong)``.
312 """
313 if self._zerod:
314 return None
315 return (obj * self._arr.ndim)(*self._arr.strides)
316
317 @property
318 def data(self):
319 """
320 A pointer to the memory area of the array as a Python integer.
321 This memory area may contain data that is not aligned, or not in
322 correct byte-order. The memory area may not even be writeable.
323 The array flags and data-type of this array should be respected
324 when passing this attribute to arbitrary C-code to avoid trouble
325 that can include Python crashing. User Beware! The value of this
326 attribute is exactly the same as:
327 ``self._array_interface_['data'][0]``.
328
329 Note that unlike ``data_as``, a reference won't be kept to the array:
330 code like ``ctypes.c_void_p((a + b).ctypes.data)`` will result in a
331 pointer to a deallocated array, and should be spelt
332 ``(a + b).ctypes.data_as(ctypes.c_void_p)``
333 """
334 return self._data.value
335
336 @property
337 def shape(self):
338 """
339 (c_intp*self.ndim): A ctypes array of length self.ndim where
340 the basetype is the C-integer corresponding to ``dtype('p')`` on this
341 platform (see `~numpy.ctypeslib.c_intp`). This base-type could be
342 `ctypes.c_int`, `ctypes.c_long`, or `ctypes.c_longlong` depending on
343 the platform. The ctypes array contains the shape of
344 the underlying array.
345 """
346 return self.shape_as(_getintp_ctype())
347
348 @property
349 def strides(self):
350 """
351 (c_intp*self.ndim): A ctypes array of length self.ndim where
352 the basetype is the same as for the shape attribute. This ctypes
353 array contains the strides information from the underlying array.
354 This strides information is important for showing how many bytes
355 must be jumped to get to the next element in the array.
356 """
357 return self.strides_as(_getintp_ctype())
358
359 @property
360 def _as_parameter_(self):
361 """
362 Overrides the ctypes semi-magic method
363
364 Enables `c_func(some_array.ctypes)`
365 """
366 return self.data_as(ctypes.c_void_p)
367
368
369def _newnames(datatype, order):
370 """
371 Given a datatype and an order object, return a new names tuple, with the
372 order indicated
373 """
374 oldnames = datatype.names
375 nameslist = list(oldnames)
376 if isinstance(order, str):
377 order = [order]
378 seen = set()
379 if isinstance(order, (list, tuple)):
380 for name in order:
381 try:
382 nameslist.remove(name)
383 except ValueError:
384 if name in seen:
385 raise ValueError(f"duplicate field name: {name}") from None
386 else:
387 raise ValueError(f"unknown field name: {name}") from None
388 seen.add(name)
389 return tuple(list(order) + nameslist)
390 raise ValueError(f"unsupported order value: {order}")
391
392def _copy_fields(ary):
393 """Return copy of structured array with padding between fields removed.
394
395 Parameters
396 ----------
397 ary : ndarray
398 Structured array from which to remove padding bytes
399
400 Returns
401 -------
402 ary_copy : ndarray
403 Copy of ary with padding bytes removed
404 """
405 dt = ary.dtype
406 copy_dtype = {'names': dt.names,
407 'formats': [dt.fields[name][0] for name in dt.names]}
408 return array(ary, dtype=copy_dtype, copy=True)
409
410def _promote_fields(dt1, dt2):
411 """ Perform type promotion for two structured dtypes.
412
413 Parameters
414 ----------
415 dt1 : structured dtype
416 First dtype.
417 dt2 : structured dtype
418 Second dtype.
419
420 Returns
421 -------
422 out : dtype
423 The promoted dtype
424
425 Notes
426 -----
427 If one of the inputs is aligned, the result will be. The titles of
428 both descriptors must match (point to the same field).
429 """
430 # Both must be structured and have the same names in the same order
431 if (dt1.names is None or dt2.names is None) or dt1.names != dt2.names:
432 raise DTypePromotionError(
433 f"field names `{dt1.names}` and `{dt2.names}` mismatch.")
434
435 # if both are identical, we can (maybe!) just return the same dtype.
436 identical = dt1 is dt2
437 new_fields = []
438 for name in dt1.names:
439 field1 = dt1.fields[name]
440 field2 = dt2.fields[name]
441 new_descr = promote_types(field1[0], field2[0])
442 identical = identical and new_descr is field1[0]
443
444 # Check that the titles match (if given):
445 if field1[2:] != field2[2:]:
446 raise DTypePromotionError(
447 f"field titles of field '{name}' mismatch")
448 if len(field1) == 2:
449 new_fields.append((name, new_descr))
450 else:
451 new_fields.append(((field1[2], name), new_descr))
452
453 res = dtype(new_fields, align=dt1.isalignedstruct or dt2.isalignedstruct)
454
455 # Might as well preserve identity (and metadata) if the dtype is identical
456 # and the itemsize, offsets are also unmodified. This could probably be
457 # sped up, but also probably just be removed entirely.
458 if identical and res.itemsize == dt1.itemsize:
459 for name in dt1.names:
460 if dt1.fields[name][1] != res.fields[name][1]:
461 return res # the dtype changed.
462 return dt1
463
464 return res
465
466
467def _getfield_is_safe(oldtype, newtype, offset):
468 """ Checks safety of getfield for object arrays.
469
470 As in _view_is_safe, we need to check that memory containing objects is not
471 reinterpreted as a non-object datatype and vice versa.
472
473 Parameters
474 ----------
475 oldtype : data-type
476 Data type of the original ndarray.
477 newtype : data-type
478 Data type of the field being accessed by ndarray.getfield
479 offset : int
480 Offset of the field being accessed by ndarray.getfield
481
482 Raises
483 ------
484 TypeError
485 If the field access is invalid
486
487 """
488 if newtype.hasobject or oldtype.hasobject:
489 if offset == 0 and newtype == oldtype:
490 return
491 if oldtype.names is not None:
492 for name in oldtype.names:
493 if (oldtype.fields[name][1] == offset and
494 oldtype.fields[name][0] == newtype):
495 return
496 raise TypeError("Cannot get/set field of an object array")
497 return
498
499def _view_is_safe(oldtype, newtype):
500 """ Checks safety of a view involving object arrays, for example when
501 doing::
502
503 np.zeros(10, dtype=oldtype).view(newtype)
504
505 Parameters
506 ----------
507 oldtype : data-type
508 Data type of original ndarray
509 newtype : data-type
510 Data type of the view
511
512 Raises
513 ------
514 TypeError
515 If the new type is incompatible with the old type.
516
517 """
518
519 # if the types are equivalent, there is no problem.
520 # for example: dtype((np.record, 'i4,i4')) == dtype((np.void, 'i4,i4'))
521 if oldtype == newtype:
522 return
523
524 if newtype.hasobject or oldtype.hasobject:
525 raise TypeError("Cannot change data-type for array of references.")
526 return
527
528
529# Given a string containing a PEP 3118 format specifier,
530# construct a NumPy dtype
531
532_pep3118_native_map = {
533 '?': '?',
534 'c': 'S1',
535 'b': 'b',
536 'B': 'B',
537 'h': 'h',
538 'H': 'H',
539 'i': 'i',
540 'I': 'I',
541 'l': 'l',
542 'L': 'L',
543 'q': 'q',
544 'Q': 'Q',
545 'e': 'e',
546 'f': 'f',
547 'd': 'd',
548 'g': 'g',
549 'Zf': 'F',
550 'Zd': 'D',
551 'Zg': 'G',
552 's': 'S',
553 'w': 'U',
554 'O': 'O',
555 'x': 'V', # padding
556}
557_pep3118_native_typechars = ''.join(_pep3118_native_map.keys())
558
559_pep3118_standard_map = {
560 '?': '?',
561 'c': 'S1',
562 'b': 'b',
563 'B': 'B',
564 'h': 'i2',
565 'H': 'u2',
566 'i': 'i4',
567 'I': 'u4',
568 'l': 'i4',
569 'L': 'u4',
570 'q': 'i8',
571 'Q': 'u8',
572 'e': 'f2',
573 'f': 'f',
574 'd': 'd',
575 'Zf': 'F',
576 'Zd': 'D',
577 's': 'S',
578 'w': 'U',
579 'O': 'O',
580 'x': 'V', # padding
581}
582_pep3118_standard_typechars = ''.join(_pep3118_standard_map.keys())
583
584_pep3118_unsupported_map = {
585 'u': 'UCS-2 strings',
586 '&': 'pointers',
587 't': 'bitfields',
588 'X': 'function pointers',
589}
590
591class _Stream:
592 def __init__(self, s):
593 self.s = s
594 self.byteorder = '@'
595
596 def advance(self, n):
597 res = self.s[:n]
598 self.s = self.s[n:]
599 return res
600
601 def consume(self, c):
602 if self.s[:len(c)] == c:
603 self.advance(len(c))
604 return True
605 return False
606
607 def consume_until(self, c):
608 if callable(c):
609 i = 0
610 while i < len(self.s) and not c(self.s[i]):
611 i = i + 1
612 return self.advance(i)
613 else:
614 i = self.s.index(c)
615 res = self.advance(i)
616 self.advance(len(c))
617 return res
618
619 @property
620 def next(self):
621 return self.s[0]
622
623 def __bool__(self):
624 return bool(self.s)
625
626
627def _dtype_from_pep3118(spec):
628 stream = _Stream(spec)
629 dtype, align = __dtype_from_pep3118(stream, is_subdtype=False)
630 return dtype
631
632def __dtype_from_pep3118(stream, is_subdtype):
633 field_spec = {
634 'names': [],
635 'formats': [],
636 'offsets': [],
637 'itemsize': 0
638 }
639 offset = 0
640 common_alignment = 1
641 is_padding = False
642
643 # Parse spec
644 while stream:
645 value = None
646
647 # End of structure, bail out to upper level
648 if stream.consume('}'):
649 break
650
651 # Sub-arrays (1)
652 shape = None
653 if stream.consume('('):
654 shape = stream.consume_until(')')
655 shape = tuple(map(int, shape.split(',')))
656
657 # Byte order
658 if stream.next in ('@', '=', '<', '>', '^', '!'):
659 byteorder = stream.advance(1)
660 if byteorder == '!':
661 byteorder = '>'
662 stream.byteorder = byteorder
663
664 # Byte order characters also control native vs. standard type sizes
665 if stream.byteorder in ('@', '^'):
666 type_map = _pep3118_native_map
667 type_map_chars = _pep3118_native_typechars
668 else:
669 type_map = _pep3118_standard_map
670 type_map_chars = _pep3118_standard_typechars
671
672 # Item sizes
673 itemsize_str = stream.consume_until(lambda c: not c.isdigit())
674 if itemsize_str:
675 itemsize = int(itemsize_str)
676 else:
677 itemsize = 1
678
679 # Data types
680 is_padding = False
681
682 if stream.consume('T{'):
683 value, align = __dtype_from_pep3118(
684 stream, is_subdtype=True)
685 elif stream.next in type_map_chars:
686 if stream.next == 'Z':
687 typechar = stream.advance(2)
688 else:
689 typechar = stream.advance(1)
690
691 is_padding = (typechar == 'x')
692 dtypechar = type_map[typechar]
693 if dtypechar in 'USV':
694 dtypechar += '%d' % itemsize
695 itemsize = 1
696 numpy_byteorder = {'@': '=', '^': '='}.get(
697 stream.byteorder, stream.byteorder)
698 value = dtype(numpy_byteorder + dtypechar)
699 align = value.alignment
700 elif stream.next in _pep3118_unsupported_map:
701 desc = _pep3118_unsupported_map[stream.next]
702 raise NotImplementedError(
703 f"Unrepresentable PEP 3118 data type {stream.next!r} ({desc})")
704 else:
705 raise ValueError(
706 f"Unknown PEP 3118 data type specifier {stream.s!r}"
707 )
708
709 #
710 # Native alignment may require padding
711 #
712 # Here we assume that the presence of a '@' character implicitly
713 # implies that the start of the array is *already* aligned.
714 #
715 extra_offset = 0
716 if stream.byteorder == '@':
717 start_padding = (-offset) % align
718 intra_padding = (-value.itemsize) % align
719
720 offset += start_padding
721
722 if intra_padding != 0:
723 if itemsize > 1 or (shape is not None and _prod(shape) > 1):
724 # Inject internal padding to the end of the sub-item
725 value = _add_trailing_padding(value, intra_padding)
726 else:
727 # We can postpone the injection of internal padding,
728 # as the item appears at most once
729 extra_offset += intra_padding
730
731 # Update common alignment
732 common_alignment = _lcm(align, common_alignment)
733
734 # Convert itemsize to sub-array
735 if itemsize != 1:
736 value = dtype((value, (itemsize,)))
737
738 # Sub-arrays (2)
739 if shape is not None:
740 value = dtype((value, shape))
741
742 # Field name
743 if stream.consume(':'):
744 name = stream.consume_until(':')
745 else:
746 name = None
747
748 if not (is_padding and name is None):
749 if name is not None and name in field_spec['names']:
750 raise RuntimeError(
751 f"Duplicate field name '{name}' in PEP3118 format"
752 )
753 field_spec['names'].append(name)
754 field_spec['formats'].append(value)
755 field_spec['offsets'].append(offset)
756
757 offset += value.itemsize
758 offset += extra_offset
759
760 field_spec['itemsize'] = offset
761
762 # extra final padding for aligned types
763 if stream.byteorder == '@':
764 field_spec['itemsize'] += (-offset) % common_alignment
765
766 # Check if this was a simple 1-item type, and unwrap it
767 if (field_spec['names'] == [None]
768 and field_spec['offsets'][0] == 0
769 and field_spec['itemsize'] == field_spec['formats'][0].itemsize
770 and not is_subdtype):
771 ret = field_spec['formats'][0]
772 else:
773 _fix_names(field_spec)
774 ret = dtype(field_spec)
775
776 # Finished
777 return ret, common_alignment
778
779def _fix_names(field_spec):
780 """ Replace names which are None with the next unused f%d name """
781 names = field_spec['names']
782 for i, name in enumerate(names):
783 if name is not None:
784 continue
785
786 j = 0
787 while True:
788 name = f'f{j}'
789 if name not in names:
790 break
791 j = j + 1
792 names[i] = name
793
794def _add_trailing_padding(value, padding):
795 """Inject the specified number of padding bytes at the end of a dtype"""
796 if value.fields is None:
797 field_spec = {
798 'names': ['f0'],
799 'formats': [value],
800 'offsets': [0],
801 'itemsize': value.itemsize
802 }
803 else:
804 fields = value.fields
805 names = value.names
806 field_spec = {
807 'names': names,
808 'formats': [fields[name][0] for name in names],
809 'offsets': [fields[name][1] for name in names],
810 'itemsize': value.itemsize
811 }
812
813 field_spec['itemsize'] += padding
814 return dtype(field_spec)
815
816def _prod(a):
817 p = 1
818 for x in a:
819 p *= x
820 return p
821
822def _gcd(a, b):
823 """Calculate the greatest common divisor of a and b"""
824 if not (math.isfinite(a) and math.isfinite(b)):
825 raise ValueError('Can only find greatest common divisor of '
826 f'finite arguments, found "{a}" and "{b}"')
827 while b:
828 a, b = b, a % b
829 return a
830
831def _lcm(a, b):
832 return a // _gcd(a, b) * b
833
834def array_ufunc_errmsg_formatter(dummy, ufunc, method, *inputs, **kwargs):
835 """ Format the error message for when __array_ufunc__ gives up. """
836 args_string = ', '.join([f'{arg!r}' for arg in inputs] +
837 [f'{k}={v!r}'
838 for k, v in kwargs.items()])
839 args = inputs + kwargs.get('out', ())
840 types_string = ', '.join(repr(type(arg).__name__) for arg in args)
841 return ('operand type(s) all returned NotImplemented from '
842 f'__array_ufunc__({ufunc!r}, {method!r}, {args_string}): {types_string}'
843 )
844
845
846def array_function_errmsg_formatter(public_api, types):
847 """ Format the error message for when __array_ufunc__ gives up. """
848 func_name = f'{public_api.__module__}.{public_api.__name__}'
849 return (f"no implementation found for '{func_name}' on types that implement "
850 f'__array_function__: {list(types)}')
851
852
853def _ufunc_doc_signature_formatter(ufunc):
854 """
855 Builds a signature string which resembles PEP 457
856
857 This is used to construct the first line of the docstring
858
859 Keep in sync with `_ufunc_inspect_signature_builder`.
860 """
861
862 # input arguments are simple
863 if ufunc.nin == 1:
864 in_args = 'x'
865 else:
866 in_args = ', '.join(f'x{i + 1}' for i in range(ufunc.nin))
867
868 # output arguments are both keyword or positional
869 if ufunc.nout == 0:
870 out_args = ', /, out=()'
871 elif ufunc.nout == 1:
872 out_args = ', /, out=None'
873 else:
874 out_args = '[, {positional}], / [, out={default}]'.format(
875 positional=', '.join(
876 f'out{i + 1}' for i in range(ufunc.nout)),
877 default=repr((None,) * ufunc.nout)
878 )
879
880 # keyword only args depend on whether this is a gufunc
881 kwargs = (
882 ", casting='same_kind'"
883 ", order='K'"
884 ", dtype=None"
885 ", subok=True"
886 )
887
888 # NOTE: gufuncs may or may not support the `axis` parameter
889 if ufunc.signature is None:
890 kwargs = f", where=True{kwargs}[, signature]"
891 else:
892 kwargs += "[, signature, axes, axis]"
893
894 # join all the parts together
895 return f'{ufunc.__name__}({in_args}{out_args}, *{kwargs})'
896
897
898def _ufunc_inspect_signature_builder(ufunc):
899 """
900 Builds a ``__signature__`` string.
901
902 Should be kept in sync with `_ufunc_doc_signature_formatter`.
903 """
904
905 from inspect import Parameter, Signature
906
907 params = []
908
909 # positional-only input parameters
910 if ufunc.nin == 1:
911 params.append(Parameter("x", Parameter.POSITIONAL_ONLY))
912 else:
913 params.extend(
914 Parameter(f"x{i}", Parameter.POSITIONAL_ONLY)
915 for i in range(1, ufunc.nin + 1)
916 )
917
918 # for the sake of simplicity, we only consider a single output parameter
919 if ufunc.nout == 1:
920 out_default = None
921 else:
922 out_default = (None,) * ufunc.nout
923 params.append(
924 Parameter("out", Parameter.POSITIONAL_OR_KEYWORD, default=out_default),
925 )
926
927 if ufunc.signature is None:
928 params.append(Parameter("where", Parameter.KEYWORD_ONLY, default=True))
929 else:
930 # NOTE: not all gufuncs support the `axis` parameters
931 params.append(Parameter("axes", Parameter.KEYWORD_ONLY, default=_NoValue))
932 params.append(Parameter("axis", Parameter.KEYWORD_ONLY, default=_NoValue))
933 params.append(Parameter("keepdims", Parameter.KEYWORD_ONLY, default=False))
934
935 params.extend((
936 Parameter("casting", Parameter.KEYWORD_ONLY, default='same_kind'),
937 Parameter("order", Parameter.KEYWORD_ONLY, default='K'),
938 Parameter("dtype", Parameter.KEYWORD_ONLY, default=None),
939 Parameter("subok", Parameter.KEYWORD_ONLY, default=True),
940 Parameter("signature", Parameter.KEYWORD_ONLY, default=None),
941 ))
942
943 return Signature(params)
944
945
946def npy_ctypes_check(cls):
947 # determine if a class comes from ctypes, in order to work around
948 # a bug in the buffer protocol for those objects, bpo-10746
949 try:
950 # ctypes class are new-style, so have an __mro__. This probably fails
951 # for ctypes classes with multiple inheritance.
952 if IS_PYPY:
953 # (..., _ctypes.basics._CData, Bufferable, object)
954 ctype_base = cls.__mro__[-3]
955 else:
956 # # (..., _ctypes._CData, object)
957 ctype_base = cls.__mro__[-2]
958 # right now, they're part of the _ctypes module
959 return '_ctypes' in ctype_base.__module__
960 except Exception:
961 return False
962
963# used to handle the _NoValue default argument for na_object
964# in the C implementation of the __reduce__ method for stringdtype
965def _convert_to_stringdtype_kwargs(coerce, na_object=_NoValue):
966 if na_object is _NoValue:
967 return StringDType(coerce=coerce)
968 return StringDType(coerce=coerce, na_object=na_object)