Introduction
Recent python versions have made some very nice improvements to the readability of exception messages. I think, that the current default warning formatting isn’t very good and could use some polish.
Compare the current warning formatting:
/usr/lib/python3.11/site-packages/the_package/the_module/the_file.py:6: SuperImportantWarning: The warning message is the most important part.
warnings.warn(
And exception formatting:
Traceback (most recent call last):
File "/usr/lib/python3.11/site-packages/the_package/the_module/__main__.py", line 6, in <module>
print(oops.x + something_else)
^^^^^^
AttributeError: 'NoneType' object has no attribute 'x'
P.S. This isn't a final proposal. I just wanted to get the conversation started. Any criticisms/comments/suggestions are welcome!
Proposal
I see 2 issues with the current warning formatting
-
The current format of
{file_path}:{line_no}: {warning_type}: {warning_message}
- puts the actually important information (
warning_type
andwarning_message
) at the end of the line, after thefile_path
andline_no
. - doesn’t follow the existing exception stack trace formatting (new users might not understand that the
8
infoo.py:8:
refers to the line number)
- puts the actually important information (
-
In the most common case, when the warning was raised by a plain
warnings.warn("message")
without specifying astacklevel
, the source line is either redundant (contains the same message string, that is already part of the warning) or completely useless (just the function call towarnings.warn
).
To fix these issues, I propose to
- Change the default formatting to more closely resemble the current exception format. Something along the lines of
{warning_type}: {warning_message} File "{file_path}", line {line_no}, in {source_location}
- Change the default value of
stacklevel
from1
toNone
and don’t print the source line, when thestacklevel
isn’t explicitly set.
With the proposed changes, warning messages will look something like this:
Before
/usr/lib/python3.11/site-packages/package/file.py:5: UserWarning: Source line information is redundant for most warnings.
warnings.warn("Source line information is redundant for most warnings.")
After
UserWarning: Source line information is redundant for most warnings.
File "/usr/lib/python3.11/site-packages/package/file.py", line 5, in foo
Before
/usr/lib/python3.11/site-packages/package/file.py:6: SuperImportantWarning: The warning message is the most important part.
warnings.warn(
After
SuperImportantWarning: The warning message is the most important part.
File "/usr/lib/python3.11/site-packages/package/file.py", line 6, in foo
Before
/usr/lib/python3.11/site-packages/package/file.py:10: DeprecationWarning: The source info is only useful when stacklevel is used.
bar(deprecated_baz().boo())
After
DeprecationWarning: The source info is only useful when stacklevel is used.
File "/usr/lib/python3.11/site-packages/package/file.py", line 10, in foo
bar(deprecated_baz().boo())
^^^^^^^^^^^^^^^^
Open questions
Do the proposed changes break backwards compatibility?
- Is the warning formatting part of the python “API”? I am assuming, that it’s not, since recent python versions have changed the exception formatting.
- Is the default value of
stacklevel
inwarnings.warn
part of the API? I couldn’t come up with any currently valid code, that would be broken by changing the default value ofstacklevel
toNone
.
What should be the new format layout for warnings?
- Should the
File ...
line appear before or after theWarningType: message
line? In the current proposal, the file is printed after the warning message, but this is actually different from how exceptions are formatted. I prefer the
format, but I can see some valid arguments for a slightly more verbose format, that is closer to the current exception format:DeprecationWarning: You shouldn't do that, use wow.blam() instead File "/path/to/file.py", line XX, in foo bar(deprecated_baz().boo()) ^^^^^^^^^^^^^^^^
or maybe without theTraceback: File "/path/to/file.py", line XX, in foo bar(deprecated_baz().boo()) ^^^^^^^^^^^^^^^^ DeprecationWarning: You shouldn't do that, use wow.blam() instead
Traceback
level, since it’s not a full tracebackFile "/path/to/file.py", line XX, in foo bar(deprecated_baz().boo()) ^^^^^^^^^^^^^^^^ DeprecationWarning: You shouldn't do that, use wow.blam() instead