-
Notifications
You must be signed in to change notification settings - Fork 107
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
Add B904
- Use raise ... from err
in except
clauses
#181
Conversation
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 - Thanks!
My only questions are (And this is open to others):
- Should this be optional?
- Should this be only on >= 3.9 (Is that when this feature was introduced?)
Also, what triggers W503? I'm guessing some code you didn't add ...
- Can we # nova: W503 them so we can look to clean them up?
@@ -2,7 +2,7 @@ | |||
# Keep in sync with setup.cfg which is used for source packages. | |||
|
|||
[flake8] | |||
ignore = E203, E302, E501, E999 | |||
ignore = E203, E302, E501, E999, W503 |
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.
Why add this? Just your personal preference? I usually follow it and move the operator.
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.
Following black
's recommendation - W503 is actually disabled by default, but enabled for bugbear by the W
in select = B,C,E,F,W,B9
.
I updated the config because I also added the first case of this pattern, in check_for_b018()
here, and black
insists on that placement of the operator.
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.
Maybe use extend-ignore
instead of ignore
here to not override the defaults?
W503
for instance is off by default unless defaults are overridden by ignore
configuration.
README.rst
Outdated
**B018**: Within an ``except`` clause, raise exceptions with ``raise ... from err`` | ||
or ``raise ... from None`` to distinguish them from errors in exception handling. | ||
See [the exception chaining tutorial](https://docs.python.org/3/tutorial/errors.html#exception-chaining) | ||
for details. | ||
|
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.
Do we need to care to make this only care if we're >= 3.9?
**B018**: Within an ``except`` clause, raise exceptions with ``raise ... from err`` | |
or ``raise ... from None`` to distinguish them from errors in exception handling. | |
See [the exception chaining tutorial](https://docs.python.org/3/tutorial/errors.html#exception-chaining) | |
for details. | |
**B018**: Within an ``except`` clause, raise exceptions with ``raise ... from err`` | |
or ``raise ... from None`` to distinguish them from errors in exception handling (i.e. prefer exception chaining). | |
See [the exception chaining tutorial](https://docs.python.org/3/tutorial/errors.html#exception-chaining) | |
for details. | |
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.
Exception chaining was added in Python 3.0! It's just not in widespread use yet, due to the syntatic incompatibility with Python 2 😅
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.
O - I went to docs here: https://docs.python.org/3.9/tutorial/errors.html#exception-chaining
- Set it to 3.8 and this section disappears ...
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.
Wow, I guess it's not been well documented... the reference docs still don't have a heading for it, though ctrl-f chain
does find some mentions.
Will give it till the morning pacific - If no one says anything against this I'll merge. |
I've added a cheeky extra clause to allow |
I do think that the Just some reasoning why making this optional should perhaps be considered. Personally don't feel strongly either way. |
Fair points @hukkin - you can always disable it in global config though, and IMO if something is off by default it might as well not exist for most users 😕 |
Yeah I agree that off-by-default warnings are not much use. Thinking a bit more about this from a library developer perspective, there's two types of errors that will be raised:
What would be great is if this warning could only be applied to the first error type, but that isn't possible really. So I feel the question is which one is more important: having a warning for the first error type, or not needlessly annoying developers raising errors of the second error type. Application developers also may find this annoying as they should generally always shut down gracefully and never let an error bubble all the way up. Then there's the argument that all software has bugs, so any error may bubble up even though not intended to do so... I think I'm starting to lean more towards "this is probably more annoying than useful". |
Here's the diff in Hypothesis, which has plenty of both examples:
Personally I think that even the internal changes are net-positive, in that the code now expresses that these exceptions were raised because of rather than while handling the previous error, and that the user-facing improvements are enough to be worth some inconvenience anyway. It's easy to turn off the check if you don't like it, but exception chaining is amazingly under-used simply because very few people know it exists and it wasn't viable for libraries until we stopped supporting Python 2 - even just saying "hey, this exists" is useful! |
I for one, am in the haven't used it bucket. I need to sit down and really understand when it's valuable and worth using etc. (If anyone has great tutorial they recommend shoot them my way or it might even be worth adding to the readme here) I've enjoyed this discussion. But now I'm torn with default or optional. Any people passing by I'd love your thoughts before we merge this. I'll give it another few days. I'm leaning towards the default. Why:
|
I think the best way to understand this is to try out the three different ways of exception chaining: Disable chaining>>> try:
... raise Exception("one")
... except Exception:
... raise Exception("two") from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
Exception: two Implicit chaining>>> try:
... raise Exception("one")
... except Exception:
... raise Exception("two")
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
Exception: one
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
Exception: two Explicit chaining>>> try:
... raise Exception("one")
... except Exception as e:
... raise Exception("two") from e
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
Exception: one
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
Exception: two Why I think the feature isn't that widely used (in addition to Python 2 compat) is that the default (implicit chaining) is pretty good: it doesn't hide information. I fear that rather than receiving thanks, we may receive complaints for an overly opinionated check with this one. |
I kind of also like the default behavior and am leaning towards hukkin's points now. So we have people each way here. What do we want to do? I think there is noise here. We could make it optional for people who want it @Zac-HD ? |
I'd still prefer it on-by-default, but you're the maintainer - if you want it optional, I'll make it optional 🙂 |
I would love a second maintainer (all thought most are not active these days) thoughts if any swing by .... |
This is useful when you want to explicitly modify the traceback, as done by Hypothesis and Trio
@asottile @The-Compiler @graingert - do you think we should recommend |
some questions:
(I actually don't think I have the commit bit here, but stop by from time to time so feel free to discount my questions / opinions) |
To get an idea of the impact, I ran this over both qutebrowser (~160 instances) and pytest (~300 instances). Both probably wouldn't profit too much from the new warning: qutebrowser because it's an application so it hardly matters; pytest due to how it's in charge of how it prints its own tracebacks anyways. I guess flake8-bugbear users appreciate a more nitpicky linter compared to "plain" flake8 users, but on the other side that's often a reason people don't like pylint, because despite all configurability, at some point it drives people away if a linter produces more noise than signal by default. I suppose it boils down to discoverability vs. a low-noise default configuration. For some projects I suspect this will be rather useless as others said, but of course bugbear doesn't know which ones those would be. FWIW I've also rarely seen I'm avoiding a concrete answer to the question because I really don't know. I'd end up disabling the check and not mind much, but others might feel differently. |
B018
- Use raise ... from err
in except
clausesB904
- Use raise ... from err
in except
clauses
OK; that's enough to convince me that disabled-by-default is sensible - and change made, it's now
Yep, you get exception changing regardless,
If you mean
Quite possibly - though |
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.
Thanks. I think this will be safer from noise. If people come round wanting this on by default we can look at changing.
Will try update CHANGES etc. + release this week.
Satisfy the new B904 rule (PyCQA/flake8-bugbear#181) from `flake8-bugbear` by using explicit exception chaining whenever an exception is being transformed.
Satisfy the new B904 rule (PyCQA/flake8-bugbear#181) from `flake8-bugbear` by using explicit exception chaining whenever an exception is being transformed.
Satisfy the new B904 rule (PyCQA/flake8-bugbear#181) from `flake8-bugbear` by using explicit exception chaining whenever an exception is being transformed. 29ee504
## Summary It looks like bugbear, [from the start](PyCQA/flake8-bugbear#181 (comment)), has had an exemption here to exempt `raise lower_case_var`. I looked at Hypothesis and Trio, which are mentioned in that issue, and Hypothesis has exactly one case of this, and Trio has none, so IMO it doesn't seem worth special-casing. Closes #5664.
## Summary It looks like bugbear, [from the start](PyCQA/flake8-bugbear#181 (comment)), has had an exemption here to exempt `raise lower_case_var`. I looked at Hypothesis and Trio, which are mentioned in that issue, and Hypothesis has exactly one case of this, and Trio has none, so IMO it doesn't seem worth special-casing. Closes astral-sh#5664.
## Summary It looks like bugbear, [from the start](PyCQA/flake8-bugbear#181 (comment)), has had an exemption here to exempt `raise lower_case_var`. I looked at Hypothesis and Trio, which are mentioned in that issue, and Hypothesis has exactly one case of this, and Trio has none, so IMO it doesn't seem worth special-casing. Closes #5664.
Sorry I’m really not sure about. With the controversy already shown above, I think it should be off by default. It only “improves” the phrasing of the error message a bit although chaining itself happens by default. It seems rather hacky to try to change the wording by introducing such a linter rule rather than making the change to python itself. The referenced part of the docs notably doesn’t say one should always use |
Closes #180, by adding a new check
B018B904 to recommend exception chaining. For example,This turned out to be remarkably easy to add to the existing code 😁