-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Use parentheses in: a = b == c #449
Comments
This is one of those "eye of the beholder" cases. For each careful Nathaniel pair of readability parens, there's going to be plenty of unnecessary ones put by less experienced developers. If I make the exception for FWIW I consider the latter change preferable.
For how small Black's codebase is, we have quite a few counter-examples there:
and so on :-) I'm not closing this one just yet but I feel this is one of those cases where we can find a large amount of examples both in favor and against parentheses. |
I also noticed myself writing However, I will still argue that you really really should distinguish these cases: name1 = name2 = name3
name1 = name2 == name3
name1 == name2 == name3 They get three radically different parses. The top and the bottom are ok on their own – they appear in different contexts, so you're not likely to mistake one for the other, and they're internally consistent. But the one in the middle is one character off from both of the other two, more complicated, and much much rarer, so it's easy for your eye to try to autocorrect it to one of the others. Parentheses fix all that. So, here's an concrete proposal: when the top node of the AST for the RHS of an assignment is in the set: I guess there's an argument for throwing '(not) is' and '(not) in' into the set that trigger this, so that it matches the set of chainable comparisons. But that's more an argument based on like, spec simplicity than that anyone is really going to think that |
I'm coming back to this now. Would you consider assignments special here? What if an assignment it augmented with a type annotation? What about keyword arguments? |
For concreteness, I guess you're asking about other uses of the on_windows: bool = os.name == "nt"
check_sockets(on_windows=os.name == "nt") or, for that matter, match_count += new_value == old_value
if on_windows := os.name == "nt": I don't think I've ever written any of these, so I don't have a strong intuition. They do look pretty strange? But at least in these cases there's already some other hint to break the visual similarity with Of course for black's purposes, we also need a rule that's simple, unambiguous, implementable, explainable. So my two suggestions would be: More aggressive, what I more or less do currently: put parentheses around any Less aggressive: put parentheses around any |
I'm not so sure about this. It feels very subtle to me when this would be considered helpful versus noisy. An additional rule for handling parentheses in some special case would make it surprising to some users, my intuition is that for every Nathaniel happy with the change, we would get a Joe who would complain at the surprising change. I see what you're after here, @njsmith, and I don't disagree. I just need to feel that this will be easy enough to explain to beginners. |
I would like to add a strong +1 to the original request for keeping (or introducing) parens in the equality check, I am just now considering using
The main readability issue for me is, without the parents, it triggers an instinctive double-take when reading code, which hampers one's ability to skim through a function. It introduces a pause, "wait, what is that line actually doing?", which is easily mitigated with parens. In the simple
then it's not as immediately clear that this is not a triple-assignment. Seeing as one concern is explanation to beginners, looking at the documentation, this situation is already currently ambiguous:
IMO, if I've deliberately written The docs could also be written as (additions in bold): ParenthesesSome parentheses are optional in the Python grammar. Any expression can be wrapped in a pair of parentheses to form an atom. There are a few interesting cases:
In those cases, parentheses are removed when the entire statement fits in one line, or if the inner expression doesn't have any delimiters to further split on, and if the statement is not an assignment to an equality check. If there is only a single delimiter and the expression starts or ends with a bracket, the parenthesis can also be successfully omitted since the existing bracket pair will organize the expression neatly anyway. Otherwise, the parentheses are added. Please note that Black does not add or remove any additional nested parentheses that you might want to have for clarity or further code organization. For example those parentheses are not going to be removed: return not (this or that)
decision = (maybe.this() and values > 0) or (maybe.that() and values < 0) For readability, Black will add parentheses to simple equality-check-assignment operations: target = (another == variable) |
OK, let's do it. |
Another test case from pip's codebase: class Foo:
def _get_candidates(self, link, canonical_package_name):
# type: (Link, str) -> List[Any]
can_not_cache = (
not self.cache_dir or
not canonical_package_name or
not link
)
# <more code here> Black's output: class Foo:
def _get_candidates(self, link, canonical_package_name):
# type: (Link, str) -> List[Any]
can_not_cache = not self.cache_dir or not canonical_package_name or not link
# <more code here> Possibly nicer output: class Foo:
def _get_candidates(self, link, canonical_package_name):
# type: (Link, str) -> List[Any]
can_not_cache = (not self.cache_dir or not canonical_package_name or not link)
# <more code here> My workaround for now, is gonna be to add a small comment and make black break the line as in the first example. |
@pradyunsg I'm not sure why that case particularly needs parentheses. It's not nearly as visually confusing as the examples with |
I now believe we shouldn't make any change here, though I won't object if there's consensus in favor of the change. @Shivansh-007's implementation makes it possible to see the effect of the change on real-world code. This shows:
|
I agree with what @felix-hilden had posted on the PR
For those who aren't familiar with the power hugging endeavour, it corresponds to #2726. This can be seen in the changes in primer also, the following changes are helping in improving the readability and are comparisons of "simple" nodes.
- left_ea = blk_vals.ndim == 1
+ left_ea = (blk_vals.ndim == 1)
- result = arr == other
+ result = (arr == other)
- actual = self.index.values == self.index
+ actual = (self.index.values == self.index) Whereas, places where the nodes are a bit "complex", I am finding it better without the parentheses: - result = Series(a1, dtype=cat_type) == Series(a2, dtype=cat_type)
+ result = (Series(a1, dtype=cat_type) == Series(a2, dtype=cat_type))
- mask &= (self._ascending_count - start) % step == 0
+ mask &= ((self._ascending_count - start) % step == 0)
- debug = env.get(str("_VIRTUALENV_PERIODIC_UPDATE_INLINE")) == str("1")
+ debug = (env.get(str("_VIRTUALENV_PERIODIC_UPDATE_INLINE")) == str("1")) Partially a crosspost of #2770 (comment) |
As this seems a bit controversial, maybe we should instead try to respect user-inserted parentheses, like in math expressions and other cases like in a comment above already: a = (2 * 2) + 1 # unchanged, even though it's clearly optional
a = (b == c) == d # unchanged, even though optional
a = (b == c) # could be unchanged This clashes with our want to not respect previous formatting though. So I'm not sure if it's worth it. But I thought I'd open up a discussion about it. |
I'd really like if removal of parenthesis was optional. Something in the config to either enable or disable removal of parenthesis. |
Looking at the results in #2770 , I prefer never adding the parentheses over always adding the parentheses (and given the number of thumbs down on OP, the status quo is certainly safer here) |
Some changes black wants to make to my code:
The parentheses were originally there to aid readability, by emphasizing that RHS of the
=
is an unusual-but-valid self-contained expression and not, like, a chained assignment or comprehension or something. Perhaps black should preserve, or even add, parentheses in cases like this? I guess thein
case is more debateable, but the= ==
case is really really easy to misread.The text was updated successfully, but these errors were encountered: