Skip to content

Commit 3c4bfd1

Browse files
committed
py/objexcept: Support errno attribute on OSError exceptions.
This commit adds the errno attribute to exceptions, so code can retrieve errno codes from an OSError using exc.errno. The implementation here simply lets `errno` (and the existing `value`) attributes work on any exception instance (they both alias args[0]). This is for efficiency and to keep code size down. The pros and cons of this are: Pros: - more compatible with CPython, less difference to document and learn - OSError().errno will correctly return None, whereas the current way of doing it via OSError().args[0] will raise an IndexError - it reduces code size on most bare-metal ports (because they already have the errno qstr) - for Python code that uses exc.errno the generated bytecode is 2 bytes smaller and more efficient to execute (compared with exc.args[0]); so bytecode loaded to RAM saves 2 bytes RAM for each use of this attribute, and bytecode that is frozen saves 2 bytes flash/ROM for each use - it's easier/shorter to type, and saves 2 bytes of space in .py files that use it (for each use) Cons: - increases code size by 4-8 bytes on minimal ports that don't already have the `errno` qstr - all exceptions now have .errno and .value attributes (a cpydiff test is added to address this) See also adafruit#2407. Signed-off-by: Damien George <[email protected]>
1 parent 5669a60 commit 3c4bfd1

File tree

6 files changed

+39
-7
lines changed

6 files changed

+39
-7
lines changed

docs/library/builtins.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,6 @@ Exceptions
176176

177177
.. exception:: OSError
178178

179-
|see_cpython| `python:OSError`. MicroPython doesn't implement ``errno``
180-
attribute, instead use the standard way to access exception arguments:
181-
``exc.args[0]``.
182-
183179
.. exception:: RuntimeError
184180

185181
.. exception:: StopIteration

docs/library/uerrno.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ Constants
1616

1717
Error codes, based on ANSI C/POSIX standard. All error codes start with
1818
"E". As mentioned above, inventory of the codes depends on
19-
:term:`MicroPython port`. Errors are usually accessible as ``exc.args[0]``
19+
:term:`MicroPython port`. Errors are usually accessible as ``exc.errno``
2020
where ``exc`` is an instance of `OSError`. Usage example::
2121

2222
try:
2323
uos.mkdir("my_dir")
2424
except OSError as exc:
25-
if exc.args[0] == uerrno.EEXIST:
25+
if exc.errno == uerrno.EEXIST:
2626
print("Directory already exists")
2727

2828
.. data:: errorcode

py/objexcept.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,9 @@ void mp_obj_exception_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
261261
if (attr == MP_QSTR_args) {
262262
decompress_error_text_maybe(self);
263263
dest[0] = MP_OBJ_FROM_PTR(self->args);
264-
} else if (self->base.type == &mp_type_StopIteration && attr == MP_QSTR_value) {
264+
} else if (attr == MP_QSTR_value || attr == MP_QSTR_errno) {
265+
// These are aliases for args[0]: .value for StopIteration and .errno for OSError.
266+
// For efficiency let these attributes apply to all exception instances.
265267
dest[0] = mp_obj_exception_get_value(self_in);
266268
}
267269
}

tests/basics/exception1.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# test basic properties of exceptions
2+
13
print(repr(IndexError()))
24
print(str(IndexError()))
35

@@ -12,3 +14,6 @@
1214
print(s.value)
1315
s = StopIteration(1, 2, 3)
1416
print(s.value)
17+
18+
print(OSError().errno)
19+
print(OSError(1, "msg").errno)

tests/basics/subclass_native3.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
# test subclassing a native exception
2+
3+
14
class MyExc(Exception):
25
pass
36

7+
48
e = MyExc(100, "Some error")
59
print(e)
610
print(repr(e))
@@ -20,3 +24,19 @@ class MyExc(Exception):
2024
raise MyExc("Some error2")
2125
except:
2226
print("Caught user exception")
27+
28+
29+
class MyStopIteration(StopIteration):
30+
pass
31+
32+
33+
print(MyStopIteration().value)
34+
print(MyStopIteration(1).value)
35+
36+
37+
class MyOSError(OSError):
38+
pass
39+
40+
41+
print(MyOSError().errno)
42+
print(MyOSError(1, "msg").errno)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""
2+
categories: Types,Exception
3+
description: All exceptions have readable ``value`` and ``errno`` attributes, not just ``StopIteration`` and ``OSError``.
4+
cause: MicroPython is optimised to reduce code size.
5+
workaround: Only use ``value`` on ``StopIteration`` exceptions, and ``errno`` on ``OSError`` exceptions. Do not use or rely on these attributes on other exceptions.
6+
"""
7+
e = Exception(1)
8+
print(e.value)
9+
print(e.errno)

0 commit comments

Comments
 (0)