immlib 1.0.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- immlib/__init__.py +131 -0
- immlib/_init.py +108 -0
- immlib/_version.py +235 -0
- immlib/doc/__init__.py +38 -0
- immlib/doc/_core.py +311 -0
- immlib/iolib/__init__.py +29 -0
- immlib/iolib/_core.py +720 -0
- immlib/pathlib/__init__.py +69 -0
- immlib/pathlib/_cache.py +152 -0
- immlib/pathlib/_core.py +869 -0
- immlib/pathlib/_osf.py +538 -0
- immlib/test/__init__.py +16 -0
- immlib/test/__main__.py +10 -0
- immlib/test/doc/__init__.py +6 -0
- immlib/test/doc/test_core.py +91 -0
- immlib/test/iolib/__init__.py +7 -0
- immlib/test/iolib/test_core.py +81 -0
- immlib/test/pathlib/__init__.py +11 -0
- immlib/test/pathlib/test_core.py +146 -0
- immlib/test/pathlib/test_osf.py +54 -0
- immlib/test/types/__init__.py +5 -0
- immlib/test/types/test_core.py +110 -0
- immlib/test/util/__init__.py +11 -0
- immlib/test/util/test_core.py +681 -0
- immlib/test/util/test_numeric.py +1374 -0
- immlib/test/util/test_quantity.py +218 -0
- immlib/test/util/test_url.py +51 -0
- immlib/test/workflow/__init__.py +9 -0
- immlib/test/workflow/test_core.py +418 -0
- immlib/test/workflow/test_plantype.py +248 -0
- immlib/types/__init__.py +29 -0
- immlib/types/_core.py +333 -0
- immlib/util/__init__.py +283 -0
- immlib/util/_core.py +2524 -0
- immlib/util/_numeric.py +2651 -0
- immlib/util/_quantity.py +523 -0
- immlib/util/_url.py +114 -0
- immlib/workflow/__init__.py +48 -0
- immlib/workflow/_core.py +1635 -0
- immlib/workflow/_plantype.py +334 -0
- immlib-1.0.0.dev2.dist-info/METADATA +76 -0
- immlib-1.0.0.dev2.dist-info/RECORD +45 -0
- immlib-1.0.0.dev2.dist-info/WHEEL +5 -0
- immlib-1.0.0.dev2.dist-info/licenses/LICENSE +21 -0
- immlib-1.0.0.dev2.dist-info/top_level.txt +1 -0
immlib/util/_core.py
ADDED
|
@@ -0,0 +1,2524 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
###############################################################################
|
|
3
|
+
# immlib/util/_core.py
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Dependencies ################################################################
|
|
7
|
+
|
|
8
|
+
import operator as op
|
|
9
|
+
from inspect import (signature, getfullargspec)
|
|
10
|
+
from functools import (wraps, partial, lru_cache)
|
|
11
|
+
from joblib import Memory
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import pint
|
|
15
|
+
import numpy as np
|
|
16
|
+
import scipy.sparse as sps
|
|
17
|
+
from pcollections import holdlazy
|
|
18
|
+
|
|
19
|
+
from ..doc import docwrap
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Strings #####################################################################
|
|
23
|
+
|
|
24
|
+
@docwrap('immlib.is_str')
|
|
25
|
+
def is_str(obj):
|
|
26
|
+
"""Returns ``True`` if an object is a string and ``False`` otherwise.
|
|
27
|
+
|
|
28
|
+
``is_str(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
29
|
+
of the ``str`` type and ``False`` otherwise.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
obj : object
|
|
34
|
+
The object whose quality as a string object is to be assessed.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
bool
|
|
39
|
+
`True` if `obj` is a string, otherwise `False`.
|
|
40
|
+
"""
|
|
41
|
+
return isinstance(obj, str)
|
|
42
|
+
from unicodedata import normalize as unicodedata_normalize
|
|
43
|
+
@docwrap('immlib.strnorm')
|
|
44
|
+
def strnorm(s, /, case=False, *, unicode=True):
|
|
45
|
+
"""Normalizes a string using the ``unicodedata`` package.
|
|
46
|
+
|
|
47
|
+
``strnorm(s)`` returns a version of `s` that has been unicode-normalized
|
|
48
|
+
using the ``unicodedata.normalize(s)`` function. Case-normalization can
|
|
49
|
+
also be requested via the `case` option.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
s : object
|
|
54
|
+
The string to be normalized.
|
|
55
|
+
case : bool, optional
|
|
56
|
+
Whether to perform case-normalization (``case=True``) or not
|
|
57
|
+
(``case=False``, the default). If two strings are case-normalized, then
|
|
58
|
+
an equality comparison will reveal whether the original (unnormalized
|
|
59
|
+
strings) were equal up to the case of the characters. Case
|
|
60
|
+
normalization is performed using the ``str.casefold()`` method.
|
|
61
|
+
unicode : bool or str, optional
|
|
62
|
+
Whether to perform unicode normalization via the
|
|
63
|
+
``unicodedata.normalize`` function. The default behavior
|
|
64
|
+
(``unicode=True``) is to perform normalization, but this can be
|
|
65
|
+
disabled with ``unicode=False``. Alternatively, a string may be given,
|
|
66
|
+
in which case it is passed to the ``unicodedata.normalize`` function as
|
|
67
|
+
the first argument; when `unicode` is ``True``, the string used is
|
|
68
|
+
``'NFD'``.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
str
|
|
73
|
+
A normalized version of `s`.
|
|
74
|
+
"""
|
|
75
|
+
if unicode is True:
|
|
76
|
+
unicode = 'NFD'
|
|
77
|
+
if unicode:
|
|
78
|
+
s = unicodedata_normalize(unicode, s)
|
|
79
|
+
if case:
|
|
80
|
+
s = s.casefold()
|
|
81
|
+
s = unicodedata_normalize(unicode, s)
|
|
82
|
+
elif case:
|
|
83
|
+
s = s.casefold()
|
|
84
|
+
return s
|
|
85
|
+
def _strbinop_prep(a, b, case=True, unicode=None, strip=False):
|
|
86
|
+
if not is_str(a) or not is_str(b): return None
|
|
87
|
+
# We do case normalization when case comparison is *not* requested
|
|
88
|
+
casenorm = not bool(case)
|
|
89
|
+
# When unicode is None, we do its normalization only when case
|
|
90
|
+
# normalization is being done.
|
|
91
|
+
if unicode is None: unicode = casenorm
|
|
92
|
+
# We now perform normalization if it is required.
|
|
93
|
+
if unicode or casenorm:
|
|
94
|
+
a = strnorm(a, case=casenorm, unicode=unicode)
|
|
95
|
+
b = strnorm(b, case=casenorm, unicode=unicode)
|
|
96
|
+
# If we requested stripping, do that now.
|
|
97
|
+
if strip is True:
|
|
98
|
+
a = a.strip()
|
|
99
|
+
b = b.strip()
|
|
100
|
+
elif strip is not False:
|
|
101
|
+
a = a.strip(strip)
|
|
102
|
+
b = b.strip(strip)
|
|
103
|
+
return (a,b)
|
|
104
|
+
@docwrap('immlib.strcmp')
|
|
105
|
+
def strcmp(a, b, /, case=True, *, unicode=None, strip=False, split=False):
|
|
106
|
+
"""Determines if the given objects are strings and compares them if so.
|
|
107
|
+
|
|
108
|
+
``strcmp(a, b)`` returns ``None`` if either `a` or `b` is not a string;
|
|
109
|
+
otherwise, it returns ``-1``, ``0``, or ``1`` if `a` is less than, equal to,
|
|
110
|
+
or greater than `b`, respectively, subject to the constraints of the
|
|
111
|
+
parameters.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
a : object
|
|
116
|
+
The first argument.
|
|
117
|
+
b : object
|
|
118
|
+
The second argument.
|
|
119
|
+
case : bool, optional
|
|
120
|
+
Whether to perform case-sensitive (``case=True``) or case-insensitive
|
|
121
|
+
(``case=False``) string comparison. The default is ``False``.
|
|
122
|
+
unicode : bool or None, optional
|
|
123
|
+
Whether to run unicode normalization on `a` and `b` prior to
|
|
124
|
+
comparison. By default, this is ``None``, which is interpreted as a
|
|
125
|
+
`True` value when `case` is ``False`` and as ``False`` value when
|
|
126
|
+
`case` is ``True``. In other words, unicode normalization is performed
|
|
127
|
+
when case-insensitive comparison is being performed but not when
|
|
128
|
+
standard string comparison is being performed. Unicode normalization is
|
|
129
|
+
always performed both before and after casefolding. Unicode
|
|
130
|
+
normalization is performed using the ``unicodedata`` package's
|
|
131
|
+
``normalize(unicode, string)`` function. If this argument is a string,
|
|
132
|
+
it is instead passed to the ``normalize`` function as the first
|
|
133
|
+
argument. When `unicode` is not a string but normalization is
|
|
134
|
+
performed, them the default string is ``'NFD'``.
|
|
135
|
+
strip : bool, optional
|
|
136
|
+
If set to ``True``, then ``a.strip()`` and ``b.strip()`` are used in
|
|
137
|
+
place of `a` and `b`. If set to ``False`` (the default), then no
|
|
138
|
+
stripping is performed. If a non-boolean value is given, then it is
|
|
139
|
+
passed as an argument to the ``strip()`` method.
|
|
140
|
+
split : bool, optional
|
|
141
|
+
If set to ``True``, then ``a.split()`` and ``b.split()`` are used in
|
|
142
|
+
place of `a` and `b`. The lists of strings that result from
|
|
143
|
+
``a.split()`` and ``b.split()`` are rejoined with no separator prior to
|
|
144
|
+
comparison. If this option is set to `False` (the default), then no
|
|
145
|
+
splitting is performed. If a non-boolean value is given, then it is
|
|
146
|
+
passed as an argument to the ``split()`` method.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
bool or None
|
|
151
|
+
``None`` if either `a` is not a string or `b` is not a string;
|
|
152
|
+
otherwise, ``-1`` if `a` is lexicographically less than `b`, ``0`` if
|
|
153
|
+
``a == b``, and ``1`` if `a` is lexicographically greater than `b`,
|
|
154
|
+
subject to the constraints of the optional parameters.
|
|
155
|
+
|
|
156
|
+
See Also
|
|
157
|
+
--------
|
|
158
|
+
strnorm, streq
|
|
159
|
+
|
|
160
|
+
"""
|
|
161
|
+
prep = _strbinop_prep(a, b, case=case, unicode=unicode, strip=strip)
|
|
162
|
+
if prep is None:
|
|
163
|
+
return None
|
|
164
|
+
(a, b) = prep
|
|
165
|
+
# If the split argument is true-ish, then we need to eliminate spaces.
|
|
166
|
+
if split is not False:
|
|
167
|
+
if split is True:
|
|
168
|
+
a = a.split()
|
|
169
|
+
b = b.split()
|
|
170
|
+
else:
|
|
171
|
+
a = a.split(split)
|
|
172
|
+
b = b.split(split)
|
|
173
|
+
# We can do this comparison by re-joining the strings with an empty
|
|
174
|
+
# separator. The lexicographically smaller string (excepting the
|
|
175
|
+
# spacers, which are now removed) will still be lexicographically
|
|
176
|
+
# smaller.
|
|
177
|
+
a = ''.join(a)
|
|
178
|
+
b = ''.join(b)
|
|
179
|
+
return (-1 if a < b else 1 if a > b else 0)
|
|
180
|
+
@docwrap('immlib.streq')
|
|
181
|
+
def streq(a, b, /, case=True, *, unicode=None, strip=False, split=False):
|
|
182
|
+
"""Determines if the given objects are equal strings or not.
|
|
183
|
+
|
|
184
|
+
``streq(a, b)`` returns ``True`` if `a` and `b` are both strings and are
|
|
185
|
+
equal to each other, subject to the constraints of the options.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
%(immlib.strcmp.parameters)s
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
bool or None
|
|
194
|
+
If `a` and `b` are both strings then ``True`` is returned if `a` equals
|
|
195
|
+
`b` and ``False`` is returned otherwise. If either `a` or `b` is not a
|
|
196
|
+
string, then ``None`` is returned.
|
|
197
|
+
|
|
198
|
+
See Also
|
|
199
|
+
--------
|
|
200
|
+
strnorm, strcmp
|
|
201
|
+
"""
|
|
202
|
+
cmpval = strcmp(a, b, case=case, unicode=unicode, strip=strip, split=split)
|
|
203
|
+
return None if cmpval is None else (cmpval == 0)
|
|
204
|
+
@docwrap('immlib.strends')
|
|
205
|
+
def strends(a, b, /, case=True, *, unicode=None, strip=False):
|
|
206
|
+
"""Determines whether or not the string `a` ends with the string `b`.
|
|
207
|
+
|
|
208
|
+
``strends(a, b)`` returns ``True`` if `a` and `b` are both strings and if
|
|
209
|
+
`a` ends with `b`, subject to the constraints of the parameters.
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
%(immlib.strcmp.parameters.case)s
|
|
214
|
+
%(immlib.strcmp.parameters.unicode)s
|
|
215
|
+
%(immlib.strcmp.parameters.strip)s
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
bool or None
|
|
220
|
+
If `a` and `b` are both strings then ``True`` is returned if `a` ends
|
|
221
|
+
with `b` and ``False`` is returned otherwise. If either `a` or `b` is
|
|
222
|
+
not a string, then ``None`` is returned.
|
|
223
|
+
"""
|
|
224
|
+
prep = _strbinop_prep(a, b, case=case, unicode=unicode, strip=strip)
|
|
225
|
+
if prep is None: return None
|
|
226
|
+
else: (a, b) = prep
|
|
227
|
+
# Check the ending
|
|
228
|
+
return a.endswith(b)
|
|
229
|
+
@docwrap('immlib.strstarts')
|
|
230
|
+
def strstarts(a, b, /, case=True, *, unicode=None, strip=False):
|
|
231
|
+
"""Determines whether or not the string `a` starts with the string `b`.
|
|
232
|
+
|
|
233
|
+
``strstarts(a, b)`` returns ``True`` if `a` and `b` are both strings and if
|
|
234
|
+
`a` starts with `b`, subject to the constraints of the parameters.
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
%(immlib.strcmp.parameters.case)s
|
|
239
|
+
%(immlib.strcmp.parameters.unicode)s
|
|
240
|
+
%(immlib.strcmp.parameters.strip)s
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
bool or None
|
|
245
|
+
``True` if `a` and `b` are both strings and if `a` startss with `b`,
|
|
246
|
+
subject to the constraints of the optional parameters. If either `a` or
|
|
247
|
+
`b` is not a string, then ``None`` is returned.
|
|
248
|
+
"""
|
|
249
|
+
prep = _strbinop_prep(a, b, case=case, unicode=unicode, strip=strip)
|
|
250
|
+
if prep is None: return None
|
|
251
|
+
else: (a, b) = prep
|
|
252
|
+
# Check the beginning.
|
|
253
|
+
return a.startswith(b)
|
|
254
|
+
@docwrap('immlib.strissym')
|
|
255
|
+
def strissym(s):
|
|
256
|
+
"""Determines if the given string is a valid symbol (identifier).
|
|
257
|
+
|
|
258
|
+
``strissym(s)`` returns ``True`` if `s` is both a string and a valid
|
|
259
|
+
identifier. Otherwise, it returns ``False`` if `s` is a string and
|
|
260
|
+
``None`` if not.
|
|
261
|
+
|
|
262
|
+
See also
|
|
263
|
+
--------
|
|
264
|
+
striskey, strisvar
|
|
265
|
+
"""
|
|
266
|
+
return s.isidentifier() if is_str(s) else None
|
|
267
|
+
from keyword import iskeyword
|
|
268
|
+
@docwrap('immlib.striskey')
|
|
269
|
+
def striskey(s):
|
|
270
|
+
"""Determines if the given string is a valid keyword.
|
|
271
|
+
|
|
272
|
+
``strissym(s)`` returns ``True`` if `s` is both a string and a valid keyword
|
|
273
|
+
(such as ``'if'`` or ``'while'``). Otherwise, it returns ``False`` if `s` is a
|
|
274
|
+
string and ``None`` if not.
|
|
275
|
+
|
|
276
|
+
See Also
|
|
277
|
+
--------
|
|
278
|
+
strissym, strisvar
|
|
279
|
+
"""
|
|
280
|
+
return iskeyword(s) if is_str(s) else None
|
|
281
|
+
@docwrap('immlib.strisvar')
|
|
282
|
+
def strisvar(s):
|
|
283
|
+
"""Determines if the given string is a valid variable name.
|
|
284
|
+
|
|
285
|
+
``strissym(s)`` returns ``True`` if `s` is both a string and a valid name
|
|
286
|
+
(i.e., a symbol but not a keyword). Otherwise, it returns ``False`` if `s`
|
|
287
|
+
is a string and ``None`` if not.
|
|
288
|
+
|
|
289
|
+
See Also
|
|
290
|
+
--------
|
|
291
|
+
strissym, striskey
|
|
292
|
+
"""
|
|
293
|
+
return (
|
|
294
|
+
None if not is_str(s) else
|
|
295
|
+
False if iskeyword(s) else
|
|
296
|
+
s.isidentifier())
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
# Builtin Python Abstract Types ###############################################
|
|
300
|
+
|
|
301
|
+
from collections.abc import Callable
|
|
302
|
+
@docwrap('immlib.is_acallable')
|
|
303
|
+
def is_acallable(obj):
|
|
304
|
+
"""Returns ``True`` if an object is a callable object like a function.
|
|
305
|
+
|
|
306
|
+
``is_acallable(obj)`` returns ``True`` if the given object `obj` is an
|
|
307
|
+
instance of the abstract callable type, ``collections.abc.Callable``. Note
|
|
308
|
+
that in general, it is more common and preferable to use the builtin
|
|
309
|
+
``callable`` function; however, ``is_acallable`` is included in ``immlib``
|
|
310
|
+
for completeness.
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
obj : object
|
|
315
|
+
The object whose quality as an ``Callable`` object is to be assessed.
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
bool
|
|
320
|
+
``True`` if `obj` is an instance of ``Callable``, otherwise ``False``.
|
|
321
|
+
"""
|
|
322
|
+
return isinstance(obj, Callable)
|
|
323
|
+
from types import LambdaType
|
|
324
|
+
@docwrap('immlib.is_lambda')
|
|
325
|
+
def is_lambda(obj):
|
|
326
|
+
"""Returns ``True`` if an object is a lambda function, otherwise ``False``.
|
|
327
|
+
|
|
328
|
+
``is_lambda(obj)`` returns ``True`` if the given object `obj` is an
|
|
329
|
+
instance of the ``types.LambdaType`` type.
|
|
330
|
+
|
|
331
|
+
Parameters
|
|
332
|
+
----------
|
|
333
|
+
obj : object
|
|
334
|
+
The object whose quality as a ``LambdaType`` object is to be assessed.
|
|
335
|
+
|
|
336
|
+
Returns
|
|
337
|
+
-------
|
|
338
|
+
bool
|
|
339
|
+
``True`` if `obj` is an instance of ``LambdaType``, otherwise
|
|
340
|
+
``False``.
|
|
341
|
+
|
|
342
|
+
"""
|
|
343
|
+
return isinstance(obj, LambdaType)
|
|
344
|
+
from collections.abc import Sized
|
|
345
|
+
@docwrap('immlib.is_asized')
|
|
346
|
+
def is_asized(obj):
|
|
347
|
+
"""Returns ``True`` if an object implements ``len()``, otherwise ``False``.
|
|
348
|
+
|
|
349
|
+
``is_asized(obj)`` returns ``True`` if the given object `obj` is an
|
|
350
|
+
instance of the abstract ``collections.abc.Sized`` type.
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
obj : object
|
|
355
|
+
The object whose quality as a ``Sized`` object is to be assessed.
|
|
356
|
+
|
|
357
|
+
Returns
|
|
358
|
+
-------
|
|
359
|
+
bool
|
|
360
|
+
``True`` if `obj` is an instance of ``Sized``, otherwise ``False``.
|
|
361
|
+
|
|
362
|
+
"""
|
|
363
|
+
return isinstance(obj, Sized)
|
|
364
|
+
from collections.abc import Container
|
|
365
|
+
@docwrap('immlib.is_acontainer')
|
|
366
|
+
def is_acontainer(obj):
|
|
367
|
+
"""Returns ``True`` if an object implements ``__contains__``, otherwise
|
|
368
|
+
``False``.
|
|
369
|
+
|
|
370
|
+
``is_acontainer(obj)`` returns ``True`` if the given object `obj` is an
|
|
371
|
+
instance of the abstract ``collections.abc.Container`` type.
|
|
372
|
+
|
|
373
|
+
Parameters
|
|
374
|
+
----------
|
|
375
|
+
obj : object
|
|
376
|
+
The object whose quality as a ``Container`` object is to be assessed.
|
|
377
|
+
|
|
378
|
+
Returns
|
|
379
|
+
-------
|
|
380
|
+
bool
|
|
381
|
+
``True`` if `obj` is an instance of ``Container``, otherwise ``False``.
|
|
382
|
+
"""
|
|
383
|
+
return isinstance(obj, Container)
|
|
384
|
+
from collections.abc import Iterable
|
|
385
|
+
@docwrap('immlib.is_aiterable')
|
|
386
|
+
def is_aiterable(obj):
|
|
387
|
+
"""Returns ``True`` if an object implements ``__iter__``, otherwise
|
|
388
|
+
``False``.
|
|
389
|
+
|
|
390
|
+
``is_aiterable(obj)`` returns ``True`` if the given object `obj` is an
|
|
391
|
+
instance of the abstract ``collections.abc.Iterable`` type.
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
obj : object
|
|
396
|
+
The object whose quality as an ``Iterable`` object is to be assessed.
|
|
397
|
+
|
|
398
|
+
Returns
|
|
399
|
+
-------
|
|
400
|
+
bool
|
|
401
|
+
``True`` if `obj` is an instance of ``Iterable``, otherwise ``False``.
|
|
402
|
+
"""
|
|
403
|
+
return isinstance(obj, Iterable)
|
|
404
|
+
from collections.abc import Iterator
|
|
405
|
+
@docwrap('immlib.is_aiterator')
|
|
406
|
+
def is_aiterator(obj):
|
|
407
|
+
"""Returns ``True`` if an object is an instance of
|
|
408
|
+
``collections.abc.Iterator``.
|
|
409
|
+
|
|
410
|
+
``is_aiterable(obj)`` returns ``True`` if the given object `obj` is an
|
|
411
|
+
instance of the abstract ``collections.abc.Iterator`` type.
|
|
412
|
+
|
|
413
|
+
Parameters
|
|
414
|
+
----------
|
|
415
|
+
obj : object
|
|
416
|
+
The object whose quality as an ``Iterator`` object is to be assessed.
|
|
417
|
+
|
|
418
|
+
Returns
|
|
419
|
+
-------
|
|
420
|
+
bool
|
|
421
|
+
``True`` if `obj` is an instance of ``Iterator``, otherwise ``False``.
|
|
422
|
+
"""
|
|
423
|
+
return isinstance(obj, Iterator)
|
|
424
|
+
from collections.abc import Reversible
|
|
425
|
+
@docwrap('immlib.is_areversible')
|
|
426
|
+
def is_areversible(obj):
|
|
427
|
+
"""Returns ``True`` if an object is an instance of ``Reversible``.
|
|
428
|
+
|
|
429
|
+
``is_areversible(obj)`` returns ``True`` if the given object `obj` is an
|
|
430
|
+
instance of the abstract ``collections.abc.Reversible`` type.
|
|
431
|
+
|
|
432
|
+
Parameters
|
|
433
|
+
----------
|
|
434
|
+
obj : object
|
|
435
|
+
The object whose quality as an ``Reversible`` object is to be assessed.
|
|
436
|
+
|
|
437
|
+
Returns
|
|
438
|
+
-------
|
|
439
|
+
bool
|
|
440
|
+
``True`` if `obj` is an instance of ``Reversible``, otherwise
|
|
441
|
+
``False``.
|
|
442
|
+
"""
|
|
443
|
+
return isinstance(obj, Reversible)
|
|
444
|
+
from collections.abc import Collection
|
|
445
|
+
@docwrap('immlib.is_acoll')
|
|
446
|
+
def is_acoll(obj):
|
|
447
|
+
"""Returns ``True`` if an object is a collection (a sized iterable
|
|
448
|
+
container).
|
|
449
|
+
|
|
450
|
+
``is_acoll(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
451
|
+
of the abstract ``collections.abc.Collection`` type.
|
|
452
|
+
|
|
453
|
+
Parameters
|
|
454
|
+
----------
|
|
455
|
+
obj : object
|
|
456
|
+
The object whose quality as an ``Collection`` object is to be assessed.
|
|
457
|
+
|
|
458
|
+
Returns
|
|
459
|
+
-------
|
|
460
|
+
bool
|
|
461
|
+
``True`` if `obj` is an instance of ``Collection``, otherwise
|
|
462
|
+
``False``.
|
|
463
|
+
"""
|
|
464
|
+
return isinstance(obj, Collection)
|
|
465
|
+
from collections.abc import Sequence
|
|
466
|
+
@docwrap('immlib.is_aseq')
|
|
467
|
+
def is_aseq(obj):
|
|
468
|
+
"""Returns ``True`` if an object is a sequence, otherwise ``False``.
|
|
469
|
+
|
|
470
|
+
``is_aseq(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
471
|
+
of the abstract ``collections.abc.Sequence`` type.
|
|
472
|
+
|
|
473
|
+
Parameters
|
|
474
|
+
----------
|
|
475
|
+
obj : object
|
|
476
|
+
The object whose quality as an ``Sequence`` object is to be assessed.
|
|
477
|
+
|
|
478
|
+
Returns
|
|
479
|
+
-------
|
|
480
|
+
bool
|
|
481
|
+
``True`` if `obj` is an instance of ``Sequence``, otherwise ``False``.
|
|
482
|
+
"""
|
|
483
|
+
return isinstance(obj, Sequence)
|
|
484
|
+
from collections.abc import MutableSequence
|
|
485
|
+
@docwrap('immlib.is_amseq')
|
|
486
|
+
def is_amseq(obj):
|
|
487
|
+
"""Returns ``True`` if an object is a mutable sequence, otherwise
|
|
488
|
+
``False``.
|
|
489
|
+
|
|
490
|
+
``is_amseq(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
491
|
+
of the abstract ``collections.abc.MutableSequence`` type.
|
|
492
|
+
|
|
493
|
+
Parameters
|
|
494
|
+
----------
|
|
495
|
+
obj : object
|
|
496
|
+
The object whose quality as an ``MutableSequence`` object is to be
|
|
497
|
+
assessed.
|
|
498
|
+
|
|
499
|
+
Returns
|
|
500
|
+
-------
|
|
501
|
+
bool
|
|
502
|
+
``True`` if `obj` is an instance of ``MutableSequence``, otherwise
|
|
503
|
+
``False``.
|
|
504
|
+
"""
|
|
505
|
+
return isinstance(obj, MutableSequence)
|
|
506
|
+
from pcollections.abc import PersistentSequence
|
|
507
|
+
@docwrap('immlib.is_apseq')
|
|
508
|
+
def is_apseq(obj):
|
|
509
|
+
"""Returns ``True`` if an object is a persistent sequence, otherwise
|
|
510
|
+
``False``.
|
|
511
|
+
|
|
512
|
+
``is_apseq(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
513
|
+
of the abstract ``pcollections.abc.PersistentSequence`` type.
|
|
514
|
+
|
|
515
|
+
Parameters
|
|
516
|
+
----------
|
|
517
|
+
obj : object
|
|
518
|
+
The object whose quality as an ``PersistentSequence`` object is to be
|
|
519
|
+
assessed.
|
|
520
|
+
|
|
521
|
+
Returns
|
|
522
|
+
-------
|
|
523
|
+
bool
|
|
524
|
+
``True`` if `obj` is an instance of ``PersistentSequence``, otherwise
|
|
525
|
+
``False``.
|
|
526
|
+
"""
|
|
527
|
+
return isinstance(obj, PersistentSequence)
|
|
528
|
+
from collections.abc import ByteString
|
|
529
|
+
@docwrap('immlib.is_abytes')
|
|
530
|
+
def is_abytes(obj):
|
|
531
|
+
"""Returns ``True`` if an object is a byte-string, otherwise ``False``.
|
|
532
|
+
|
|
533
|
+
``is_abytes(obj)`` returns ``True`` if the given object `obj` is an
|
|
534
|
+
instance of the abstract ``collections.abc.ByteString`` type.
|
|
535
|
+
|
|
536
|
+
Parameters
|
|
537
|
+
----------
|
|
538
|
+
obj : object
|
|
539
|
+
The object whose quality as an ``ByteString`` object is to be assessed.
|
|
540
|
+
|
|
541
|
+
Returns
|
|
542
|
+
-------
|
|
543
|
+
bool
|
|
544
|
+
``True`` if `obj` is an instance of ``ByteString``, otherwise
|
|
545
|
+
``False``.
|
|
546
|
+
"""
|
|
547
|
+
return isinstance(obj, ByteString)
|
|
548
|
+
@docwrap('immlib.is_bytes')
|
|
549
|
+
def is_bytes(obj):
|
|
550
|
+
"""Returns ``True`` if an object is a ``bytes`` object, otherwise
|
|
551
|
+
``False``.
|
|
552
|
+
|
|
553
|
+
``is_bytes(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
554
|
+
of the ``bytes`` type and returns ``False`` otherwise.
|
|
555
|
+
|
|
556
|
+
Parameters
|
|
557
|
+
----------
|
|
558
|
+
obj : object
|
|
559
|
+
The object whose quality as an ``bytes`` object is to be assessed.
|
|
560
|
+
|
|
561
|
+
Returns
|
|
562
|
+
-------
|
|
563
|
+
bool
|
|
564
|
+
``True`` if `obj` is an instance of ``bytes``, otherwise ``False``.
|
|
565
|
+
"""
|
|
566
|
+
return isinstance(obj, bytes)
|
|
567
|
+
from collections.abc import Set
|
|
568
|
+
@docwrap('immlib.is_aset')
|
|
569
|
+
def is_aset(obj):
|
|
570
|
+
"""Returns ``True`` if an object is a set type, otherwise ``False``.
|
|
571
|
+
|
|
572
|
+
``is_aset(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
573
|
+
of the abstract ``collections.abc.Set`` type.
|
|
574
|
+
|
|
575
|
+
Parameters
|
|
576
|
+
----------
|
|
577
|
+
obj : object
|
|
578
|
+
The object whose quality as an ``Set`` object is to be assessed.
|
|
579
|
+
|
|
580
|
+
Returns
|
|
581
|
+
-------
|
|
582
|
+
bool
|
|
583
|
+
``True`` if `obj` is an instance of ``Set``, otherwise ``False``.
|
|
584
|
+
"""
|
|
585
|
+
return isinstance(obj, Set)
|
|
586
|
+
from collections.abc import MutableSet
|
|
587
|
+
@docwrap('immlib.is_amset')
|
|
588
|
+
def is_amset(obj):
|
|
589
|
+
"""Returns ``True`` if an object is a mutable set, otherwise ``False``.
|
|
590
|
+
|
|
591
|
+
``is_amset(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
592
|
+
of the abstract ``collections.abc.MutableSet`` type.
|
|
593
|
+
|
|
594
|
+
Parameters
|
|
595
|
+
----------
|
|
596
|
+
obj : object
|
|
597
|
+
The object whose quality as an ``MutableSet`` object is to be assessed.
|
|
598
|
+
|
|
599
|
+
Returns
|
|
600
|
+
-------
|
|
601
|
+
bool
|
|
602
|
+
``True`` if `obj` is an instance of ``MutableSet``, otherwise
|
|
603
|
+
``False``.
|
|
604
|
+
"""
|
|
605
|
+
return isinstance(obj, MutableSet)
|
|
606
|
+
from pcollections.abc import PersistentSet
|
|
607
|
+
@docwrap('immlib.is_apset')
|
|
608
|
+
def is_apset(obj):
|
|
609
|
+
"""Returns ``True`` if an object is a persistent set, otherwise ``False``.
|
|
610
|
+
|
|
611
|
+
``is_apset(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
612
|
+
of the abstract ``pcollections.abc.PersistentSet`` type.
|
|
613
|
+
|
|
614
|
+
Parameters
|
|
615
|
+
----------
|
|
616
|
+
obj : object
|
|
617
|
+
The object whose quality as an ``PersistentSet`` object is to be
|
|
618
|
+
assessed.
|
|
619
|
+
|
|
620
|
+
Returns
|
|
621
|
+
-------
|
|
622
|
+
bool
|
|
623
|
+
``True`` if `obj` is an instance of ``PersistentSet``, otherwise
|
|
624
|
+
``False``.
|
|
625
|
+
"""
|
|
626
|
+
return isinstance(obj, PersistentSet)
|
|
627
|
+
from collections.abc import Mapping
|
|
628
|
+
@docwrap('immlib.is_amap')
|
|
629
|
+
def is_amap(obj):
|
|
630
|
+
"""Returns ``True`` if an object is an abstract mapping, otherwise
|
|
631
|
+
``False``.
|
|
632
|
+
|
|
633
|
+
``is_amap(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
634
|
+
of the abstract ``collections.abc.Mapping`` type.
|
|
635
|
+
|
|
636
|
+
Parameters
|
|
637
|
+
----------
|
|
638
|
+
obj : object
|
|
639
|
+
The object whose quality as an ``Mapping`` object is to be assessed.
|
|
640
|
+
|
|
641
|
+
Returns
|
|
642
|
+
-------
|
|
643
|
+
bool
|
|
644
|
+
``True`` if `obj` is an instance of ``Mapping``, otherwise ``False``.
|
|
645
|
+
"""
|
|
646
|
+
return isinstance(obj, Mapping)
|
|
647
|
+
from collections.abc import MutableMapping
|
|
648
|
+
@docwrap('immlib.is_ammap')
|
|
649
|
+
def is_ammap(obj):
|
|
650
|
+
"""Returns ``True`` if an object is a mutable mapping, otherwise ``False``.
|
|
651
|
+
|
|
652
|
+
``is_ammap(obj)`` returns ``True`` if the given object ``obj`` is an
|
|
653
|
+
instance of the abstract ``collections.abc.MutableMapping`` type.
|
|
654
|
+
|
|
655
|
+
Parameters
|
|
656
|
+
----------
|
|
657
|
+
obj : object
|
|
658
|
+
The object whose quality as an ``MutableMapping`` object is to be
|
|
659
|
+
assessed.
|
|
660
|
+
|
|
661
|
+
Returns
|
|
662
|
+
-------
|
|
663
|
+
bool
|
|
664
|
+
``True`` if `obj` is an instance of ``MutableMapping``, otherwise
|
|
665
|
+
``False``.
|
|
666
|
+
"""
|
|
667
|
+
return isinstance(obj, MutableMapping)
|
|
668
|
+
from pcollections.abc import PersistentMapping
|
|
669
|
+
@docwrap('immlib.is_apmap')
|
|
670
|
+
def is_apmap(obj):
|
|
671
|
+
"""Returns ``True`` if an object is a persistent mapping, otherwise
|
|
672
|
+
``False``.
|
|
673
|
+
|
|
674
|
+
``is_apmap(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
675
|
+
of the abstract ``pcollections.abc.PersistentMapping`` type.
|
|
676
|
+
|
|
677
|
+
Parameters
|
|
678
|
+
----------
|
|
679
|
+
obj : object
|
|
680
|
+
The object whose quality as an ``PersistentMapping`` object is to be
|
|
681
|
+
assessed.
|
|
682
|
+
|
|
683
|
+
Returns
|
|
684
|
+
-------
|
|
685
|
+
bool
|
|
686
|
+
``True`` if `obj` is an instance of ``PersistentMapping``, otherwise
|
|
687
|
+
``False``.
|
|
688
|
+
"""
|
|
689
|
+
return isinstance(obj, PersistentMapping)
|
|
690
|
+
from collections.abc import Hashable
|
|
691
|
+
@docwrap('immlib.is_ahashable')
|
|
692
|
+
def is_ahashable(obj):
|
|
693
|
+
"""Returns ``True`` if an object is a hashable object, otherwise ``False``.
|
|
694
|
+
|
|
695
|
+
``is_ahashable(obj)`` returns ``True`` if the given object `obj` is an
|
|
696
|
+
instance of the abstract ``collections.abc.Hashable`` type. This differs
|
|
697
|
+
from the ``can_hash`` function, which checks whehter calling ``hash`` on an
|
|
698
|
+
object raises an exception.
|
|
699
|
+
|
|
700
|
+
Parameters
|
|
701
|
+
----------
|
|
702
|
+
obj : object
|
|
703
|
+
The object whose quality as an ``Hashable`` object is to be assessed.
|
|
704
|
+
|
|
705
|
+
Returns
|
|
706
|
+
-------
|
|
707
|
+
boolean
|
|
708
|
+
``True`` if `obj` is an instance of ``Hashable``, otherwise ``False``.
|
|
709
|
+
|
|
710
|
+
See Also
|
|
711
|
+
--------
|
|
712
|
+
can_hash
|
|
713
|
+
"""
|
|
714
|
+
return isinstance(obj, Hashable)
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
# Builtin Python Concrete Types ###############################################
|
|
718
|
+
|
|
719
|
+
@docwrap('immlib.is_list')
|
|
720
|
+
def is_list(obj):
|
|
721
|
+
"""Returns ``True`` if an object is a ``list`` object.
|
|
722
|
+
|
|
723
|
+
``is_list(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
724
|
+
of the ``list`` type.
|
|
725
|
+
|
|
726
|
+
Parameters
|
|
727
|
+
----------
|
|
728
|
+
obj : object
|
|
729
|
+
The object whose quality as an ``list`` object is to be assessed.
|
|
730
|
+
|
|
731
|
+
Returns
|
|
732
|
+
-------
|
|
733
|
+
bool
|
|
734
|
+
``True`` if `obj` is an instance of ``list``, otherwise ``False``.
|
|
735
|
+
"""
|
|
736
|
+
return isinstance(obj, list)
|
|
737
|
+
@docwrap('immlib.is_tuple')
|
|
738
|
+
def is_tuple(obj):
|
|
739
|
+
"""Returns ``True`` if an object is a ``tuple`` object.
|
|
740
|
+
|
|
741
|
+
``is_tuple(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
742
|
+
of the ``tuple`` type.
|
|
743
|
+
|
|
744
|
+
Parameters
|
|
745
|
+
----------
|
|
746
|
+
obj : object
|
|
747
|
+
The object whose quality as an ``tuple`` object is to be assessed.
|
|
748
|
+
|
|
749
|
+
Returns
|
|
750
|
+
-------
|
|
751
|
+
bool
|
|
752
|
+
``True`` if `obj` is an instance of ``tuple``, otherwise ``False``.
|
|
753
|
+
"""
|
|
754
|
+
return isinstance(obj, tuple)
|
|
755
|
+
from pcollections import plist
|
|
756
|
+
@docwrap('immlib.is_plist')
|
|
757
|
+
def is_plist(obj):
|
|
758
|
+
"""Returns ``True`` if an object is a persistent list object.
|
|
759
|
+
|
|
760
|
+
``is_plist(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
761
|
+
of the ``pcollections.plist`` type and ``False`` otherwise.
|
|
762
|
+
|
|
763
|
+
Parameters
|
|
764
|
+
----------
|
|
765
|
+
obj : object
|
|
766
|
+
The object whose quality as a ``plist`` object is to be assessed.
|
|
767
|
+
|
|
768
|
+
Returns
|
|
769
|
+
-------
|
|
770
|
+
bool
|
|
771
|
+
``True`` if `obj` is an instance of ``plist``, otherwise ``False``.
|
|
772
|
+
"""
|
|
773
|
+
return isinstance(obj, plist)
|
|
774
|
+
from pcollections import tlist
|
|
775
|
+
@docwrap('immlib.is_tlist')
|
|
776
|
+
def is_tlist(obj):
|
|
777
|
+
"""Returns ``True`` if an object is a transient list object.
|
|
778
|
+
|
|
779
|
+
``is_tlist(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
780
|
+
of the ``pcollections.tlist`` type and ``False`` otherwise.
|
|
781
|
+
|
|
782
|
+
Parameters
|
|
783
|
+
----------
|
|
784
|
+
obj : object
|
|
785
|
+
The object whose quality as a ``tlist`` object is to be assessed.
|
|
786
|
+
|
|
787
|
+
Returns
|
|
788
|
+
-------
|
|
789
|
+
bool
|
|
790
|
+
``True`` if `obj` is an instance of ``tlist``, otherwise ``False``.
|
|
791
|
+
"""
|
|
792
|
+
return isinstance(obj, tlist)
|
|
793
|
+
from pcollections import llist
|
|
794
|
+
@docwrap('immlib.is_llist')
|
|
795
|
+
def is_llist(obj):
|
|
796
|
+
"""Returns ``True`` if an object is a persistent lazy list object.
|
|
797
|
+
|
|
798
|
+
``is_llist(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
799
|
+
of the ``pcollections.llist`` type and ``False`` otherwise.
|
|
800
|
+
|
|
801
|
+
Parameters
|
|
802
|
+
----------
|
|
803
|
+
obj : object
|
|
804
|
+
The object whose quality as a ``llist`` object is to be assessed.
|
|
805
|
+
|
|
806
|
+
Returns
|
|
807
|
+
-------
|
|
808
|
+
bool
|
|
809
|
+
``True`` if `obj` is an instance of ``llist``, otherwise ``False``.
|
|
810
|
+
"""
|
|
811
|
+
return isinstance(obj, llist)
|
|
812
|
+
@docwrap('immlib.is_set')
|
|
813
|
+
def is_set(obj):
|
|
814
|
+
"""Returns ``True`` if an object is a ``set`` object.
|
|
815
|
+
|
|
816
|
+
``is_set(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
817
|
+
of the ``set`` type. Note that this is not the same as ``is_aset`` which
|
|
818
|
+
determines whether the object is of the ``collections.abc.Set`` abstract
|
|
819
|
+
type.
|
|
820
|
+
|
|
821
|
+
Parameters
|
|
822
|
+
----------
|
|
823
|
+
obj : object
|
|
824
|
+
The object whose quality as an ``set`` object is to be assessed.
|
|
825
|
+
|
|
826
|
+
Returns
|
|
827
|
+
-------
|
|
828
|
+
bool
|
|
829
|
+
``True`` if `obj` is an instance of ``set``, otherwise ``False``.
|
|
830
|
+
|
|
831
|
+
See Also
|
|
832
|
+
--------
|
|
833
|
+
is_aset, is_amset, is_apset, is_pset, is_tset, is_frozenset
|
|
834
|
+
"""
|
|
835
|
+
return isinstance(obj, set)
|
|
836
|
+
@docwrap('immlib.is_frozenset')
|
|
837
|
+
def is_frozenset(obj):
|
|
838
|
+
"""Returns ``True`` if an object is a ``frozenset`` object.
|
|
839
|
+
|
|
840
|
+
``is_frozenset(obj)`` returns ``True`` if the given object `obj` is an
|
|
841
|
+
instance of the ``frozenset`` type.
|
|
842
|
+
|
|
843
|
+
Parameters
|
|
844
|
+
----------
|
|
845
|
+
obj : object
|
|
846
|
+
The object whose quality as an ``frozenset`` object is to be assessed.
|
|
847
|
+
|
|
848
|
+
Returns
|
|
849
|
+
-------
|
|
850
|
+
bool
|
|
851
|
+
``True`` if `obj` is an instance of ``frozenset``, otherwise ``False``.
|
|
852
|
+
|
|
853
|
+
See Also
|
|
854
|
+
--------
|
|
855
|
+
is_set, is_aset, is_amset, is_apset, is_pset, is_tset
|
|
856
|
+
"""
|
|
857
|
+
return isinstance(obj, frozenset)
|
|
858
|
+
from pcollections import pset
|
|
859
|
+
@docwrap('immlib.is_pset')
|
|
860
|
+
def is_pset(obj):
|
|
861
|
+
"""Returns ``True`` if an object is a persistent set object.
|
|
862
|
+
|
|
863
|
+
``is_pset(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
864
|
+
of the ``pcollections.pset`` type and ``False`` otherwise.
|
|
865
|
+
|
|
866
|
+
Parameters
|
|
867
|
+
----------
|
|
868
|
+
obj : object
|
|
869
|
+
The object whose quality as a ``pset`` object is to be assessed.
|
|
870
|
+
|
|
871
|
+
Returns
|
|
872
|
+
-------
|
|
873
|
+
bool
|
|
874
|
+
``True`` if `obj` is an instance of ``pset``, otherwise ``False``.
|
|
875
|
+
|
|
876
|
+
See Also
|
|
877
|
+
--------
|
|
878
|
+
is_set, is_aset, is_amset, is_apset, is_tset, is_frozenset
|
|
879
|
+
|
|
880
|
+
"""
|
|
881
|
+
return isinstance(obj, pset)
|
|
882
|
+
from pcollections import tset
|
|
883
|
+
@docwrap('immlib.is_tset')
|
|
884
|
+
def is_tset(obj):
|
|
885
|
+
"""Returns ``True`` if an object is a transient set object.
|
|
886
|
+
|
|
887
|
+
``is_tset(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
888
|
+
of the ``pcollections.tset`` type and ``False`` otherwise.
|
|
889
|
+
|
|
890
|
+
Parameters
|
|
891
|
+
----------
|
|
892
|
+
obj : object
|
|
893
|
+
The object whose quality as a ``tset`` object is to be assessed.
|
|
894
|
+
|
|
895
|
+
Returns
|
|
896
|
+
-------
|
|
897
|
+
bool
|
|
898
|
+
``True`` if `obj` is an instance of ``tset``, otherwise ``False``.
|
|
899
|
+
"""
|
|
900
|
+
return isinstance(obj, tset)
|
|
901
|
+
@docwrap('immlib.is_dict')
|
|
902
|
+
def is_dict(obj):
|
|
903
|
+
"""Returns ``True`` if an object is a ``dict`` object.
|
|
904
|
+
|
|
905
|
+
``is_dict(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
906
|
+
of the ``dict`` type.
|
|
907
|
+
|
|
908
|
+
Parameters
|
|
909
|
+
----------
|
|
910
|
+
obj : object
|
|
911
|
+
The object whose quality as an ``dict`` object is to be assessed.
|
|
912
|
+
|
|
913
|
+
Returns
|
|
914
|
+
-------
|
|
915
|
+
bool
|
|
916
|
+
``True`` if `obj` is an instance of ``dict``, otherwise ``False``.
|
|
917
|
+
"""
|
|
918
|
+
return isinstance(obj, dict)
|
|
919
|
+
from collections import OrderedDict
|
|
920
|
+
@docwrap('immlib.is_odict')
|
|
921
|
+
def is_odict(obj):
|
|
922
|
+
"""Returns ``True`` if an object is an ``OrderedDict`` object.
|
|
923
|
+
|
|
924
|
+
``is_odict(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
925
|
+
of the ``collections.OrderedDict`` type.
|
|
926
|
+
|
|
927
|
+
Parameters
|
|
928
|
+
----------
|
|
929
|
+
obj : object
|
|
930
|
+
The object whose quality as an ``OrderedDict`` object is to be
|
|
931
|
+
assessed.
|
|
932
|
+
|
|
933
|
+
Returns
|
|
934
|
+
-------
|
|
935
|
+
bool
|
|
936
|
+
``True`` if `obj` is an instance of ``OrderedDict``, otherwise
|
|
937
|
+
``False``.
|
|
938
|
+
"""
|
|
939
|
+
return isinstance(obj, OrderedDict)
|
|
940
|
+
from collections import defaultdict
|
|
941
|
+
@docwrap('immlib.is_ddict')
|
|
942
|
+
def is_ddict(obj):
|
|
943
|
+
"""Returns ``True`` if an object is a ``defaultdict`` object.
|
|
944
|
+
|
|
945
|
+
``is_ddict(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
946
|
+
of the ``collections.defaultdict`` type.
|
|
947
|
+
|
|
948
|
+
Parameters
|
|
949
|
+
----------
|
|
950
|
+
obj : object
|
|
951
|
+
The object whose quality as a ``defaultdict`` object is to be assessed.
|
|
952
|
+
|
|
953
|
+
Returns
|
|
954
|
+
-------
|
|
955
|
+
bool
|
|
956
|
+
``True`` if `obj` is an instance of ``defaultdict``, otherwise
|
|
957
|
+
``False``.
|
|
958
|
+
"""
|
|
959
|
+
return isinstance(obj, defaultdict)
|
|
960
|
+
from pcollections import pdict
|
|
961
|
+
@docwrap('immlib.is_pdict')
|
|
962
|
+
def is_pdict(obj):
|
|
963
|
+
"""Returns ``True`` if an object is a persistent dictionary object.
|
|
964
|
+
|
|
965
|
+
``is_pdict(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
966
|
+
of the ``pcollections.pdict`` type and ``False`` otherwise.
|
|
967
|
+
|
|
968
|
+
.. Note:: The ``ldict`` type is a subtype of ``pdict``, so for
|
|
969
|
+
``is_pdict(ldict())`` returns ``True``.
|
|
970
|
+
|
|
971
|
+
Parameters
|
|
972
|
+
----------
|
|
973
|
+
obj : object
|
|
974
|
+
The object whose quality as a ``pdict`` object is to be assessed.
|
|
975
|
+
|
|
976
|
+
Returns
|
|
977
|
+
-------
|
|
978
|
+
bool
|
|
979
|
+
``True`` if `obj` is an instance of ``pdict``, otherwise ``False``.
|
|
980
|
+
"""
|
|
981
|
+
return isinstance(obj, pdict)
|
|
982
|
+
from pcollections import tdict, tldict
|
|
983
|
+
@docwrap('immlib.is_tdict')
|
|
984
|
+
def is_tdict(obj):
|
|
985
|
+
"""Returns ``True`` if an object is a transient dictionary object.
|
|
986
|
+
|
|
987
|
+
``is_tdict(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
988
|
+
of the ``pcollections.tdict`` type and ``False`` otherwise.
|
|
989
|
+
|
|
990
|
+
Parameters
|
|
991
|
+
----------
|
|
992
|
+
obj : object
|
|
993
|
+
The object whose quality as a ``tdict`` object is to be assessed.
|
|
994
|
+
|
|
995
|
+
Returns
|
|
996
|
+
-------
|
|
997
|
+
bool
|
|
998
|
+
``True`` if `obj` is an instance of ``tdict``, otherwise ``False``.
|
|
999
|
+
"""
|
|
1000
|
+
return isinstance(obj, tdict)
|
|
1001
|
+
from pcollections import ldict
|
|
1002
|
+
@docwrap('immlib.is_ldict')
|
|
1003
|
+
def is_ldict(obj):
|
|
1004
|
+
"""Returns ``True`` if an object is a persistent lazy dictionary object.
|
|
1005
|
+
|
|
1006
|
+
``is_ldict(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
1007
|
+
of the ``pcollections.ldict`` type and ``False`` otherwise.
|
|
1008
|
+
|
|
1009
|
+
Parameters
|
|
1010
|
+
----------
|
|
1011
|
+
obj : object
|
|
1012
|
+
The object whose quality as a ``ldict`` object is to be assessed.
|
|
1013
|
+
|
|
1014
|
+
Returns
|
|
1015
|
+
-------
|
|
1016
|
+
bool
|
|
1017
|
+
``True`` if `obj` is an instance of ``ldict``, otherwise ``False``.
|
|
1018
|
+
"""
|
|
1019
|
+
return isinstance(obj, ldict)
|
|
1020
|
+
@docwrap('immlib.hashsafe')
|
|
1021
|
+
def hashsafe(obj):
|
|
1022
|
+
"""Returns ``hash(obj)`` if `obj` is hashable, otherwise returns ``None``.
|
|
1023
|
+
|
|
1024
|
+
This function attempts to hash an object and returns ``None`` when doing so
|
|
1025
|
+
raises a ``TypeError``.
|
|
1026
|
+
|
|
1027
|
+
.. Note:: A fairly reliable test of whether an object is immutable or not
|
|
1028
|
+
in Python is whether it can be hashed.
|
|
1029
|
+
|
|
1030
|
+
Parameters
|
|
1031
|
+
----------
|
|
1032
|
+
obj : object
|
|
1033
|
+
The object to be hashed.
|
|
1034
|
+
|
|
1035
|
+
Returns
|
|
1036
|
+
-------
|
|
1037
|
+
int or None
|
|
1038
|
+
If the object is hashable, returns the hashcode; otherwise, returns
|
|
1039
|
+
``None``.
|
|
1040
|
+
|
|
1041
|
+
See Also
|
|
1042
|
+
--------
|
|
1043
|
+
can_hash, is_ahashable
|
|
1044
|
+
"""
|
|
1045
|
+
try:
|
|
1046
|
+
return hash(obj)
|
|
1047
|
+
except TypeError:
|
|
1048
|
+
return None
|
|
1049
|
+
@docwrap('immlib.can_hash')
|
|
1050
|
+
def can_hash(obj):
|
|
1051
|
+
"""Returns ``True`` if `obj` is safe to hash and ``False`` otherwise.
|
|
1052
|
+
|
|
1053
|
+
``can_hash(obj)`` is equivalent to ``hashsafe(obj) is not None``. This
|
|
1054
|
+
differs from ``is_ahashable(obj)`` in that ``is_ahashable`` only checks
|
|
1055
|
+
whether `obj` is an instance of ``Hashable`` while ``hashsafe(obj)``
|
|
1056
|
+
attempts to hash `obj` and returns ``None`` when a ``TypeError`` is raised.
|
|
1057
|
+
|
|
1058
|
+
.. Note:: A fairly reliable test of whether an object is immutable or not
|
|
1059
|
+
in Python is whether it can be hashed.
|
|
1060
|
+
|
|
1061
|
+
See Also
|
|
1062
|
+
--------
|
|
1063
|
+
hashsafe, is_ahashable
|
|
1064
|
+
"""
|
|
1065
|
+
return hashsafe(obj) is not None
|
|
1066
|
+
@docwrap('immlib.itersafe')
|
|
1067
|
+
def itersafe(obj):
|
|
1068
|
+
"""Returns an iterator of the given object or ``None`` if it is not
|
|
1069
|
+
iterable.
|
|
1070
|
+
|
|
1071
|
+
``itersafe(obj)`` is equivalent to ``iter(obj)`` with the exception that,
|
|
1072
|
+
if `obj` is not iterable, it returns ``None`` instead of raising an
|
|
1073
|
+
exception.
|
|
1074
|
+
|
|
1075
|
+
Parameters
|
|
1076
|
+
----------
|
|
1077
|
+
obj : object
|
|
1078
|
+
The object to be iterated.
|
|
1079
|
+
|
|
1080
|
+
Returns
|
|
1081
|
+
-------
|
|
1082
|
+
iterator or None
|
|
1083
|
+
If `obj` is iterable, returns ``iter(obj)``; otherwise, returns
|
|
1084
|
+
``None``.
|
|
1085
|
+
|
|
1086
|
+
See Also
|
|
1087
|
+
--------
|
|
1088
|
+
can_iter, is_aiterable
|
|
1089
|
+
"""
|
|
1090
|
+
try:
|
|
1091
|
+
return iter(obj)
|
|
1092
|
+
except TypeError:
|
|
1093
|
+
return None
|
|
1094
|
+
@docwrap('immlib.can_iter')
|
|
1095
|
+
def can_iter(obj):
|
|
1096
|
+
"""Returns ``True`` if `obj` is safe to iterate and ``False`` otherwise.
|
|
1097
|
+
|
|
1098
|
+
``can_iter(obj)`` is equivalent to ``itersafe(obj) is not None``. This
|
|
1099
|
+
differs from ``is_aiterable(obj)`` in that ``is_aiterable`` only checks
|
|
1100
|
+
whether `obj` is an instance of ``Iterable``; ``itersafe`` tries to run
|
|
1101
|
+
``iter(obj)`` and returns ``None`` when a ``TypeError`` is raised.
|
|
1102
|
+
|
|
1103
|
+
See Also
|
|
1104
|
+
--------
|
|
1105
|
+
itersafe, is_aiterable
|
|
1106
|
+
"""
|
|
1107
|
+
return itersafe(obj) is not None
|
|
1108
|
+
@docwrap('immlib.is_pcoll')
|
|
1109
|
+
def is_pcoll(obj):
|
|
1110
|
+
"""Detects if an object is a ``plist``, ``pset``, ``pdict``, ``llist`` or
|
|
1111
|
+
``ldict``.
|
|
1112
|
+
|
|
1113
|
+
``is_pcoll(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
1114
|
+
of the persistent collection types ``plist``, ``pset``, ``pdict``,
|
|
1115
|
+
``llist``, or ``ldict``. Otherwise, ``False`` is returned.
|
|
1116
|
+
|
|
1117
|
+
Note that this function tests against a specific set of concrete types;
|
|
1118
|
+
instances of objects whose types are subclasses of these types will be
|
|
1119
|
+
treated as instances of the base types; however other immutable types not
|
|
1120
|
+
defined in ``immlib`` or ``pcollections`` will not be recognized by this
|
|
1121
|
+
function.
|
|
1122
|
+
|
|
1123
|
+
Parameters
|
|
1124
|
+
----------
|
|
1125
|
+
obj : object
|
|
1126
|
+
The object whose quality as a persistent collection is to be assessed.
|
|
1127
|
+
|
|
1128
|
+
Returns
|
|
1129
|
+
-------
|
|
1130
|
+
bool
|
|
1131
|
+
``True`` if `obj` is a persistent collection and ``False`` otherwise.
|
|
1132
|
+
"""
|
|
1133
|
+
return isinstance(obj, is_pcoll.types)
|
|
1134
|
+
is_pcoll.types = (plist, pset, pdict, llist, ldict)
|
|
1135
|
+
@docwrap('immlib.is_tcoll')
|
|
1136
|
+
def is_tcoll(obj):
|
|
1137
|
+
"""Returns ``True`` if an object is a transient ``tlist``, ``tset``, or
|
|
1138
|
+
``tdict``.
|
|
1139
|
+
|
|
1140
|
+
``is_tcoll(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
1141
|
+
of the ``tdict``, ``tset``, or ``tlist`` types, all of which are transient
|
|
1142
|
+
collections. Otherwise, ``False`` is returned.
|
|
1143
|
+
|
|
1144
|
+
Parameters
|
|
1145
|
+
----------
|
|
1146
|
+
obj : object
|
|
1147
|
+
The object whose quality as a transient collection is to be assessed.
|
|
1148
|
+
|
|
1149
|
+
Returns
|
|
1150
|
+
-------
|
|
1151
|
+
bool
|
|
1152
|
+
``True`` if `obj` is a ``tlist``, ``tset``, or ``tdict`` and ``False``
|
|
1153
|
+
otherwise.
|
|
1154
|
+
"""
|
|
1155
|
+
return isinstance(obj, is_tcoll.types)
|
|
1156
|
+
is_tcoll.types = (tlist, tset, tdict)
|
|
1157
|
+
@docwrap('immlib.is_mcoll')
|
|
1158
|
+
def is_mcoll(obj):
|
|
1159
|
+
"""Returns ``True`` if an object is a mutable ``list``, ``set``, or
|
|
1160
|
+
``dict``.
|
|
1161
|
+
|
|
1162
|
+
``is_mcoll(obj)`` returns ``True`` if the given object `obj` is an instance
|
|
1163
|
+
of the ``dict``, ``set``, or ``list`` types, all of which are mutable
|
|
1164
|
+
collections. Otherwise, ``False`` is returned.
|
|
1165
|
+
|
|
1166
|
+
Parameters
|
|
1167
|
+
----------
|
|
1168
|
+
obj : object
|
|
1169
|
+
The object whose quality as a mutable collection is to be assessed.
|
|
1170
|
+
|
|
1171
|
+
Returns
|
|
1172
|
+
-------
|
|
1173
|
+
bool
|
|
1174
|
+
``True`` if `obj` is a ``list``, ``set``, or ``dict`` and ``False``
|
|
1175
|
+
otherwise.
|
|
1176
|
+
"""
|
|
1177
|
+
return isinstance(obj, is_mcoll.types)
|
|
1178
|
+
is_mcoll.types = (list, set, dict)
|
|
1179
|
+
@docwrap('immlib.to_pcoll')
|
|
1180
|
+
def to_pcoll(obj):
|
|
1181
|
+
"""Returns a persistent copy of `obj`.
|
|
1182
|
+
|
|
1183
|
+
``to_pcoll(obj)`` returns `obj` itself if `obj` is a persistent collection;
|
|
1184
|
+
otherwise, it returns a persistent copy of `obj`. If `obj` is not a
|
|
1185
|
+
collection that can be converted into a persistent collection, then an
|
|
1186
|
+
error is raised.
|
|
1187
|
+
|
|
1188
|
+
Parameters
|
|
1189
|
+
----------
|
|
1190
|
+
obj : collection
|
|
1191
|
+
An object that is to be converted into a persistent collection.
|
|
1192
|
+
|
|
1193
|
+
Returns
|
|
1194
|
+
-------
|
|
1195
|
+
object
|
|
1196
|
+
A persistent version of `obj`.
|
|
1197
|
+
|
|
1198
|
+
Raises
|
|
1199
|
+
------
|
|
1200
|
+
TypeError
|
|
1201
|
+
If `obj` cannot be converted into a persistent collection.
|
|
1202
|
+
"""
|
|
1203
|
+
if is_pcoll(obj):
|
|
1204
|
+
return obj
|
|
1205
|
+
if isinstance(obj, Sequence):
|
|
1206
|
+
return plist(obj)
|
|
1207
|
+
elif isinstance(obj, Set):
|
|
1208
|
+
return pset(obj)
|
|
1209
|
+
elif isinstance(obj, Mapping):
|
|
1210
|
+
return pdict(obj)
|
|
1211
|
+
else:
|
|
1212
|
+
raise TypeError(f"argument is not a recognized collection")
|
|
1213
|
+
from pcollections.abc import (
|
|
1214
|
+
TransientSequence, TransientSet, TransientMapping)
|
|
1215
|
+
@docwrap('immlib.to_tcoll')
|
|
1216
|
+
def to_tcoll(obj, /, copy=True):
|
|
1217
|
+
"""Returns a transient copy of `obj`.
|
|
1218
|
+
|
|
1219
|
+
``to_tcoll(obj)`` returns a copy of `obj` as a transient collection. If
|
|
1220
|
+
`obj` is not a collection that can be converted into a transient
|
|
1221
|
+
collection, then an error is raised.
|
|
1222
|
+
|
|
1223
|
+
.. Note:: If `obj` is already a transient collection, then a copy of `obj`
|
|
1224
|
+
is returned.
|
|
1225
|
+
|
|
1226
|
+
.. Note:: If ``to_tcoll`` is given a lazy dict (``pcollections.ldict``) or
|
|
1227
|
+
a lazi list (``pcollections.llist``), the resulting transient
|
|
1228
|
+
dictionary is made using ``obj.transient()`` and so respects the
|
|
1229
|
+
laziness of the elements.
|
|
1230
|
+
|
|
1231
|
+
Parameters
|
|
1232
|
+
----------
|
|
1233
|
+
obj : collection
|
|
1234
|
+
An object that is to be converted into a persistent collection.
|
|
1235
|
+
copy : boolean, optional
|
|
1236
|
+
If `obj` is already a transient collection, then a copy is made if and
|
|
1237
|
+
only if ``copy`` is ``True``; otherwise, `obj` is returned as-is when
|
|
1238
|
+
it is already a transient type. The default is ``True``.
|
|
1239
|
+
|
|
1240
|
+
Returns
|
|
1241
|
+
-------
|
|
1242
|
+
object
|
|
1243
|
+
A transient version of `obj`. The returned value is always either a
|
|
1244
|
+
``tlist``, ``tset``, or ``tdict`` object.
|
|
1245
|
+
|
|
1246
|
+
Raises
|
|
1247
|
+
------
|
|
1248
|
+
TypeError
|
|
1249
|
+
If `obj` cannot be converted into a transient collection.
|
|
1250
|
+
"""
|
|
1251
|
+
if not copy and is_tcoll(obj):
|
|
1252
|
+
return obj
|
|
1253
|
+
if isinstance(obj, (PersistentSequence, PersistentSet, PersistentMapping)):
|
|
1254
|
+
return obj.transient()
|
|
1255
|
+
elif isinstance(obj, (TransientSequence, TransientSet, TransientMapping)):
|
|
1256
|
+
# This makes a duplicate but prevents tldicts from becoming tdicts and
|
|
1257
|
+
# tllists from becoming tlists.
|
|
1258
|
+
return obj.persistent().transient()
|
|
1259
|
+
elif isinstance(obj, Sequence):
|
|
1260
|
+
return tlist(obj)
|
|
1261
|
+
elif isinstance(obj, Set):
|
|
1262
|
+
return tset(obj)
|
|
1263
|
+
elif isinstance(obj, Mapping):
|
|
1264
|
+
return tdict(obj)
|
|
1265
|
+
else:
|
|
1266
|
+
raise TypeError(f"argument is not a recognized collection")
|
|
1267
|
+
@docwrap('immlib.to_mcoll')
|
|
1268
|
+
def to_mcoll(obj, /, copy=True):
|
|
1269
|
+
"""Returns a mutable copy of `obj`.
|
|
1270
|
+
|
|
1271
|
+
``to_mcoll(obj)`` returns a mutable copy of the given collection `obj`. If
|
|
1272
|
+
`obj` is already a mutable collection, then a duplicate is returned. If
|
|
1273
|
+
`obj` is not a collection that can be converted into a mutable collection,
|
|
1274
|
+
then an error is raised.
|
|
1275
|
+
|
|
1276
|
+
A mutable collection, according to this function, is a Python ``list``,
|
|
1277
|
+
``set``, or ``dict``, depending on the type of `obj`. When `obj` is a
|
|
1278
|
+
``Sequence``, the result is a ``list``; when `obj` is a ``Set``, the result
|
|
1279
|
+
is a ``set``; and when `obj` is a ``Mapping``, the result is a ``dict``.
|
|
1280
|
+
|
|
1281
|
+
Parameters
|
|
1282
|
+
----------
|
|
1283
|
+
obj : collection
|
|
1284
|
+
An object that is to be converted into a persistent collection.
|
|
1285
|
+
copy : boolean, optional
|
|
1286
|
+
If `obj` is already a mutable collection, then a copy is made if and
|
|
1287
|
+
only if ``copy`` is ``True``; otherwise, `obj` is returned as-is when
|
|
1288
|
+
it is already a mutable type. The default is ``True``.
|
|
1289
|
+
|
|
1290
|
+
Returns
|
|
1291
|
+
-------
|
|
1292
|
+
object
|
|
1293
|
+
A mutable version of `obj`; the return value's type will always be one
|
|
1294
|
+
of ``list``, ``set``, or ``dict``.
|
|
1295
|
+
|
|
1296
|
+
Raises
|
|
1297
|
+
------
|
|
1298
|
+
TypeError
|
|
1299
|
+
If `obj` cannot be converted into a mutable collection.
|
|
1300
|
+
"""
|
|
1301
|
+
if not copy and is_mcoll(obj):
|
|
1302
|
+
return obj
|
|
1303
|
+
if isinstance(obj, Sequence):
|
|
1304
|
+
return list(obj)
|
|
1305
|
+
elif isinstance(obj, Set):
|
|
1306
|
+
return set(obj)
|
|
1307
|
+
elif isinstance(obj, Mapping):
|
|
1308
|
+
return dict(obj)
|
|
1309
|
+
else:
|
|
1310
|
+
raise TypeError(f"argument is not a collection")
|
|
1311
|
+
@docwrap('immlib.freezearray')
|
|
1312
|
+
def freezearray(arr):
|
|
1313
|
+
"""Freezes a NumPy array or SciPy sparse array in-place.
|
|
1314
|
+
|
|
1315
|
+
``freezearray(x)`` sets the ``'WRITEABLE'`` bit on the numpy array ``x`` or
|
|
1316
|
+
on ``x.data`` if ``x`` is a SciPy sparse array. If ``x`` is neither a NumPy
|
|
1317
|
+
array nor a SciPy sparse array, then a ``TypeError`` is raised. No value is
|
|
1318
|
+
returned.
|
|
1319
|
+
|
|
1320
|
+
``freezearray(q)`` is equivalent to ``freezearray(q.m)`` if ``q`` is a
|
|
1321
|
+
``pint.Quantity`` object.
|
|
1322
|
+
|
|
1323
|
+
.. Warning:: This function mutates its argument in-place.
|
|
1324
|
+
|
|
1325
|
+
See Also
|
|
1326
|
+
--------
|
|
1327
|
+
frozenarray
|
|
1328
|
+
"""
|
|
1329
|
+
if isinstance(arr, pint.Quantity):
|
|
1330
|
+
arr = arr.m
|
|
1331
|
+
if isinstance(arr, np.ndarray):
|
|
1332
|
+
arr.setflags(write=False)
|
|
1333
|
+
elif sps.issparse(arr):
|
|
1334
|
+
arr.data.setflags(write=False)
|
|
1335
|
+
else:
|
|
1336
|
+
raise TypeError(
|
|
1337
|
+
f"freezearray requires a numpy array or scipy sparse array,"
|
|
1338
|
+
f" but type {type(arr)} was given")
|
|
1339
|
+
@docwrap('immlib.frozenarray')
|
|
1340
|
+
def frozenarray(obj, /, dtype=None, *, copy=False, **kwargs):
|
|
1341
|
+
"""Roughly equivalent to ``numpy.array`` but returns read-only arrays.
|
|
1342
|
+
|
|
1343
|
+
``frozenarray(obj)`` is equivalent to ``numpy.array(obj)`` with a small
|
|
1344
|
+
number of exceptions:
|
|
1345
|
+
|
|
1346
|
+
- Primarily, the returned object is always a frozen array (i.e., an array
|
|
1347
|
+
with the ``'WRITEABLE'`` flag set to ``False``).
|
|
1348
|
+
- The default value of the ``copy`` option is ``False``, meaning that a
|
|
1349
|
+
copy of the array will only be made if required by the other parameters
|
|
1350
|
+
or if the array is not already read-only. If you wish to make an array
|
|
1351
|
+
read-only rather than obtaining a read-only copy of it, use the
|
|
1352
|
+
``freezearray()`` function.
|
|
1353
|
+
- SciPy sparse arrays are also handled by setting the write flag on the
|
|
1354
|
+
``obj.data`` member.
|
|
1355
|
+
- If ``obj`` is a ``pint.Quantity`` object, then an equivalent quantity
|
|
1356
|
+
with the magnitude made read-only is returned.
|
|
1357
|
+
|
|
1358
|
+
If a PyTorch tensor is passed to ``frozenarray``, it will be converted into
|
|
1359
|
+
a frozen NumPy array; PyTorch tensors themselves cannot be frozen, however.
|
|
1360
|
+
|
|
1361
|
+
See Also
|
|
1362
|
+
--------
|
|
1363
|
+
numpy.array : Create an array that is not frozen.
|
|
1364
|
+
freezearray : Convert an argument to a frozen array in-place.
|
|
1365
|
+
"""
|
|
1366
|
+
if isinstance(obj, pint.Quantity):
|
|
1367
|
+
arr = frozenarray(obj.m, dtype=dtype, copy=copy, **kwargs)
|
|
1368
|
+
return obj if not copy and arr is obj.m else type(obj)(arr, obj.u)
|
|
1369
|
+
elif sps.issparse(obj):
|
|
1370
|
+
arr = frozenarray(obj.data, dtype=dtype, copy=copy, **kwargs)
|
|
1371
|
+
if not copy and obj.data is arr:
|
|
1372
|
+
return obj
|
|
1373
|
+
obj = obj.copy()
|
|
1374
|
+
obj.data = arr
|
|
1375
|
+
return obj
|
|
1376
|
+
elif isinstance(obj, np.ndarray):
|
|
1377
|
+
if obj.flags['WRITEABLE']:
|
|
1378
|
+
copy = True
|
|
1379
|
+
arr = np.array(obj, dtype=dtype, copy=copy, **kwargs)
|
|
1380
|
+
arr.setflags(write=False)
|
|
1381
|
+
return arr
|
|
1382
|
+
else:
|
|
1383
|
+
# PyTorch tensors might be sparse, so have to be treated specially.
|
|
1384
|
+
from ._numeric import torch, to_array
|
|
1385
|
+
if torch.is_tensor(obj):
|
|
1386
|
+
return frozenarray(to_array(obj), dtype=dtype, **kwargs)
|
|
1387
|
+
arr = np.array(obj, dtype=dtype, **kwargs)
|
|
1388
|
+
arr.setflags(write=False)
|
|
1389
|
+
return arr
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
# Mapping/Sequence Utilities ##################################################
|
|
1393
|
+
|
|
1394
|
+
@docwrap('immlib.get')
|
|
1395
|
+
def get(d, k, /, *args, **kwargs):
|
|
1396
|
+
"""Returns a value from either a mapping or a sequence.
|
|
1397
|
+
|
|
1398
|
+
The ``get`` function is essentially a function version of the ``get``
|
|
1399
|
+
method that works for both ``Mapping`` and ``Sequence`` types (e.g.,
|
|
1400
|
+
``dict``, ``list``, ``tuple``, and related types that implement their
|
|
1401
|
+
abstract bases).
|
|
1402
|
+
|
|
1403
|
+
``get(d, k)`` extracts element `k` from object `d` and returns it. If `k`
|
|
1404
|
+
is not a valid index of `d` (i.e., `k` is not a key of `d`, if `d` is a
|
|
1405
|
+
mapping, or is not an integer index of `d` if `d` is a sequence), then the
|
|
1406
|
+
optional value ``default`` is returned. If ``detault`` is not explicitly
|
|
1407
|
+
provided, then an error is raised. Note that if a non-integer key is
|
|
1408
|
+
provided for a sequence, this is treated as a missing index.
|
|
1409
|
+
|
|
1410
|
+
.. Note:: The default value may be expressed as either a third positional
|
|
1411
|
+
argument or a named argument (``default``).
|
|
1412
|
+
|
|
1413
|
+
Parameters
|
|
1414
|
+
----------
|
|
1415
|
+
d : object
|
|
1416
|
+
The dict-like or list-like object from which an element is being
|
|
1417
|
+
extracted.
|
|
1418
|
+
k : object
|
|
1419
|
+
The key or index into `d` whose value is to be extracted.
|
|
1420
|
+
args
|
|
1421
|
+
The default value to be returned if an item is not found. The default
|
|
1422
|
+
value may be specified as a third positional argument.
|
|
1423
|
+
kwargs
|
|
1424
|
+
The default value to be returned if an item is not found. The default
|
|
1425
|
+
value may be specified as a named argument with the name ``"default"``.
|
|
1426
|
+
|
|
1427
|
+
Returns
|
|
1428
|
+
-------
|
|
1429
|
+
object
|
|
1430
|
+
The object ``d[k]``, if the key `k` is found in the collection `d`.
|
|
1431
|
+
Otherwise, ``default`` is returned.
|
|
1432
|
+
|
|
1433
|
+
Raises
|
|
1434
|
+
------
|
|
1435
|
+
KeyError
|
|
1436
|
+
If the key or index `k` is not found in the collection `d` and no
|
|
1437
|
+
``default`` option is given.
|
|
1438
|
+
"""
|
|
1439
|
+
nargs = len(args)
|
|
1440
|
+
nkw = len(kwargs)
|
|
1441
|
+
if nargs + nkw > 1:
|
|
1442
|
+
raise TypeError(f"get takes 2 or 3 arguments; got {2 + nargs + nkw}")
|
|
1443
|
+
if nargs == 1:
|
|
1444
|
+
error = False
|
|
1445
|
+
default = args[0]
|
|
1446
|
+
elif nkw == 1:
|
|
1447
|
+
if "default" in kwargs:
|
|
1448
|
+
error = False
|
|
1449
|
+
default = kwargs.pop("default")
|
|
1450
|
+
else:
|
|
1451
|
+
k = repr(next(iter(kwargs.keys())))
|
|
1452
|
+
raise TypeError(f"get() got an unexpected keyword argument {k}")
|
|
1453
|
+
else:
|
|
1454
|
+
error = True
|
|
1455
|
+
default = None
|
|
1456
|
+
if isinstance(d, (Mapping, Sequence)):
|
|
1457
|
+
try:
|
|
1458
|
+
return d[k]
|
|
1459
|
+
except IndexError:
|
|
1460
|
+
pass
|
|
1461
|
+
except TypeError:
|
|
1462
|
+
pass
|
|
1463
|
+
except KeyError:
|
|
1464
|
+
pass
|
|
1465
|
+
else:
|
|
1466
|
+
raise TypeError(f"cannot get item from type {type(d)}")
|
|
1467
|
+
# If we reach this point, we failed to find the key.
|
|
1468
|
+
if error:
|
|
1469
|
+
raise KeyError(k)
|
|
1470
|
+
else:
|
|
1471
|
+
return default
|
|
1472
|
+
@docwrap('immlib.nestget')
|
|
1473
|
+
def nestget(d, /, *args, **kwargs):
|
|
1474
|
+
"""Returns a value from a data structure of nested mappings and sequences.
|
|
1475
|
+
|
|
1476
|
+
The ``nestget`` function is essentially a nested version of the ``get``
|
|
1477
|
+
function that works for both ``Mapping`` and ``Sequence`` types (e.g.,
|
|
1478
|
+
``dict``, ``list``, ``tuple``, and related types that implement their
|
|
1479
|
+
abstract bases).
|
|
1480
|
+
|
|
1481
|
+
``nestget(data, k1, k2, k3...)`` extracts element ``k1`` from ``data`` then
|
|
1482
|
+
element ``k2`` from that value, then element ``k3`` from that value, etc.,
|
|
1483
|
+
until there are no more keys; the final value is returned. If any of the
|
|
1484
|
+
values are missing, then the optional value ``default`` is returned if it
|
|
1485
|
+
is provided and an error is raised if it is not. Note that the provided
|
|
1486
|
+
keys may be integer indices for list-like objects that may be included in
|
|
1487
|
+
the nesting. If a string key is given for a list-like container, then this
|
|
1488
|
+
is treated as a missing key, not an error.
|
|
1489
|
+
|
|
1490
|
+
This function raises a ``KeyError` when a key or index is not found in the
|
|
1491
|
+
relevant container, but this behavior can be changed by passing the
|
|
1492
|
+
optional named parameter ``default``. If ``default`` is provided, then this
|
|
1493
|
+
value is returned if any keys are missing.
|
|
1494
|
+
|
|
1495
|
+
Parameters
|
|
1496
|
+
----------
|
|
1497
|
+
d : object
|
|
1498
|
+
The dict-like or list-like object from which an element is being
|
|
1499
|
+
extracted.
|
|
1500
|
+
args
|
|
1501
|
+
The list of keys and indices to be extracted.
|
|
1502
|
+
kwargs
|
|
1503
|
+
The default value to be returned if an item is not found can be
|
|
1504
|
+
specified using the named option ``default``. If this option is not
|
|
1505
|
+
provided, then an error is raised should the item not be found.
|
|
1506
|
+
|
|
1507
|
+
Returns
|
|
1508
|
+
-------
|
|
1509
|
+
object
|
|
1510
|
+
The object found at the given nested position in the data structure
|
|
1511
|
+
`d`. If one of the provided keys does not exist in the associated
|
|
1512
|
+
sub-collection of `d`, then the ``default`` option is returned.
|
|
1513
|
+
|
|
1514
|
+
Raises
|
|
1515
|
+
------
|
|
1516
|
+
KeyError
|
|
1517
|
+
If the given sequence of keys cannot be found in the nested data
|
|
1518
|
+
structure and no ``default`` option is provided.
|
|
1519
|
+
"""
|
|
1520
|
+
if "default" in kwargs:
|
|
1521
|
+
error = False
|
|
1522
|
+
default = kwargs.pop("default")
|
|
1523
|
+
else:
|
|
1524
|
+
error = True
|
|
1525
|
+
default = None
|
|
1526
|
+
if len(kwargs) > 0:
|
|
1527
|
+
k = repr(next(iter(kwargs.keys())))
|
|
1528
|
+
raise TypeError(f"nestget() got an unexpected keyword argument {k}")
|
|
1529
|
+
for k in args:
|
|
1530
|
+
if isinstance(d, Mapping):
|
|
1531
|
+
if k in d:
|
|
1532
|
+
d = d[k]
|
|
1533
|
+
continue
|
|
1534
|
+
elif isinstance(d, Sequence):
|
|
1535
|
+
try:
|
|
1536
|
+
d = d[k]
|
|
1537
|
+
continue
|
|
1538
|
+
except IndexError:
|
|
1539
|
+
pass
|
|
1540
|
+
except TypeError:
|
|
1541
|
+
pass
|
|
1542
|
+
else:
|
|
1543
|
+
raise TypeError(f"cannot get item from type {type(d)}")
|
|
1544
|
+
# If we reach this point, we failed to find the key.
|
|
1545
|
+
if error:
|
|
1546
|
+
raise KeyError(k)
|
|
1547
|
+
else:
|
|
1548
|
+
return default
|
|
1549
|
+
return d
|
|
1550
|
+
from pcollections import lazy
|
|
1551
|
+
def _lazyvalmap_extract(f, d, k, *args, **kw):
|
|
1552
|
+
return f(d[k], *args, **kw)
|
|
1553
|
+
@docwrap('immlib.lazyvalmap')
|
|
1554
|
+
def lazyvalmap(f, d, /, *args, **kwargs):
|
|
1555
|
+
"""Returns a dict object whose values are transformed by a function.
|
|
1556
|
+
|
|
1557
|
+
``lazyvalmap(f, d)`` returns a dict whose keys are the same as those of the
|
|
1558
|
+
given dict object and whose values, for each key ``k`` are ``f(d[k])``. All
|
|
1559
|
+
values are created lazily.
|
|
1560
|
+
|
|
1561
|
+
``lazyvalmap(f, d, *args, **kw)`` additionally passes the given arguments
|
|
1562
|
+
to the function `f`, such that in the resulting map, each key ``k`` is
|
|
1563
|
+
mapped to ``f(d[k], *args, **kw)``.
|
|
1564
|
+
|
|
1565
|
+
Parameters
|
|
1566
|
+
----------
|
|
1567
|
+
f : function
|
|
1568
|
+
The function used to create the values in the new dictionary.
|
|
1569
|
+
d : collections.abc.Mapping
|
|
1570
|
+
A mapping whose keys are to be preserved and remapped to a function of
|
|
1571
|
+
their values.
|
|
1572
|
+
args
|
|
1573
|
+
Additional positional arguments to pass to `f`.
|
|
1574
|
+
kwargs
|
|
1575
|
+
Additional named arguments to pass to `f`.
|
|
1576
|
+
|
|
1577
|
+
Returns
|
|
1578
|
+
-------
|
|
1579
|
+
pcollections.ldict
|
|
1580
|
+
This function always returns a lazy dictionary object of type
|
|
1581
|
+
``pcollections.ldict``.
|
|
1582
|
+
"""
|
|
1583
|
+
t = tldict()
|
|
1584
|
+
if is_ammap(d):
|
|
1585
|
+
# For mutable maps, we do not try to respect laziness; they may change
|
|
1586
|
+
# so we cannot rely on them.
|
|
1587
|
+
for (k,v) in d.items():
|
|
1588
|
+
t[k] = lazy(f, v, *args, **kwargs)
|
|
1589
|
+
else:
|
|
1590
|
+
# Anything else we assume is immutable, so we respect any possible lazy
|
|
1591
|
+
# implementation.
|
|
1592
|
+
for k in d.keys():
|
|
1593
|
+
t[k] = lazy(_lazyvalmap_extract, f, d, k, *args, **kwargs)
|
|
1594
|
+
return t.persistent()
|
|
1595
|
+
@docwrap('immlib.valmap')
|
|
1596
|
+
def valmap(f, d, /, *args, **kwargs):
|
|
1597
|
+
"""Returns a dictionary object whose values are transformed by a function.
|
|
1598
|
+
|
|
1599
|
+
``valmap(f, d)`` returns a dict whose keys are the same as those of the
|
|
1600
|
+
given dict object and whose values, for each key ``k`` are ``f(d[k])``.
|
|
1601
|
+
|
|
1602
|
+
``valmap(f, d, *args, **kw)`` additionally passes the given arguments to
|
|
1603
|
+
the function `f`, such that in the resulting map, each key ``k`` is
|
|
1604
|
+
mapped to ``f(d[k], *args, **kw)``.
|
|
1605
|
+
|
|
1606
|
+
Unlike ``lazyvalmap``, this function returns either a ``dict``, a
|
|
1607
|
+
``pdict``, or an ``ldict`` depending on the input argument `d`. If `d` is a
|
|
1608
|
+
(lazy) ``ldict``, then an ``ldict`` is returned; if `d` is a ``pdict``, a
|
|
1609
|
+
``pdict`` is returned, and otherwise, a ``dict`` is returnd.
|
|
1610
|
+
|
|
1611
|
+
Parameters
|
|
1612
|
+
----------
|
|
1613
|
+
f : function
|
|
1614
|
+
The function used to create the values in the new dictionary.
|
|
1615
|
+
d : collections.abc.Mapping
|
|
1616
|
+
A mapping whose keys are to be preserved and remapped to a function of
|
|
1617
|
+
their values.
|
|
1618
|
+
args
|
|
1619
|
+
Additional positional arguments to pass to `f`.
|
|
1620
|
+
kwargs
|
|
1621
|
+
Additional named arguments to pass to `f`.
|
|
1622
|
+
|
|
1623
|
+
Returns
|
|
1624
|
+
-------
|
|
1625
|
+
collections.abc.Mapping object
|
|
1626
|
+
This function always returns a dictionary object whose type is either
|
|
1627
|
+
``pcollections.pdict``, ``pcollections.ldict``, or ``dict``, depending
|
|
1628
|
+
on the type of `d`.
|
|
1629
|
+
"""
|
|
1630
|
+
if is_ldict(d):
|
|
1631
|
+
return lazyvalmap(f, d, *args, **kwargs)
|
|
1632
|
+
elif is_pdict(d):
|
|
1633
|
+
t = tdict()
|
|
1634
|
+
for (k,v) in d.items():
|
|
1635
|
+
t[k] = f(v, *args, **kwargs)
|
|
1636
|
+
return t.persistent()
|
|
1637
|
+
else:
|
|
1638
|
+
return {k: f(v, *args, **kwargs) for (k,v) in d.items()}
|
|
1639
|
+
@docwrap('immlib.lazykeymap')
|
|
1640
|
+
def lazykeymap(f, d, /, *args, **kwargs):
|
|
1641
|
+
"""Returns a object of type ``pcollections.ldict`` whose values are a
|
|
1642
|
+
function of the keys of the mapping `d`.
|
|
1643
|
+
|
|
1644
|
+
``keymap(f, d)`` returns a dict whose keys are the same as those of the
|
|
1645
|
+
given dict object and whose values, for each key ``k`` are ``f(k)``. If `d`
|
|
1646
|
+
is a sequence or iterable, then it is treated as a sequence of keys.
|
|
1647
|
+
|
|
1648
|
+
``keymap(f, d, *args, **kw)`` additionally passes the given arguments to
|
|
1649
|
+
the function `f`, such that in the resulting map, each key ``k`` is
|
|
1650
|
+
mapped to ``f(k, *args, **kw)``.
|
|
1651
|
+
|
|
1652
|
+
Parameters
|
|
1653
|
+
----------
|
|
1654
|
+
f : function
|
|
1655
|
+
The function used to create the values in the new dictionary.
|
|
1656
|
+
d : collections.abc.Mapping or iterable
|
|
1657
|
+
A mapping whose keys are to be preserved and remapped to a function of
|
|
1658
|
+
themselves. Alternatively, this may be an iterable of the keys instead
|
|
1659
|
+
of a dict with matching keys.
|
|
1660
|
+
args
|
|
1661
|
+
Additional positional arguments to pass to `f`.
|
|
1662
|
+
kwargs
|
|
1663
|
+
Additional named arguments to pass to `f`.
|
|
1664
|
+
|
|
1665
|
+
Returns
|
|
1666
|
+
-------
|
|
1667
|
+
pcollections.ldict
|
|
1668
|
+
This function always returns an object of type ``ldict`` whose values
|
|
1669
|
+
are lazy.
|
|
1670
|
+
"""
|
|
1671
|
+
if is_amap(d):
|
|
1672
|
+
keys = d.keys()
|
|
1673
|
+
else:
|
|
1674
|
+
keys = d
|
|
1675
|
+
t = tldict()
|
|
1676
|
+
for k in keys:
|
|
1677
|
+
t[k] = lazy(f, k, *args, **kwargs)
|
|
1678
|
+
return t.persistent()
|
|
1679
|
+
@docwrap('immlib.keymap')
|
|
1680
|
+
def keymap(f, d, /, *args, **kwargs):
|
|
1681
|
+
"""Returns a dict object whose values are a function of a dict's keys.
|
|
1682
|
+
|
|
1683
|
+
``keymap(f, d)`` returns a dict whose keys are the same as those of the
|
|
1684
|
+
given dict object and whose values, for each key ``k`` are ``f(k)``.
|
|
1685
|
+
|
|
1686
|
+
``keymap(f, d, *args, **kw)`` additionally passes the given arguments to
|
|
1687
|
+
the function `f`, such that in the resulting map, each key ``k`` is mapped
|
|
1688
|
+
to ``f(k, *args, **kw)``.
|
|
1689
|
+
|
|
1690
|
+
This function returns either a ``dict`` or a ``pdict``. If ``d`` is a
|
|
1691
|
+
``pdict``, a ``pdict`` is returned, and otherwise, a ``dict`` is
|
|
1692
|
+
returnd. Unlike the ``valmap`` function, an ``ldict`` is never returned
|
|
1693
|
+
because the lazy values of such a dictionary are not accessed by
|
|
1694
|
+
``keymap``; if a lazy dictionary is required, then the function
|
|
1695
|
+
``lazykeymap`` should be used instead.
|
|
1696
|
+
|
|
1697
|
+
Parameters
|
|
1698
|
+
----------
|
|
1699
|
+
f : function
|
|
1700
|
+
The function used to create the values in the new dictionary.
|
|
1701
|
+
d : collections.abc.Mapping or iterable
|
|
1702
|
+
A mapping whose keys are to be preserved and remapped to a function of
|
|
1703
|
+
themselves. Alternatively, this may be an iterable of the keys instead
|
|
1704
|
+
of a dict with matching keys.
|
|
1705
|
+
args
|
|
1706
|
+
Additional positional arguments to pass to `f`.
|
|
1707
|
+
kwargs
|
|
1708
|
+
Additional named arguments to pass to `f`.
|
|
1709
|
+
|
|
1710
|
+
Returns
|
|
1711
|
+
-------
|
|
1712
|
+
collections.abc.Mapping object
|
|
1713
|
+
This function always returns a dictionary object whose type is either
|
|
1714
|
+
``pcollections.pdict`` or ``dict``, depending on the type of `d`.
|
|
1715
|
+
"""
|
|
1716
|
+
if is_pdict(d):
|
|
1717
|
+
t = tdict()
|
|
1718
|
+
for k in d.keys():
|
|
1719
|
+
t[k] = f(k, *args, **kwargs)
|
|
1720
|
+
return t.persistent()
|
|
1721
|
+
elif is_amap(d):
|
|
1722
|
+
keys = d.keys()
|
|
1723
|
+
else:
|
|
1724
|
+
keys = d
|
|
1725
|
+
return {k: f(k, *args, **kwargs) for k in keys}
|
|
1726
|
+
def _lazyitemmap_extract(f, d, k, *args, **kw):
|
|
1727
|
+
return f(k, d[k], *args, **kw)
|
|
1728
|
+
@docwrap('immlib.lazyitemmap')
|
|
1729
|
+
def lazyitemmap(f, d, /, *args, **kwargs):
|
|
1730
|
+
"""Returns an ``ldict`` object whose values are a function of a dict's
|
|
1731
|
+
items.
|
|
1732
|
+
|
|
1733
|
+
``lazyitemmap(f, d)`` yields an ``ldict`` whose keys are the same as those
|
|
1734
|
+
of the given dict object and whose values, for each key ``k``, are lazily
|
|
1735
|
+
computed as ``f(k, d[k])``.
|
|
1736
|
+
|
|
1737
|
+
``itemmap(f, d, *args, **kw)`` additionally passes the given arguments to
|
|
1738
|
+
the function `f`, such that in the resulting map, each key ``k`` is
|
|
1739
|
+
mapped to ``f(k, d[k], *args, **kw)``.
|
|
1740
|
+
|
|
1741
|
+
Parameters
|
|
1742
|
+
----------
|
|
1743
|
+
f : function
|
|
1744
|
+
The function used to create the values in the new dictionary; it must
|
|
1745
|
+
accept two arguments (``f(k, d[k])``) plus any additional arguments
|
|
1746
|
+
provided in ``*args`` and ``**kwargs``.
|
|
1747
|
+
d : collections.abc.Mapping
|
|
1748
|
+
A mapping whose items are to be preserved and remapped to a function
|
|
1749
|
+
of their keys and values.
|
|
1750
|
+
args
|
|
1751
|
+
Additional positional arguments to pass to `f`.
|
|
1752
|
+
kwargs
|
|
1753
|
+
Additional named arguments to pass to `f`.
|
|
1754
|
+
|
|
1755
|
+
Returns
|
|
1756
|
+
-------
|
|
1757
|
+
pcollections.ldict
|
|
1758
|
+
This function always returns a lazy dictionary object of type
|
|
1759
|
+
``pcollections.ldict``.
|
|
1760
|
+
"""
|
|
1761
|
+
t = tldict()
|
|
1762
|
+
if is_ammap(d):
|
|
1763
|
+
# For mutable maps, we do not try to respect laziness; they may change
|
|
1764
|
+
# so we cannot rely on them.
|
|
1765
|
+
for (k,v) in d.items():
|
|
1766
|
+
t[k] = lazy(f, k, v, *args, **kwargs)
|
|
1767
|
+
else:
|
|
1768
|
+
# Otherwise, we assume that it's an immutable map, and we respect any
|
|
1769
|
+
# possible laziness that could be implemented.
|
|
1770
|
+
for k in d.keys():
|
|
1771
|
+
t[k] = lazy(_lazyitemmap_extract, f, d, k, *args, **kwargs)
|
|
1772
|
+
return t.persistent()
|
|
1773
|
+
@docwrap('immlib.itemmap')
|
|
1774
|
+
def itemmap(f, d, /, *args, **kwargs):
|
|
1775
|
+
"""Returns a dictionary object whose values are a function of a given
|
|
1776
|
+
dictionary's items.
|
|
1777
|
+
|
|
1778
|
+
``itemmap(f, d)`` returns a dict whose keys are the same as those of the
|
|
1779
|
+
given mapping object `d` and whose values, for each key ``k`` are ``f(k,
|
|
1780
|
+
d[k])``.
|
|
1781
|
+
|
|
1782
|
+
``itemmap(f, d, *args, **kw)`` additionally passes the given arguments to
|
|
1783
|
+
the function `f`, such that in the resulting map, each key ``k`` is
|
|
1784
|
+
mapped to ``f(k, d[k], *args, **kw)``.
|
|
1785
|
+
|
|
1786
|
+
Unlike ``lazyitemmap``, this function returns either a ``dict``, a
|
|
1787
|
+
``pdict``, or an ``ldict`` depending on the input argument `d`. If `d`
|
|
1788
|
+
is an ``ldict``, then an ``ldict`` is returned; if `d` is a ``pdict``, a
|
|
1789
|
+
``pdict`` is returned, and otherwise, a ``dict`` is returnd.
|
|
1790
|
+
|
|
1791
|
+
Parameters
|
|
1792
|
+
----------
|
|
1793
|
+
f : function
|
|
1794
|
+
The function used to create the values in the new dictionary; it must
|
|
1795
|
+
accept two arguments (``f(k, d[k])``) plus any additional arguments
|
|
1796
|
+
provided in ``*args`` and ``**kwargs``.
|
|
1797
|
+
d : collections.abc.Mapping
|
|
1798
|
+
A mapping whose keys are to be preserved and remapped to a function of
|
|
1799
|
+
their values.
|
|
1800
|
+
args
|
|
1801
|
+
Additional positional arguments to pass to `f`.
|
|
1802
|
+
kwargs
|
|
1803
|
+
Additional named arguments to pass to `f`.
|
|
1804
|
+
|
|
1805
|
+
Returns
|
|
1806
|
+
-------
|
|
1807
|
+
pcollections.pdict or pcollections.ldict or dict
|
|
1808
|
+
This function always returns a dictionary object whose type is either
|
|
1809
|
+
``pcollections.pdict``, ``pcollections.ldict``, or ``dict``, depending
|
|
1810
|
+
on the type of `d`.
|
|
1811
|
+
"""
|
|
1812
|
+
if is_ldict(d):
|
|
1813
|
+
return lazyitemmap(f, d, *args, **kwargs)
|
|
1814
|
+
elif is_pdict(d):
|
|
1815
|
+
t = tdict()
|
|
1816
|
+
for (k,v) in d.items():
|
|
1817
|
+
t[k] = f(k, v, *args, **kwargs)
|
|
1818
|
+
return t.persistent()
|
|
1819
|
+
else:
|
|
1820
|
+
return {k: f(k, v, *args, **kwargs) for (k,v) in d.items()}
|
|
1821
|
+
@docwrap('immlib.dictmap')
|
|
1822
|
+
def dictmap(f, keys, /, *args, **kw):
|
|
1823
|
+
"""Returns a dict with the given keys and the values ``map(f, keys)``.
|
|
1824
|
+
|
|
1825
|
+
``dictmap(f, keys)`` returns a dict object whose keys are the elements of
|
|
1826
|
+
``iter(keys)`` and whose values are the elements of ``map(f, keys)``.
|
|
1827
|
+
|
|
1828
|
+
``dictmap(f, keys, *args, **kw)`` returns a dict object whose keys are the
|
|
1829
|
+
elements of ``iter(keys)`` and whose values are the elements of
|
|
1830
|
+
``[f(k, *args, **kw) for k in iter(keys)]``.
|
|
1831
|
+
|
|
1832
|
+
Parameters
|
|
1833
|
+
----------
|
|
1834
|
+
f : function
|
|
1835
|
+
The function used to create the values in the new dictionary; it must
|
|
1836
|
+
accept one arguments (``f(k)``) plus any additional arguments provided
|
|
1837
|
+
in ``*args`` and ``**kwargs``.
|
|
1838
|
+
keys : iterable
|
|
1839
|
+
An iterable object whose values are to become the keys of the new
|
|
1840
|
+
dictionary.
|
|
1841
|
+
args
|
|
1842
|
+
Additional positional arguments to pass to `f`.
|
|
1843
|
+
kwargs
|
|
1844
|
+
Additional named arguments to pass to `f`.
|
|
1845
|
+
|
|
1846
|
+
Returns
|
|
1847
|
+
-------
|
|
1848
|
+
dict
|
|
1849
|
+
A dictionary of the given `keys` with each key ``k`` mapped to
|
|
1850
|
+
``f(k)``.
|
|
1851
|
+
"""
|
|
1852
|
+
return {k: f(k, *args, **kw) for k in keys}
|
|
1853
|
+
@docwrap('immlib.pdictmap')
|
|
1854
|
+
def pdictmap(f, keys, /, *args, **kw):
|
|
1855
|
+
"""Returns a ``pdict`` with the given keys and the values ``map(f, keys)``.
|
|
1856
|
+
|
|
1857
|
+
``pdictmap(f, keys)`` returns a ``pdict`` object whose keys are the
|
|
1858
|
+
elements of ``iter(keys)`` and whose values are the elements of
|
|
1859
|
+
``map(f, keys)``.
|
|
1860
|
+
|
|
1861
|
+
``pdictmap(f, keys, *args, **kw)`` returns a dict object whose keys are
|
|
1862
|
+
the elements of ``iter(keys)`` and whose values are the elements of
|
|
1863
|
+
``[f(k, *args, **kw) for k in iter(keys)]``.
|
|
1864
|
+
|
|
1865
|
+
Parameters
|
|
1866
|
+
----------
|
|
1867
|
+
f : function
|
|
1868
|
+
The function used to create the values in the new dictionary; it must
|
|
1869
|
+
accept one arguments (``f(k)``) plus any additional arguments provided
|
|
1870
|
+
in ``*args`` and ``**kwargs``.
|
|
1871
|
+
keys : iterable
|
|
1872
|
+
An iterable object whose values are to become the keys of the new
|
|
1873
|
+
dictionary.
|
|
1874
|
+
args
|
|
1875
|
+
Additional positional arguments to pass to `f`.
|
|
1876
|
+
kwargs
|
|
1877
|
+
Additional named arguments to pass to `f`.
|
|
1878
|
+
|
|
1879
|
+
Returns
|
|
1880
|
+
-------
|
|
1881
|
+
pcollections.pdict
|
|
1882
|
+
A persistent dictionary of the given `keys` with each key ``k`` mapped
|
|
1883
|
+
to ``f(k)``.
|
|
1884
|
+
"""
|
|
1885
|
+
t = tdict()
|
|
1886
|
+
for k in keys:
|
|
1887
|
+
t[k] = f(k, *args, **kw)
|
|
1888
|
+
return t.persistent()
|
|
1889
|
+
@docwrap('immlib.ldictmap')
|
|
1890
|
+
def ldictmap(f, keys, *args, **kw):
|
|
1891
|
+
"""Returns a lazy dictionary with the given keys and the values
|
|
1892
|
+
``map(f, keys)``.
|
|
1893
|
+
|
|
1894
|
+
``lazydictmap(f, keys)`` returns a ``pcollections.ldict`` object whose keys
|
|
1895
|
+
are the elements of ``iter(keys)`` and whose values are the elements of
|
|
1896
|
+
``map(f, keys)``. All values are lazy.
|
|
1897
|
+
|
|
1898
|
+
``lazydictmap(f, keys, *args, **kw)`` returns a `pcollections.ldict` object
|
|
1899
|
+
whose keys are the elements of ``iter(keys)`` and whose values are the
|
|
1900
|
+
elements of ``[f(k, *args, **kw) for k in iter(keys)]``, lazily calculated.
|
|
1901
|
+
|
|
1902
|
+
Parameters
|
|
1903
|
+
----------
|
|
1904
|
+
f : function
|
|
1905
|
+
The function used to create the values in the new dictionary; it must
|
|
1906
|
+
accept one arguments (``f(k)``) plus any additional arguments provided
|
|
1907
|
+
in ``*args`` and ``**kwargs``.
|
|
1908
|
+
keys : iterable
|
|
1909
|
+
An iterable object whose values are to become the keys of the new
|
|
1910
|
+
dictionary.
|
|
1911
|
+
args
|
|
1912
|
+
Additional positional arguments to pass to `f`.
|
|
1913
|
+
kwargs
|
|
1914
|
+
Additional named arguments to pass to `f`.
|
|
1915
|
+
|
|
1916
|
+
Returns
|
|
1917
|
+
-------
|
|
1918
|
+
pcollections.ldict
|
|
1919
|
+
A persistent lazy dictionary of the given `keys` with each key ``k``
|
|
1920
|
+
mapped to ``f(k)``.
|
|
1921
|
+
"""
|
|
1922
|
+
t = tldict()
|
|
1923
|
+
for k in keys:
|
|
1924
|
+
t[k] = lazy(f, k, *args, **kw)
|
|
1925
|
+
return t.persistent()
|
|
1926
|
+
@docwrap('immlib.merge')
|
|
1927
|
+
def merge(*args, **kwargs):
|
|
1928
|
+
'''Merges dict-like objects left-to-right. See also ``rmerge``.
|
|
1929
|
+
|
|
1930
|
+
``merge(...)`` collapses all arguments, which must be ``Mapping`` objects
|
|
1931
|
+
of some kind (``dict``, ``pdict``, ``ldict``, or a similar type), into a
|
|
1932
|
+
single mapping from left-to-right (i.e., with values in dictionaries to the
|
|
1933
|
+
right in the argument list overwriting values to the left in the argument
|
|
1934
|
+
list). The mapping that is returned depends on the inputs: if any of the
|
|
1935
|
+
input mappings are ``ldict`` objects, then an ``ldict`` is returned (and
|
|
1936
|
+
the laziness of arguments is respected); otherwise, a ``pdict`` object is
|
|
1937
|
+
retuend.
|
|
1938
|
+
|
|
1939
|
+
Named arguments may be passed after the dictionaries; these are
|
|
1940
|
+
collectively considered equivalent to one additional dictionary argument to
|
|
1941
|
+
the right of the positional mapping arguments.
|
|
1942
|
+
|
|
1943
|
+
Parameters
|
|
1944
|
+
----------
|
|
1945
|
+
args
|
|
1946
|
+
A sequence of ``collections.abc.Mapping`` objects such as ``dict``
|
|
1947
|
+
objects.
|
|
1948
|
+
kwargs
|
|
1949
|
+
Additional key-value pairs that are merged into the result last.
|
|
1950
|
+
|
|
1951
|
+
Returns
|
|
1952
|
+
-------
|
|
1953
|
+
pcollections.pdict or pcollections.ldict
|
|
1954
|
+
A dictionary that represents the merger of all given dictionaries and
|
|
1955
|
+
key-value pairs. If any of the arguments are lazy dictionaries
|
|
1956
|
+
(``pcollections.ldict``) then the return value is also lazy in order to
|
|
1957
|
+
respect the laziness of the arguments.
|
|
1958
|
+
|
|
1959
|
+
See Also
|
|
1960
|
+
--------
|
|
1961
|
+
rmerge : Merges dictionaries from right to left.
|
|
1962
|
+
'''
|
|
1963
|
+
if len(args) == 0:
|
|
1964
|
+
return pdict(kwargs)
|
|
1965
|
+
# Make the initial dictionary.
|
|
1966
|
+
res = args[0]
|
|
1967
|
+
lazy = is_ldict(res)
|
|
1968
|
+
res = tdict(holdlazy(res) if lazy else res)
|
|
1969
|
+
for d in args[1:]:
|
|
1970
|
+
if is_ldict(d):
|
|
1971
|
+
lazy = True
|
|
1972
|
+
res.update(holdlazy(d))
|
|
1973
|
+
else:
|
|
1974
|
+
res.update(d)
|
|
1975
|
+
res.update(kwargs)
|
|
1976
|
+
if not lazy:
|
|
1977
|
+
from pcollections import lazy as _lazy
|
|
1978
|
+
lazy = any(isinstance(u, _lazy) for u in kwargs.values())
|
|
1979
|
+
return ldict(res) if lazy else pdict(res)
|
|
1980
|
+
def rmerge(*args, **kwargs):
|
|
1981
|
+
'''Merges dictionary objects right-to-left. See also ``merge``.
|
|
1982
|
+
|
|
1983
|
+
``rmerge(...)`` collapses all arguments, which must be python ``Mapping``
|
|
1984
|
+
objects of some kind, into a single mapping from right-to-left. The mapping
|
|
1985
|
+
that is returned depends on the inputs: if any of the input mappings are
|
|
1986
|
+
lazydict objects, then a lazydict is returned (and the laziness of
|
|
1987
|
+
arguments is respected); otherwise, a frozendict object is retuend.
|
|
1988
|
+
|
|
1989
|
+
Named arguments may be passed after the dictionaries; these are
|
|
1990
|
+
collectively considered equivalent to one additional dictionary argument to
|
|
1991
|
+
the right of the positional mapping arguments.
|
|
1992
|
+
|
|
1993
|
+
.. Note:: The ``rmerge`` function is identical to the ``merge`` function
|
|
1994
|
+
but with reversed arguments. In other words, ``merge(*args, **kw)`` is
|
|
1995
|
+
equivalent to ``rmerge(kw, **reversed(args))``.
|
|
1996
|
+
|
|
1997
|
+
Parameters
|
|
1998
|
+
----------
|
|
1999
|
+
args
|
|
2000
|
+
A sequence of ``collections.abc.Mapping`` objects such as ``dict``
|
|
2001
|
+
objectss.
|
|
2002
|
+
kwargs
|
|
2003
|
+
Additional key-value pairs that are merged into the result first.
|
|
2004
|
+
|
|
2005
|
+
Returns
|
|
2006
|
+
-------
|
|
2007
|
+
pcollections.pdict or pcollections.ldict
|
|
2008
|
+
A dictionary that represents the merger of all given dictionaries and
|
|
2009
|
+
key-value pairs. If any of the arguments are lazy dictionaries
|
|
2010
|
+
(``pcollections.ldict``) then the return value is also lazy in order to
|
|
2011
|
+
respect the laziness of the arguments.
|
|
2012
|
+
|
|
2013
|
+
See Also
|
|
2014
|
+
--------
|
|
2015
|
+
merge : Merges dictionaries from left to right.
|
|
2016
|
+
'''
|
|
2017
|
+
from pcollections import lazy as _lazy
|
|
2018
|
+
if len(args) == 0:
|
|
2019
|
+
return pdict(kwargs)
|
|
2020
|
+
# Make the initial dictionary.
|
|
2021
|
+
res = tdict(kwargs)
|
|
2022
|
+
lazy = any(isinstance(v, _lazy) for v in kwargs.values())
|
|
2023
|
+
for d in reversed(args):
|
|
2024
|
+
if is_ldict(d):
|
|
2025
|
+
lazy = True
|
|
2026
|
+
res.update(holdlazy(d))
|
|
2027
|
+
else:
|
|
2028
|
+
res.update(d)
|
|
2029
|
+
return ldict(res) if lazy else pdict(res)
|
|
2030
|
+
@docwrap('immlib.assoc')
|
|
2031
|
+
def assoc(d, /, *args, **kwargs):
|
|
2032
|
+
"""Returns a copy of the given dictionary with additional key-value pairs.
|
|
2033
|
+
|
|
2034
|
+
``assoc(d, key, val)`` returns a copy of the dictionary `d` with the given
|
|
2035
|
+
key-value pair associated in the new copy. The return value is always the
|
|
2036
|
+
same type as the argument `d` but is always an updated copy. The argument
|
|
2037
|
+
`d` is never mutated.
|
|
2038
|
+
|
|
2039
|
+
``assoc(d, key1, val1, key2, val2 ...)`` associates all the given keys to
|
|
2040
|
+
the given values in the returned copy.
|
|
2041
|
+
|
|
2042
|
+
``assoc(d, key1=val1, key2=val2 ...)`` uses the keyword arguments as the
|
|
2043
|
+
arguments that are to be associated. These may be mixed with positional
|
|
2044
|
+
key-value pairs.
|
|
2045
|
+
|
|
2046
|
+
``assoc(d)`` returns a copy of `d`.
|
|
2047
|
+
|
|
2048
|
+
Parameters
|
|
2049
|
+
----------
|
|
2050
|
+
d : dict-like
|
|
2051
|
+
A dictionary that is to be copied and updated with the following
|
|
2052
|
+
arguments.
|
|
2053
|
+
args
|
|
2054
|
+
Sequential pairs of keys and values (i.e., ``len(args)`` must be even)
|
|
2055
|
+
that should be updated in the returned dictionary.
|
|
2056
|
+
kwargs
|
|
2057
|
+
Additional key-value pairs to be updated in the returned dictionary.
|
|
2058
|
+
|
|
2059
|
+
Returns
|
|
2060
|
+
-------
|
|
2061
|
+
dict-like
|
|
2062
|
+
A copy of `d` with updated keys and values.
|
|
2063
|
+
"""
|
|
2064
|
+
if len(args) % 2 != 0:
|
|
2065
|
+
raise ValueError("assoc requires matched key-value arguments")
|
|
2066
|
+
ks = args[0::2]
|
|
2067
|
+
vs = args[1::2]
|
|
2068
|
+
if is_ammap(d):
|
|
2069
|
+
# This is a mutable mapping, so we copy it.
|
|
2070
|
+
d = d.copy()
|
|
2071
|
+
for (k,v) in zip(ks,vs):
|
|
2072
|
+
d[k] = v
|
|
2073
|
+
for (k,v) in kwargs.items():
|
|
2074
|
+
d[k] = v
|
|
2075
|
+
elif is_apmap(d):
|
|
2076
|
+
nels = len(ks) + len(kwargs)
|
|
2077
|
+
if nels > 1:
|
|
2078
|
+
d = d.transient()
|
|
2079
|
+
for (k,v) in zip(ks,vs):
|
|
2080
|
+
d[k] = v
|
|
2081
|
+
for (k,v) in kwargs.items():
|
|
2082
|
+
d[k] = v
|
|
2083
|
+
d = d.persistent()
|
|
2084
|
+
else:
|
|
2085
|
+
for (k,v) in zip(ks,vs):
|
|
2086
|
+
d = d.set(k, v)
|
|
2087
|
+
for (k,v) in kwargs.items():
|
|
2088
|
+
d = d.set(k, v)
|
|
2089
|
+
else:
|
|
2090
|
+
raise TypeError(f"cannot assoc to type {type(d)}")
|
|
2091
|
+
return d
|
|
2092
|
+
@docwrap('immlib.dissoc')
|
|
2093
|
+
def dissoc(d, /, *args):
|
|
2094
|
+
"""Returns a copy of the given dictionary with certain keys removed.
|
|
2095
|
+
|
|
2096
|
+
``dissoc(d, key)`` returns a copy of the dictionary `d` with the given
|
|
2097
|
+
``key`` disssociated in the new copy. The return value is always the same
|
|
2098
|
+
type as the argument `d`.
|
|
2099
|
+
|
|
2100
|
+
``dissoc(d, key1, key2 ...)`` dissociates all the given keys from their
|
|
2101
|
+
values in the returned copy.
|
|
2102
|
+
|
|
2103
|
+
``dissoc(d)`` returns a copy of `d`.
|
|
2104
|
+
|
|
2105
|
+
Parameters
|
|
2106
|
+
----------
|
|
2107
|
+
d : dict-like
|
|
2108
|
+
A dictionary that is to be copied and updated according to the
|
|
2109
|
+
following arguments.
|
|
2110
|
+
args
|
|
2111
|
+
Keys that should be removed from the copy of `d` that is returned.
|
|
2112
|
+
|
|
2113
|
+
Returns
|
|
2114
|
+
-------
|
|
2115
|
+
dict-like
|
|
2116
|
+
A copy of `d` with the given keys removed.
|
|
2117
|
+
"""
|
|
2118
|
+
if is_ammap(d):
|
|
2119
|
+
# This is a mutable mapping, so we copy it.
|
|
2120
|
+
d = d.copy()
|
|
2121
|
+
for k in args:
|
|
2122
|
+
if k in d:
|
|
2123
|
+
del d[k]
|
|
2124
|
+
return d
|
|
2125
|
+
elif is_pdict(d):
|
|
2126
|
+
if len(args) == 1:
|
|
2127
|
+
return d.delete(args[0])
|
|
2128
|
+
else:
|
|
2129
|
+
d = d.transient()
|
|
2130
|
+
for k in args:
|
|
2131
|
+
del d[k]
|
|
2132
|
+
return d.persistent()
|
|
2133
|
+
else:
|
|
2134
|
+
raise TypeError(f"cannot dissoc from type {type(d)}")
|
|
2135
|
+
from pcollections import unlazy
|
|
2136
|
+
def _lambdadict_call(data, fn):
|
|
2137
|
+
spec = getfullargspec(fn)
|
|
2138
|
+
dflts = spec.defaults or pdict()
|
|
2139
|
+
args = []
|
|
2140
|
+
kwargs = {}
|
|
2141
|
+
pos = True
|
|
2142
|
+
for k in spec.args:
|
|
2143
|
+
if k in data:
|
|
2144
|
+
v = unlazy(data[k])
|
|
2145
|
+
if pos:
|
|
2146
|
+
args.append(v)
|
|
2147
|
+
else:
|
|
2148
|
+
kwargs[k] = v
|
|
2149
|
+
else:
|
|
2150
|
+
pos = False
|
|
2151
|
+
if k in dflts:
|
|
2152
|
+
kwargs[k] = dflts[k]
|
|
2153
|
+
for k in spec.kwonlyargs:
|
|
2154
|
+
if k in data:
|
|
2155
|
+
kwargs[k] = unlazy(data[k])
|
|
2156
|
+
else:
|
|
2157
|
+
if k in dflts:
|
|
2158
|
+
kwargs[k] = dflts[k]
|
|
2159
|
+
return fn(*args, **kwargs)
|
|
2160
|
+
def lambdadict(*args, **kwargs):
|
|
2161
|
+
"""Builds and returns a ``ldict`` with lambda functions calculated lazily.
|
|
2162
|
+
|
|
2163
|
+
``lambdadict(args...)`` is equivalent to ``merge(args...)`` except that
|
|
2164
|
+
always returns an object of type ``pcollections.ldict`` and that any lambda
|
|
2165
|
+
function in the values provided by the merged arguments is made into a lazy
|
|
2166
|
+
partial function whose inputs come from the lambda-function variable names
|
|
2167
|
+
in the same resulting ``ldict``.
|
|
2168
|
+
|
|
2169
|
+
.. Warning:: This function will gladly return an ``ldict`` that
|
|
2170
|
+
encapsulates an infinite loop if you are not careful. For example, the
|
|
2171
|
+
following lambdadict will infinitely loop when either key is requested:
|
|
2172
|
+
``ld = lambdadict(a=lambda b:b, b=lambda a:a)``.
|
|
2173
|
+
|
|
2174
|
+
Examples
|
|
2175
|
+
--------
|
|
2176
|
+
>>> d = lambdadict(a=1, b=2, c=lambda a,b: a + b)
|
|
2177
|
+
>>> d.is_lazy('c')
|
|
2178
|
+
True
|
|
2179
|
+
|
|
2180
|
+
>>> d.is_ready('c')
|
|
2181
|
+
False
|
|
2182
|
+
|
|
2183
|
+
>>> d['c']
|
|
2184
|
+
3
|
|
2185
|
+
|
|
2186
|
+
>>> d
|
|
2187
|
+
{|'a': 1, 'b': 2, 'c': 3|}
|
|
2188
|
+
"""
|
|
2189
|
+
d = merge(*args, **kwargs)
|
|
2190
|
+
finals = d.transient()
|
|
2191
|
+
if isinstance(d, ldict):
|
|
2192
|
+
d = d.as_pdict()
|
|
2193
|
+
for (k,v) in d.items():
|
|
2194
|
+
if isinstance(v, LambdaType):
|
|
2195
|
+
finals[k] = lazy(_lambdadict_call, finals, v)
|
|
2196
|
+
else:
|
|
2197
|
+
finals[k] = v
|
|
2198
|
+
return ldict(finals)
|
|
2199
|
+
|
|
2200
|
+
|
|
2201
|
+
# Argument Utilities ##########################################################
|
|
2202
|
+
|
|
2203
|
+
from collections import namedtuple
|
|
2204
|
+
argstuple = namedtuple('argstuple', ('args', 'kwargs'))
|
|
2205
|
+
class args(argstuple):
|
|
2206
|
+
"""An object type that represents a set of function arguments.
|
|
2207
|
+
|
|
2208
|
+
``args(x1, x2 ... k1=v1, k2=v2 ...)`` yields an ``args`` object that
|
|
2209
|
+
represents the positional arguments ``x1, x2 ...`` and the named arguments
|
|
2210
|
+
``k1=v1``, ``k2=v2``, etc.
|
|
2211
|
+
|
|
2212
|
+
If ``a`` is an instance of ``args`` and ``f`` is a function, then the
|
|
2213
|
+
arguments in ``a`` can be applied to ``f`` using either of the following
|
|
2214
|
+
methods:
|
|
2215
|
+
|
|
2216
|
+
- ``f @ a``
|
|
2217
|
+
- ``a.passto(f)``
|
|
2218
|
+
|
|
2219
|
+
Note that if ``f`` is an object that defines the ``__matmul__`` method,
|
|
2220
|
+
then the former syntax will call that method instead of the ``__rmatmul__``
|
|
2221
|
+
method of the ``args`` object ``a`` and thus won't work.
|
|
2222
|
+
"""
|
|
2223
|
+
def __new__(cls, *args, **kwargs):
|
|
2224
|
+
return argstuple.__new__(cls, args, kwargs)
|
|
2225
|
+
def __rmatmul__(self, fn):
|
|
2226
|
+
return fn(*self.args, **self.kwargs)
|
|
2227
|
+
def passto(self, fn):
|
|
2228
|
+
return fn(*self.args, **self.kwargs)
|
|
2229
|
+
def copy(self, args=None, kwargs=None):
|
|
2230
|
+
"""Returns a copy of the current ``args``, potentially with updates."""
|
|
2231
|
+
if args is None:
|
|
2232
|
+
args = self.args
|
|
2233
|
+
if kwargs is None:
|
|
2234
|
+
kwargs = self.kwargs
|
|
2235
|
+
if args is self.args and kwargs is self.kwargs:
|
|
2236
|
+
return self
|
|
2237
|
+
return argstuple.__new__(type(self), args, kwargs)
|
|
2238
|
+
@docwrap('immlib.argfilter')
|
|
2239
|
+
def argfilter(fn=None, /, **kwargs):
|
|
2240
|
+
"""A decorator that creates decorators that filter function arguments.
|
|
2241
|
+
|
|
2242
|
+
A function decorated with ``@argfilter`` is turned into a an argument
|
|
2243
|
+
filter function, which itself can be used to decorate functions whose
|
|
2244
|
+
arguments need to be filtered.
|
|
2245
|
+
|
|
2246
|
+
In the definition of the filter function, the names of arguments must match
|
|
2247
|
+
those of the arguments they will be filtering on other functions. The
|
|
2248
|
+
filter function must return a tuple of the filtered values in the order
|
|
2249
|
+
they are defined in the function's argument list. The arguments may be
|
|
2250
|
+
given in any order, but a ``*`` in the arguments list indicates that any of
|
|
2251
|
+
the arguments following the ``*`` are not themselves being filtered and
|
|
2252
|
+
thus will not be returned from the filter function.
|
|
2253
|
+
|
|
2254
|
+
When a filter function is used to decorate another function, the decorator
|
|
2255
|
+
can optionally be given named arguments where the name corresponds to one
|
|
2256
|
+
of the arguments to the origional filter definition and the value
|
|
2257
|
+
corresponds to the name that is used for this parameter in the decorated
|
|
2258
|
+
functions. In this way, the parameter names don't have to match exactly
|
|
2259
|
+
those of the filter function and can instead be specified in the
|
|
2260
|
+
decoration.
|
|
2261
|
+
|
|
2262
|
+
Examples
|
|
2263
|
+
--------
|
|
2264
|
+
>>> @argfilter
|
|
2265
|
+
... def fix_angle(angle, *, unit):
|
|
2266
|
+
... angle = np.asarray(angle)
|
|
2267
|
+
... if unit == 'degrees':
|
|
2268
|
+
... angle = np.pi / 180 * angle
|
|
2269
|
+
... elif unit != 'radians':
|
|
2270
|
+
... raise ValueError(f'unrecognized unit: {unit}')
|
|
2271
|
+
... return (angle,)
|
|
2272
|
+
... @fix_angle
|
|
2273
|
+
... def cos_halfangle(angle, unit='radians'):
|
|
2274
|
+
... return np.cos(angle / 2)
|
|
2275
|
+
|
|
2276
|
+
>>> cos_halfangle([0, 360], 'degrees')
|
|
2277
|
+
array([1., -1.])
|
|
2278
|
+
|
|
2279
|
+
>>> @fix_angle(angle='theta')
|
|
2280
|
+
... def sin_halfangle(theta, unit='radians'):
|
|
2281
|
+
... return np.sin(theta / 2)
|
|
2282
|
+
|
|
2283
|
+
>>> sin_halfangle([0, 360], 'degrees')
|
|
2284
|
+
array([0., 0.])
|
|
2285
|
+
"""
|
|
2286
|
+
sig = signature(fn)
|
|
2287
|
+
pos_args = {}
|
|
2288
|
+
pok_args = {}
|
|
2289
|
+
for (k,u) in sig.parameters.items():
|
|
2290
|
+
if u.kind == u.POSITIONAL_ONLY or u.kind == u.POSITIONAL_OR_KEYWORD:
|
|
2291
|
+
pos_args[k] = u
|
|
2292
|
+
elif u.kind == u.KEYWORD_ONLY:
|
|
2293
|
+
pok_args[k] = u
|
|
2294
|
+
else:
|
|
2295
|
+
raise ValueError("variadic filter arguments are not supported")
|
|
2296
|
+
# We make a function that uses these when it is used as a decorator. There
|
|
2297
|
+
# are two ways to call this decorator: one as @filter_func and the other is
|
|
2298
|
+
# @filter_func(origname1=newname1, origname2=newname2...) in order to
|
|
2299
|
+
# indicate that some of the arguments need to be renamed. These are written
|
|
2300
|
+
# as the private functions below and are then wrapped up into a partial.
|
|
2301
|
+
def fn_decr(f=None, /, **kwargs):
|
|
2302
|
+
return _argfilter_decr(pos_args, pok_args, fn, f, **kwargs)
|
|
2303
|
+
return wraps(fn)(fn_decr)
|
|
2304
|
+
def _argfilter_decr(pos_args, pok_args, filter_fn, fn=None, /, **kwargs):
|
|
2305
|
+
if len(kwargs) == 0:
|
|
2306
|
+
if fn is None:
|
|
2307
|
+
raise ValueError(
|
|
2308
|
+
"argfilter decorator requires either a function or options")
|
|
2309
|
+
else:
|
|
2310
|
+
return _argfilter_init(pos_args, pok_args, filter_fn, {}, fn)
|
|
2311
|
+
elif fn is None:
|
|
2312
|
+
return partial(
|
|
2313
|
+
_argfilter_init,
|
|
2314
|
+
pos_args, pok_args, filter_fn,
|
|
2315
|
+
kwargs)
|
|
2316
|
+
else:
|
|
2317
|
+
raise ValueError(
|
|
2318
|
+
"argfilter decorator requires a function or options, but not both")
|
|
2319
|
+
def _argfilter_init(pos_args, pok_args, filter_fn, tr, f):
|
|
2320
|
+
sig = signature(f)
|
|
2321
|
+
params = sig.parameters
|
|
2322
|
+
# Prep some data structures so that we can quickly extract the filtered
|
|
2323
|
+
# arguments when the function is called.
|
|
2324
|
+
keys = []
|
|
2325
|
+
argdat = []
|
|
2326
|
+
kwdat = {}
|
|
2327
|
+
for (args, dat) in ((pos_args,argdat), (pok_args,kwdat)):
|
|
2328
|
+
for (k0,arg) in args.items():
|
|
2329
|
+
k = tr.get(k0, k0)
|
|
2330
|
+
dflt = arg.default
|
|
2331
|
+
if k in params:
|
|
2332
|
+
p = params[k]
|
|
2333
|
+
if p.default is not p.empty:
|
|
2334
|
+
dflt = p.default
|
|
2335
|
+
else:
|
|
2336
|
+
if dflt is arg.empty:
|
|
2337
|
+
if k0 != k:
|
|
2338
|
+
k = f'{k0} ({k})'
|
|
2339
|
+
raise ValueError(f"filtered parameter {k} not found")
|
|
2340
|
+
app = (k, dflt)
|
|
2341
|
+
if isinstance(dat, list):
|
|
2342
|
+
dat.append(app)
|
|
2343
|
+
else:
|
|
2344
|
+
dat[k0] = app
|
|
2345
|
+
# Now pass these along to the argilter initialization function.
|
|
2346
|
+
keys = tuple(u[0] for u in argdat)
|
|
2347
|
+
fn = partial(_argfilter_dispatch, filter_fn, f, sig, argdat, kwdat, keys)
|
|
2348
|
+
return wraps(f)(fn)
|
|
2349
|
+
def _argfilter_dispatch(filter_fn, f, fsig,
|
|
2350
|
+
filt_argdat, filt_kwdat, filt_keys,
|
|
2351
|
+
*args, **kwargs):
|
|
2352
|
+
b = fsig.bind(*args, **kwargs)
|
|
2353
|
+
argmap = b.arguments
|
|
2354
|
+
filtered_vals = filter_fn(
|
|
2355
|
+
*(argmap.get(k, d) for (k,d) in filt_argdat),
|
|
2356
|
+
**{k0: argmap.get(k, d) for (k0,(k,d)) in filt_kwdat.items()})
|
|
2357
|
+
# The number of filtered items must be equal to the number we expect.
|
|
2358
|
+
if len(filtered_vals) != len(filt_keys):
|
|
2359
|
+
raise ValueError(
|
|
2360
|
+
f"filter on function {f.__name__} returned {len(filtered_vals)}"
|
|
2361
|
+
f" items but expected {len(filt_keys)}")
|
|
2362
|
+
argmap.update(zip(filt_keys, filtered_vals))
|
|
2363
|
+
return f(*b.args, **b.kwargs)
|
|
2364
|
+
|
|
2365
|
+
|
|
2366
|
+
# unitregistry ################################################################
|
|
2367
|
+
|
|
2368
|
+
# We put the unitregistry here and not in the quantity namespace because we
|
|
2369
|
+
# need it both for quantity and numeric and it causes a circular import if
|
|
2370
|
+
# placed in the quantity file.
|
|
2371
|
+
@docwrap('immlib.unitregistry')
|
|
2372
|
+
def unitregistry(obj, /, *args):
|
|
2373
|
+
"""Returns the ``pint.UnitRegistry`` object for the given unit or quantity.
|
|
2374
|
+
|
|
2375
|
+
``unitregistry(u)`` for a ``pint.Unit`` object ``u`` returns the
|
|
2376
|
+
``pint.UnitRegistry`` object in which ``u`` is registered.
|
|
2377
|
+
|
|
2378
|
+
``unitregistry(q)`` for a ``pint.Quantity`` object ``q`` returns the
|
|
2379
|
+
``pint.UnitRegistry`` object in which ``q`` is registered.
|
|
2380
|
+
|
|
2381
|
+
``unitregistry(ureg)`` returns ``ureg`` is ``ureg`` is itself a
|
|
2382
|
+
``pint.UnitRegistry`` object.
|
|
2383
|
+
|
|
2384
|
+
``unitregistry(Ellipsis)`` returns ``immlib.units``, the default unit
|
|
2385
|
+
registry for ``immlib``.
|
|
2386
|
+
|
|
2387
|
+
``unitregistry(x)`` raises a ``TypeError`` for any other type of object.
|
|
2388
|
+
|
|
2389
|
+
``unitregistry(x, default)`` returns ``unitregistry(x)`` unless a
|
|
2390
|
+
``TypeError`` would be raised, in which case it returns the given
|
|
2391
|
+
``default`` value. If ``default`` is ``Ellipsis``, then ``immlib.units`` is
|
|
2392
|
+
used as the default.
|
|
2393
|
+
|
|
2394
|
+
"""
|
|
2395
|
+
nargs = len(args)
|
|
2396
|
+
if nargs > 1:
|
|
2397
|
+
raise TypeError(
|
|
2398
|
+
f"unitregistry() takes from 1 to 2 positional arguments but"
|
|
2399
|
+
f" {1+len(args)} were given")
|
|
2400
|
+
elif isinstance(obj, (pint.Unit, pint.Quantity)):
|
|
2401
|
+
return obj._REGISTRY
|
|
2402
|
+
elif isinstance(obj, pint.UnitRegistry):
|
|
2403
|
+
return obj
|
|
2404
|
+
elif obj is Ellipsis:
|
|
2405
|
+
from immlib import units
|
|
2406
|
+
return units
|
|
2407
|
+
elif len(args) == 0:
|
|
2408
|
+
raise TypeError(
|
|
2409
|
+
f"unitregistry() cannot convert object of type {type(obj)} to a"
|
|
2410
|
+
f" pint.UnitRegistry")
|
|
2411
|
+
else:
|
|
2412
|
+
default = args[0]
|
|
2413
|
+
if default is Ellipsis:
|
|
2414
|
+
from immlib import units as default
|
|
2415
|
+
return default
|
|
2416
|
+
|
|
2417
|
+
|
|
2418
|
+
# Caching #####################################################################
|
|
2419
|
+
|
|
2420
|
+
@docwrap('immlib.util.to_pathcache')
|
|
2421
|
+
def to_pathcache(obj):
|
|
2422
|
+
"""Returns a ``joblib.Memory`` object that corresponds to the given path
|
|
2423
|
+
object.
|
|
2424
|
+
|
|
2425
|
+
``to_pathcache(obj)`` converts the given object `obj` into a
|
|
2426
|
+
``joblib.Memory`` cache manager. The object may be any of the following:
|
|
2427
|
+
|
|
2428
|
+
- a ``joblib.Memory`` object;
|
|
2429
|
+
- a filename or pathlib object pointing to a directory; or
|
|
2430
|
+
- a tuple containing a filename or pathlib object followed by a dict-like
|
|
2431
|
+
object of options to ``joblib.Memory``.
|
|
2432
|
+
|
|
2433
|
+
If the `obj` is ``None``, then ``None`` is returned. However, a
|
|
2434
|
+
``joblib.Memory`` object whose location parameter is ``None`` can be
|
|
2435
|
+
created by using the object ``(None, opts)`` where ``opts`` may be ``None``
|
|
2436
|
+
or an empty dictionary.
|
|
2437
|
+
|
|
2438
|
+
The ``joblib.Memory`` constructor takes certain arguments; this function
|
|
2439
|
+
makes one change to those arguments: the ``verbose`` option is by default 0
|
|
2440
|
+
when filtered through this function, meaning that no output will be printed
|
|
2441
|
+
unless a ``verbose`` argument of greater than 0 is explicitly given.
|
|
2442
|
+
|
|
2443
|
+
See Also
|
|
2444
|
+
--------
|
|
2445
|
+
joblib.Memory, to_lrucache
|
|
2446
|
+
"""
|
|
2447
|
+
# If we have been given a Memory object, just return it; otherwise, we
|
|
2448
|
+
# check to parse the object into path or path + options.
|
|
2449
|
+
if isinstance(obj, Memory):
|
|
2450
|
+
return obj
|
|
2451
|
+
elif is_tuple(obj):
|
|
2452
|
+
n = len(obj)
|
|
2453
|
+
if n == 1:
|
|
2454
|
+
(obj,opts) = (obj[0], {})
|
|
2455
|
+
elif n == 2:
|
|
2456
|
+
(obj,opts) = obj
|
|
2457
|
+
else:
|
|
2458
|
+
raise ValueError("only 1- or 2-tuples can become pathcaches")
|
|
2459
|
+
if opts is None:
|
|
2460
|
+
opts = {}
|
|
2461
|
+
elif isinstance(obj, args):
|
|
2462
|
+
(obj,opts) = (obj.args, obj.kwargs)
|
|
2463
|
+
if len(obj) == 1:
|
|
2464
|
+
obj = obj[0]
|
|
2465
|
+
else:
|
|
2466
|
+
raise TypeError(
|
|
2467
|
+
f"to_pathcache() takes exactly one argument ({len(obj)} given")
|
|
2468
|
+
else:
|
|
2469
|
+
opts = {}
|
|
2470
|
+
# We change the default argument of verbose into 0 in this function because
|
|
2471
|
+
# we don't want unintentional logging.
|
|
2472
|
+
if 'verbose' not in opts:
|
|
2473
|
+
opts['verbose'] = 0
|
|
2474
|
+
# Whether there were or were not any options, then we now have either a
|
|
2475
|
+
# string or pathlib path that we want to pass to the memory constructor.
|
|
2476
|
+
if isinstance(obj, Path) or isinstance(obj, str) or obj is None:
|
|
2477
|
+
return Memory(obj, **opts)
|
|
2478
|
+
else:
|
|
2479
|
+
raise TypeError(
|
|
2480
|
+
f"to_pathcache: arg must be path, str, or None; not {type(obj)}")
|
|
2481
|
+
@docwrap('immlib.util.to_lrucache')
|
|
2482
|
+
def to_lrucache(obj):
|
|
2483
|
+
"""Returns an ``lru_cache`` function appropriate for the given object.
|
|
2484
|
+
|
|
2485
|
+
``to_lrucache(obj)`` converts the given object `obj` into either
|
|
2486
|
+
``None``, the ``lru_cache`` function, or a function returned by
|
|
2487
|
+
``lru_cache``. The object may be any of the following:
|
|
2488
|
+
|
|
2489
|
+
- ``lru_cache`` itself, in which case it is just returned;
|
|
2490
|
+
- ``None`` or 0, indicating that no caching should be used (``None`` is
|
|
2491
|
+
returned in these cases);
|
|
2492
|
+
- ``inf``, indicating that an infinite cache should be returned; or
|
|
2493
|
+
- a positive integer indicating the number of most recently used items
|
|
2494
|
+
to keep in the cache.
|
|
2495
|
+
|
|
2496
|
+
See Also
|
|
2497
|
+
--------
|
|
2498
|
+
functools.lru_cache
|
|
2499
|
+
"""
|
|
2500
|
+
from ._numeric import (is_number, is_integer)
|
|
2501
|
+
if obj is lru_cache:
|
|
2502
|
+
return obj
|
|
2503
|
+
elif obj is None:
|
|
2504
|
+
return None
|
|
2505
|
+
elif is_number(obj):
|
|
2506
|
+
if obj == 0:
|
|
2507
|
+
return None
|
|
2508
|
+
elif obj == np.inf:
|
|
2509
|
+
return lru_cache(maxsize=None)
|
|
2510
|
+
elif not is_integer(obj):
|
|
2511
|
+
raise TypeError("to_lrucache size must be an int")
|
|
2512
|
+
elif obj < 1:
|
|
2513
|
+
raise ValueError("to_lrucache size must be > 0")
|
|
2514
|
+
else:
|
|
2515
|
+
return lru_cache(maxsize=int(obj))
|
|
2516
|
+
else:
|
|
2517
|
+
raise TypeError(f"bad type for to_lrucache: {type(obj)}")
|
|
2518
|
+
|
|
2519
|
+
|
|
2520
|
+
# Other #######################################################################
|
|
2521
|
+
|
|
2522
|
+
def identfn(x):
|
|
2523
|
+
"The identify function; ``identfn(x)`` returns `x`."
|
|
2524
|
+
return x
|