oprattr 0.1.0__tar.gz
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.
Potentially problematic release.
This version of oprattr might be problematic. Click here for more details.
- oprattr-0.1.0/.gitignore +13 -0
- oprattr-0.1.0/.python-version +1 -0
- oprattr-0.1.0/PKG-INFO +11 -0
- oprattr-0.1.0/README.md +1 -0
- oprattr-0.1.0/pyproject.toml +23 -0
- oprattr-0.1.0/src/oprattr/__init__.py +204 -0
- oprattr-0.1.0/src/oprattr/_operations.py +179 -0
- oprattr-0.1.0/src/oprattr/_types.py +144 -0
- oprattr-0.1.0/src/oprattr/mixins.py +415 -0
- oprattr-0.1.0/src/oprattr/operators.py +41 -0
- oprattr-0.1.0/src/oprattr/py.typed +0 -0
- oprattr-0.1.0/tests/print-exceptions.py +32 -0
- oprattr-0.1.0/tests/test_object.py +514 -0
- oprattr-0.1.0/uv.lock +416 -0
oprattr-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
oprattr-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oprattr
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author-email: Matthew Young <myoung.space.science@gmail.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: numpy>=2.2.1
|
|
8
|
+
Requires-Dist: scipy>=1.15.0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# oprattr: Self-Consistent Operations on Object Attributes
|
oprattr-0.1.0/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# oprattr: Self-Consistent Operations on Object Attributes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "oprattr"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Matthew Young", email = "myoung.space.science@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"numpy>=2.2.1",
|
|
12
|
+
"scipy>=1.15.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["hatchling"]
|
|
17
|
+
build-backend = "hatchling.build"
|
|
18
|
+
|
|
19
|
+
[dependency-groups]
|
|
20
|
+
dev = [
|
|
21
|
+
"ipython>=8.31.0",
|
|
22
|
+
"pytest>=8.3.4",
|
|
23
|
+
]
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import numbers
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
import numpy
|
|
6
|
+
|
|
7
|
+
from . import mixins
|
|
8
|
+
from . import operators
|
|
9
|
+
from . import _types
|
|
10
|
+
from ._operations import (
|
|
11
|
+
unary,
|
|
12
|
+
equality,
|
|
13
|
+
ordering,
|
|
14
|
+
additive,
|
|
15
|
+
multiplicative,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
T = typing.TypeVar('T')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Operand(_types.Object[T], mixins.Numpy):
|
|
23
|
+
"""A concrete implementation of a real-valued object."""
|
|
24
|
+
|
|
25
|
+
def __abs__(self):
|
|
26
|
+
"""Called for abs(self)."""
|
|
27
|
+
return unary(operators.abs, self)
|
|
28
|
+
|
|
29
|
+
def __pos__(self):
|
|
30
|
+
"""Called for +self."""
|
|
31
|
+
return unary(operators.pos, self)
|
|
32
|
+
|
|
33
|
+
def __neg__(self):
|
|
34
|
+
"""Called for -self."""
|
|
35
|
+
return unary(operators.neg, self)
|
|
36
|
+
|
|
37
|
+
def __eq__(self, other):
|
|
38
|
+
"""Called for self == other."""
|
|
39
|
+
return equality(operators.eq, self, other)
|
|
40
|
+
|
|
41
|
+
def __ne__(self, other):
|
|
42
|
+
"""Called for self != other."""
|
|
43
|
+
return equality(operators.ne, self, other)
|
|
44
|
+
|
|
45
|
+
def __lt__(self, other):
|
|
46
|
+
"""Called for self < other."""
|
|
47
|
+
return ordering(operators.lt, self, other)
|
|
48
|
+
|
|
49
|
+
def __le__(self, other):
|
|
50
|
+
"""Called for self <= other."""
|
|
51
|
+
return ordering(operators.le, self, other)
|
|
52
|
+
|
|
53
|
+
def __gt__(self, other):
|
|
54
|
+
"""Called for self > other."""
|
|
55
|
+
return ordering(operators.gt, self, other)
|
|
56
|
+
|
|
57
|
+
def __ge__(self, other):
|
|
58
|
+
"""Called for self >= other."""
|
|
59
|
+
return ordering(operators.ge, self, other)
|
|
60
|
+
|
|
61
|
+
def __add__(self, other):
|
|
62
|
+
"""Called for self + other."""
|
|
63
|
+
return additive(operators.add, self, other)
|
|
64
|
+
|
|
65
|
+
def __radd__(self, other):
|
|
66
|
+
"""Called for other + self."""
|
|
67
|
+
return additive(operators.add, other, self)
|
|
68
|
+
|
|
69
|
+
def __sub__(self, other):
|
|
70
|
+
"""Called for self - other."""
|
|
71
|
+
return additive(operators.sub, self, other)
|
|
72
|
+
|
|
73
|
+
def __rsub__(self, other):
|
|
74
|
+
"""Called for other - self."""
|
|
75
|
+
return additive(operators.sub, other, self)
|
|
76
|
+
|
|
77
|
+
def __mul__(self, other):
|
|
78
|
+
"""Called for self * other."""
|
|
79
|
+
return multiplicative(operators.mul, self, other)
|
|
80
|
+
|
|
81
|
+
def __rmul__(self, other):
|
|
82
|
+
"""Called for other * self."""
|
|
83
|
+
return multiplicative(operators.mul, other, self)
|
|
84
|
+
|
|
85
|
+
def __truediv__(self, other):
|
|
86
|
+
"""Called for self / other."""
|
|
87
|
+
return multiplicative(operators.truediv, self, other)
|
|
88
|
+
|
|
89
|
+
def __rtruediv__(self, other):
|
|
90
|
+
"""Called for other / self."""
|
|
91
|
+
return multiplicative(operators.truediv, other, self)
|
|
92
|
+
|
|
93
|
+
def __floordiv__(self, other):
|
|
94
|
+
"""Called for self // other."""
|
|
95
|
+
return multiplicative(operators.floordiv, self, other)
|
|
96
|
+
|
|
97
|
+
def __rfloordiv__(self, other):
|
|
98
|
+
"""Called for other // self."""
|
|
99
|
+
return multiplicative(operators.floordiv, other, self)
|
|
100
|
+
|
|
101
|
+
def __mod__(self, other):
|
|
102
|
+
"""Called for self % other."""
|
|
103
|
+
return multiplicative(operators.mod, self, other)
|
|
104
|
+
|
|
105
|
+
def __rmod__(self, other):
|
|
106
|
+
"""Called for other % self."""
|
|
107
|
+
return multiplicative(operators.mod, other, self)
|
|
108
|
+
|
|
109
|
+
def __pow__(self, other):
|
|
110
|
+
"""Called for self ** other."""
|
|
111
|
+
if isinstance(other, numbers.Real):
|
|
112
|
+
return multiplicative(operators.pow, self, other)
|
|
113
|
+
return NotImplemented
|
|
114
|
+
|
|
115
|
+
def __rpow__(self, other):
|
|
116
|
+
"""Called for other ** self."""
|
|
117
|
+
return super().__rpow__(other)
|
|
118
|
+
|
|
119
|
+
def __array__(self, *args, **kwargs):
|
|
120
|
+
"""Called for numpy.array(self)."""
|
|
121
|
+
return numpy.array(self._data, *args, **kwargs)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@Operand.implementation(numpy.array_equal)
|
|
125
|
+
def array_equal(
|
|
126
|
+
x: numpy.typing.ArrayLike,
|
|
127
|
+
y: numpy.typing.ArrayLike,
|
|
128
|
+
**kwargs
|
|
129
|
+
) -> bool:
|
|
130
|
+
"""Called for numpy.array_equal(x, y)"""
|
|
131
|
+
return numpy.array_equal(numpy.array(x), numpy.array(y), **kwargs)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@Operand.implementation(numpy.gradient)
|
|
135
|
+
def gradient(x: Operand[T], *args, **kwargs):
|
|
136
|
+
"""Called for numpy.gradient(x)."""
|
|
137
|
+
data = numpy.gradient(x._data, *args, **kwargs)
|
|
138
|
+
meta = {}
|
|
139
|
+
for key, value in x._meta.items():
|
|
140
|
+
try:
|
|
141
|
+
v = numpy.gradient(value, **kwargs)
|
|
142
|
+
except TypeError as exc:
|
|
143
|
+
raise TypeError(
|
|
144
|
+
"Cannot compute numpy.gradient(x)"
|
|
145
|
+
f" because metadata attribute {key!r}"
|
|
146
|
+
" does not support this operation"
|
|
147
|
+
) from exc
|
|
148
|
+
else:
|
|
149
|
+
meta[key] = v
|
|
150
|
+
if isinstance(data, (list, tuple)):
|
|
151
|
+
r = [type(x)(array, **meta) for array in data]
|
|
152
|
+
if isinstance(data, tuple):
|
|
153
|
+
return tuple(r)
|
|
154
|
+
return r
|
|
155
|
+
return type(x)(data, **meta)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def wrapnumpy(f: typing.Callable):
|
|
159
|
+
"""Implement a numpy function for objects with metadata."""
|
|
160
|
+
@functools.wraps(f)
|
|
161
|
+
def method(x: Operand[T], **kwargs):
|
|
162
|
+
"""Apply a numpy function to x."""
|
|
163
|
+
data = f(x._data, **kwargs)
|
|
164
|
+
meta = {}
|
|
165
|
+
for key, value in x._meta.items():
|
|
166
|
+
try:
|
|
167
|
+
v = f(value, **kwargs)
|
|
168
|
+
except TypeError as exc:
|
|
169
|
+
raise TypeError(
|
|
170
|
+
f"Cannot compute numpy.{f.__qualname__}(x)"
|
|
171
|
+
f" because metadata attribute {key!r}"
|
|
172
|
+
" does not support this operation"
|
|
173
|
+
) from exc
|
|
174
|
+
else:
|
|
175
|
+
meta[key] = v
|
|
176
|
+
return type(x)(data, **meta)
|
|
177
|
+
return method
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
_OPERAND_UFUNCS = (
|
|
181
|
+
numpy.sqrt,
|
|
182
|
+
numpy.sin,
|
|
183
|
+
numpy.cos,
|
|
184
|
+
numpy.tan,
|
|
185
|
+
numpy.log,
|
|
186
|
+
numpy.log2,
|
|
187
|
+
numpy.log10,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
_OPERAND_FUNCTIONS = (
|
|
192
|
+
numpy.squeeze,
|
|
193
|
+
numpy.mean,
|
|
194
|
+
numpy.sum,
|
|
195
|
+
numpy.cumsum,
|
|
196
|
+
numpy.transpose,
|
|
197
|
+
numpy.trapezoid,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
for f in _OPERAND_UFUNCS + _OPERAND_FUNCTIONS:
|
|
202
|
+
Operand.implement(f, wrapnumpy(f))
|
|
203
|
+
|
|
204
|
+
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from . import operators
|
|
4
|
+
from ._types import Object
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MetadataError(TypeError):
|
|
8
|
+
"""A metadata-related TypeError occurred."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
f: operators.Operator,
|
|
13
|
+
*args,
|
|
14
|
+
error: typing.Optional[str]=None,
|
|
15
|
+
key: typing.Optional[str]=None,
|
|
16
|
+
) -> None:
|
|
17
|
+
super().__init__(*args)
|
|
18
|
+
self._f = f
|
|
19
|
+
self._error = error
|
|
20
|
+
self._key = key
|
|
21
|
+
|
|
22
|
+
def __str__(self):
|
|
23
|
+
"""Called when handling the exception."""
|
|
24
|
+
types = [type(arg) for arg in self.args]
|
|
25
|
+
return _build_error_message(
|
|
26
|
+
self._f,
|
|
27
|
+
*types,
|
|
28
|
+
error=self._error,
|
|
29
|
+
key=self._key,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _build_error_message(
|
|
34
|
+
f: operators.Operator,
|
|
35
|
+
*types: type,
|
|
36
|
+
error: typing.Optional[str]=None,
|
|
37
|
+
key: typing.Optional[str]=None,
|
|
38
|
+
) -> str:
|
|
39
|
+
"""Helper for `_raise_metadata_exception`.
|
|
40
|
+
|
|
41
|
+
This function should avoid raising an exception if at all possible, and
|
|
42
|
+
instead return the default error message, since it is already being called
|
|
43
|
+
as the result of an error elsewhere.
|
|
44
|
+
"""
|
|
45
|
+
errmsg = f"Cannot compute {f}"
|
|
46
|
+
errstr = error.lower() if isinstance(error, str) else ''
|
|
47
|
+
if errstr == 'unequal':
|
|
48
|
+
return f"{errmsg} between objects with unequal metadata"
|
|
49
|
+
if errstr in {'non-empty', 'nonempty'}:
|
|
50
|
+
if len(types) == 2:
|
|
51
|
+
a, b = types
|
|
52
|
+
endstr = "because {} has metadata"
|
|
53
|
+
if issubclass(a, Object):
|
|
54
|
+
return f"{errmsg} between {a} and {b} {endstr.format(str(a))}"
|
|
55
|
+
if issubclass(b, Object):
|
|
56
|
+
return f"{errmsg} between {a} and {b} {endstr.format(str(b))}"
|
|
57
|
+
if errstr == 'type':
|
|
58
|
+
if key is None:
|
|
59
|
+
keystr = "a metadata attribute"
|
|
60
|
+
else:
|
|
61
|
+
keystr = f"metadata attribute {key!r}"
|
|
62
|
+
midstr = f"because {keystr}"
|
|
63
|
+
endstr = "does not support this operation"
|
|
64
|
+
if len(types) == 1:
|
|
65
|
+
return f"{errmsg} of {types[0]} {midstr} {endstr}"
|
|
66
|
+
if len(types) == 2:
|
|
67
|
+
a, b = types
|
|
68
|
+
return f"{errmsg} between {a} and {b} {midstr} {endstr}"
|
|
69
|
+
return errmsg
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def unary(f: operators.Operator, a):
|
|
73
|
+
"""Compute the unary operation f(a)."""
|
|
74
|
+
if isinstance(a, Object):
|
|
75
|
+
meta = {}
|
|
76
|
+
for key, value in a._meta.items():
|
|
77
|
+
try:
|
|
78
|
+
v = f(value)
|
|
79
|
+
except TypeError as exc:
|
|
80
|
+
raise MetadataError(f, a, error='type', key=key) from exc
|
|
81
|
+
else:
|
|
82
|
+
meta[key] = v
|
|
83
|
+
return type(a)(f(a._data), **meta)
|
|
84
|
+
return f(a)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def equality(f: operators.Operator, a, b):
|
|
88
|
+
"""Compute the equality operation f(a, b)."""
|
|
89
|
+
if isinstance(a, Object) and isinstance(b, Object):
|
|
90
|
+
if a._meta != b._meta:
|
|
91
|
+
return f is operators.ne
|
|
92
|
+
return f(a._data, b._data)
|
|
93
|
+
if isinstance(a, Object):
|
|
94
|
+
if not a._meta:
|
|
95
|
+
return f(a._data, b)
|
|
96
|
+
return f is operators.ne
|
|
97
|
+
if isinstance(b, Object):
|
|
98
|
+
if not b._meta:
|
|
99
|
+
return f(a, b._data)
|
|
100
|
+
return f is operators.ne
|
|
101
|
+
return f(a, b)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def ordering(f: operators.Operator, a, b):
|
|
105
|
+
"""Compute the ordering operation f(a, b)."""
|
|
106
|
+
if isinstance(a, Object) and isinstance(b, Object):
|
|
107
|
+
if a._meta == b._meta:
|
|
108
|
+
return f(a._data, b._data)
|
|
109
|
+
raise MetadataError(f, a, b, error='unequal') from None
|
|
110
|
+
if isinstance(a, Object):
|
|
111
|
+
if not a._meta:
|
|
112
|
+
return f(a._data, b)
|
|
113
|
+
raise MetadataError(f, a, b, error='non-empty') from None
|
|
114
|
+
if isinstance(b, Object):
|
|
115
|
+
if not b._meta:
|
|
116
|
+
return f(a, b._data)
|
|
117
|
+
raise MetadataError(f, a, b, error='non-empty') from None
|
|
118
|
+
return f(a, b)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def additive(f: operators.Operator, a, b):
|
|
122
|
+
"""Compute the additive operation f(a, b)."""
|
|
123
|
+
if isinstance(a, Object) and isinstance(b, Object):
|
|
124
|
+
if a._meta == b._meta:
|
|
125
|
+
return type(a)(f(a._data, b._data), **a._meta)
|
|
126
|
+
raise MetadataError(f, a, b, error='unequal') from None
|
|
127
|
+
if isinstance(a, Object):
|
|
128
|
+
if not a._meta:
|
|
129
|
+
return type(a)(f(a._data, b))
|
|
130
|
+
raise MetadataError(f, a, b, error='non-empty') from None
|
|
131
|
+
if isinstance(b, Object):
|
|
132
|
+
if not b._meta:
|
|
133
|
+
return type(b)(f(a, b._data))
|
|
134
|
+
raise MetadataError(f, a, b, error='non-empty') from None
|
|
135
|
+
return f(a, b)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def multiplicative(f: operators.Operator, a, b):
|
|
139
|
+
"""Compute the multiplicative operation f(a, b)."""
|
|
140
|
+
if isinstance(a, Object) and isinstance(b, Object):
|
|
141
|
+
keys = set(a._meta) & set(b._meta)
|
|
142
|
+
meta = {}
|
|
143
|
+
for key in keys:
|
|
144
|
+
try:
|
|
145
|
+
v = f(a._meta[key], b._meta[key])
|
|
146
|
+
except TypeError as exc:
|
|
147
|
+
raise MetadataError(f, a, b, error='type', key=key) from exc
|
|
148
|
+
else:
|
|
149
|
+
meta[key] = v
|
|
150
|
+
for key, value in a._meta.items():
|
|
151
|
+
if key not in keys:
|
|
152
|
+
meta[key] = value
|
|
153
|
+
for key, value in b._meta.items():
|
|
154
|
+
if key not in keys:
|
|
155
|
+
meta[key] = value
|
|
156
|
+
return type(a)(f(a._data, b._data), **meta)
|
|
157
|
+
if isinstance(a, Object):
|
|
158
|
+
meta = {}
|
|
159
|
+
for key, value in a._meta.items():
|
|
160
|
+
try:
|
|
161
|
+
v = f(value, b)
|
|
162
|
+
except TypeError as exc:
|
|
163
|
+
raise MetadataError(f, a, b, error='type', key=key) from exc
|
|
164
|
+
else:
|
|
165
|
+
meta[key] = v
|
|
166
|
+
return type(a)(f(a._data, b), **meta)
|
|
167
|
+
if isinstance(b, Object):
|
|
168
|
+
meta = {}
|
|
169
|
+
for key, value in b._meta.items():
|
|
170
|
+
try:
|
|
171
|
+
v = f(a, value)
|
|
172
|
+
except TypeError as exc:
|
|
173
|
+
raise MetadataError(f, a, b, error='type', key=key) from exc
|
|
174
|
+
else:
|
|
175
|
+
meta[key] = v
|
|
176
|
+
return type(b)(f(a, b._data), **meta)
|
|
177
|
+
return f(a, b)
|
|
178
|
+
|
|
179
|
+
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import numbers
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
import numpy.typing
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@typing.runtime_checkable
|
|
9
|
+
class Real(typing.Protocol):
|
|
10
|
+
"""Abstract protocol for real-valued objects."""
|
|
11
|
+
|
|
12
|
+
@abc.abstractmethod
|
|
13
|
+
def __abs__(self):
|
|
14
|
+
return NotImplemented
|
|
15
|
+
|
|
16
|
+
@abc.abstractmethod
|
|
17
|
+
def __pos__(self):
|
|
18
|
+
return NotImplemented
|
|
19
|
+
|
|
20
|
+
@abc.abstractmethod
|
|
21
|
+
def __neg__(self):
|
|
22
|
+
return NotImplemented
|
|
23
|
+
|
|
24
|
+
@abc.abstractmethod
|
|
25
|
+
def __eq__(self, other):
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
@abc.abstractmethod
|
|
29
|
+
def __ne__(self, other):
|
|
30
|
+
return True
|
|
31
|
+
|
|
32
|
+
@abc.abstractmethod
|
|
33
|
+
def __le__(self, other):
|
|
34
|
+
return NotImplemented
|
|
35
|
+
|
|
36
|
+
@abc.abstractmethod
|
|
37
|
+
def __lt__(self, other):
|
|
38
|
+
return NotImplemented
|
|
39
|
+
|
|
40
|
+
@abc.abstractmethod
|
|
41
|
+
def __ge__(self, other):
|
|
42
|
+
return NotImplemented
|
|
43
|
+
|
|
44
|
+
@abc.abstractmethod
|
|
45
|
+
def __gt__(self, other):
|
|
46
|
+
return NotImplemented
|
|
47
|
+
|
|
48
|
+
@abc.abstractmethod
|
|
49
|
+
def __add__(self, other):
|
|
50
|
+
return NotImplemented
|
|
51
|
+
|
|
52
|
+
@abc.abstractmethod
|
|
53
|
+
def __radd__(self, other):
|
|
54
|
+
return NotImplemented
|
|
55
|
+
|
|
56
|
+
@abc.abstractmethod
|
|
57
|
+
def __sub__(self, other):
|
|
58
|
+
return NotImplemented
|
|
59
|
+
|
|
60
|
+
@abc.abstractmethod
|
|
61
|
+
def __rsub__(self, other):
|
|
62
|
+
return NotImplemented
|
|
63
|
+
|
|
64
|
+
@abc.abstractmethod
|
|
65
|
+
def __mul__(self, other):
|
|
66
|
+
return NotImplemented
|
|
67
|
+
|
|
68
|
+
@abc.abstractmethod
|
|
69
|
+
def __rmul__(self, other):
|
|
70
|
+
return NotImplemented
|
|
71
|
+
|
|
72
|
+
@abc.abstractmethod
|
|
73
|
+
def __truediv__(self, other):
|
|
74
|
+
return NotImplemented
|
|
75
|
+
|
|
76
|
+
@abc.abstractmethod
|
|
77
|
+
def __rtruediv__(self, other):
|
|
78
|
+
return NotImplemented
|
|
79
|
+
|
|
80
|
+
@abc.abstractmethod
|
|
81
|
+
def __floordiv__(self, other):
|
|
82
|
+
return NotImplemented
|
|
83
|
+
|
|
84
|
+
@abc.abstractmethod
|
|
85
|
+
def __rfloordiv__(self, other):
|
|
86
|
+
return NotImplemented
|
|
87
|
+
|
|
88
|
+
@abc.abstractmethod
|
|
89
|
+
def __mod__(self, other):
|
|
90
|
+
return NotImplemented
|
|
91
|
+
|
|
92
|
+
@abc.abstractmethod
|
|
93
|
+
def __rmod__(self, other):
|
|
94
|
+
return NotImplemented
|
|
95
|
+
|
|
96
|
+
@abc.abstractmethod
|
|
97
|
+
def __pow__(self, other):
|
|
98
|
+
return NotImplemented
|
|
99
|
+
|
|
100
|
+
@abc.abstractmethod
|
|
101
|
+
def __rpow__(self, other):
|
|
102
|
+
return NotImplemented
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
DataType = typing.TypeVar(
|
|
106
|
+
'DataType',
|
|
107
|
+
int,
|
|
108
|
+
float,
|
|
109
|
+
numbers.Number,
|
|
110
|
+
numpy.number,
|
|
111
|
+
numpy.typing.ArrayLike,
|
|
112
|
+
numpy.typing.NDArray,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Object(Real, typing.Generic[DataType]):
|
|
117
|
+
"""A real-valued object with metadata attributes."""
|
|
118
|
+
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
__data: DataType,
|
|
122
|
+
**metadata,
|
|
123
|
+
) -> None:
|
|
124
|
+
if not isinstance(__data, Real):
|
|
125
|
+
raise TypeError("Data input to Object must be real-valued")
|
|
126
|
+
self._data = __data
|
|
127
|
+
self._meta = metadata
|
|
128
|
+
|
|
129
|
+
def __repr__(self):
|
|
130
|
+
"""Called for repr(self)."""
|
|
131
|
+
try:
|
|
132
|
+
datastr = numpy.array2string(
|
|
133
|
+
self._data,
|
|
134
|
+
separator=", ",
|
|
135
|
+
threshold=6,
|
|
136
|
+
edgeitems=2,
|
|
137
|
+
prefix=f"{self.__class__.__qualname__}(",
|
|
138
|
+
suffix=")"
|
|
139
|
+
)
|
|
140
|
+
except Exception:
|
|
141
|
+
datastr = str(self._data)
|
|
142
|
+
metastr = "metadata={" + ", ".join(f"{k!r}" for k in self._meta) + "}"
|
|
143
|
+
return f"{self.__class__.__qualname__}({datastr}, {metastr})"
|
|
144
|
+
|