-
-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-101410: support custom messages for domain errors in the math module #124299
base: main
Are you sure you want to change the base?
Conversation
…h module This adds basic support to override default messages for domain errors in the math_1() helper. The sqrt(), atanh(), log2(), log10() and log() functions were modified as examples. New macro supports gradual changing of error messages in other 1-arg functions. Co-authored-by: Sergey B Kirpichev <skirpichev@gmail.com>
#101492 somehow stuck, here I would like to continue this work. @CharlieZhao95, are you ok with this? The given pr introduces a new macro and change only few functions, that covers all cases in math_1(). I hope, this also address reviews in the mentioned pr (tests added, error messages print converted argument, etc). I kept only short error messages. Not sure if it worth printing the function name, as it usually shown in tracebacks. >>> import math
>>> for i in range(5, -10, -2):
... print(math.log(i))
...
1.6094379124341003
1.0986122886681098
0.0
Traceback (most recent call last):
File "<python-input-1>", line 2, in <module>
print(math.log(i))
~~~~~~~~^^^
ValueError: expected a positive input, got -1 Not sure also if we must suggest cmath in error messages. But if we decide to do so, probably it does make sense for almost every domain error in the math module and we can do this with a template. diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c
index 665b02e702..c4516fabda 100644
--- a/Modules/mathmodule.c
+++ b/Modules/mathmodule.c
@@ -1046,7 +1046,11 @@ math_2(PyObject *const *args, Py_ssize_t nargs,
#define FUNC1D(funcname, func, can_overflow, docstring, err_msg) \
static PyObject * math_##funcname(PyObject *self, PyObject *args) { \
- return math_1(args, func, can_overflow, err_msg); \
+ return math_1(args, func, can_overflow, \
+ (err_msg \
+ "\nSee cmath." \
+ #funcname \
+ "(), that supports complex numbers")); \
}\
PyDoc_STRVAR(math_##funcname##_doc, docstring);
CC @rhettinger as author of the issue, I would appreciate feedback |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. Just some nitpicks.
return math_1(args, func, can_overflow, NULL); \ | ||
}\ | ||
PyDoc_STRVAR(math_##funcname##_doc, docstring); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you align line continuations here please?
Modules/mathmodule.c
Outdated
@@ -2229,11 +2244,11 @@ math_log(PyObject *module, PyObject * const *args, Py_ssize_t nargs) | |||
if (!_PyArg_CheckPositional("log", nargs, 1, 2)) | |||
return NULL; | |||
|
|||
num = loghelper(args[0], m_log); | |||
num = loghelper(args[0], m_log, "expected a positive input, got %R"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use "strictly positive" instead of "positive" input here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What we will gain with a more longer variant? I don't see difference.
return math_1(args, func, can_overflow, NULL); \ | ||
}\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return math_1(args, func, can_overflow, NULL); \ | |
}\ | |
return math_1(args, func, can_overflow, NULL); \ | |
} \ |
@picnixz, you meant this and ditto for the new macro?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I doubt it's a good idea, see other macros nearby.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More informative error messages is good, but I afraid that the cost may be too high. If this is really an error due to invalid input data, then some cost is more tolerable. But if the exception is used in the EAFP style of solving computational error (for example, the argument of sqrt()
in sqrt(b*b-4*a*c)
can be a small negative number due to limited precision computation and can be replaced by 0), additional cost harms the average performance.
Modules/mathmodule.c
Outdated
@@ -2189,8 +2205,7 @@ loghelper(PyObject* arg, double (*func)(double)) | |||
|
|||
/* Negative or zero inputs give a ValueError. */ | |||
if (!_PyLong_IsPositive((PyLongObject *)arg)) { | |||
PyErr_SetString(PyExc_ValueError, | |||
"math domain error"); | |||
PyErr_Format(PyExc_ValueError, err_msg, arg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Danger! The argument can be arbitrary negative integer. Creating it decimal representation has superlinear complexity. Even if it could have linear complexity, it can be too long, and the result can be too long. Oh, and normally you will get a ValueError due to the int_max_str_digits limit. In any case the cost of formatting the error message can be too high.
cc @tim-one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll refactor loghelper() case. Probably we can omit argument value in errors from log(). The only problem is that it (unfortunately) has 2-arg form too.
But it's only printing. Currently, math_1() uses PyFloat_FromDouble() for converted by PyFloat_AsDouble() value; well, maybe I should just use PyOS_double_to_string(). Anyway it doesn't seems to be too expensive in either way. On another hand, with this - we don't print the original value of the argument (something like Fraction(1/2**1000). Maybe it's better to omit the value at all? |
If include the value, it should be a converted value, not the original value, because conversion can change the value (for example turn a small positive value into 0). The repr of the original value has also larger chance to produce long and expensive result. |
This is how it works. loghelper() is an exception. I see only one solution for loghelper(): use two messages. One for PyLong-branch (this message will come from loghelper argument to distinguish arguments of 2-arg form) and a common template for the math_1() fallback. |
* use PyOS_double_to_string * drop loghelper arg * don't print argument value in PyLong-branch of loghelper()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. You addressed all my concerns. Extreme slow cases were removed, and formatting error messages in common case was optimized.
I would approve this PR, but I want first to know opinion of the author of the request, @rhettinger.
And I think that we can add custom error messages for more functions: acos(), asin(), gamma(), lgamma(). Maybe pow(), where rules are complex.
I would prefer to make first required changes in helpers. This pr changes math_1(). Following pr can add custom error messages for all math_1-based functions, it will be mostly mechanical process. Then we can address e.g. atan2 and pow. |
This adds basic support to override default messages for domain errors in the math_1() helper. The sqrt(), atanh(), log2(), log10() and log() functions were modified as examples. New macro supports gradual changing of error messages in other 1-arg functions.