I’m adding a warning to a public SciPy function, scipy.sparse.diags_array. This function is called by another public function, scipy.sparse.diags. The question is what stacklevel argument should be given in warnings.warn() in scipy.sparse.diags_array? For a caller of diags_array, the stack level should be 2, but for a caller of diags, it should be 3. Has anyone dealt with this and come up with a clever solution?
One suggestion I’ve seen is to have the code in diags catch the warning and reissue it, but that involves dealing with warnings filters that are not thread-safe (at least not until Python 3.14).
One possibility is to inspect the frame stack in diags_array to see if the caller is diags (from the file _construct.py). This works, but it adds a bit of overhead when a warning is generated:
[... this is in the function diags_array() ...]
if (dtype != future_dtype):
# Generate a warning about the future change in behavior.
import inspect
filename, _, funcname, _, _ = inspect.getframeinfo(inspect.currentframe().f_back)
stacklevel = 2
if filename.endswith('_construct.py') and funcname == 'diags':
stacklevel = 3
warnings.warn(
f"Input has data type {future_dtype}, but the result has been cast "
f"to {dtype}. In the future, the output data type will match "
"the input. To avoid this warning, set the `dtype` parameter to "
"`None` to have the output dtype match the input, or set it to the "
"desired output data type. "
"In SciPy version 1.19, the default behavior will change to "
"always determine the output data type from the input data types.",
FutureWarning,
stacklevel=stacklevel
)
[...]
My impression is that both this inspect usage and the code @j-bowhay linked to is way too complex, fragile and high-overhead for what it’s doing. My thoughts are:
Calling another public function in the same library that emits a warning isn’t all that different from one library calling a function in another library that may emit a warning. The stacklevel in the latter case isn’t going to be perfect for the end user, but it’s not a significant enough problem to worry about.
If you expect the warning to be commonly hit and you care to get it right, refactor the warning-generating code out and put it in a utility function, then call it directly from both public functions so they require the same stacklevel. This should always be possible (e.g., rename public diags_array to _diags_array and call the private implementation from both diags and diags_array).