Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/numpy/lib/_iotools.py: 15%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""A collection of functions designed to help I/O with ascii files.
3"""
4__docformat__ = "restructuredtext en"
6import itertools
8import numpy as np
9import numpy._core.numeric as nx
10from numpy._utils import asbytes, asunicode
13def _decode_line(line, encoding=None):
14 """Decode bytes from binary input streams.
16 Defaults to decoding from 'latin1'.
18 Parameters
19 ----------
20 line : str or bytes
21 Line to be decoded.
22 encoding : str
23 Encoding used to decode `line`.
25 Returns
26 -------
27 decoded_line : str
29 """
30 if type(line) is bytes:
31 if encoding is None:
32 encoding = "latin1"
33 line = line.decode(encoding)
35 return line
38def _is_string_like(obj):
39 """
40 Check whether obj behaves like a string.
41 """
42 try:
43 obj + ''
44 except (TypeError, ValueError):
45 return False
46 return True
49def _is_bytes_like(obj):
50 """
51 Check whether obj behaves like a bytes object.
52 """
53 try:
54 obj + b''
55 except (TypeError, ValueError):
56 return False
57 return True
60def has_nested_fields(ndtype):
61 """
62 Returns whether one or several fields of a dtype are nested.
64 Parameters
65 ----------
66 ndtype : dtype
67 Data-type of a structured array.
69 Raises
70 ------
71 AttributeError
72 If `ndtype` does not have a `names` attribute.
74 Examples
75 --------
76 >>> import numpy as np
77 >>> dt = np.dtype([('name', 'S4'), ('x', float), ('y', float)])
78 >>> np.lib._iotools.has_nested_fields(dt)
79 False
81 """
82 return any(ndtype[name].names is not None for name in ndtype.names or ())
85def flatten_dtype(ndtype, flatten_base=False):
86 """
87 Unpack a structured data-type by collapsing nested fields and/or fields
88 with a shape.
90 Note that the field names are lost.
92 Parameters
93 ----------
94 ndtype : dtype
95 The datatype to collapse
96 flatten_base : bool, optional
97 If True, transform a field with a shape into several fields. Default is
98 False.
100 Examples
101 --------
102 >>> import numpy as np
103 >>> dt = np.dtype([('name', 'S4'), ('x', float), ('y', float),
104 ... ('block', int, (2, 3))])
105 >>> np.lib._iotools.flatten_dtype(dt)
106 [dtype('S4'), dtype('float64'), dtype('float64'), dtype('int64')]
107 >>> np.lib._iotools.flatten_dtype(dt, flatten_base=True)
108 [dtype('S4'),
109 dtype('float64'),
110 dtype('float64'),
111 dtype('int64'),
112 dtype('int64'),
113 dtype('int64'),
114 dtype('int64'),
115 dtype('int64'),
116 dtype('int64')]
118 """
119 names = ndtype.names
120 if names is None:
121 if flatten_base:
122 return [ndtype.base] * int(np.prod(ndtype.shape))
123 return [ndtype.base]
124 else:
125 types = []
126 for field in names:
127 info = ndtype.fields[field]
128 flat_dt = flatten_dtype(info[0], flatten_base)
129 types.extend(flat_dt)
130 return types
133class LineSplitter:
134 """
135 Object to split a string at a given delimiter or at given places.
137 Parameters
138 ----------
139 delimiter : str, int, or sequence of ints, optional
140 If a string, character used to delimit consecutive fields.
141 If an integer or a sequence of integers, width(s) of each field.
142 comments : str, optional
143 Character used to mark the beginning of a comment. Default is '#'.
144 autostrip : bool, optional
145 Whether to strip each individual field. Default is True.
147 """
149 def autostrip(self, method):
150 """
151 Wrapper to strip each member of the output of `method`.
153 Parameters
154 ----------
155 method : function
156 Function that takes a single argument and returns a sequence of
157 strings.
159 Returns
160 -------
161 wrapped : function
162 The result of wrapping `method`. `wrapped` takes a single input
163 argument and returns a list of strings that are stripped of
164 white-space.
166 """
167 return lambda input: [_.strip() for _ in method(input)]
169 def __init__(self, delimiter=None, comments='#', autostrip=True,
170 encoding=None):
171 delimiter = _decode_line(delimiter)
172 comments = _decode_line(comments)
174 self.comments = comments
176 # Delimiter is a character
177 if (delimiter is None) or isinstance(delimiter, str):
178 delimiter = delimiter or None
179 _handyman = self._delimited_splitter
180 # Delimiter is a list of field widths
181 elif hasattr(delimiter, '__iter__'):
182 _handyman = self._variablewidth_splitter
183 idx = np.cumsum([0] + list(delimiter))
184 delimiter = [slice(i, j) for (i, j) in itertools.pairwise(idx)]
185 # Delimiter is a single integer
186 elif int(delimiter):
187 (_handyman, delimiter) = (
188 self._fixedwidth_splitter, int(delimiter))
189 else:
190 (_handyman, delimiter) = (self._delimited_splitter, None)
191 self.delimiter = delimiter
192 if autostrip:
193 self._handyman = self.autostrip(_handyman)
194 else:
195 self._handyman = _handyman
196 self.encoding = encoding
198 def _delimited_splitter(self, line):
199 """Chop off comments, strip, and split at delimiter. """
200 if self.comments is not None:
201 line = line.split(self.comments)[0]
202 line = line.strip(" \r\n")
203 if not line:
204 return []
205 return line.split(self.delimiter)
207 def _fixedwidth_splitter(self, line):
208 if self.comments is not None:
209 line = line.split(self.comments)[0]
210 line = line.strip("\r\n")
211 if not line:
212 return []
213 fixed = self.delimiter
214 slices = [slice(i, i + fixed) for i in range(0, len(line), fixed)]
215 return [line[s] for s in slices]
217 def _variablewidth_splitter(self, line):
218 if self.comments is not None:
219 line = line.split(self.comments)[0]
220 if not line:
221 return []
222 slices = self.delimiter
223 return [line[s] for s in slices]
225 def __call__(self, line):
226 return self._handyman(_decode_line(line, self.encoding))
229class NameValidator:
230 """
231 Object to validate a list of strings to use as field names.
233 The strings are stripped of any non alphanumeric character, and spaces
234 are replaced by '_'. During instantiation, the user can define a list
235 of names to exclude, as well as a list of invalid characters. Names in
236 the exclusion list are appended a '_' character.
238 Once an instance has been created, it can be called with a list of
239 names, and a list of valid names will be created. The `__call__`
240 method accepts an optional keyword "default" that sets the default name
241 in case of ambiguity. By default this is 'f', so that names will
242 default to `f0`, `f1`, etc.
244 Parameters
245 ----------
246 excludelist : sequence, optional
247 A list of names to exclude. This list is appended to the default
248 list ['return', 'file', 'print']. Excluded names are appended an
249 underscore: for example, `file` becomes `file_` if supplied.
250 deletechars : str, optional
251 A string combining invalid characters that must be deleted from the
252 names.
253 case_sensitive : {True, False, 'upper', 'lower'}, optional
254 * If True, field names are case-sensitive.
255 * If False or 'upper', field names are converted to upper case.
256 * If 'lower', field names are converted to lower case.
258 The default value is True.
259 replace_space : '_', optional
260 Character(s) used in replacement of white spaces.
262 Notes
263 -----
264 Calling an instance of `NameValidator` is the same as calling its
265 method `validate`.
267 Examples
268 --------
269 >>> import numpy as np
270 >>> validator = np.lib._iotools.NameValidator()
271 >>> validator(['file', 'field2', 'with space', 'CaSe'])
272 ('file_', 'field2', 'with_space', 'CaSe')
274 >>> validator = np.lib._iotools.NameValidator(excludelist=['excl'],
275 ... deletechars='q',
276 ... case_sensitive=False)
277 >>> validator(['excl', 'field2', 'no_q', 'with space', 'CaSe'])
278 ('EXCL', 'FIELD2', 'NO_Q', 'WITH_SPACE', 'CASE')
280 """
282 defaultexcludelist = 'return', 'file', 'print'
283 defaultdeletechars = frozenset(r"""~!@#$%^&*()-=+~\|]}[{';: /?.>,<""")
285 def __init__(self, excludelist=None, deletechars=None,
286 case_sensitive=None, replace_space='_'):
287 # Process the exclusion list ..
288 if excludelist is None:
289 excludelist = []
290 excludelist.extend(self.defaultexcludelist)
291 self.excludelist = excludelist
292 # Process the list of characters to delete
293 if deletechars is None:
294 delete = set(self.defaultdeletechars)
295 else:
296 delete = set(deletechars)
297 delete.add('"')
298 self.deletechars = delete
299 # Process the case option .....
300 if (case_sensitive is None) or (case_sensitive is True):
301 self.case_converter = lambda x: x
302 elif (case_sensitive is False) or case_sensitive.startswith('u'):
303 self.case_converter = lambda x: x.upper()
304 elif case_sensitive.startswith('l'):
305 self.case_converter = lambda x: x.lower()
306 else:
307 msg = f'unrecognized case_sensitive value {case_sensitive}.'
308 raise ValueError(msg)
310 self.replace_space = replace_space
312 def validate(self, names, defaultfmt="f%i", nbfields=None):
313 """
314 Validate a list of strings as field names for a structured array.
316 Parameters
317 ----------
318 names : sequence of str
319 Strings to be validated.
320 defaultfmt : str, optional
321 Default format string, used if validating a given string
322 reduces its length to zero.
323 nbfields : integer, optional
324 Final number of validated names, used to expand or shrink the
325 initial list of names.
327 Returns
328 -------
329 validatednames : list of str
330 The list of validated field names.
332 Notes
333 -----
334 A `NameValidator` instance can be called directly, which is the
335 same as calling `validate`. For examples, see `NameValidator`.
337 """
338 # Initial checks ..............
339 if (names is None):
340 if (nbfields is None):
341 return None
342 names = []
343 if isinstance(names, str):
344 names = [names, ]
345 if nbfields is not None:
346 nbnames = len(names)
347 if (nbnames < nbfields):
348 names = list(names) + [''] * (nbfields - nbnames)
349 elif (nbnames > nbfields):
350 names = names[:nbfields]
351 # Set some shortcuts ...........
352 deletechars = self.deletechars
353 excludelist = self.excludelist
354 case_converter = self.case_converter
355 replace_space = self.replace_space
356 # Initializes some variables ...
357 validatednames = []
358 seen = {}
359 nbempty = 0
361 for item in names:
362 item = case_converter(item).strip()
363 if replace_space:
364 item = item.replace(' ', replace_space)
365 item = ''.join([c for c in item if c not in deletechars])
366 if item == '':
367 item = defaultfmt % nbempty
368 while item in names:
369 nbempty += 1
370 item = defaultfmt % nbempty
371 nbempty += 1
372 elif item in excludelist:
373 item += '_'
374 cnt = seen.get(item, 0)
375 if cnt > 0:
376 validatednames.append(item + '_%d' % cnt)
377 else:
378 validatednames.append(item)
379 seen[item] = cnt + 1
380 return tuple(validatednames)
382 def __call__(self, names, defaultfmt="f%i", nbfields=None):
383 return self.validate(names, defaultfmt=defaultfmt, nbfields=nbfields)
386def str2bool(value):
387 """
388 Tries to transform a string supposed to represent a boolean to a boolean.
390 Parameters
391 ----------
392 value : str
393 The string that is transformed to a boolean.
395 Returns
396 -------
397 boolval : bool
398 The boolean representation of `value`.
400 Raises
401 ------
402 ValueError
403 If the string is not 'True' or 'False' (case independent)
405 Examples
406 --------
407 >>> import numpy as np
408 >>> np.lib._iotools.str2bool('TRUE')
409 True
410 >>> np.lib._iotools.str2bool('false')
411 False
413 """
414 value = value.upper()
415 if value == 'TRUE':
416 return True
417 elif value == 'FALSE':
418 return False
419 else:
420 raise ValueError("Invalid boolean")
423class ConverterError(Exception):
424 """
425 Exception raised when an error occurs in a converter for string values.
427 """
428 pass
431class ConverterLockError(ConverterError):
432 """
433 Exception raised when an attempt is made to upgrade a locked converter.
435 """
436 pass
439class ConversionWarning(UserWarning):
440 """
441 Warning issued when a string converter has a problem.
443 Notes
444 -----
445 In `genfromtxt` a `ConversionWarning` is issued if raising exceptions
446 is explicitly suppressed with the "invalid_raise" keyword.
448 """
449 pass
452class StringConverter:
453 """
454 Factory class for function transforming a string into another object
455 (int, float).
457 After initialization, an instance can be called to transform a string
458 into another object. If the string is recognized as representing a
459 missing value, a default value is returned.
461 Attributes
462 ----------
463 func : function
464 Function used for the conversion.
465 default : any
466 Default value to return when the input corresponds to a missing
467 value.
468 type : type
469 Type of the output.
470 _status : int
471 Integer representing the order of the conversion.
472 _mapper : sequence of tuples
473 Sequence of tuples (dtype, function, default value) to evaluate in
474 order.
475 _locked : bool
476 Holds `locked` parameter.
478 Parameters
479 ----------
480 dtype_or_func : {None, dtype, function}, optional
481 If a `dtype`, specifies the input data type, used to define a basic
482 function and a default value for missing data. For example, when
483 `dtype` is float, the `func` attribute is set to `float` and the
484 default value to `np.nan`. If a function, this function is used to
485 convert a string to another object. In this case, it is recommended
486 to give an associated default value as input.
487 default : any, optional
488 Value to return by default, that is, when the string to be
489 converted is flagged as missing. If not given, `StringConverter`
490 tries to supply a reasonable default value.
491 missing_values : {None, sequence of str}, optional
492 ``None`` or sequence of strings indicating a missing value. If ``None``
493 then missing values are indicated by empty entries. The default is
494 ``None``.
495 locked : bool, optional
496 Whether the StringConverter should be locked to prevent automatic
497 upgrade or not. Default is False.
499 """
500 _mapper = [(nx.bool, str2bool, False),
501 (nx.int_, int, -1),]
503 # On 32-bit systems, we need to make sure that we explicitly include
504 # nx.int64 since ns.int_ is nx.int32.
505 if nx.dtype(nx.int_).itemsize < nx.dtype(nx.int64).itemsize:
506 _mapper.append((nx.int64, int, -1))
508 _mapper.extend([(nx.float64, float, nx.nan),
509 (nx.complex128, complex, nx.nan + 0j),
510 (nx.longdouble, nx.longdouble, nx.nan),
511 # If a non-default dtype is passed, fall back to generic
512 # ones (should only be used for the converter)
513 (nx.integer, int, -1),
514 (nx.floating, float, nx.nan),
515 (nx.complexfloating, complex, nx.nan + 0j),
516 # Last, try with the string types (must be last, because
517 # `_mapper[-1]` is used as default in some cases)
518 (nx.str_, asunicode, '???'),
519 (nx.bytes_, asbytes, '???'),
520 ])
522 @classmethod
523 def _getdtype(cls, val):
524 """Returns the dtype of the input variable."""
525 return np.array(val).dtype
527 @classmethod
528 def _getsubdtype(cls, val):
529 """Returns the type of the dtype of the input variable."""
530 return np.array(val).dtype.type
532 @classmethod
533 def _dtypeortype(cls, dtype):
534 """Returns dtype for datetime64 and type of dtype otherwise."""
536 # This is a bit annoying. We want to return the "general" type in most
537 # cases (ie. "string" rather than "S10"), but we want to return the
538 # specific type for datetime64 (ie. "datetime64[us]" rather than
539 # "datetime64").
540 if dtype.type == np.datetime64:
541 return dtype
542 return dtype.type
544 @classmethod
545 def upgrade_mapper(cls, func, default=None):
546 """
547 Upgrade the mapper of a StringConverter by adding a new function and
548 its corresponding default.
550 The input function (or sequence of functions) and its associated
551 default value (if any) is inserted in penultimate position of the
552 mapper. The corresponding type is estimated from the dtype of the
553 default value.
555 Parameters
556 ----------
557 func : var
558 Function, or sequence of functions
560 Examples
561 --------
562 >>> import dateutil.parser
563 >>> import datetime
564 >>> dateparser = dateutil.parser.parse
565 >>> defaultdate = datetime.date(2000, 1, 1)
566 >>> StringConverter.upgrade_mapper(dateparser, default=defaultdate)
567 """
568 # Func is a single functions
569 if callable(func):
570 cls._mapper.insert(-1, (cls._getsubdtype(default), func, default))
571 return
572 elif hasattr(func, '__iter__'):
573 if isinstance(func[0], (tuple, list)):
574 for _ in func:
575 cls._mapper.insert(-1, _)
576 return
577 if default is None:
578 default = [None] * len(func)
579 else:
580 default = list(default)
581 default.append([None] * (len(func) - len(default)))
582 for fct, dft in zip(func, default):
583 cls._mapper.insert(-1, (cls._getsubdtype(dft), fct, dft))
585 @classmethod
586 def _find_map_entry(cls, dtype):
587 # if a converter for the specific dtype is available use that
588 for i, (deftype, func, default_def) in enumerate(cls._mapper):
589 if dtype.type == deftype:
590 return i, (deftype, func, default_def)
592 # otherwise find an inexact match
593 for i, (deftype, func, default_def) in enumerate(cls._mapper):
594 if np.issubdtype(dtype.type, deftype):
595 return i, (deftype, func, default_def)
597 raise LookupError
599 def __init__(self, dtype_or_func=None, default=None, missing_values=None,
600 locked=False):
601 # Defines a lock for upgrade
602 self._locked = bool(locked)
603 # No input dtype: minimal initialization
604 if dtype_or_func is None:
605 self.func = str2bool
606 self._status = 0
607 self.default = default or False
608 dtype = np.dtype('bool')
609 else:
610 # Is the input a np.dtype ?
611 try:
612 self.func = None
613 dtype = np.dtype(dtype_or_func)
614 except TypeError:
615 # dtype_or_func must be a function, then
616 if not callable(dtype_or_func):
617 errmsg = ("The input argument `dtype` is neither a"
618 " function nor a dtype (got '%s' instead)")
619 raise TypeError(errmsg % type(dtype_or_func))
620 # Set the function
621 self.func = dtype_or_func
622 # If we don't have a default, try to guess it or set it to
623 # None
624 if default is None:
625 try:
626 default = self.func('0')
627 except ValueError:
628 default = None
629 dtype = self._getdtype(default)
631 # find the best match in our mapper
632 try:
633 self._status, (_, func, default_def) = self._find_map_entry(dtype)
634 except LookupError:
635 # no match
636 self.default = default
637 _, func, _ = self._mapper[-1]
638 self._status = 0
639 else:
640 # use the found default only if we did not already have one
641 if default is None:
642 self.default = default_def
643 else:
644 self.default = default
646 # If the input was a dtype, set the function to the last we saw
647 if self.func is None:
648 self.func = func
650 # If the status is 1 (int), change the function to
651 # something more robust.
652 if self.func == self._mapper[1][1]:
653 if issubclass(dtype.type, np.uint64):
654 self.func = np.uint64
655 elif issubclass(dtype.type, np.int64):
656 self.func = np.int64
657 else:
658 self.func = lambda x: int(float(x))
659 # Store the list of strings corresponding to missing values.
660 if missing_values is None:
661 self.missing_values = {''}
662 else:
663 if isinstance(missing_values, str):
664 missing_values = missing_values.split(",")
665 self.missing_values = set(list(missing_values) + [''])
667 self._callingfunction = self._strict_call
668 self.type = self._dtypeortype(dtype)
669 self._checked = False
670 self._initial_default = default
672 def _loose_call(self, value):
673 try:
674 return self.func(value)
675 except ValueError:
676 return self.default
678 def _strict_call(self, value):
679 try:
681 # We check if we can convert the value using the current function
682 new_value = self.func(value)
684 # In addition to having to check whether func can convert the
685 # value, we also have to make sure that we don't get overflow
686 # errors for integers.
687 if self.func is int:
688 try:
689 np.array(value, dtype=self.type)
690 except OverflowError:
691 raise ValueError
693 # We're still here so we can now return the new value
694 return new_value
696 except ValueError:
697 if value.strip() in self.missing_values:
698 if not self._status:
699 self._checked = False
700 return self.default
701 raise ValueError(f"Cannot convert string '{value}'")
703 def __call__(self, value):
704 return self._callingfunction(value)
706 def _do_upgrade(self):
707 # Raise an exception if we locked the converter...
708 if self._locked:
709 errmsg = "Converter is locked and cannot be upgraded"
710 raise ConverterLockError(errmsg)
711 _statusmax = len(self._mapper)
712 # Complains if we try to upgrade by the maximum
713 _status = self._status
714 if _status == _statusmax:
715 errmsg = "Could not find a valid conversion function"
716 raise ConverterError(errmsg)
717 elif _status < _statusmax - 1:
718 _status += 1
719 self.type, self.func, default = self._mapper[_status]
720 self._status = _status
721 if self._initial_default is not None:
722 self.default = self._initial_default
723 else:
724 self.default = default
726 def upgrade(self, value):
727 """
728 Find the best converter for a given string, and return the result.
730 The supplied string `value` is converted by testing different
731 converters in order. First the `func` method of the
732 `StringConverter` instance is tried, if this fails other available
733 converters are tried. The order in which these other converters
734 are tried is determined by the `_status` attribute of the instance.
736 Parameters
737 ----------
738 value : str
739 The string to convert.
741 Returns
742 -------
743 out : any
744 The result of converting `value` with the appropriate converter.
746 """
747 self._checked = True
748 try:
749 return self._strict_call(value)
750 except ValueError:
751 self._do_upgrade()
752 return self.upgrade(value)
754 def iterupgrade(self, value):
755 self._checked = True
756 if not hasattr(value, '__iter__'):
757 value = (value,)
758 _strict_call = self._strict_call
759 try:
760 for _m in value:
761 _strict_call(_m)
762 except ValueError:
763 self._do_upgrade()
764 self.iterupgrade(value)
766 def update(self, func, default=None, testing_value=None,
767 missing_values='', locked=False):
768 """
769 Set StringConverter attributes directly.
771 Parameters
772 ----------
773 func : function
774 Conversion function.
775 default : any, optional
776 Value to return by default, that is, when the string to be
777 converted is flagged as missing. If not given,
778 `StringConverter` tries to supply a reasonable default value.
779 testing_value : str, optional
780 A string representing a standard input value of the converter.
781 This string is used to help defining a reasonable default
782 value.
783 missing_values : {sequence of str, None}, optional
784 Sequence of strings indicating a missing value. If ``None``, then
785 the existing `missing_values` are cleared. The default is ``''``.
786 locked : bool, optional
787 Whether the StringConverter should be locked to prevent
788 automatic upgrade or not. Default is False.
790 Notes
791 -----
792 `update` takes the same parameters as the constructor of
793 `StringConverter`, except that `func` does not accept a `dtype`
794 whereas `dtype_or_func` in the constructor does.
796 """
797 self.func = func
798 self._locked = locked
800 # Don't reset the default to None if we can avoid it
801 if default is not None:
802 self.default = default
803 self.type = self._dtypeortype(self._getdtype(default))
804 else:
805 try:
806 tester = func(testing_value or '1')
807 except (TypeError, ValueError):
808 tester = None
809 self.type = self._dtypeortype(self._getdtype(tester))
811 # Add the missing values to the existing set or clear it.
812 if missing_values is None:
813 # Clear all missing values even though the ctor initializes it to
814 # set(['']) when the argument is None.
815 self.missing_values = set()
816 else:
817 if not np.iterable(missing_values):
818 missing_values = [missing_values]
819 if not all(isinstance(v, str) for v in missing_values):
820 raise TypeError("missing_values must be strings or unicode")
821 self.missing_values.update(missing_values)
824def easy_dtype(ndtype, names=None, defaultfmt="f%i", **validationargs):
825 """
826 Convenience function to create a `np.dtype` object.
828 The function processes the input `dtype` and matches it with the given
829 names.
831 Parameters
832 ----------
833 ndtype : var
834 Definition of the dtype. Can be any string or dictionary recognized
835 by the `np.dtype` function, or a sequence of types.
836 names : str or sequence, optional
837 Sequence of strings to use as field names for a structured dtype.
838 For convenience, `names` can be a string of a comma-separated list
839 of names.
840 defaultfmt : str, optional
841 Format string used to define missing names, such as ``"f%i"``
842 (default) or ``"fields_%02i"``.
843 validationargs : optional
844 A series of optional arguments used to initialize a
845 `NameValidator`.
847 Examples
848 --------
849 >>> import numpy as np
850 >>> np.lib._iotools.easy_dtype(float)
851 dtype('float64')
852 >>> np.lib._iotools.easy_dtype("i4, f8")
853 dtype([('f0', '<i4'), ('f1', '<f8')])
854 >>> np.lib._iotools.easy_dtype("i4, f8", defaultfmt="field_%03i")
855 dtype([('field_000', '<i4'), ('field_001', '<f8')])
857 >>> np.lib._iotools.easy_dtype((int, float, float), names="a,b,c")
858 dtype([('a', '<i8'), ('b', '<f8'), ('c', '<f8')])
859 >>> np.lib._iotools.easy_dtype(float, names="a,b,c")
860 dtype([('a', '<f8'), ('b', '<f8'), ('c', '<f8')])
862 """
863 try:
864 ndtype = np.dtype(ndtype)
865 except TypeError:
866 validate = NameValidator(**validationargs)
867 nbfields = len(ndtype)
868 if names is None:
869 names = [''] * len(ndtype)
870 elif isinstance(names, str):
871 names = names.split(",")
872 names = validate(names, nbfields=nbfields, defaultfmt=defaultfmt)
873 ndtype = np.dtype({"formats": ndtype, "names": names})
874 else:
875 # Explicit names
876 if names is not None:
877 validate = NameValidator(**validationargs)
878 if isinstance(names, str):
879 names = names.split(",")
880 # Simple dtype: repeat to match the nb of names
881 if ndtype.names is None:
882 formats = tuple([ndtype.type] * len(names))
883 names = validate(names, defaultfmt=defaultfmt)
884 ndtype = np.dtype(list(zip(names, formats)))
885 # Structured dtype: just validate the names as needed
886 else:
887 ndtype.names = validate(names, nbfields=len(ndtype.names),
888 defaultfmt=defaultfmt)
889 # No implicit names
890 elif ndtype.names is not None:
891 validate = NameValidator(**validationargs)
892 # Default initial names : should we change the format ?
893 numbered_names = tuple(f"f{i}" for i in range(len(ndtype.names)))
894 if ((ndtype.names == numbered_names) and (defaultfmt != "f%i")):
895 ndtype.names = validate([''] * len(ndtype.names),
896 defaultfmt=defaultfmt)
897 # Explicit initial names : just validate
898 else:
899 ndtype.names = validate(ndtype.names, defaultfmt=defaultfmt)
900 return ndtype