Encoding bytestrings using NumpyJSONEncoder triggers RecursionError
jenshnielsen opened this issue · 2 comments
jenshnielsen commented
The following code
import json
from qcodes.utils.helpers import NumpyJSONEncoder
a = { "voltageDC": {
"__class__": "qcodes_fluke8842aDriver_2.voltageDC",
"full_name": "fDMMInst1_voltageDC",
"value": 5.7e-05,
"ts": "2022-09-12 10:18:34",
"raw_value": b"+000.057E-3\r\n",
"name": "voltageDC",
"unit": "V",
"inter_delay": 0,
"post_delay": 0,
"instrument": "qcodes_fluke8842aDriver_2.Fluke8842aDriver",
"instrument_name": "fDMMInst1",
"label": "voltageDC",
}}
json.dumps(a, cls=NumpyJSONEncoder)
trigger
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
Cell In [19], line 18
2 from qcodes.utils.helpers import NumpyJSONEncoder
4 a = { "voltageDC": {
5 "__class__": "qcodes_fluke8842aDriver_2.voltageDC",
6 "full_name": "fDMMInst1_voltageDC",
(...)
16 "label": "voltageDC",
17 }}
---> 18 json.dumps(a, cls=NumpyJSONEncoder)
File ~\Miniconda3\envs\qcodespip38\lib\json\__init__.py:234, in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
232 if cls is None:
233 cls = JSONEncoder
--> 234 return cls(
235 skipkeys=skipkeys, ensure_ascii=ensure_ascii,
236 check_circular=check_circular, allow_nan=allow_nan, indent=indent,
237 separators=separators, default=default, sort_keys=sort_keys,
238 **kw).encode(obj)
File ~\Miniconda3\envs\qcodespip38\lib\json\encoder.py:199, in JSONEncoder.encode(self, o)
195 return encode_basestring(o)
196 # This doesn't pass the iterator directly to ''.join() because the
197 # exceptions aren't as detailed. The list call should be roughly
198 # equivalent to the PySequence_Fast that ''.join() would do.
--> 199 chunks = self.iterencode(o, _one_shot=True)
200 if not isinstance(chunks, (list, tuple)):
201 chunks = list(chunks)
File ~\Miniconda3\envs\qcodespip38\lib\json\encoder.py:257, in JSONEncoder.iterencode(self, o, _one_shot)
252 else:
253 _iterencode = _make_iterencode(
254 markers, self.default, _encoder, self.indent, floatstr,
255 self.key_separator, self.item_separator, self.sort_keys,
256 self.skipkeys, _one_shot)
--> 257 return _iterencode(o, 0)
File ~\source\repos\Qcodes\qcodes\utils\json_utils.py:54, in NumpyJSONEncoder.default(self, obj)
51 elif isinstance(obj, np.ndarray):
52 # for numpy arrays
53 return obj.tolist()
---> 54 elif isinstance(obj, numbers.Complex) and not isinstance(obj, numbers.Real):
55 return {
56 "__dtype__": "complex",
57 "re": float(obj.real),
58 "im": float(obj.imag),
59 }
60 elif isinstance(obj, uncertainties.UFloat):
File ~\Miniconda3\envs\qcodespip38\lib\abc.py:98, in ABCMeta.__instancecheck__(cls, instance)
96 def __instancecheck__(cls, instance):
97 """Override for isinstance(instance, cls)."""
---> 98 return _abc_instancecheck(cls, instance)
RecursionError: maximum recursion depth exceeded in comparison
This should either fail with a more meaningful error, skip the problematic element or correctly handle the bytestring.
jenshnielsen commented
This is caused by this path in the json encoder
return {
"__class__": type(obj).__name__,
"__args__": getattr(obj, "__getnewargs__")(),
}
which returns
{'__class__': 'bytes', '__args__': (b'+000.057E-3\r\n',)}
causing the recursion
jenshnielsen commented
A simple work around would be not to try to pickle bytes.
if hasattr(obj, "__getnewargs__") and not isinstance(obj, (bytes, bytearray)):