In patching the __rshift__
operator of primitive python types with a callable, the patching utilises a wrapper:
def _patch_rshift(py_class, func):
assert isinstance(func, FunctionType)
py_type_name = 'tp_as_number'
py_type_method = 'nb_rshift'
py_obj = PyTypeObject.from_address(id(py_class))
type_ptr = getattr(py_obj, py_type_name)
if not type_ptr:
tp_as_obj = PyNumberMethods()
FUNC_INDIRECTION_REFS[(py_class, '__rshift__')] = tp_as_obj
tp_as_new_ptr = ctypes.cast(ctypes.addressof(tp_as_obj),
ctypes.POINTER(PyNumberMethods))
setattr(py_obj, py_type_name, tp_as_new_ptr)
type_head = type_ptr[0]
c_func_t = binary_func_p
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except BaseException as be:
# Need to raise a python style error here:
wrapper.exc_info = sys.exc_info()
return False
c_func = c_func_t(wrapper)
C_FUNC_CALLBACK_REFS[(py_class, '__rshift__')] = c_func
setattr(type_head, py_type_method, c_func)
The challenge is now to, once an Exception is caught inside wrapper
to raise an exception here just as any normal python exception.
Raising like:
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except BaseException as be:
raise
or not catching at all:
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
Yields:
Windows fatal exception: access violation
Process finished with exit code -1073741819 (0xC0000005)
The desired behavior is to simply re-raise the caught exception, but with a python-style output, if possible.
Related answers rely on the platform being Windows-centered to achieve the desired outcome, which is not suitable, do not preserve the original exception, or do not achieve the desired python exception-style behaviour:
Get error message from ctypes windll
UPDATE:
After some more digging, it would appear that raising anywhere in this method triggers a segfault. None the wiser on how to solve it.
Not a solution, but a workaround:
Manually collecting the error information (Using traceback
and inspect
), printing to sys.stderr
, and then returning a pointer to a CTypes error singleton (As done in forbiddenfruit) prevents the segfault from occuring.
The output would then be:
(For brevity, some of the methods used here are not included as they do not add any value to the solution, I opted for a pytest style error format.)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except BaseException as be:
# Traceback will not help use here, assemble a custom error message:
from custom_inspect_utils import get_calling_expression_recursively
import sys
calling_file, calling_expression = get_calling_expression_recursively()
indentation = " "*(len(calling_expression)-len(calling_expression.lstrip()))
print(f"\n{calling_file}\n>{calling_expression}E{indentation}{type(be).__name__}: {be}", file=sys.stderr)
print(f"\n\tAs this package patches primitive types, the python RTE also raised:", file=sys.stderr)
return ErrorSingleton
e.g. After patching list
to implement __rshift__
, the expression [1,2,3] >> wrong_target
where one expects a CustomException
will now first output
source_file.py:330 (method_name)
> [1,2,3] >> wrong_target
E CustomException: Message.
followed by a TypeError
:
TypeError: unsupported operand type(s) for >>: 'list' and 'WrongType'
User contributions licensed under CC BY-SA 3.0