Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tables/flavor.py: 48%
154 statements
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-10 06:15 +0000
« prev ^ index » next coverage.py v7.2.5, created at 2023-05-10 06:15 +0000
1"""Utilities for handling different array flavors in PyTables.
3Variables
4=========
6`__docformat`__
7 The format of documentation strings in this module.
8`internal_flavor`
9 The flavor used internally by PyTables.
10`all_flavors`
11 List of all flavors available to PyTables.
12`alias_map`
13 Maps old flavor names to the most similar current flavor.
14`description_map`
15 Maps flavors to short descriptions of their supported objects.
16`identifier_map`
17 Maps flavors to functions that can identify their objects.
19 The function associated with a given flavor will return a true
20 value if the object passed to it can be identified as being of
21 that flavor.
23 See the `flavor_of()` function for a friendlier interface to
24 flavor identification.
26`converter_map`
27 Maps (source, destination) flavor pairs to converter functions.
29 Converter functions get an array of the source flavor and return
30 an array of the destination flavor.
32 See the `array_of_flavor()` and `flavor_to_flavor()` functions for
33 friendlier interfaces to flavor conversion.
35"""
37import warnings
39import numpy as np
41from .exceptions import FlavorError, FlavorWarning
44__docformat__ = 'reStructuredText'
45"""The format of documentation strings in this module."""
47internal_flavor = 'numpy'
48"""The flavor used internally by PyTables."""
50# This is very slightly slower than a set for a small number of values
51# in terms of (infrequent) lookup time, but allows `flavor_of()`
52# (which may be called much more frequently) to check for flavors in
53# order, beginning with the most common one.
54all_flavors = [] # filled as flavors are registered
55"""List of all flavors available to PyTables."""
57alias_map = {} # filled as flavors are registered
58"""Maps old flavor names to the most similar current flavor."""
60description_map = {} # filled as flavors are registered
61"""Maps flavors to short descriptions of their supported objects."""
63identifier_map = {} # filled as flavors are registered
64"""Maps flavors to functions that can identify their objects.
66The function associated with a given flavor will return a true value
67if the object passed to it can be identified as being of that flavor.
69See the `flavor_of()` function for a friendlier interface to flavor
70identification.
71"""
73converter_map = {} # filled as flavors are registered
74"""Maps (source, destination) flavor pairs to converter functions.
76Converter functions get an array of the source flavor and return an
77array of the destination flavor.
79See the `array_of_flavor()` and `flavor_to_flavor()` functions for
80friendlier interfaces to flavor conversion.
81"""
84def check_flavor(flavor):
85 """Raise a ``FlavorError`` if the `flavor` is not valid."""
87 if flavor not in all_flavors:
88 available_flavs = ", ".join(flav for flav in all_flavors)
89 raise FlavorError(
90 "flavor ``%s`` is unsupported or unavailable; "
91 "available flavors in this system are: %s"
92 % (flavor, available_flavs))
95def array_of_flavor2(array, src_flavor, dst_flavor):
96 """Get a version of the given `array` in a different flavor.
98 The input `array` must be of the given `src_flavor`, and the
99 returned array will be of the indicated `dst_flavor`. Both
100 flavors may be the same, but it is not guaranteed that the
101 returned array will be the same object as the input one in this
102 case.
104 If the conversion is not supported, a ``FlavorError`` is raised.
106 """
108 convkey = (src_flavor, dst_flavor)
109 if convkey not in converter_map:
110 raise FlavorError("conversion from flavor ``%s`` to flavor ``%s`` "
111 "is unsupported or unavailable in this system"
112 % (src_flavor, dst_flavor))
114 convfunc = converter_map[convkey]
115 return convfunc(array)
118def flavor_to_flavor(array, src_flavor, dst_flavor):
119 """Get a version of the given `array` in a different flavor.
121 The input `array` must be of the given `src_flavor`, and the
122 returned array will be of the indicated `dst_flavor` (see below
123 for an exception to this). Both flavors may be the same, but it
124 is not guaranteed that the returned array will be the same object
125 as the input one in this case.
127 If the conversion is not supported, a `FlavorWarning` is issued
128 and the input `array` is returned as is.
130 """
132 try:
133 return array_of_flavor2(array, src_flavor, dst_flavor)
134 except FlavorError as fe:
135 warnings.warn("%s; returning an object of the ``%s`` flavor instead"
136 % (fe.args[0], src_flavor), FlavorWarning)
137 return array
140def internal_to_flavor(array, dst_flavor):
141 """Get a version of the given `array` in a different `dst_flavor`.
143 The input `array` must be of the internal flavor, and the returned
144 array will be of the given `dst_flavor`. See `flavor_to_flavor()`
145 for more information.
147 """
149 return flavor_to_flavor(array, internal_flavor, dst_flavor)
152def array_as_internal(array, src_flavor):
153 """Get a version of the given `array` in the internal flavor.
155 The input `array` must be of the given `src_flavor`, and the
156 returned array will be of the internal flavor.
158 If the conversion is not supported, a ``FlavorError`` is raised.
160 """
162 return array_of_flavor2(array, src_flavor, internal_flavor)
165def flavor_of(array):
166 """Identify the flavor of a given `array`.
168 If the `array` can not be matched with any flavor, a ``TypeError``
169 is raised.
171 """
173 for flavor in all_flavors:
174 if identifier_map[flavor](array):
175 return flavor
176 type_name = type(array).__name__
177 supported_descs = "; ".join(description_map[fl] for fl in all_flavors)
178 raise TypeError(
179 "objects of type ``%s`` are not supported in this context, sorry; "
180 "supported objects are: %s" % (type_name, supported_descs))
183def array_of_flavor(array, dst_flavor):
184 """Get a version of the given `array` in a different `dst_flavor`.
186 The flavor of the input `array` is guessed, and the returned array
187 will be of the given `dst_flavor`.
189 If the conversion is not supported, a ``FlavorError`` is raised.
191 """
193 return array_of_flavor2(array, flavor_of(array), dst_flavor)
196def restrict_flavors(keep=('python',)):
197 """Disable all flavors except those in keep.
199 Providing an empty keep sequence implies disabling all flavors (but the
200 internal one). If the sequence is not specified, only optional flavors are
201 disabled.
203 .. important:: Once you disable a flavor, it can not be enabled again.
205 """
207 remove = set(all_flavors) - set(keep) - {internal_flavor}
208 for flavor in remove:
209 _disable_flavor(flavor)
212# Flavor registration
213#
214# The order in which flavors appear in `all_flavors` determines the
215# order in which they will be tested for by `flavor_of()`, so place
216# most frequent flavors first.
217all_flavors.append('numpy') # this is the internal flavor
219all_flavors.append('python') # this is always supported
222def _register_aliases():
223 """Register aliases of *available* flavors."""
225 for flavor in all_flavors:
226 aliases = eval('_%s_aliases' % flavor)
227 for alias in aliases:
228 alias_map[alias] = flavor
231def _register_descriptions():
232 """Register descriptions of *available* flavors."""
233 for flavor in all_flavors:
234 description_map[flavor] = eval('_%s_desc' % flavor)
237def _register_identifiers():
238 """Register identifier functions of *available* flavors."""
240 for flavor in all_flavors:
241 identifier_map[flavor] = eval('_is_%s' % flavor)
244def _register_converters():
245 """Register converter functions between *available* flavors."""
247 def identity(array):
248 return array
249 for src_flavor in all_flavors:
250 for dst_flavor in all_flavors:
251 # Converters with the same source and destination flavor
252 # are used when available, since they may perform some
253 # optimizations on the resulting array (e.g. making it
254 # contiguous). Otherwise, an identity function is used.
255 convfunc = None
256 try:
257 convfunc = eval(f'_conv_{src_flavor}_to_{dst_flavor}')
258 except NameError:
259 if src_flavor == dst_flavor:
260 convfunc = identity
261 if convfunc:
262 converter_map[(src_flavor, dst_flavor)] = convfunc
265def _register_all():
266 """Register all *available* flavors."""
268 _register_aliases()
269 _register_descriptions()
270 _register_identifiers()
271 _register_converters()
274def _deregister_aliases(flavor):
275 """Deregister aliases of a given `flavor` (no checks)."""
277 rm_aliases = []
278 for (an_alias, a_flavor) in alias_map.items():
279 if a_flavor == flavor:
280 rm_aliases.append(an_alias)
281 for an_alias in rm_aliases:
282 del alias_map[an_alias]
285def _deregister_description(flavor):
286 """Deregister description of a given `flavor` (no checks)."""
288 del description_map[flavor]
291def _deregister_identifier(flavor):
292 """Deregister identifier function of a given `flavor` (no checks)."""
294 del identifier_map[flavor]
297def _deregister_converters(flavor):
298 """Deregister converter functions of a given `flavor` (no checks)."""
300 rm_flavor_pairs = []
301 for flavor_pair in converter_map:
302 if flavor in flavor_pair:
303 rm_flavor_pairs.append(flavor_pair)
304 for flavor_pair in rm_flavor_pairs:
305 del converter_map[flavor_pair]
308def _disable_flavor(flavor):
309 """Completely disable the given `flavor` (no checks)."""
311 _deregister_aliases(flavor)
312 _deregister_description(flavor)
313 _deregister_identifier(flavor)
314 _deregister_converters(flavor)
315 all_flavors.remove(flavor)
318# Implementation of flavors
319_python_aliases = [
320 'List', 'Tuple',
321 'Int', 'Float', 'String',
322 'VLString', 'Object',
323]
324_python_desc = ("homogeneous list or tuple, "
325 "integer, float, complex or bytes")
328def _is_python(array):
329 return isinstance(array, (tuple, list, int, float, complex, bytes))
332_numpy_aliases = []
333_numpy_desc = "NumPy array, record or scalar"
336if np.lib.NumpyVersion(np.__version__) >= np.lib.NumpyVersion('1.19.0'):
337 def toarray(array, *args, **kwargs):
338 with warnings.catch_warnings():
339 warnings.simplefilter('error')
340 try:
341 array = np.array(array, *args, **kwargs)
342 except np.VisibleDeprecationWarning:
343 raise ValueError(
344 'cannot guess the desired dtype from the input')
346 return array
347else:
348 toarray = np.array
351def _is_numpy(array):
352 return isinstance(array, (np.ndarray, np.generic))
355def _numpy_contiguous(convfunc):
356 """Decorate `convfunc` to return a *contiguous* NumPy array.
358 Note: When arrays are 0-strided, the copy is avoided. This allows
359 to use `array` to still carry info about the dtype and shape.
360 """
362 def conv_to_numpy(array):
363 nparr = convfunc(array)
364 if (hasattr(nparr, 'flags') and
365 not nparr.flags.contiguous and
366 sum(nparr.strides) != 0):
367 nparr = nparr.copy() # copying the array makes it contiguous
368 return nparr
369 conv_to_numpy.__name__ = convfunc.__name__
370 conv_to_numpy.__doc__ = convfunc.__doc__
371 return conv_to_numpy
374@_numpy_contiguous
375def _conv_numpy_to_numpy(array):
376 # Passes contiguous arrays through and converts scalars into
377 # scalar arrays.
378 nparr = np.asarray(array)
379 if nparr.dtype.kind == 'U':
380 # from Python 3 loads of common strings are disguised as Unicode
381 try:
382 # try to convert to basic 'S' type
383 return nparr.astype('S')
384 except UnicodeEncodeError:
385 pass
386 # pass on true Unicode arrays downstream in case it can be
387 # handled in the future
388 return nparr
391@_numpy_contiguous
392def _conv_python_to_numpy(array):
393 nparr = toarray(array)
394 if nparr.dtype.kind == 'U':
395 # from Python 3 loads of common strings are disguised as Unicode
396 try:
397 # try to convert to basic 'S' type
398 return nparr.astype('S')
399 except UnicodeEncodeError:
400 pass
401 # pass on true Unicode arrays downstream in case it can be
402 # handled in the future
403 return nparr
406def _conv_numpy_to_python(array):
407 if array.shape != ():
408 # Lists are the default for returning multidimensional objects
409 array = array.tolist()
410 else:
411 # 0-dim or scalar case
412 array = array.item()
413 return array
416# Now register everything related with *available* flavors.
417_register_all()
420def _test():
421 """Run ``doctest`` on this module."""
423 import doctest
424 doctest.testmod()
427if __name__ == '__main__':
428 _test()